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

rusackas pushed a commit to branch feat/granular-theming-v2
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 1f3d2cc305e3f188665f6a5bba2cd842ff12bc4c
Author: Claude <[email protected]>
AuthorDate: Wed May 13 11:07:54 2026 -0700

    feat(dashboard): end-to-end component theming on Chart — Phase 4a
    
    Closes the loop on per-component theming for Chart: adds an "Apply
    theme" item to \`SliceHeaderControls\` (gated on dashboard edit mode)
    that opens the Phase-3 \`ThemeSelectorModal\`. On save, the Phase-3
    \`setComponentThemeId\` action updates \`meta.themeId\`; the Phase-1
    \`ComponentThemeProvider\` (already wrapping ChartHolder since Phase 1)
    re-resolves and re-renders the chart with the new theme tokens.
    
    The full inheritance chain (Instance → Dashboard → Tab → Row/Col →
    Chart) is functionally complete for Chart with this commit. Subsequent
    per-component PRs (Markdown, Row, Column, Tabs) will repeat the same
    three-step recipe — menu item, modal mount, body wrapper — in
    isolation so each user-visible menu/UX change can be reviewed without
    dragging in the theming framework changes (already merged via Phases
    1-3).
    
    - Adds \`MenuKeys.ApplyTheme\` to the dashboard menu-key enum.
    - \`SliceHeaderControls\` gets local \`themeModalOpen\` state, a
      Redux selector for \`dashboardState.editMode\`, a handler that opens
      the modal on menu click, the menu item itself (push gated on
      editMode), and a \`<ThemeSelectorModal>\` mounted with the
      component's \`layoutId\`.
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
 SIP.md                                             | 21 +++++++++++++++++-
 .../components/SliceHeaderControls/index.tsx       | 25 ++++++++++++++++++++++
 superset-frontend/src/dashboard/types.ts           |  1 +
 3 files changed, 46 insertions(+), 1 deletion(-)

diff --git a/SIP.md b/SIP.md
index 8be321d732c..d73bafcd000 100644
--- a/SIP.md
+++ b/SIP.md
@@ -196,7 +196,26 @@ Each phase brings its own tests; the cumulative bar:
   3 passing tests on `setComponentThemeId`: preserves other meta keys
   + sets numeric `themeId`; stores explicit `null` for the clear path;
   no-op when the component id isn't in the layout.
-- _(Phase 4)_ — pending.
+- _(Phase 4)_ — in progress.
+  - **Chart (4a)**: ✅ landed locally. End-to-end demo on Chart works
+    now: `SliceHeaderControls` has a new "Apply theme" item (gated on
+    dashboard edit mode); clicking it opens the Phase-3
+    `ThemeSelectorModal` keyed to the component's layoutId; on save the
+    Phase-3 action updates `meta.themeId`; the Phase-1
+    `ComponentThemeProvider` (already wrapping ChartHolder) re-resolves
+    and re-renders the chart with the new theme tokens. The full
+    Instance → Dashboard → Tab → Row/Col → Chart inheritance chain is
+    functionally complete for Chart.
+
+    Open follow-ups for the **Markdown / Row / Column / Tabs** PRs:
+    - Each gets the menu-pattern conversion (`MarkdownModeDropdown`,
+      gear icon, none → shared `ComponentHeaderControls`).
+    - Each wraps its body in `<ComponentThemeProvider layoutId=...>`.
+    - Each mounts a `<ThemeSelectorModal>` with an "Apply theme" menu
+      item that opens it.
+    - Each per-component PR can be reviewed in isolation for the menu/UX
+      change without dragging in the theming framework changes (those
+      are already merged in Phases 1-3).
 
 ### Phase 1 status
 
diff --git 
a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx 
b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
index 508b83c0bc1..12b160edba4 100644
--- a/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
+++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/index.tsx
@@ -57,6 +57,7 @@ import { useDrillDetailMenuItems } from 
'src/components/Chart/useDrillDetailMenu
 import { LOG_ACTIONS_CHART_DOWNLOAD_AS_IMAGE } from 'src/logger/LogUtils';
 import { MenuKeys, RootState } from 'src/dashboard/types';
 import DrillDetailModal from 
'src/components/Chart/DrillDetail/DrillDetailModal';
+import ThemeSelectorModal from 'src/dashboard/components/ThemeSelectorModal';
 import { usePermissions } from 'src/hooks/usePermissions';
 import { useDatasetDrillInfo } from 'src/hooks/apiResources/datasets';
 import { ResourceStatus } from 'src/hooks/apiResources/apiResources';
@@ -166,6 +167,13 @@ const SliceHeaderControls = (
   const [drillModalIsOpen, setDrillModalIsOpen] = useState(false);
   // setting openKeys undefined falls back to uncontrolled behaviour
   const [isDropdownVisible, setIsDropdownVisible] = useState(false);
+  const [themeModalOpen, setThemeModalOpen] = useState(false);
+
+  // Per-component theming is an edit-mode affordance only — viewers see the
+  // applied theme but can't change it.
+  const editMode = useSelector<RootState, boolean>(
+    state => !!state.dashboardState.editMode,
+  );
   const [openScopingModal, scopingModal] = useCrossFiltersScopingModal(
     props.slice.slice_id,
   );
@@ -258,6 +266,9 @@ const SliceHeaderControls = (
         // eslint-disable-next-line no-unused-expressions
         props.toggleExpandSlice?.(props.slice.slice_id);
         break;
+      case MenuKeys.ApplyTheme:
+        setThemeModalOpen(true);
+        break;
       case MenuKeys.ExploreChart:
         // eslint-disable-next-line no-unused-expressions
         props.logExploreChart?.(props.slice.slice_id);
@@ -450,6 +461,13 @@ const SliceHeaderControls = (
     });
   }
 
+  if (editMode) {
+    newMenuItems.push({
+      key: MenuKeys.ApplyTheme,
+      label: t('Apply theme'),
+    });
+  }
+
   if (canExplore) {
     newMenuItems.push({
       key: MenuKeys.ExploreChart,
@@ -681,6 +699,13 @@ const SliceHeaderControls = (
         dataset={datasetWithVerboseMap}
       />
       {canEditCrossFilters && scopingModal}
+      {editMode && (
+        <ThemeSelectorModal
+          layoutId={componentId}
+          show={themeModalOpen}
+          onHide={() => setThemeModalOpen(false)}
+        />
+      )}
       {isFullSize && <Global styles={fullscreenStyles(theme)} />}
     </>
   );
diff --git a/superset-frontend/src/dashboard/types.ts 
b/superset-frontend/src/dashboard/types.ts
index 74e8815179b..9aa306e6655 100644
--- a/superset-frontend/src/dashboard/types.ts
+++ b/superset-frontend/src/dashboard/types.ts
@@ -379,6 +379,7 @@ export enum MenuKeys {
   ExportFullXlsx = 'export_full_xlsx',
   ForceRefresh = 'force_refresh',
   Fullscreen = 'fullscreen',
+  ApplyTheme = 'apply_theme',
   ToggleChartDescription = 'toggle_chart_description',
   ViewQuery = 'view_query',
   ViewResults = 'view_results',

Reply via email to