From 1f3d2cc305e3f188665f6a5bba2cd842ff12bc4c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 11:07:54 -0700 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20end-to-end=20component=20the?= =?UTF-8?q?ming=20on=20Chart=20=E2=80=94=20Phase=204a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 \`\` mounted with the component's \`layoutId\`. Co-Authored-By: Claude Sonnet 4.6 --- 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 ``. + - Each mounts a `` 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( + 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 && ( + setThemeModalOpen(false)} + /> + )} {isFullSize && } ); 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',