@adia-ai/web-modules 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +22 -0
- package/editor/editor-shell/css/editor-shell.bespoke.css +7 -0
- package/editor/editor-shell/css/editor-shell.layout.css +15 -4
- package/editor/editor-sidebar/editor-sidebar.js +14 -2
- package/index.js +1 -0
- package/package.json +6 -2
- package/shell/admin-shell/css/admin-shell.main.css +35 -0
- package/theme/index.js +1 -0
- package/theme/theme-panel/theme-panel.a2ui.json +173 -0
- package/theme/theme-panel/theme-panel.css +50 -0
- package/theme/theme-panel/theme-panel.examples.html +104 -0
- package/theme/theme-panel/theme-panel.html +73 -0
- package/theme/theme-panel/theme-panel.js +533 -0
- package/theme/theme-panel/theme-panel.test.js +274 -0
- package/theme/theme-panel/theme-panel.yaml +213 -0
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,31 @@ Built from `@adia-ai/web-components` primitives.
|
|
|
11
11
|
|
|
12
12
|
_No pending changes._
|
|
13
13
|
|
|
14
|
+
## [0.4.3] - 2026-05-11
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- `README.md` — currency sweep mentioning the `theme` cluster + `@adia-ai/web-modules/theme` subpath export shipped in v0.4.2 (peer-agent cleanup; source unchanged).
|
|
19
|
+
|
|
20
|
+
### Lockstep
|
|
21
|
+
|
|
22
|
+
9-package coordinated PATCH cut to v0.4.3. Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry). No source change to any web-module element — `README.md` doc-only. See root [CHANGELOG.md `## [0.4.3]`](../../CHANGELOG.md).
|
|
23
|
+
|
|
24
|
+
## [0.4.2] - 2026-05-11
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **`<theme-panel>` module — new `theme/` cluster.** Module-tier appearance-preferences control surface. Owns the three knobs of the AdiaUI theming contract: `[data-theme]` named themes (8 built-in slugs + tolerant for custom), `--a-density` / `--a-radius-k` parametric overrides, and `color-scheme` light/dark switching, plus optional `localStorage` persistence behind a small attribute API ([`packages/web-modules/theme/theme-panel/`](./theme/theme-panel/)). Reflected state — `[active-theme]`, `[active-scheme]`, `[active-density]`, `[active-radius]` — lets external CSS / JS react via `:has(theme-panel[active-scheme="dark"])` patterns without listening to events. One `theme-change` event per user-visible change (source ∈ `theme | slider | preset | scheme | reset | programmatic`). Attribute API: `[themes="slug1 slug2…"]` (override default 8), `[parametric]` (renders density + radius sliders), `[presets]` (renders compact / reset / spacious preset row), `[scheme-toggle]` (renders inline light/dark toggle — absorbs the legacy standalone `#theme-toggle` button per OD-002=A). 23 unit tests. Promoted from the ~30-line inline `#theme-panel` block + ~90 LOC of controller wiring previously duplicated in `site/index.html` and `playgrounds/admin-shell/`. Spec: [`docs/specs/theme-panel-module.md`](../../docs/specs/theme-panel-module.md). Plan: [`docs/plans/theme-panel-module-2026-05-11.md`](../../docs/plans/theme-panel-module-2026-05-11.md). Commits `63d9deb4` (module Phase 1) + `a35d3131` (site consumer migration Phase 2; site.js −90 LOC + index.html −27 inline lines) + `c844aef4` (admin-shell playground consumer migration Phase 3; contents.html −25 lines + contents.js −48 LOC).
|
|
29
|
+
- **New subpath export `@adia-ai/web-modules/theme`** — consumers wanting just this cluster can `import '@adia-ai/web-modules/theme'` instead of the full barrel. `package.json` `exports` / `files` / `sideEffects` updated.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- **`<editor-sidebar>` wrapping a `<pane-ui>` no longer collapses to ~1px in CSS grid `auto`-tracked editor shells.** The wrapper's `ResizeObserver` (already observing the inner pane for `[collapsed]` reflection) now also mirrors `pane.offsetWidth` onto `this.style.width`. Without this mirror, the CSS-grid algorithm's intrinsic-size lookup on the bespoke wrapper does NOT propagate through to the inner `<pane-ui>`'s explicit width — the wrapper's `max-content` is reported as ~1px (the border), the grid `auto` track collapses, and the pane overflows visually into the canvas. Affects every `editor-shell:has(> editor-canvas)` host with bespoke sidebar children (a2ui-editor + construct-canvas; the `smoke:consumers` Playwright probe now verifies both have 240/280 px sidebars and the canvas claims the remainder). The [editor cluster `bespoke.css`](./editor/editor-shell/css/editor-shell.bespoke.css) is unchanged — the bug was always on the JS side of the width contract, not the layout CSS.
|
|
34
|
+
|
|
35
|
+
### Lockstep
|
|
36
|
+
|
|
37
|
+
9-package coordinated PATCH cut to v0.4.2 (per [`docs/specs/package-architecture.md` § 15](../../docs/specs/package-architecture.md#15-versioning-policy)). Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4.0` covers `0.4.x` under semver). Source change scoped to one file (`editor/editor-sidebar/editor-sidebar.js`); rides alongside the `@adia-ai/web-components` v0.4.2 `<input-ui type="number">` rewrite. See root [CHANGELOG.md `## [0.4.2]`](../../CHANGELOG.md) for the cut narrative.
|
|
38
|
+
|
|
14
39
|
## [0.4.1] - 2026-05-10
|
|
15
40
|
|
|
16
41
|
### Added
|
package/README.md
CHANGED
|
@@ -5,6 +5,24 @@ Composite custom elements built from
|
|
|
5
5
|
into clusters by use case; each cluster ships as a subpath export so
|
|
6
6
|
consumers install only what they need.
|
|
7
7
|
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @adia-ai/web-modules @adia-ai/web-components
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`@adia-ai/web-components` is a peer dependency (modules compose primitives at runtime). Consumers can import the full barrel or a specific cluster subpath:
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import '@adia-ai/web-modules'; // every cluster
|
|
18
|
+
import '@adia-ai/web-modules/shell'; // admin-shell + admin-sidebar + admin-command
|
|
19
|
+
import '@adia-ai/web-modules/chat'; // chat-shell + chat-thread + chat-composer + …
|
|
20
|
+
import '@adia-ai/web-modules/editor'; // editor-shell + editor-canvas + editor-sidebar + …
|
|
21
|
+
import '@adia-ai/web-modules/simple'; // simple-shell + simple-content + simple-hero
|
|
22
|
+
import '@adia-ai/web-modules/theme'; // theme-panel
|
|
23
|
+
import '@adia-ai/web-modules/runtime'; // gen-root + a2ui-root
|
|
24
|
+
```
|
|
25
|
+
|
|
8
26
|
Each cluster carries a **shell host** (behavior-only orchestrator) plus
|
|
9
27
|
a **family of bespoke shell-tier children** (cluster-namespaced custom
|
|
10
28
|
elements with state-as-attribute semantics) per [ADR-0023](../../.brain/adrs/0023-bespoke-shell-tier-children.md). The bespoke vocabulary is the only recognized authoring shape since v0.4.0 ([ADR-0024](../../.brain/adrs/0024-legacy-shell-shapes-retired.md)).
|
|
@@ -23,6 +41,7 @@ elements with state-as-attribute semantics) per [ADR-0023](../../.brain/adrs/002
|
|
|
23
41
|
| `chat` | `<chat-shell>` | `<chat-thread>`, `<chat-composer>`, `<chat-sidebar>` (JS-bearing); `<chat-empty>`, `<chat-header>`, `<chat-status>` (CSS-only); 6 children total | Conversational surface — streaming messages, composer with `[disabled]` propagation, scroll-to-bottom thread, `[streaming]` reflected. |
|
|
24
42
|
| `editor` | `<editor-shell>` | `<editor-toolbar>`, `<editor-canvas>`, `<editor-sidebar>` (JS-bearing); `<editor-statusbar>`, `<editor-canvas-empty>` (CSS-only); 5 children total | Design-tool surface — toolbar with `[full-screen]`, central canvas with `[focused]`/`[empty]`, sidebar wraps `<pane-ui resizable>` for delegation. |
|
|
25
43
|
| `runtime` | `<gen-root>`, `<a2ui-root>` | — | Render roots that turn JSON or gen-UI intents into live DOM. |
|
|
44
|
+
| `theme` | — (controls-only) | `<theme-panel>` (JS-bearing); 1 child total | Appearance-preferences control surface — `[data-theme]` named themes, `--a-density` / `--a-radius-k` parametric overrides, `color-scheme` light/dark, opt-in `[persist]`. Drops into any consumer's `<popover-ui slot="content">` topbar. Spec: [`docs/specs/theme-panel-module.md`](../../docs/specs/theme-panel-module.md). |
|
|
26
45
|
|
|
27
46
|
Future clusters on the strategic horizon: `data` (kanban, filters,
|
|
28
47
|
table-toolbar), `agent` (agent-trace, reasoning panels).
|
|
@@ -44,6 +63,7 @@ See [`bespoke-shell-children` skill](../../.agents/skills/bespoke-shell-children
|
|
|
44
63
|
import '@adia-ai/web-modules/chat'; // chat-shell + bespoke children
|
|
45
64
|
import '@adia-ai/web-modules/editor'; // editor-shell + bespoke children
|
|
46
65
|
import '@adia-ai/web-modules/runtime'; // gen-root, a2ui-root
|
|
66
|
+
import '@adia-ai/web-modules/theme'; // theme-panel (appearance prefs)
|
|
47
67
|
</script>
|
|
48
68
|
|
|
49
69
|
<!-- Bespoke shape (canonical since v0.4.0): -->
|
|
@@ -118,6 +138,8 @@ web-modules/
|
|
|
118
138
|
├── runtime/
|
|
119
139
|
│ ├── gen-ui/ <gen-ui> render root
|
|
120
140
|
│ └── a2ui-root/ <a2ui-root> A2UI render root
|
|
141
|
+
├── theme/
|
|
142
|
+
│ └── theme-panel/ <theme-panel> appearance-preferences control surface
|
|
121
143
|
└── index.js barrel re-export of every cluster
|
|
122
144
|
```
|
|
123
145
|
|
|
@@ -71,6 +71,13 @@ editor-shell > editor-canvas {
|
|
|
71
71
|
min-width: 0;
|
|
72
72
|
display: flex;
|
|
73
73
|
flex-direction: column;
|
|
74
|
+
/* Stretch children to full width (layout.css's editor-shell editor-canvas
|
|
75
|
+
rule sets align-items: center for an empty-state placeholder, which
|
|
76
|
+
would otherwise leave #canvas-host at its min-width: 320px centered
|
|
77
|
+
inside the canvas region — leaving large empty space on either side.
|
|
78
|
+
The empty-state child handles its own centering separately. */
|
|
79
|
+
align-items: stretch;
|
|
80
|
+
justify-content: flex-start;
|
|
74
81
|
position: relative;
|
|
75
82
|
overflow: auto;
|
|
76
83
|
background: var(--editor-canvas-bg, var(--a-bg-subtle));
|
|
@@ -81,8 +81,13 @@ editor-shell > header > [slot="action-leading"] {
|
|
|
81
81
|
margin-inline-end: auto;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/* ── Body: pane | canvas | pane ──
|
|
85
|
-
editor-shell
|
|
84
|
+
/* ── Body: pane | canvas | pane ──
|
|
85
|
+
Legacy raw-children body wrapper (pre-bespoke, when editor-shell was a
|
|
86
|
+
single flex column with a [data-body] wrapper around the pane/canvas
|
|
87
|
+
row). With <editor-canvas> + <editor-sidebar> bespoke children, the
|
|
88
|
+
grid layout in bespoke.css owns the body shape. This rule is kept
|
|
89
|
+
for any non-bespoke consumer still passing raw inner content. */
|
|
90
|
+
editor-shell > [data-body] {
|
|
86
91
|
display: flex;
|
|
87
92
|
flex: 1;
|
|
88
93
|
min-width: 0;
|
|
@@ -94,8 +99,14 @@ editor-shell editor-canvas {
|
|
|
94
99
|
flex: 1;
|
|
95
100
|
min-width: 0;
|
|
96
101
|
display: flex;
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
/* Children stretch to fill; the empty-state placeholder
|
|
103
|
+
(editor-canvas-empty) does its own centering via bespoke.css.
|
|
104
|
+
align-items: center here would shrink-wrap children to their
|
|
105
|
+
intrinsic width and center them — which leaves a 320px-min
|
|
106
|
+
#canvas-host centered inside a wide canvas region with empty
|
|
107
|
+
bands on both sides. */
|
|
108
|
+
align-items: stretch;
|
|
109
|
+
justify-content: flex-start;
|
|
99
110
|
background: var(--editor-canvas-bg);
|
|
100
111
|
color: var(--editor-canvas-fg);
|
|
101
112
|
}
|
|
@@ -77,8 +77,20 @@ class EditorSidebar extends UIElement {
|
|
|
77
77
|
// Restore persisted width
|
|
78
78
|
this.#restoreWidth();
|
|
79
79
|
|
|
80
|
-
// Observe inner pane width to maintain [collapsed] reflected
|
|
81
|
-
this
|
|
80
|
+
// Observe inner pane width to (a) maintain [collapsed] reflected and
|
|
81
|
+
// (b) mirror the width onto this wrapper so the parent's grid track
|
|
82
|
+
// sizes correctly. Without the mirror, an `auto`/`max-content` grid
|
|
83
|
+
// track on the wrapping editor-sidebar collapses to ~1px because the
|
|
84
|
+
// intrinsic-size lookup doesn't propagate through to <pane-ui>'s
|
|
85
|
+
// explicit width — the pane then overflows out of the sidebar and
|
|
86
|
+
// visually overlaps the canvas.
|
|
87
|
+
this.#ro = new ResizeObserver((entries) => {
|
|
88
|
+
this.#syncCollapsed();
|
|
89
|
+
// Use offsetWidth so the sidebar wraps the full pane border-box
|
|
90
|
+
// (contentRect would be 1px short on side="leading|trailing").
|
|
91
|
+
const w = this.#pane.offsetWidth;
|
|
92
|
+
if (w > 0) this.style.width = `${w}px`;
|
|
93
|
+
});
|
|
82
94
|
this.#ro.observe(this.#pane);
|
|
83
95
|
|
|
84
96
|
// Track resize via pointerdown on resize-handle children of the pane
|
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-modules",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "AdiaUI composite custom elements — shell, chat, editor, runtime clusters built from @adia-ai/web-components primitives. Subpath exports per cluster.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"./simple/*": "./simple/*/*.js",
|
|
16
16
|
"./runtime": "./runtime/index.js",
|
|
17
17
|
"./runtime/*": "./runtime/*/*.js",
|
|
18
|
+
"./theme": "./theme/index.js",
|
|
19
|
+
"./theme/*": "./theme/*/*.js",
|
|
18
20
|
"./package.json": "./package.json"
|
|
19
21
|
},
|
|
20
22
|
"files": [
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
"editor/",
|
|
24
26
|
"simple/",
|
|
25
27
|
"runtime/",
|
|
28
|
+
"theme/",
|
|
26
29
|
"index.js",
|
|
27
30
|
"README.md",
|
|
28
31
|
"CHANGELOG.md"
|
|
@@ -33,7 +36,8 @@
|
|
|
33
36
|
"./shell/**/*.js",
|
|
34
37
|
"./chat/**/*.js",
|
|
35
38
|
"./editor/**/*.js",
|
|
36
|
-
"./runtime/**/*.js"
|
|
39
|
+
"./runtime/**/*.js",
|
|
40
|
+
"./theme/**/*.js"
|
|
37
41
|
],
|
|
38
42
|
"peerDependencies": {
|
|
39
43
|
"@adia-ai/web-components": "^0.4.0",
|
|
@@ -122,6 +122,41 @@ admin-shell > main > :is(section, section-ui) {
|
|
|
122
122
|
|
|
123
123
|
admin-shell > main > :is(section, section-ui)::-webkit-scrollbar { display: none; }
|
|
124
124
|
|
|
125
|
+
/* ── Subnav rail layout ──
|
|
126
|
+
When the main section contains an `[data-subnav]` aside (the
|
|
127
|
+
left-rail nav pattern — `<aside data-subnav>` followed by the
|
|
128
|
+
`[data-content-root]` article), grid-layout the section so the
|
|
129
|
+
rail sits on the inline-start side and the content fills the
|
|
130
|
+
rest. Mirrors the docs-site convention (site.css `:has(#subnav-rail)`)
|
|
131
|
+
so consumer apps using admin-shell + the subnav-rail authoring
|
|
132
|
+
shape get the canonical layout for free. */
|
|
133
|
+
admin-shell > main > :is(section, section-ui):has(> [data-subnav]:not([hidden])) {
|
|
134
|
+
display: grid;
|
|
135
|
+
grid-template-columns: var(--subnav-width, 14rem) 1fr;
|
|
136
|
+
grid-template-rows: minmax(0, 1fr);
|
|
137
|
+
gap: 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
admin-shell > main > :is(section, section-ui) > [data-subnav] {
|
|
141
|
+
min-height: 0;
|
|
142
|
+
overflow-y: auto;
|
|
143
|
+
overscroll-behavior: contain;
|
|
144
|
+
border-inline-end: 1px solid var(--a-border-subtle);
|
|
145
|
+
padding-block: var(--a-space-2);
|
|
146
|
+
padding-inline: var(--a-space-2);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* Mobile: collapse to single column; subnav stacks above content. */
|
|
150
|
+
@container (max-width: 40rem) {
|
|
151
|
+
admin-shell > main > :is(section, section-ui):has(> [data-subnav]:not([hidden])) {
|
|
152
|
+
grid-template-columns: 1fr;
|
|
153
|
+
}
|
|
154
|
+
admin-shell > main > :is(section, section-ui) > [data-subnav] {
|
|
155
|
+
border-inline-end: none;
|
|
156
|
+
border-block-end: 1px solid var(--a-border-subtle);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
125
160
|
/* ── Main > footer (status bar) ──
|
|
126
161
|
Legacy pattern: last-child auto-pushed to trailing edge via
|
|
127
162
|
margin-inline-start: auto (works when authors use a simple
|
package/theme/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ThemePanel } from './theme-panel/theme-panel.js';
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://adiaui.dev/a2ui/v0_9/components/ThemePanel.json",
|
|
4
|
+
"title": "ThemePanel",
|
|
5
|
+
"description": "Module-tier appearance-preferences control surface. Owns the three knobs\nof the AdiaUI theming contract: [data-theme=<slug>] named themes,\n--a-density / --a-radius-k parametric overrides, and color-scheme\nlight/dark switching, plus optional localStorage persistence behind a\nsmall attribute API.\n\nDrops into any consumer's <popover-ui slot=\"content\"> (the canonical\ncomposition) or directly into a sidebar section. Eight named themes\nship by default; section visibility flips on/off via boolean attributes\n([parametric], [presets], [scheme-toggle]).\n\nPromoted from the duplicated <div id=\"theme-panel\"> block in site/ and\nplaygrounds/admin-shell/ — see docs/specs/theme-panel-module.md.\n",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"allOf": [
|
|
8
|
+
{
|
|
9
|
+
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"properties": {
|
|
16
|
+
"active-density": {
|
|
17
|
+
"description": "Reflected — mirrors the current --a-density value on the target\nelement. Stringified number.\n",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"default": ""
|
|
20
|
+
},
|
|
21
|
+
"active-radius": {
|
|
22
|
+
"description": "Reflected — mirrors the current --a-radius-k value on the target\nelement. Stringified number.\n",
|
|
23
|
+
"type": "string",
|
|
24
|
+
"default": ""
|
|
25
|
+
},
|
|
26
|
+
"active-scheme": {
|
|
27
|
+
"description": "Reflected — the resolved color-scheme (auto collapsed to a\nconcrete value). Either \"light\" or \"dark\".\n",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"default": ""
|
|
30
|
+
},
|
|
31
|
+
"active-theme": {
|
|
32
|
+
"description": "Reflected — the currently selected theme slug. Empty string when\nunset (default theme). Read-only externally; use .apply({theme})\nto change.\n",
|
|
33
|
+
"type": "string",
|
|
34
|
+
"default": ""
|
|
35
|
+
},
|
|
36
|
+
"component": {
|
|
37
|
+
"const": "ThemePanel"
|
|
38
|
+
},
|
|
39
|
+
"parametric": {
|
|
40
|
+
"description": "Renders the density + radius slider block. Sliders bind to\n--a-density and --a-radius-k on the target element.\n",
|
|
41
|
+
"type": "boolean",
|
|
42
|
+
"default": false
|
|
43
|
+
},
|
|
44
|
+
"persist": {
|
|
45
|
+
"description": "Persists selections to localStorage. Defaults to ephemeral so\nplaygrounds and embedded demos do not silently mutate a user's\ndocs-shell preferences.\n",
|
|
46
|
+
"type": "boolean",
|
|
47
|
+
"default": false
|
|
48
|
+
},
|
|
49
|
+
"presets": {
|
|
50
|
+
"description": "Renders the compact / reset / spacious preset row. Each preset\napplies a (density, radius) pair; \"default\" also clears the\n[data-theme] attribute.\n",
|
|
51
|
+
"type": "boolean",
|
|
52
|
+
"default": false
|
|
53
|
+
},
|
|
54
|
+
"scheme": {
|
|
55
|
+
"description": "Initial color-scheme. \"auto\" follows prefers-color-scheme via a\nmatchMedia listener; user clicks on the scheme toggle promote to\nan explicit light or dark choice.\n",
|
|
56
|
+
"type": "string",
|
|
57
|
+
"enum": [
|
|
58
|
+
"light",
|
|
59
|
+
"dark",
|
|
60
|
+
"auto"
|
|
61
|
+
],
|
|
62
|
+
"default": "auto"
|
|
63
|
+
},
|
|
64
|
+
"scheme-toggle": {
|
|
65
|
+
"description": "Renders an integrated light/dark <segmented-ui> at the top of the\npanel. When absent, the panel omits scheme entirely — consumers\ncan still drive scheme via the .apply({scheme}) public method.\n",
|
|
66
|
+
"type": "boolean",
|
|
67
|
+
"default": false
|
|
68
|
+
},
|
|
69
|
+
"storage-prefix": {
|
|
70
|
+
"description": "Namespace for localStorage keys when [persist] is set. Four keys\nare written: {prefix}theme, {prefix}scheme, {prefix}density,\n{prefix}radius.\n",
|
|
71
|
+
"type": "string",
|
|
72
|
+
"default": "adia-theme-"
|
|
73
|
+
},
|
|
74
|
+
"target": {
|
|
75
|
+
"description": "CSS selector for the element that receives [data-theme] and\n--a-density / --a-radius-k writes. Defaults to :root (the\n<html> element). Scoped targets are useful for preview-pane\ndemos.\n",
|
|
76
|
+
"type": "string",
|
|
77
|
+
"default": ":root"
|
|
78
|
+
},
|
|
79
|
+
"themes": {
|
|
80
|
+
"description": "Space-separated list of theme slugs to render as buttons. Author\nmay restrict ([themes=\"default ocean\"]) or extend. Tolerant —\nunknown slugs render a button; clicking applies [data-theme=slug]\nregardless of whether themes.css has a matching block.\n",
|
|
81
|
+
"type": "string",
|
|
82
|
+
"default": "default ocean forest sunset lavender rose slate midnight"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"required": [
|
|
86
|
+
"component"
|
|
87
|
+
],
|
|
88
|
+
"unevaluatedProperties": false,
|
|
89
|
+
"x-adiaui": {
|
|
90
|
+
"anti_patterns": [],
|
|
91
|
+
"category": "layout",
|
|
92
|
+
"events": {
|
|
93
|
+
"theme-change": {
|
|
94
|
+
"description": "Bubbles when any user-visible state changes (theme click, slider\ninput, preset click, scheme flip, reset). One event per change.\nNo event on auto-rehydrate-from-storage at boot.\n",
|
|
95
|
+
"detail": {
|
|
96
|
+
"theme": "string",
|
|
97
|
+
"density": "number",
|
|
98
|
+
"radius": "number",
|
|
99
|
+
"scheme": "string",
|
|
100
|
+
"source": "string"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"examples": [],
|
|
105
|
+
"keywords": [
|
|
106
|
+
"theme",
|
|
107
|
+
"theme-panel",
|
|
108
|
+
"palette",
|
|
109
|
+
"density",
|
|
110
|
+
"radius",
|
|
111
|
+
"scheme",
|
|
112
|
+
"dark-mode",
|
|
113
|
+
"light-mode",
|
|
114
|
+
"color-scheme",
|
|
115
|
+
"preferences",
|
|
116
|
+
"settings",
|
|
117
|
+
"color",
|
|
118
|
+
"appearance",
|
|
119
|
+
"skin"
|
|
120
|
+
],
|
|
121
|
+
"name": "ThemePanel",
|
|
122
|
+
"related": [
|
|
123
|
+
"Popover",
|
|
124
|
+
"Button",
|
|
125
|
+
"Slider",
|
|
126
|
+
"Field",
|
|
127
|
+
"Divider",
|
|
128
|
+
"Text",
|
|
129
|
+
"Segmented",
|
|
130
|
+
"AdminShell"
|
|
131
|
+
],
|
|
132
|
+
"slots": {},
|
|
133
|
+
"states": [
|
|
134
|
+
{
|
|
135
|
+
"description": "Default — panel rendered, awaiting input.",
|
|
136
|
+
"name": "idle"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"description": "[persist] is set; selections write to localStorage under\n[storage-prefix].\n",
|
|
140
|
+
"attribute": "persist",
|
|
141
|
+
"name": "persisted"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"description": "[scheme=\"auto\"] and no user override this session; a\nprefers-color-scheme listener reapplies on system flip.\n",
|
|
145
|
+
"name": "scheme-auto"
|
|
146
|
+
}
|
|
147
|
+
],
|
|
148
|
+
"synonyms": {
|
|
149
|
+
"theme": [
|
|
150
|
+
"palette",
|
|
151
|
+
"skin",
|
|
152
|
+
"appearance"
|
|
153
|
+
],
|
|
154
|
+
"density": [
|
|
155
|
+
"compactness",
|
|
156
|
+
"spacing-scale"
|
|
157
|
+
],
|
|
158
|
+
"radius": [
|
|
159
|
+
"corner-roundness",
|
|
160
|
+
"border-radius"
|
|
161
|
+
],
|
|
162
|
+
"scheme": [
|
|
163
|
+
"mode",
|
|
164
|
+
"color-scheme",
|
|
165
|
+
"dark-mode"
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
"tag": "theme-panel",
|
|
169
|
+
"tokens": {},
|
|
170
|
+
"traits": [],
|
|
171
|
+
"version": 1
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
+
<theme-panel> — appearance-preferences control surface.
|
|
3
|
+
Replaces the inline style= attributes from the two consumers.
|
|
4
|
+
Two-block @scope pattern per component-token-contract.md.
|
|
5
|
+
Zero raw color values (repo hard rule); leans on global tokens.
|
|
6
|
+
═══════════════════════════════════════════════════════════════ */
|
|
7
|
+
|
|
8
|
+
/* ── 1. Token declarations ── */
|
|
9
|
+
|
|
10
|
+
theme-panel {
|
|
11
|
+
--theme-panel-gap: var(--a-space-3);
|
|
12
|
+
--theme-panel-pad: var(--a-space-3);
|
|
13
|
+
--theme-panel-min-width: 260px;
|
|
14
|
+
--theme-panel-row-gap: var(--a-space-1);
|
|
15
|
+
--theme-panel-preset-gap: var(--a-space-2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* ── 2. Scoped layout ── */
|
|
19
|
+
|
|
20
|
+
@scope (theme-panel) {
|
|
21
|
+
:scope {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
gap: var(--theme-panel-gap);
|
|
25
|
+
padding: var(--theme-panel-pad);
|
|
26
|
+
min-width: var(--theme-panel-min-width);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
[part="scheme"] {
|
|
30
|
+
display: flex;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
[part="scheme"] > segmented-ui {
|
|
34
|
+
flex: 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
[part="themes"] {
|
|
38
|
+
display: flex;
|
|
39
|
+
gap: var(--theme-panel-row-gap);
|
|
40
|
+
flex-wrap: wrap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
[part="presets"] {
|
|
44
|
+
display: flex;
|
|
45
|
+
gap: var(--theme-panel-preset-gap);
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* field-ui already provides its own label + layout; nothing extra here */
|
|
50
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<header>
|
|
2
|
+
<div>
|
|
3
|
+
<h1>Theme Panel</h1>
|
|
4
|
+
<div data-actions>
|
|
5
|
+
<tag-ui size="sm">theme-panel</tag-ui>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
<p>Module-tier appearance-preferences control surface — eight named themes, parametric density & radius sliders, three presets, optional light/dark scheme toggle, and opt-in <code>localStorage</code> persistence. Drop into any <code><popover-ui slot="content"></code> in your shell's topbar.</p>
|
|
9
|
+
</header>
|
|
10
|
+
|
|
11
|
+
<section data-section>
|
|
12
|
+
<h2 variant="section">Minimal — themes only</h2>
|
|
13
|
+
<p data-note>Default theme grid. No sliders, no presets, no scheme toggle. Suitable for embedded demos where only theme swapping is offered.</p>
|
|
14
|
+
<code-ui language="html"><theme-panel></theme-panel></code-ui>
|
|
15
|
+
<theme-panel></theme-panel>
|
|
16
|
+
</section>
|
|
17
|
+
|
|
18
|
+
<section data-section>
|
|
19
|
+
<h2 variant="section">With parametric sliders</h2>
|
|
20
|
+
<p data-note>Adds the density + radius slider block. Selecting a theme also reads the theme's computed knob values back into the sliders.</p>
|
|
21
|
+
<code-ui language="html"><theme-panel parametric></theme-panel></code-ui>
|
|
22
|
+
<theme-panel parametric></theme-panel>
|
|
23
|
+
</section>
|
|
24
|
+
|
|
25
|
+
<section data-section>
|
|
26
|
+
<h2 variant="section">Full panel — parametric + presets + scheme toggle</h2>
|
|
27
|
+
<p data-note>The docs-shell configuration. Theme + scheme + parametric + presets, all surfaces visible. <code>persist</code> is set so refreshing the page restores your selections.</p>
|
|
28
|
+
<code-ui language="html"><theme-panel parametric presets scheme-toggle persist></theme-panel></code-ui>
|
|
29
|
+
<theme-panel parametric presets scheme-toggle persist></theme-panel>
|
|
30
|
+
</section>
|
|
31
|
+
|
|
32
|
+
<section data-section>
|
|
33
|
+
<h2 variant="section">Restricted theme list</h2>
|
|
34
|
+
<p data-note>The <code>[themes]</code> attribute is tolerant — any space-separated list of slugs renders as buttons. Pass a subset to limit choices, or extend with custom slugs (you supply the matching CSS).</p>
|
|
35
|
+
<code-ui language="html"><theme-panel themes="default ocean midnight" parametric></theme-panel></code-ui>
|
|
36
|
+
<theme-panel themes="default ocean midnight" parametric></theme-panel>
|
|
37
|
+
</section>
|
|
38
|
+
|
|
39
|
+
<section data-section>
|
|
40
|
+
<h2 variant="section">Inside a popover (canonical composition)</h2>
|
|
41
|
+
<p data-note>The usual shape — a palette-icon button trigger pops the panel from a topbar. This is how <code>site/</code> and <code>playgrounds/admin-shell/</code> consume the module.</p>
|
|
42
|
+
<code-ui language="html"><popover-ui placement="bottom-end">
|
|
43
|
+
<button-ui icon="palette" variant="ghost" size="sm" slot="trigger"></button-ui>
|
|
44
|
+
<theme-panel slot="content" parametric presets scheme-toggle></theme-panel>
|
|
45
|
+
</popover-ui></code-ui>
|
|
46
|
+
<popover-ui placement="bottom-end">
|
|
47
|
+
<button-ui icon="palette" variant="ghost" size="sm" slot="trigger"></button-ui>
|
|
48
|
+
<theme-panel slot="content" parametric presets scheme-toggle></theme-panel>
|
|
49
|
+
</popover-ui>
|
|
50
|
+
</section>
|
|
51
|
+
|
|
52
|
+
<section data-section>
|
|
53
|
+
<h2 variant="section">Scoped target — preview pane</h2>
|
|
54
|
+
<p data-note>By default, the panel writes to <code>:root</code> (the <code><html></code> element). Pass <code>[target]</code> to scope theming to a region — useful for editor previews and component playgrounds.</p>
|
|
55
|
+
<code-ui language="html"><theme-panel target="#preview-pane" parametric></theme-panel>
|
|
56
|
+
<div id="preview-pane">…preview content…</div></code-ui>
|
|
57
|
+
<theme-panel target="#preview-pane" parametric></theme-panel>
|
|
58
|
+
<div id="preview-pane" style="margin-top:var(--a-space-3); padding:var(--a-space-4); border:1px solid var(--a-border); border-radius:calc(var(--a-radius-2) * var(--a-radius-k, 1));">
|
|
59
|
+
<strong>Preview pane</strong> — theming applied here only.
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
|
|
63
|
+
<section data-section>
|
|
64
|
+
<h2 variant="section">Attributes</h2>
|
|
65
|
+
<table>
|
|
66
|
+
<thead><tr><th>Attribute</th><th>Type</th><th>Default</th><th>Notes</th></tr></thead>
|
|
67
|
+
<tbody>
|
|
68
|
+
<tr><td><code>themes</code></td><td>string</td><td>8 named slugs</td><td>Space-separated theme slug list.</td></tr>
|
|
69
|
+
<tr><td><code>parametric</code></td><td>boolean</td><td><code>false</code></td><td>Renders density + radius sliders.</td></tr>
|
|
70
|
+
<tr><td><code>presets</code></td><td>boolean</td><td><code>false</code></td><td>Renders compact / reset / spacious row.</td></tr>
|
|
71
|
+
<tr><td><code>scheme-toggle</code></td><td>boolean</td><td><code>false</code></td><td>Renders integrated light/dark switch.</td></tr>
|
|
72
|
+
<tr><td><code>persist</code></td><td>boolean</td><td><code>false</code></td><td>Writes selections to localStorage.</td></tr>
|
|
73
|
+
<tr><td><code>storage-prefix</code></td><td>string</td><td><code>adia-theme-</code></td><td>LS key namespace when persisting.</td></tr>
|
|
74
|
+
<tr><td><code>target</code></td><td>string (selector)</td><td><code>:root</code></td><td>Element to receive writes.</td></tr>
|
|
75
|
+
<tr><td><code>scheme</code></td><td>enum</td><td><code>auto</code></td><td><code>light</code> / <code>dark</code> / <code>auto</code>.</td></tr>
|
|
76
|
+
</tbody>
|
|
77
|
+
</table>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<section data-section>
|
|
81
|
+
<h2 variant="section">Reflected state</h2>
|
|
82
|
+
<p>Read-only externally; use <code>.apply()</code> to mutate. Style with <code>:has(theme-panel[active-scheme="dark"])</code>.</p>
|
|
83
|
+
<table>
|
|
84
|
+
<thead><tr><th>Attribute</th><th>Notes</th></tr></thead>
|
|
85
|
+
<tbody>
|
|
86
|
+
<tr><td><code>active-theme</code></td><td>Currently selected slug; empty for default.</td></tr>
|
|
87
|
+
<tr><td><code>active-scheme</code></td><td>Resolved scheme (auto collapsed to light or dark).</td></tr>
|
|
88
|
+
<tr><td><code>active-density</code></td><td>Current <code>--a-density</code> on target.</td></tr>
|
|
89
|
+
<tr><td><code>active-radius</code></td><td>Current <code>--a-radius-k</code> on target.</td></tr>
|
|
90
|
+
</tbody>
|
|
91
|
+
</table>
|
|
92
|
+
</section>
|
|
93
|
+
|
|
94
|
+
<section data-section>
|
|
95
|
+
<h2 variant="section">Events & methods</h2>
|
|
96
|
+
<table>
|
|
97
|
+
<thead><tr><th>Surface</th><th>Shape</th><th>Notes</th></tr></thead>
|
|
98
|
+
<tbody>
|
|
99
|
+
<tr><td><code>theme-change</code> event</td><td><code>{ theme, scheme, density, radius, source }</code></td><td>Bubbles. <code>source</code> ∈ <code>theme | slider | preset | scheme | reset | programmatic</code>.</td></tr>
|
|
100
|
+
<tr><td><code>.apply(partial)</code></td><td><code>(p: { theme?, scheme?, density?, radius? }) => void</code></td><td>Programmatic write; emits <code>theme-change</code> with <code>source: 'programmatic'</code>.</td></tr>
|
|
101
|
+
<tr><td><code>.reset()</code></td><td><code>() => void</code></td><td>Clears all state; emits <code>theme-change</code> with <code>source: 'reset'</code>.</td></tr>
|
|
102
|
+
</tbody>
|
|
103
|
+
</table>
|
|
104
|
+
</section>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="auto">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Theme Panel — AdiaUI</title>
|
|
7
|
+
|
|
8
|
+
<!-- Token base -->
|
|
9
|
+
<link rel="stylesheet" href="../../../web-components/styles/resets.css">
|
|
10
|
+
<link rel="stylesheet" href="../../../web-components/styles/tokens.css">
|
|
11
|
+
<link rel="stylesheet" href="../../../web-components/styles/themes.css">
|
|
12
|
+
|
|
13
|
+
<!-- Primitive CSS used inside the panel -->
|
|
14
|
+
<link rel="stylesheet" href="../../../web-components/components/button/button.css">
|
|
15
|
+
<link rel="stylesheet" href="../../../web-components/components/popover/popover.css">
|
|
16
|
+
<link rel="stylesheet" href="../../../web-components/components/slider/slider.css">
|
|
17
|
+
<link rel="stylesheet" href="../../../web-components/components/field/field.css">
|
|
18
|
+
<link rel="stylesheet" href="../../../web-components/components/divider/divider.css">
|
|
19
|
+
<link rel="stylesheet" href="../../../web-components/components/text/text.css">
|
|
20
|
+
<link rel="stylesheet" href="../../../web-components/components/segmented/segmented.css">
|
|
21
|
+
<link rel="stylesheet" href="../../../web-components/components/segment/segment.css">
|
|
22
|
+
<link rel="stylesheet" href="../../../web-components/components/icon/icon.css">
|
|
23
|
+
<link rel="stylesheet" href="../../../web-components/components/code/code.css">
|
|
24
|
+
<link rel="stylesheet" href="../../../web-components/components/tag/tag.css">
|
|
25
|
+
|
|
26
|
+
<!-- Module CSS -->
|
|
27
|
+
<link rel="stylesheet" href="./theme-panel.css">
|
|
28
|
+
|
|
29
|
+
<!-- Primitive JS -->
|
|
30
|
+
<script type="module" src="../../../web-components/components/button/button.js"></script>
|
|
31
|
+
<script type="module" src="../../../web-components/components/popover/popover.js"></script>
|
|
32
|
+
<script type="module" src="../../../web-components/components/slider/slider.js"></script>
|
|
33
|
+
<script type="module" src="../../../web-components/components/field/field.js"></script>
|
|
34
|
+
<script type="module" src="../../../web-components/components/divider/divider.js"></script>
|
|
35
|
+
<script type="module" src="../../../web-components/components/text/text.js"></script>
|
|
36
|
+
<script type="module" src="../../../web-components/components/segmented/segmented.js"></script>
|
|
37
|
+
<script type="module" src="../../../web-components/components/icon/icon.js"></script>
|
|
38
|
+
<script type="module" src="../../../web-components/components/code/code.js"></script>
|
|
39
|
+
<script type="module" src="../../../web-components/components/tag/tag.js"></script>
|
|
40
|
+
|
|
41
|
+
<!-- Module JS -->
|
|
42
|
+
<script type="module" src="./theme-panel.js"></script>
|
|
43
|
+
|
|
44
|
+
<style>
|
|
45
|
+
:where(html, body) {
|
|
46
|
+
margin: 0; min-height: 100vh;
|
|
47
|
+
background: var(--a-bg);
|
|
48
|
+
color: var(--a-fg);
|
|
49
|
+
font-family: var(--a-font);
|
|
50
|
+
}
|
|
51
|
+
main { max-width: 960px; margin-inline: auto; padding: var(--a-space-6) var(--a-space-5); }
|
|
52
|
+
</style>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
|
|
56
|
+
<main id="demo-root">
|
|
57
|
+
<p>Loading examples…</p>
|
|
58
|
+
</main>
|
|
59
|
+
|
|
60
|
+
<script type="module">
|
|
61
|
+
const root = document.getElementById('demo-root');
|
|
62
|
+
try {
|
|
63
|
+
const res = await fetch('./theme-panel.examples.html');
|
|
64
|
+
if (!res.ok) throw new Error(`fetch failed (${res.status})`);
|
|
65
|
+
root.innerHTML = await res.text();
|
|
66
|
+
} catch (err) {
|
|
67
|
+
root.innerHTML = `<p style="color:var(--a-danger-strong);">Failed to load theme-panel.examples.html — ${err.message}</p>`;
|
|
68
|
+
console.error('[theme-panel.html]', err);
|
|
69
|
+
}
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
</body>
|
|
73
|
+
</html>
|