@gjsify/adwaita-storybook 0.11.0

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.
@@ -0,0 +1,190 @@
1
+ // Control-row builders — render each @gjsify/stories ControlType as an
2
+ // @gjsify/adwaita-web row, wired two-way to a StoryElement's args. The web
3
+ // counterpart of StorybookWindow's _createControlRow family.
4
+ //
5
+ // All the per-kind value coercion (initial-value seeding, SELECT index↔value,
6
+ // RANGE/NUMBER formatting + type guards, the refresh "set only if differs"
7
+ // guard) now lives in @gjsify/storybook-core's bindControl. This file supplies
8
+ // ONLY the adwaita-web leaf-widget factory — the single renderer seam.
9
+
10
+ import type { StoryControl, StoryNumberControl, StorySelectControl } from '@gjsify/stories';
11
+ import {
12
+ bindControl,
13
+ type ControlRow as CoreControlRow,
14
+ type ControlWidget,
15
+ type ControlWidgetFactory,
16
+ } from '@gjsify/storybook-core';
17
+ import type { StoryElement } from './story-element.js';
18
+
19
+ /** A built control: the row element plus a refresher that re-syncs it from args. */
20
+ export type ControlRow = CoreControlRow<HTMLElement>;
21
+
22
+ /** Build the row for a single control, or null if it cannot be rendered. */
23
+ export function createControlRow(story: StoryElement, control: StoryControl): ControlRow | null {
24
+ return bindControl(story, control, ADWAITA_WEB_FACTORY);
25
+ }
26
+
27
+ /**
28
+ * The @gjsify/adwaita-web leaf-widget factory — builds one `adw-*` (or range/
29
+ * color) element per control kind with get/set/onChange over the DOM events the
30
+ * widgets emit. The only renderer-specific seam; all coercion is in bindControl.
31
+ */
32
+ const ADWAITA_WEB_FACTORY: ControlWidgetFactory<HTMLElement> = {
33
+ text(label: string): ControlWidget<HTMLElement, string> {
34
+ const row = document.createElement('adw-entry-row') as HTMLElement & { text: string };
35
+ row.setAttribute('title', label);
36
+ let cb: (v: string) => void = () => {};
37
+ row.addEventListener('changed', (e) => cb((e as CustomEvent<{ text: string }>).detail.text));
38
+ return {
39
+ node: row,
40
+ get: () => row.text ?? '',
41
+ set: (v) => {
42
+ row.text = v;
43
+ },
44
+ onChange: (fn) => {
45
+ cb = fn;
46
+ },
47
+ };
48
+ },
49
+
50
+ color(label: string, desc: string | undefined): ControlWidget<HTMLElement, string> {
51
+ const row = document.createElement('adw-action-row');
52
+ row.setAttribute('title', label);
53
+ if (desc) row.setAttribute('subtitle', desc);
54
+
55
+ const input = document.createElement('input');
56
+ input.type = 'color';
57
+ input.className = 'sb-color-input';
58
+ input.slot = 'suffix';
59
+ row.appendChild(input);
60
+
61
+ let cb: (v: string) => void = () => {};
62
+ input.addEventListener('input', () => cb(input.value));
63
+ return {
64
+ node: row,
65
+ get: () => input.value,
66
+ set: (v) => {
67
+ input.value = v;
68
+ },
69
+ onChange: (fn) => {
70
+ cb = fn;
71
+ },
72
+ };
73
+ },
74
+
75
+ boolean(label: string): ControlWidget<HTMLElement, boolean> {
76
+ const row = document.createElement('adw-switch-row');
77
+ row.setAttribute('title', label);
78
+ let cb: (v: boolean) => void = () => {};
79
+ row.addEventListener('notify::active', (e) => cb((e as CustomEvent<{ active: boolean }>).detail.active));
80
+ return {
81
+ node: row,
82
+ get: () => row.hasAttribute('active'),
83
+ set: (v) => {
84
+ row.toggleAttribute('active', v);
85
+ },
86
+ onChange: (fn) => {
87
+ cb = fn;
88
+ },
89
+ };
90
+ },
91
+
92
+ number(control: StoryNumberControl): ControlWidget<HTMLElement, number> {
93
+ const row = document.createElement('adw-spin-row') as HTMLElement & { value: number };
94
+ row.setAttribute('title', label(control));
95
+ row.setAttribute('min', String(control.min ?? 0));
96
+ row.setAttribute('max', String(control.max ?? 100));
97
+ row.setAttribute('step', String(control.step ?? 1));
98
+ let cb: (v: number) => void = () => {};
99
+ row.addEventListener('notify::value', (e) => cb((e as CustomEvent<{ value: number }>).detail.value));
100
+ return {
101
+ node: row,
102
+ get: () => row.value ?? 0,
103
+ set: (v) => {
104
+ row.value = v;
105
+ },
106
+ onChange: (fn) => {
107
+ cb = fn;
108
+ },
109
+ };
110
+ },
111
+
112
+ select(control: StorySelectControl): ControlWidget<HTMLElement, number> {
113
+ const options = control.options ?? [];
114
+ const row = document.createElement('adw-combo-row') as HTMLElement & { selected: number };
115
+ row.setAttribute('title', label(control));
116
+ row.setAttribute('items', JSON.stringify(options.map((o) => o.label)));
117
+ let cb: (v: number) => void = () => {};
118
+ row.addEventListener('notify::selected', (e) => cb((e as CustomEvent<{ selected: number }>).detail.selected));
119
+ return {
120
+ node: row,
121
+ get: () => row.selected ?? 0,
122
+ set: (v) => {
123
+ row.selected = v;
124
+ },
125
+ onChange: (fn) => {
126
+ cb = fn;
127
+ },
128
+ };
129
+ },
130
+
131
+ range(control: StoryNumberControl): ControlWidget<HTMLElement, number> {
132
+ const min = control.min ?? 0;
133
+ const max = control.max ?? 100;
134
+ const step = control.step ?? 1;
135
+ const asInteger = Number.isInteger(step);
136
+ const format = (v: number): string => (asInteger ? String(Math.round(v)) : v.toFixed(2));
137
+
138
+ const row = document.createElement('div');
139
+ row.className = 'sb-range-row';
140
+
141
+ const header = document.createElement('div');
142
+ header.className = 'sb-range-header';
143
+ const titleEl = document.createElement('span');
144
+ titleEl.className = 'sb-range-title';
145
+ titleEl.textContent = label(control);
146
+ const valueEl = document.createElement('span');
147
+ valueEl.className = 'sb-range-value';
148
+ header.append(titleEl, valueEl);
149
+
150
+ const input = document.createElement('input');
151
+ input.type = 'range';
152
+ input.className = 'sb-range-input';
153
+ input.min = String(min);
154
+ input.max = String(max);
155
+ input.step = String(step);
156
+
157
+ if (control.description) {
158
+ const descEl = document.createElement('span');
159
+ descEl.className = 'sb-range-description';
160
+ descEl.textContent = control.description;
161
+ row.append(header, descEl, input);
162
+ } else {
163
+ row.append(header, input);
164
+ }
165
+
166
+ let cb: (v: number) => void = () => {};
167
+ input.addEventListener('input', () => {
168
+ const value = parseFloat(input.value);
169
+ valueEl.textContent = format(value);
170
+ cb(value);
171
+ });
172
+
173
+ return {
174
+ node: row,
175
+ get: () => parseFloat(input.value),
176
+ set: (v) => {
177
+ input.value = String(v);
178
+ valueEl.textContent = format(v);
179
+ },
180
+ onChange: (fn) => {
181
+ cb = fn;
182
+ },
183
+ };
184
+ },
185
+ };
186
+
187
+ /** A control's display label — explicit `label` else its `name`. */
188
+ function label(control: StoryControl): string {
189
+ return control.label || control.name;
190
+ }
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import"./_virtual/_rolldown/runtime2.js";import{bindControl as e}from"@gjsify/storybook-core";function createControlRow(n,r){return e(n,r,t)}const t={text(e){let t=document.createElement(`adw-entry-row`);t.setAttribute(`title`,e);let cb=()=>{};return t.addEventListener(`changed`,e=>cb(e.detail.text)),{node:t,get:()=>t.text??``,set:e=>{t.text=e},onChange:e=>{cb=e}}},color(e,t){let n=document.createElement(`adw-action-row`);n.setAttribute(`title`,e),t&&n.setAttribute(`subtitle`,t);let r=document.createElement(`input`);r.type=`color`,r.className=`sb-color-input`,r.slot=`suffix`,n.appendChild(r);let cb=()=>{};return r.addEventListener(`input`,()=>cb(r.value)),{node:n,get:()=>r.value,set:e=>{r.value=e},onChange:e=>{cb=e}}},boolean(e){let t=document.createElement(`adw-switch-row`);t.setAttribute(`title`,e);let cb=()=>{};return t.addEventListener(`notify::active`,e=>cb(e.detail.active)),{node:t,get:()=>t.hasAttribute(`active`),set:e=>{t.toggleAttribute(`active`,e)},onChange:e=>{cb=e}}},number(e){let t=document.createElement(`adw-spin-row`);t.setAttribute(`title`,label(e)),t.setAttribute(`min`,String(e.min??0)),t.setAttribute(`max`,String(e.max??100)),t.setAttribute(`step`,String(e.step??1));let cb=()=>{};return t.addEventListener(`notify::value`,e=>cb(e.detail.value)),{node:t,get:()=>t.value??0,set:e=>{t.value=e},onChange:e=>{cb=e}}},select(e){let t=e.options??[],n=document.createElement(`adw-combo-row`);n.setAttribute(`title`,label(e)),n.setAttribute(`items`,JSON.stringify(t.map(e=>e.label)));let cb=()=>{};return n.addEventListener(`notify::selected`,e=>cb(e.detail.selected)),{node:n,get:()=>n.selected??0,set:e=>{n.selected=e},onChange:e=>{cb=e}}},range(e){let t=e.min??0,n=e.max??100,r=e.step??1,i=Number.isInteger(r),format=e=>i?String(Math.round(e)):e.toFixed(2),a=document.createElement(`div`);a.className=`sb-range-row`;let o=document.createElement(`div`);o.className=`sb-range-header`;let s=document.createElement(`span`);s.className=`sb-range-title`,s.textContent=label(e);let c=document.createElement(`span`);c.className=`sb-range-value`,o.append(s,c);let l=document.createElement(`input`);if(l.type=`range`,l.className=`sb-range-input`,l.min=String(t),l.max=String(n),l.step=String(r),e.description){let t=document.createElement(`span`);t.className=`sb-range-description`,t.textContent=e.description,a.append(o,t,l)}else a.append(o,l);let cb=()=>{};return l.addEventListener(`input`,()=>{let e=parseFloat(l.value);c.textContent=format(e),cb(e)}),{node:a,get:()=>parseFloat(l.value),set:e=>{l.value=String(e),c.textContent=format(e)},onChange:e=>{cb=e}}}};function label(e){return e.label||e.name}export{createControlRow};
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ import{createControlRow as e}from"./controls2.js";import{e as t,injectStorybookStyles as n}from"./styles2.js";import{StoryElement as r}from"./story-element2.js";import{e as i}from"./registry2.js";import{StorybookWebApp as a,mountStorybook as o}from"./app2.js";import{isWebStoryModule as s}from"./types2.js";import{ControlType as c,argsFromControls as l}from"@gjsify/stories";export{c as ControlType,t as STORYBOOK_WEB_CSS,r as StoryElement,i as StoryRegistry,a as StorybookWebApp,l as argsFromControls,e as createControlRow,n as injectStorybookStyles,s as isWebStoryModule,o as mountStorybook};
package/src/index.ts ADDED
@@ -0,0 +1,35 @@
1
+ // @gjsify/adwaita-storybook — browser renderer for the @gjsify/stories contract.
2
+ // The web counterpart of @gjsify/storybook: author *.web.ts files as
3
+ // StoryElement subclasses and render them in a generic Adwaita component browser
4
+ // built from @gjsify/adwaita-web, looking and behaving like the native GTK
5
+ // storybook so the two targets can be screenshot-compared 1:1.
6
+ //
7
+ // A thin DOM adapter over @gjsify/storybook-core — the renderer-agnostic logic
8
+ // (story base, registry, control binding, app controller) lives in core; this
9
+ // package keeps only the @gjsify/adwaita-web view layer.
10
+
11
+ // Re-export the renderer-agnostic authoring contract so a story only needs one
12
+ // import (`@gjsify/adwaita-storybook`). The web-specific WebStoryModule /
13
+ // WebStoryDecorator (which reference StoryElement) intentionally shadow the
14
+ // generic ones from @gjsify/stories.
15
+ export {
16
+ ControlType,
17
+ argsFromControls,
18
+ type StoryArgs,
19
+ type StoryArgValue,
20
+ type StoryBooleanControl,
21
+ type StoryColorControl,
22
+ type StoryControl,
23
+ type StoryMeta,
24
+ type StoryNumberControl,
25
+ type StorySelectControl,
26
+ type StorySelectOption,
27
+ type StoryTextControl,
28
+ } from '@gjsify/stories';
29
+
30
+ export { StoryElement, type StoryArgsListener } from './story-element.js';
31
+ export { StoryRegistry } from './registry.js';
32
+ export { createControlRow, type ControlRow } from './controls.js';
33
+ export { StorybookWebApp, mountStorybook, type StorybookWebOptions, type StorySummary } from './app.js';
34
+ export { STORYBOOK_WEB_CSS, injectStorybookStyles } from './styles.js';
35
+ export { isWebStoryModule, type WebStoryConstructor, type WebStoryDecorator, type WebStoryModule } from './types.js';
@@ -0,0 +1 @@
1
+ import{StoryRegistry as e}from"@gjsify/storybook-core";export{e as StoryRegistry};
@@ -0,0 +1,8 @@
1
+ // StoryRegistry — collects web story modules and instantiates their elements.
2
+ //
3
+ // The registration/instantiation logic (validate, filter, decorator reduceRight
4
+ // fold over initialize()) is renderer-agnostic and now lives in
5
+ // @gjsify/storybook-core. This file just re-exports the generic class; callers
6
+ // type it as `StoryRegistry<StoryElement>`.
7
+
8
+ export { StoryRegistry } from '@gjsify/storybook-core';
@@ -0,0 +1 @@
1
+ import{StoryRegistry as e}from"@gjsify/storybook-core";export{e};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{StoryViewBase as e}from"@gjsify/storybook-core";var StoryElement=class extends e{constructor(e,t=`Default`,n){super(),this.initBase(e,t,n)}get element(){return this.root}createChrome(e,t){return t?{root:t,hasStage:!1,setStageContent:()=>{},setChromeText:()=>{}}:this._installDefaultChrome()}_installDefaultChrome(){let e=document.createElement(`div`);e.className=`sb-story-page`;let t=document.createElement(`div`);t.className=`sb-story-clamp`;let n=document.createElement(`div`);n.className=`sb-story-group`;let r=document.createElement(`div`);r.className=`sb-story-group-header`;let i=document.createElement(`span`);i.className=`sb-story-group-title`;let a=document.createElement(`span`);a.className=`sb-story-group-description`,r.append(i,a);let o=document.createElement(`div`);return o.className=`story-stage`,n.append(r,o),t.append(n),e.append(t),{root:e,hasStage:!0,setStageContent:e=>o.replaceChildren(e),setChromeText:(e,t)=>{i.textContent=e,a.textContent=t,a.hidden=t.length===0}}}};export{StoryElement};
@@ -0,0 +1,100 @@
1
+ // StoryElement — base class for browser story implementations. The web
2
+ // counterpart of @gjsify/storybook's StoryWidget (which extends Adw.Bin): it
3
+ // builds DOM with @gjsify/adwaita-web components instead of GTK widgets, but
4
+ // exposes the same authoring surface (getMetadata, initialize, updateArgs,
5
+ // teardown, addContent, meta/story/args) so a story is a near-1:1 port of its
6
+ // GTK twin and the two render identically.
7
+ //
8
+ // Thin DOM adapter over @gjsify/storybook-core's StoryViewBase: the
9
+ // renderer-agnostic state (meta/story/args, onArgsChanged/setArg, the
10
+ // initialize/updateArgs/teardown/addContent surface) lives in the base; this
11
+ // class supplies ONLY the @gjsify/adwaita-web DOM chrome via createChrome().
12
+
13
+ import type { StoryArgs, StoryMeta } from '@gjsify/stories';
14
+ import { type StoryChrome, StoryViewBase } from '@gjsify/storybook-core';
15
+
16
+ /** Notified whenever a story's args change (the controls panel re-syncs through this). */
17
+ export type StoryArgsListener = (args: StoryArgs) => void;
18
+
19
+ /**
20
+ * Base class for browser story elements.
21
+ *
22
+ * Provides default chrome — a clamped boxed group with a title + description
23
+ * above a centered, dashed-framed preview stage — built programmatically. Simple
24
+ * stories compose their preview by calling {@link addContent}; a subclass that
25
+ * needs a bespoke layout passes its own root element to the constructor (then
26
+ * {@link addContent} is a no-op).
27
+ */
28
+ export class StoryElement extends StoryViewBase<HTMLElement> {
29
+ /**
30
+ * @param meta The story's metadata (the same renderer-agnostic object
31
+ * its GTK twin uses).
32
+ * @param story Variant name (defaults to "Default").
33
+ * @param customRoot Opt out of the default chrome by supplying a root element.
34
+ */
35
+ constructor(meta: StoryMeta, story = 'Default', customRoot?: HTMLElement) {
36
+ super();
37
+ this.initBase(meta, story, customRoot);
38
+ }
39
+
40
+ /**
41
+ * The root node inserted into the storybook's preview area. Alias for the
42
+ * base's {@link root} getter — kept because consumers reference `.element`.
43
+ */
44
+ get element(): HTMLElement {
45
+ return this.root;
46
+ }
47
+
48
+ /** Build the @gjsify/adwaita-web DOM chrome — the single renderer seam. */
49
+ protected createChrome(_meta: StoryMeta, customRoot?: HTMLElement): StoryChrome<HTMLElement> {
50
+ if (customRoot) {
51
+ return {
52
+ root: customRoot,
53
+ hasStage: false,
54
+ setStageContent: () => {},
55
+ setChromeText: () => {},
56
+ };
57
+ }
58
+ return this._installDefaultChrome();
59
+ }
60
+
61
+ private _installDefaultChrome(): StoryChrome<HTMLElement> {
62
+ const page = document.createElement('div');
63
+ page.className = 'sb-story-page';
64
+
65
+ const clamp = document.createElement('div');
66
+ clamp.className = 'sb-story-clamp';
67
+
68
+ const group = document.createElement('div');
69
+ group.className = 'sb-story-group';
70
+
71
+ const header = document.createElement('div');
72
+ header.className = 'sb-story-group-header';
73
+ const titleEl = document.createElement('span');
74
+ titleEl.className = 'sb-story-group-title';
75
+ const descEl = document.createElement('span');
76
+ descEl.className = 'sb-story-group-description';
77
+ header.append(titleEl, descEl);
78
+
79
+ // Subtle dashed frame so the preview's bounds stay locatable even when
80
+ // the widget is transparent or in an empty state (mirrors the native
81
+ // .story-stage in @gjsify/storybook).
82
+ const stage = document.createElement('div');
83
+ stage.className = 'story-stage';
84
+
85
+ group.append(header, stage);
86
+ clamp.append(group);
87
+ page.append(clamp);
88
+
89
+ return {
90
+ root: page,
91
+ hasStage: true,
92
+ setStageContent: (child) => stage.replaceChildren(child),
93
+ setChromeText: (title, description) => {
94
+ titleEl.textContent = title;
95
+ descEl.textContent = description;
96
+ descEl.hidden = description.length === 0;
97
+ },
98
+ };
99
+ }
100
+ }
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import"./_virtual/_rolldown/runtime2.js";import{StoryViewBase as e}from"@gjsify/storybook-core";var StoryElement=class extends e{constructor(e,t=`Default`,n){super(),this.initBase(e,t,n)}get element(){return this.root}createChrome(e,t){return t?{root:t,hasStage:!1,setStageContent:()=>{},setChromeText:()=>{}}:this._installDefaultChrome()}_installDefaultChrome(){let e=document.createElement(`div`);e.className=`sb-story-page`;let t=document.createElement(`div`);t.className=`sb-story-clamp`;let n=document.createElement(`div`);n.className=`sb-story-group`;let r=document.createElement(`div`);r.className=`sb-story-group-header`;let i=document.createElement(`span`);i.className=`sb-story-group-title`;let a=document.createElement(`span`);a.className=`sb-story-group-description`,r.append(i,a);let o=document.createElement(`div`);return o.className=`story-stage`,n.append(r,o),t.append(n),e.append(t),{root:e,hasStage:!0,setStageContent:e=>o.replaceChildren(e),setChromeText:(e,t)=>{i.textContent=e,a.textContent=t,a.hidden=t.length===0}}}};export{StoryElement};
package/src/styles.js ADDED
@@ -0,0 +1,198 @@
1
+ import"./_virtual/_rolldown/runtime.js";const e=`
2
+ .sb-window {
3
+ width: 100%;
4
+ height: 100%;
5
+ min-width: 0;
6
+ min-height: 0;
7
+ }
8
+
9
+ /* Each pane in the outer split view is a flex column that fills its height. */
10
+ .sb-sidebar-pane,
11
+ .sb-content-pane {
12
+ display: flex;
13
+ flex-direction: column;
14
+ min-height: 0;
15
+ min-width: 0;
16
+ flex: 1 1 auto;
17
+ }
18
+
19
+ /* The toolbar-view content holds either a scroller (sidebar) or the split view
20
+ (content); the inner panes own the scrolling, so this level never scrolls. */
21
+ .sb-sidebar-pane adw-toolbar-view > .adw-toolbar-view-content,
22
+ .sb-content-pane adw-toolbar-view > .adw-toolbar-view-content {
23
+ overflow: hidden;
24
+ }
25
+
26
+ /* Background scheme: the content header + preview + controls all sit on the
27
+ window (story) background as one surface; only the left story-list column
28
+ (its header + list) keeps the distinct sidebar shade. Matches the native
29
+ renderer. The default adw-header-bar background is its own headerbar shade,
30
+ so override it per pane here. */
31
+ .sb-content-pane adw-header-bar {
32
+ background-color: var(--window-bg-color);
33
+ }
34
+ .sb-sidebar-pane adw-header-bar {
35
+ background-color: var(--sidebar-bg-color);
36
+ }
37
+
38
+ .sb-sidebar-scroll {
39
+ flex: 1 1 auto;
40
+ min-height: 0;
41
+ overflow-y: auto;
42
+ background-color: var(--sidebar-bg-color);
43
+ }
44
+
45
+ /* --- Story list (mirrors GTK .navigation-sidebar) --- */
46
+ .sb-sidebar-list {
47
+ padding: var(--spacing-xs);
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: 1px;
51
+ }
52
+
53
+ .sb-category {
54
+ font-size: var(--font-size-small);
55
+ font-weight: 700;
56
+ opacity: var(--dim-opacity);
57
+ padding: 10px var(--spacing-m) 4px;
58
+ }
59
+
60
+ .sb-story-row {
61
+ padding: var(--spacing-xs) var(--spacing-m);
62
+ margin: 0 var(--spacing-xs);
63
+ border-radius: var(--button-radius);
64
+ cursor: pointer;
65
+ color: var(--window-fg-color);
66
+ font-size: var(--font-size-base);
67
+ white-space: nowrap;
68
+ overflow: hidden;
69
+ text-overflow: ellipsis;
70
+ }
71
+
72
+ .sb-story-row:hover {
73
+ background-color: var(--button-bg-color);
74
+ }
75
+
76
+ .sb-story-row.selected {
77
+ background-color: var(--accent-bg-color);
78
+ color: var(--accent-fg-color);
79
+ }
80
+
81
+ /* --- Preview area --- */
82
+ .sb-preview-scroll {
83
+ flex: 1 1 auto;
84
+ min-height: 0;
85
+ min-width: 0;
86
+ overflow: auto;
87
+ background-color: var(--window-bg-color);
88
+ }
89
+
90
+ .sb-story-page {
91
+ display: flex;
92
+ justify-content: center;
93
+ padding: var(--spacing-xl) var(--spacing-m);
94
+ min-height: 100%;
95
+ box-sizing: border-box;
96
+ }
97
+
98
+ .sb-story-clamp {
99
+ width: 100%;
100
+ max-width: 600px;
101
+ }
102
+
103
+ .sb-story-group-header {
104
+ padding: var(--spacing-xs) 2px;
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: 2px;
108
+ }
109
+
110
+ .sb-story-group-title {
111
+ font-size: var(--font-size-heading);
112
+ font-weight: 700;
113
+ color: var(--window-fg-color);
114
+ }
115
+
116
+ .sb-story-group-description {
117
+ font-size: var(--font-size-base);
118
+ opacity: var(--dim-opacity);
119
+ color: var(--window-fg-color);
120
+ }
121
+
122
+ /* Centered, dashed-framed preview stage (mirrors the native .story-stage). */
123
+ .story-stage {
124
+ display: flex;
125
+ flex-direction: column;
126
+ align-items: center;
127
+ justify-content: center;
128
+ gap: var(--spacing-m);
129
+ padding: var(--spacing-l);
130
+ margin-top: var(--spacing-s);
131
+ border: 1px dashed rgba(127, 127, 127, 0.28);
132
+ border-radius: var(--card-radius);
133
+ min-height: 80px;
134
+ }
135
+
136
+ /* --- Controls panel --- */
137
+ .sb-controls-scroll {
138
+ height: 100%;
139
+ overflow-y: auto;
140
+ background-color: var(--window-bg-color);
141
+ }
142
+
143
+ .sb-controls-page {
144
+ padding: var(--spacing-l);
145
+ }
146
+
147
+ /* Range-control card (mirrors the native .story-range-row). */
148
+ .sb-range-row {
149
+ display: flex;
150
+ flex-direction: column;
151
+ gap: var(--spacing-xs);
152
+ padding: var(--spacing-m);
153
+ border-bottom: 1px solid var(--card-shade-color);
154
+ }
155
+
156
+ .sb-range-row:last-child {
157
+ border-bottom: none;
158
+ }
159
+
160
+ .sb-range-header {
161
+ display: flex;
162
+ justify-content: space-between;
163
+ align-items: baseline;
164
+ gap: var(--spacing-m);
165
+ }
166
+
167
+ .sb-range-title {
168
+ font-size: var(--font-size-base);
169
+ font-weight: 700;
170
+ color: var(--card-fg-color);
171
+ }
172
+
173
+ .sb-range-value {
174
+ font-size: var(--font-size-base);
175
+ font-variant-numeric: tabular-nums;
176
+ opacity: var(--dim-opacity);
177
+ }
178
+
179
+ .sb-range-description {
180
+ font-size: var(--font-size-small);
181
+ opacity: var(--dim-opacity);
182
+ }
183
+
184
+ .sb-range-input {
185
+ width: 100%;
186
+ accent-color: var(--accent-bg-color);
187
+ }
188
+
189
+ .sb-color-input {
190
+ width: 42px;
191
+ height: 28px;
192
+ padding: 0;
193
+ border: 1px solid var(--card-shade-color);
194
+ border-radius: var(--button-radius);
195
+ background: none;
196
+ cursor: pointer;
197
+ }
198
+ `;let t=!1;function injectStorybookStyles(){if(t||typeof document>`u`)return;if(document.getElementById(`adwaita-storybook-style`)){t=!0;return}let n=document.createElement(`style`);n.id=`adwaita-storybook-style`,n.textContent=e,document.head.appendChild(n),t=!0}export{e as STORYBOOK_WEB_CSS,injectStorybookStyles};