Skip to content

Commit 8873c6d

Browse files
SaKaNa-Yantfubot
andauthored
feat(hub-ui): inherit panel accents from startup branding (#384)
Co-authored-by: Anthony Fu (via agent) <reg-github-bot@antfu.me>
1 parent a4d6de5 commit 8873c6d

33 files changed

Lines changed: 231 additions & 79 deletions

File tree

‎design/build-shadow-css.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface BuildShadowCssOptions {
2626
/**
2727
* Absolute path to the primary-ramp override stylesheet, appended AFTER
2828
* the UnoCSS output so its `:host`/`:root, :host` block wins over Wind's
29-
* own primary declarations (see each package's `primary-ramp.css`).
29+
* own primary declarations (the shared `design/primary-ramp.css`).
3030
*/
3131
primaryRampPath: string
3232
/**

‎design/panel-theme.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { DevframeBranding } from '../packages/hub-ui/src/types'
2+
import { setupDevframeConnection } from 'devframe/client'
3+
4+
/** Apply startup branding to a built-in devframe's iframe SPA. */
5+
export function applyPanelBranding(): () => void {
6+
if (window.parent === window)
7+
return () => {}
8+
9+
const root = document.documentElement
10+
const previous = root.style.getPropertyValue('--devframe-primary')
11+
const priority = root.style.getPropertyPriority('--devframe-primary')
12+
let disposed = false
13+
let applied = false
14+
15+
void setupDevframeConnection().then(({ connectionMeta }) => {
16+
const configs = connectionMeta.configs as { ui?: { branding?: DevframeBranding } } | undefined
17+
const color = configs?.ui?.branding?.primaryColor
18+
if (disposed || !color || !CSS.supports('color', color))
19+
return
20+
root.style.setProperty('--devframe-primary', color)
21+
applied = true
22+
}).catch(() => {
23+
// Connection failures leave the SPA's default palette intact.
24+
})
25+
26+
return () => {
27+
disposed = true
28+
if (!applied)
29+
return
30+
if (previous)
31+
root.style.setProperty('--devframe-primary', previous, priority)
32+
else
33+
root.style.removeProperty('--devframe-primary')
34+
}
35+
}

packages/hub-ui/src/client/primary-ramp.css renamed to design/primary-ramp.css

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,43 @@
11
/*
2-
* Primary color as a single overridable variable: `--devframe-primary`.
3-
*
4-
* Appended AFTER the UnoCSS output (see `scripts/build-css.ts`) so this block
5-
* wins over Wind3's own `:root, :host` primary declarations, and imported after
6-
* `virtual:uno.css` in the Storybook preview for the same reason.
2+
* Primary color as a single overridable variable: `--devframe-primary`. Shared
3+
* by every devframe surface: appended after each shadow-root build's UnoCSS
4+
* output (the hub-ui dock, the json-render renderer module; see
5+
* `build-shadow-css.ts`), imported last by every built-in devframe panel SPA so
6+
* it inherits a rebranded hub's accent, and imported after `virtual:uno.css` in
7+
* the hub-ui Storybook preview. It always wins over Wind's own `:root, :host`
8+
* primary declarations because it comes last.
79
*
810
* Each stop (`--colors-primary-*`) is derived from `--devframe-primary` via
911
* `color-mix`. When `--devframe-primary` is unset, the intermediate
1012
* `--devframe-primary-<stop>` vars become guaranteed-invalid (their `color-mix`
1113
* references an unset var with no fallback), so each `--colors-primary-<stop>`
1214
* falls back to the exact devframe default hex; the default look is preserved,
1315
* and only an explicit override derives a new ramp. The vars inherit through
14-
* the tree, so setting `--devframe-primary` on the dock host retints
15-
* everything, and setting it on a group's chrome container retints just that
16-
* group (the group-accent mechanism).
16+
* the tree, so setting `--devframe-primary` on a host (the dock host, a viewer
17+
* mount container, or a panel's `<html>`) retints everything below it, and
18+
* setting it on a group's chrome container retints just that group (the
19+
* group-accent mechanism).
1720
*
18-
* The dock's shadow root is built on Wind3, which bakes `primary`-based
19-
* utilities (`text-primary`, `bg-primary`, `btn-primary`, `ring-primary-500`,
20-
* …) to literal `rgb()` triplets rather than referencing these variables, so
21-
* `scripts/build-css.ts` rewires those baked colors into CSS relative-color
22-
* syntax reading `--colors-primary-<stop>` (see `rewireBakedPrimaryColors` in
23-
* `design/uno.config.ts`) so this block actually retints them, not just the
24-
* handful of rules (the glow gradient below) that reference the variables
25-
* directly. The stops declared here (`DEFAULT`/`600`/`500`/`400`/`300`) must
26-
* match `OVERRIDABLE_PRIMARY_STOPS` there.
21+
* A shadow root built on Wind3 bakes `primary`-based utilities (`text-primary`,
22+
* `bg-primary`, `btn-primary`, `ring-primary-500`, …) to literal `rgb()`
23+
* triplets rather than referencing these variables, so `build-shadow-css.ts`
24+
* rewires those baked colors into CSS relative-color syntax reading
25+
* `--colors-primary-<stop>` (see `rewireBakedPrimaryColors` in `uno.config.ts`)
26+
* so this block actually retints them, not just the handful of rules (the dock's
27+
* glow gradient) that reference the variables directly. The stops declared here
28+
* (`DEFAULT`/`600`/`500`/`400`/`300`) must match `OVERRIDABLE_PRIMARY_STOPS`
29+
* there.
2730
*
28-
* `:host` is the effective selector inside the dock's shadow root; `:root` is
29-
* for the light-DOM Storybook preview (it matches nothing in the shadow root).
31+
* `:host` is the effective selector inside a shadow root; `:root` is for the
32+
* light-DOM panel SPAs and the Storybook preview (it matches nothing in a
33+
* shadow root).
3034
*
3135
* `.devframes-accent-scope` re-declares the whole block on a group's chrome
3236
* container: because custom properties inherit as computed values, a descendant
3337
* that only overrides `--devframe-primary` would keep the `:host`-computed
3438
* stops; re-running the derivation here recomputes them from the container's
3539
* own `--devframe-primary` (a group's `accentColor`), retinting just that group.
40+
* Inert on surfaces that never apply the class.
3641
*/
3742
:root,
3843
:host,

‎design/uno.config.ts‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Preset } from 'unocss'
22
import { fileURLToPath } from 'node:url'
3-
import { presetAnthonyDesign } from '@antfu/design/unocss'
3+
import { presetAnthonyDesign, resolvePrimary } from '@antfu/design/unocss'
44
import {
55
defineConfig,
66
presetIcons,
@@ -60,7 +60,16 @@ export function createDesignConfig(options: CreateDesignConfigOptions = {}) {
6060
* shared border color (matching `border-base`) for unqualified borders.
6161
*/
6262
preflights: [{ getCSS: () => '*,::before,::after{border-color:#8882}' }],
63+
theme: {
64+
// Stable palette for status marks and preview content that intentionally
65+
// keeps the default accent when the surrounding panel adopts a theme.
66+
colors: { devframe: resolvePrimary('#3a6a45') },
67+
},
6368
shortcuts: {
69+
/** Fixed semantic colors stay independent of the panel's primary accent. */
70+
'color-status-positive': 'color-devframe-600 dark:color-devframe-300',
71+
'color-preview-accent': 'color-devframe-600 dark:color-devframe-300',
72+
'bg-preview-accent': 'bg-devframe',
6473
/** Fixed navbar height, shared by every surface's top nav. */
6574
'h-nav': 'h-10',
6675
/** Named z-index layers, shared across every surface. */
@@ -131,8 +140,8 @@ export const shadowSurfaceSafelist: string[] = [
131140
]
132141

133142
/**
134-
* The primary-ramp stops a shadow-root surface's `primary-ramp.css` exposes
135-
* as overridable `--colors-primary-<stop>` custom properties (derived from
143+
* The primary-ramp stops the shared `design/primary-ramp.css` exposes as
144+
* overridable `--colors-primary-<stop>` custom properties (derived from
136145
* `--devframe-primary`). Must match that file's declarations exactly.
137146
*/
138147
const OVERRIDABLE_PRIMARY_STOPS = ['DEFAULT', '600', '500', '400', '300'] as const

‎docs/content/1.guide/18.hub-initiate.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ interface DevframeHubUi {
5353
`@devframes/hub-ui`'s `createUi()` is the reference (standalone `viewer` SPA + floating dock); its `setup(ctx)` publishes config to `ctx.staticConfig.ui` (`ConnectionMeta.configs.ui`):
5454

5555
- **`viewer`**: set to `false` to disable the standalone viewer.
56-
- **`branding`**: rebrand the UI (logo, name, primary color). `background` accepts any CSS `background` value (color, gradient, image, or `transparent`) or `{ light, dark }` variants. These flat forms apply everywhere. Use `{ standalone, iframe? }` to specialize the framed viewer; an omitted `iframe` value falls back to `standalone`.
56+
- **`branding`**: rebrand the UI (logo, name, primary color). `background` accepts any CSS `background` value (color, gradient, image, or `transparent`) or `{ light, dark }` variants. These flat forms apply everywhere. Use `{ standalone, iframe? }` to specialize the framed viewer; an omitted `iframe` value falls back to `standalone`. Built-in devframe panels read `branding.primaryColor` from the existing connection metadata at startup and map it to CSS primary tokens. Standalone SPAs keep their default accent; status and preview palettes remain independent. Reload panels after changing startup branding.
5757
- **`dockPreferences`** tunes the dock rail: `categoryOrder`, floating-dock `maxVisibleItems`, first-run `defaultMode` (`'float'`/`'edge'`) and `defaultPosition`.
5858
- **`embeddedVisibility`** sets the floating dock's reveal policy:
5959
- `'normal'` (default): shows immediately.

‎examples/hub-deno/src/hub.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const hub: HubInstance = globalRef.__hubDenoMinimal ??= initHub({
4040
* Rebrand the reference UI to Deno's own navy in one field, no CSS:
4141
* `createUi`'s `branding` option publishes `ConnectionMeta.configs.ui.branding`,
4242
* which the dock reads at connect time and feeds into `--devframe-primary`
43-
* (see `@devframes/hub-ui`'s `primary-ramp.css`).
43+
* (see the shared `design/primary-ramp.css`).
4444
*/
4545
ui: createUi({ branding: { primaryColor: '#70ffaf', productName: 'Devframes on Deno' } }),
4646
/**

‎examples/hub-fastify/src/hub.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export const hub: HubInstance = globalRef.__hubFastifyMinimal ??= initHub({
4040
* Rebrand the reference UI to Fastify's own black in one field, no CSS:
4141
* `createUi`'s `branding` option publishes `ConnectionMeta.configs.ui.branding`,
4242
* which the dock reads at connect time and feeds into `--devframe-primary`
43-
* (see `@devframes/hub-ui`'s `primary-ramp.css`).
43+
* (see the shared `design/primary-ramp.css`).
4444
*/
4545
ui: createUi({ branding: { primaryColor: '#2f2f2f', productName: 'Devframes on Fastify' } }),
4646
/**

‎examples/hub-hono/src/app.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export const hub: HubInstance = globalRef.__hubHonoMinimal ??= initHub({
4343
* Rebrand the reference UI to Hono's own orange in one field, no CSS:
4444
* `createUi`'s `branding` option publishes `ConnectionMeta.configs.ui.branding`,
4545
* which the dock reads at connect time and feeds into `--devframe-primary`
46-
* (see `@devframes/hub-ui`'s `primary-ramp.css`).
46+
* (see the shared `design/primary-ramp.css`).
4747
*/
4848
ui: createUi({ branding: { primaryColor: '#e36002', productName: 'Devframes on Hono' } }),
4949
/**

‎examples/hub-next/src/client/hub.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ async function loadHub(): Promise<HubInstance> {
6565
// `@devframes/next/hub` runs the socket on a side-car (Next routes can't
6666
// accept WS upgrades). `createUi`'s `branding` option rebrands the dock via
6767
// `ConnectionMeta.configs.ui.branding`, read at connect time and fed into
68-
// `--devframe-primary` (see `@devframes/hub-ui`'s `primary-ramp.css`).
68+
// `--devframe-primary` (see the shared `design/primary-ramp.css`).
6969
return createNextDevframeHub({
7070
devframes,
7171
ui: (hubUi.createUi as typeof CreateUi)({ branding: { primaryColor: '#3f8ba9', productName: 'Devframes on Next.js' } }),

‎examples/hub-nitro/hub.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export const hub: HubInstance = globalRef.__hubNitroMinimal ??= initHub({
4141
* Rebrand the reference UI to Nitro's own pink/red in one field, no CSS:
4242
* `createUi`'s `branding` option publishes `ConnectionMeta.configs.ui.branding`,
4343
* which the dock reads at connect time and feeds into `--devframe-primary`
44-
* (see `@devframes/hub-ui`'s `primary-ramp.css`).
44+
* (see the shared `design/primary-ramp.css`).
4545
*/
4646
ui: createUi({ branding: { primaryColor: '#ff2056', productName: 'Devframes on Nitro' } }),
4747
/**

0 commit comments

Comments
 (0)