Commit Graph

8 Commits

Author SHA1 Message Date
Claude
4be803cb6e fix(sip): add Apache license header to SIP.md (RAT)
License Check (Apache RAT) flagged `SIP.md` at the repo root for
missing the license header. Add it as an HTML comment so the
rendered Markdown is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 17:37:08 -07:00
Claude
ad37366e93 feat(dashboard): live theme preview in ThemeSelectorModal
When the modal is open, the targeted component re-renders with the
candidate theme as soon as the user picks an option — no dashboard-
dirty round-trip required. Cancel reverts; Apply commits to Redux.

Implementation:

- New \`previewThemeStore\` (module-level subscribable map keyed by
  layoutId, distinguishing "no preview" / "preview value=null" /
  "preview value=number"). Tiny surface: \`set\`/\`clear\`/\`get\`/
  \`subscribe\`. No-op \`set\` / \`clear\` calls don't fire listeners.
- \`useEffectiveThemeId\` now subscribes via \`useSyncExternalStore\`
  and prefers the preview value over the Redux-resolved id when
  present.
- \`ThemeSelectorModal\` writes the in-flight selection through the
  store as the user picks options; cleanup on close (Cancel, X
  button, escape) clears it. Apply dispatches the Redux action
  *before* hiding, so the post-cleanup re-resolution lands on the
  saved value (no flicker).

Snapshot of the resolved id at open-time goes through a \`useRef\`
because \`currentThemeId\` itself becomes reactive (it would already
reflect the in-flight preview), so we can't read it for "what should
we revert to?".

6 new tests for the preview store: get-undefined-for-unknown,
set-stores-numeric, set-stores-explicit-null, clear-removes,
subscriber-fires-on-real-change-and-not-on-no-op, multi-layoutId-
independence. Total dashboard ComponentThemeProvider suite is now
14 passing tests.

Drops "live preview" from the deferred-items list in SIP.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 16:56:26 -07:00
Claude
1be84f1769 feat(dashboard): component theming for Tabs, Row, Column, Markdown — Phase 4b-4e
Same three-step recipe applied to each grid-component type:

  (a) wrap body in <ComponentThemeProvider layoutId={id}>
  (b) add "Apply theme" item to the component's menu via
      ComponentHeaderControls
  (c) mount <ThemeSelectorModal> gated on editMode

- TabsRenderer (4b): wraps StyledTabsContainer; dots menu lands in the
  existing left HoverMenu next to drag/delete.
- Row (4c): wraps WithPopoverMenu body; dots menu in the left HoverMenu
  next to drag/delete/setting-icon. The existing gear icon (opens the
  BackgroundStyleDropdown focus popover) is preserved as-is.
- Column (4d): same recipe as Row, top-positioned HoverMenu.
- Markdown (4e): class component, so themeModalOpen lives on
  this.state. Dots menu lands inside the existing WithPopoverMenu
  menuItems array next to MarkdownModeDropdown; the Edit/Preview
  toggle is intentionally preserved unchanged.

Note on scope: the SIP originally imagined Phase 4 would also converge
MarkdownModeDropdown and the Row/Column gear icon onto the shared dots
menu. Those user-visible UX displacements are intentionally deferred
so this phase adds the theming affordance *additively* — every existing
menu control is untouched. The menu-pattern unification can be picked
up later without coupling it to theming.

Functional outcome: every grid-component type (Chart, Markdown, Row,
Column, Tabs) now supports the full inheritance chain end-to-end:
Instance -> Dashboard -> Tab -> Row/Col -> Chart/Markdown. Setting a
themeId at any level applies to that subtree; clearing it falls
through to the parent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:59:43 -07:00
Claude
1f3d2cc305 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>
2026-05-14 13:52:06 -07:00
Claude
96880a5e8a feat(dashboard): ThemeSelectorModal + setComponentThemeId action — Phase 3
End-to-end mechanism for applying a CRUD theme to a single dashboard
grid component. Two pieces:

1. `setComponentThemeId(componentId, themeId | null)` — thin Redux
   action that merges \`themeId\` into the target component's \`meta\`
   via the existing \`updateComponents\` thunk, preserving every other
   meta field. Explicit \`null\` clears the override and falls back to
   the inherited theme; the resolver in Phase 1 treats null and
   undefined identically. No-ops when the component id isn't in the
   layout.

2. \`ThemeSelectorModal\` — parent-owned modal that fetches non-system
   themes (same query as the dashboard Properties modal:
   \`is_system:false\` filter on \`/api/v1/theme/\`), preselects the
   currently-resolved override via the Phase-1 \`useEffectiveThemeId\`
   hook, and exposes Apply / Cancel / Clear-override-(inherit) actions.
   Each call site provides \`layoutId\` + the \`show\`/\`onHide\` toggle.

No call site for the modal yet — Phase 4 wires the "Apply theme" menu
item into each component's \`ComponentHeaderControls\` to open it.

3 passing tests on the action: merge preserves other meta keys, clear
stores explicit null (not undefined), no-op for missing component.

SIP.md updated with the Phase 3 implementation notes and the deferred-
to-Phase-4 wiring detail.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:52:06 -07:00
Claude
5d9f0780fc feat(dashboard): ComponentHeaderControls — Phase 2
Shared vertical-dots menu component for dashboard grid components.
Generic `items: ComponentMenuItem[]` API — each component (Chart,
Markdown, Row, Column, Tabs) plugs in its own list; the visual chrome
(dots icon trigger, dropdown surface, accessible label, divider
handling, danger/disabled styling) lives in this one component.

Built on `MenuDotsDropdown` from `@superset-ui/core/components` so the
trigger styling matches Chart's existing `SliceHeaderControls` — Phase
4's per-component PRs will converge `SliceHeaderControls` and the
other menu patterns (Markdown's `MarkdownModeDropdown`, Row/Col's
gear-icon + `WithPopoverMenu`) onto this same component.

Phase 2 lands the component + tests only. The actual per-component
menu conversions are user-visible UX changes (e.g. Markdown loses its
toggle-style Edit/Preview switcher and gains a dots menu) and ship in
Phase 4 alongside theme wiring per component, so each can be reviewed
in isolation rather than as a sweeping refactor.

4 passing tests: empty items renders nothing, trigger renders, onClick
fires from menu selection, disabled items don't fire onClick.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:52:06 -07:00
Claude
96e8ddc95c feat(dashboard): per-component theme provider — Phase 1 (Chart PoC)
Adds the skeleton for granular (per-component) theming on dashboard grid
components, with the inheritance chain:
  Instance theme -> Dashboard theme -> Tab theme
                 -> Row/Col theme   -> Chart/Markdown theme

This commit lands Phase 1 from the SIP (`SIP.md` at repo root): the
storage shape and the resolver, wired into `ChartHolder` as the
proof-of-concept call site. No UI yet — `themeId` must be set via Redux
devtools / position_json hand-edit to verify visually. Phase 2 will
introduce the `ComponentHeaderControls` menu and Phase 3 the
`ThemeSelectorModal` that drives this from a real UI.

Surface:

- `LayoutItemMeta.themeId?: number | null` — optional CRUD theme id
  stored per-component in `position_json` meta (no schema migration; the
  meta map is already open-ended). `null` and `undefined` both mean
  "inherit from parent".

- `pickEffectiveThemeId(layoutId, layout)` — pure resolver. Walks
  `parents` up the layout map from the given node until it finds a
  numeric `themeId` or hits `DASHBOARD_ROOT_ID`. Hop-capped at 32 to
  defend against malformed parents chains.

- `useEffectiveThemeId(layoutId)` — Redux hook variant.

- `<ComponentThemeProvider layoutId={...}>` — wraps children in the
  resolved theme's `SupersetThemeProvider`. Lazy-fetches via the
  existing `ThemeController.createDashboardThemeProvider`, which already
  caches by id so N components sharing one theme = 1 fetch. Pass-through
  when no ancestor sets a `themeId`.

- `ChartHolder.tsx` — wraps the existing `<AntdThemeProvider>` (which is
  a popup-container shim for fullscreen mode, not a token provider) so
  per-component tokens are set before antd's ConfigProvider for popup
  targeting.

Tests: 8 unit cases for `pickEffectiveThemeId` covering own / inherited /
null-skip / no-ancestor / root-stop / malformed-parents / other-meta /
missing-id.

Closes the spirit of the closed PR #36749 (which became unrebasable
after .jsx -> .tsx + React 18 + theme controller churn).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:52:06 -07:00
Claude
9959465017 docs(sip): draft granular component theming SIP
Draft Superset Improvement Proposal for component-level theming on
dashboard grid components (Charts, Markdown, Row, Column, Tabs) with
inheritance Instance -> Dashboard -> Tab -> Row/Col -> Chart.

Supersedes the closed PR #36749 (became unrebasable after .jsx -> .tsx
conversion, React 18 upgrade, and theme-controller churn since 2025-12).

This is a living document — kept in lockstep with the work on
`feat/granular-theming-v2`. Each phase updates the status / shortcomings
/ test-plan sections.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 13:52:06 -07:00