mirror of
https://github.com/apache/superset.git
synced 2026-05-22 00:05:15 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
21
SIP.md
21
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
|
||||
|
||||
|
||||
@@ -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)} />}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user