You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by en...@apache.org on 2026/08/27 13:13:07 UTC

(superset) 16/46: fix(dashboard-v2): unify Properties tab label styling and form spacing

This is an automated email from the ASF dual-hosted git repository.

EnxDev pushed a commit to branch enxdev/poc/dashboard-v2-editing-ui-flow
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 1db69171e4c1fa3ba275d6116c2f9ea3543f8f75
Author: Enzo Martellucci <en...@gmail.com>
AuthorDate: Wed Aug 26 10:53:41 2026 +0200

    fix(dashboard-v2): unify Properties tab label styling and form spacing
    
    Schema-driven widgets (e.g. Balloons) rendered their Form tab bare, so it
    never got PropsForm's FormShell rhythm fixes, and three controls
    (Metrics/Dimensions' multi-list, the color swatch, and the raw-JSON
    fallback) built their own bold Typography label instead of a Form.Item
    like every sibling control. Share FormShell between both halves of the
    Properties tab, and normalize those three controls to Form.Item — the
    raw-JSON fallback also gets Input.TextArea in place of a bare <textarea>.
---
 .../src/pages/DashboardBuilderV2/PropsForm.tsx     |   9 +-
 .../DashboardBuilderV2/SchemaControlPanel.tsx      |   9 +-
 .../DashboardBuilderV2/schemaControlRenderers.tsx  | 114 ++++++++++-----------
 3 files changed, 70 insertions(+), 62 deletions(-)

diff --git a/superset-frontend/src/pages/DashboardBuilderV2/PropsForm.tsx b/superset-frontend/src/pages/DashboardBuilderV2/PropsForm.tsx
index 967ddf8e439..be6dce46192 100644
--- a/superset-frontend/src/pages/DashboardBuilderV2/PropsForm.tsx
+++ b/superset-frontend/src/pages/DashboardBuilderV2/PropsForm.tsx
@@ -27,7 +27,7 @@ import { provider } from 'src/core/dashboard/store';
 import inferPropsSchema, { untypedKeys } from './inferPropsSchema';
 
 /**
- * What the generated form is made to agree with.
+ * What a generated form is made to agree with.
  *
  * The controls come from a third-party renderer set, and it lays a form out
  * for a page of its own rather than for a rail beside a canvas. Four things
@@ -54,8 +54,13 @@ import inferPropsSchema, { untypedKeys } from './inferPropsSchema';
  * Scoped to this element rather than fixed in the renderers, which the
  * semantic-layer modal also draws from and which are not this change's to move
  * — the same reason `DashboardProperties` scopes its own input fix.
+ *
+ * Exported because `SchemaControlPanel` renders the same third-party renderer
+ * set for schema-driven widgets and hits the identical four problems — the
+ * Properties tab has two forms sharing one rail, and they read as one rhythm
+ * only if they share this wrapper too.
  */
-const FormShell = styled.div`
+export const FormShell = styled.div`
   ${({ theme }) => css`
     /* A group's name, at the weight Section titles a group with. */
     > form > b,
diff --git a/superset-frontend/src/pages/DashboardBuilderV2/SchemaControlPanel.tsx b/superset-frontend/src/pages/DashboardBuilderV2/SchemaControlPanel.tsx
index a7d4dd30d12..413b0cb6ef5 100644
--- a/superset-frontend/src/pages/DashboardBuilderV2/SchemaControlPanel.tsx
+++ b/superset-frontend/src/pages/DashboardBuilderV2/SchemaControlPanel.tsx
@@ -49,6 +49,7 @@ import {
 } from 'src/features/semanticLayers/jsonFormsHelpers';
 import { provider, useDashboardRevision } from 'src/core/dashboard/store';
 import { fetchQueryData } from 'src/core/dashboard/chartData';
+import { FormShell } from './PropsForm';
 import { schemaControlRenderers } from './schemaControlRenderers';
 
 type DataBindingSpec = dashboardApi.DataBindingSpec;
@@ -227,8 +228,10 @@ export default function SchemaControlPanel({ nodeId }: { nodeId: string }) {
       {schema && (
         // Vertical layout via class, not an antd `Form` wrapper: the renderers
         // read the layout off the nearest `.ant-form` ancestor class, so a real
-        // `Form` here would take the edits with it (see `PropsForm`).
-        <div
+        // `Form` here would take the edits with it (see `PropsForm`). Wrapped in
+        // the same `FormShell` as `PropsForm`'s generic form, so both halves of
+        // the Properties tab share one spacing rhythm.
+        <FormShell
           className="ant-form ant-form-vertical"
           data-test="schema-control-panel"
         >
@@ -254,7 +257,7 @@ export default function SchemaControlPanel({ nodeId }: { nodeId: string }) {
               }
             }}
           />
-        </div>
+        </FormShell>
       )}
     </>
   );
diff --git a/superset-frontend/src/pages/DashboardBuilderV2/schemaControlRenderers.tsx b/superset-frontend/src/pages/DashboardBuilderV2/schemaControlRenderers.tsx
index 610e583daf2..bb8339aa18e 100644
--- a/superset-frontend/src/pages/DashboardBuilderV2/schemaControlRenderers.tsx
+++ b/superset-frontend/src/pages/DashboardBuilderV2/schemaControlRenderers.tsx
@@ -51,6 +51,7 @@ import {
   Button,
   Flex,
   Form,
+  Input,
   Select,
   Typography,
 } from '@superset-ui/core/components';
@@ -106,17 +107,16 @@ function CodeControl({
   };
 
   return (
-    <Flex vertical gap="small">
-      <Typography.Text strong>{label}</Typography.Text>
-      <textarea
+    <Form.Item label={label}>
+      <Input.TextArea
         value={text}
         onChange={onChange}
         rows={12}
         spellCheck={false}
-        style={{ fontFamily: 'monospace', fontSize: 12, width: '100%' }}
+        style={{ fontFamily: 'monospace', fontSize: 12 }}
       />
       {error && <Typography.Text type="danger">{error}</Typography.Text>}
-    </Flex>
+    </Form.Item>
   );
 }
 
@@ -133,14 +133,13 @@ function ColorControl({
     (schema as Record<string, unknown>).default ??
     theme.colorText) as string;
   return (
-    <Flex align="center" gap="small">
+    <Form.Item label={label}>
       <input
         type="color"
         value={value}
         onChange={event => handleChange(path, event.target.value)}
       />
-      <Typography.Text>{label}</Typography.Text>
-    </Flex>
+    </Form.Item>
   );
 }
 
@@ -275,55 +274,56 @@ function ReferenceMultiList({
   };
 
   return (
-    <Flex vertical gap="small">
-      <Typography.Text strong>{label}</Typography.Text>
-      {values.map((value, index) => {
-        const option = options.find(candidate => candidate.value === value);
-        return (
-          <Flex
-            key={value}
-            align="center"
-            gap="small"
-            draggable
-            onDragStart={() => {
-              dragIndexRef.current = index;
-            }}
-            onDragOver={event => event.preventDefault()}
-            onDrop={() => {
-              if (
-                dragIndexRef.current !== null &&
-                dragIndexRef.current !== index
-              ) {
-                move(dragIndexRef.current, index);
-              }
-              dragIndexRef.current = null;
-            }}
-          >
-            <Icons.HolderOutlined iconSize="s" />
-            <div style={{ flex: 1 }}>{option?.label ?? value}</div>
-            <Button
-              buttonSize="xsmall"
-              buttonStyle="link"
-              aria-label={t('Remove')}
-              icon={<Icons.CloseOutlined iconSize="s" />}
-              onClick={() => onChange(values.filter((_, i) => i !== index))}
-            />
-          </Flex>
-        );
-      })}
-      {available.length > 0 && (
-        <Select
-          value={undefined}
-          placeholder={t('Add field')}
-          ariaLabel={t('Add %s', label)}
-          options={available}
-          loading={loading}
-          disabled={disabled}
-          onChange={next => onChange([...values, next as string])}
-          css={{ width: '100%' }}
-        />
-      )}
-    </Flex>
+    <Form.Item label={label}>
+      <Flex vertical gap="small">
+        {values.map((value, index) => {
+          const option = options.find(candidate => candidate.value === value);
+          return (
+            <Flex
+              key={value}
+              align="center"
+              gap="small"
+              draggable
+              onDragStart={() => {
+                dragIndexRef.current = index;
+              }}
+              onDragOver={event => event.preventDefault()}
+              onDrop={() => {
+                if (
+                  dragIndexRef.current !== null &&
+                  dragIndexRef.current !== index
+                ) {
+                  move(dragIndexRef.current, index);
+                }
+                dragIndexRef.current = null;
+              }}
+            >
+              <Icons.HolderOutlined iconSize="s" />
+              <div style={{ flex: 1 }}>{option?.label ?? value}</div>
+              <Button
+                buttonSize="xsmall"
+                buttonStyle="link"
+                aria-label={t('Remove')}
+                icon={<Icons.CloseOutlined iconSize="s" />}
+                onClick={() => onChange(values.filter((_, i) => i !== index))}
+              />
+            </Flex>
+          );
+        })}
+        {available.length > 0 && (
+          <Select
+            value={undefined}
+            placeholder={t('Add field')}
+            ariaLabel={t('Add %s', label)}
+            options={available}
+            loading={loading}
+            disabled={disabled}
+            onChange={next => onChange([...values, next as string])}
+            css={{ width: '100%' }}
+          />
+        )}
+      </Flex>
+    </Form.Item>
   );
 }