@gjsify/example-gtk-adwaita-storybook 0.19.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.
- package/README.md +120 -0
- package/dist/adw-runasync.mjs +1 -0
- package/dist/gjs.js +91 -0
- package/dist/interact-probe.node.mjs +87 -0
- package/dist/probe.node.mjs +87 -0
- package/dist/render-probe.node.mjs +87 -0
- package/dist/runasync-probe.mjs +1 -0
- package/dist/storybook.node.mjs +87 -0
- package/package.json +64 -0
- package/src/browser/buttons/button-content.web.ts +55 -0
- package/src/browser/buttons/button-styles.web.ts +79 -0
- package/src/browser/buttons/split-button.web.ts +86 -0
- package/src/browser/buttons/toggle-group.web.ts +56 -0
- package/src/browser/embed.ts +46 -0
- package/src/browser/feedback/about-dialog.web.ts +82 -0
- package/src/browser/feedback/alert-dialog.web.ts +72 -0
- package/src/browser/feedback/preferences-dialog.web.ts +106 -0
- package/src/browser/feedback/toast.web.ts +62 -0
- package/src/browser/index.html +23 -0
- package/src/browser/layout/clamp.web.ts +45 -0
- package/src/browser/layout/header-bar.web.ts +56 -0
- package/src/browser/layout/toolbar-view.web.ts +95 -0
- package/src/browser/layout/wrap-box.web.ts +45 -0
- package/src/browser/main.ts +14 -0
- package/src/browser/navigation/bottom-sheet.web.ts +119 -0
- package/src/browser/navigation/navigation-split-view.web.ts +99 -0
- package/src/browser/navigation/navigation-view.web.ts +104 -0
- package/src/browser/navigation/overlay-split-view.web.ts +106 -0
- package/src/browser/navigation/sidebar.web.ts +84 -0
- package/src/browser/presentation/avatar.web.ts +36 -0
- package/src/browser/presentation/banner.web.ts +41 -0
- package/src/browser/presentation/spinner.web.ts +28 -0
- package/src/browser/presentation/status-page.web.ts +43 -0
- package/src/browser/presentation/window-title.web.ts +34 -0
- package/src/browser/rows/action-row.web.ts +61 -0
- package/src/browser/rows/button-row.web.ts +64 -0
- package/src/browser/rows/combo-row.web.ts +44 -0
- package/src/browser/rows/entry-row.web.ts +42 -0
- package/src/browser/rows/expander-row.web.ts +53 -0
- package/src/browser/rows/password-entry-row.web.ts +43 -0
- package/src/browser/rows/preferences-group.web.ts +68 -0
- package/src/browser/rows/spin-row.web.ts +47 -0
- package/src/browser/rows/switch-row.web.ts +43 -0
- package/src/browser/stories.ts +82 -0
- package/src/browser/view-switching/carousel.web.ts +106 -0
- package/src/browser/view-switching/inline-view-switcher.web.ts +55 -0
- package/src/browser/view-switching/tab-view.web.ts +64 -0
- package/src/browser/view-switching/view-switcher.web.ts +55 -0
- package/src/buttons/button-content.meta.ts +26 -0
- package/src/buttons/button-content.story.ts +49 -0
- package/src/buttons/button-styles.meta.ts +26 -0
- package/src/buttons/button-styles.story.ts +77 -0
- package/src/buttons/split-button.meta.ts +45 -0
- package/src/buttons/split-button.story.ts +102 -0
- package/src/buttons/toggle-group.meta.ts +34 -0
- package/src/buttons/toggle-group.story.ts +56 -0
- package/src/feedback/about-dialog.meta.ts +13 -0
- package/src/feedback/about-dialog.story.ts +64 -0
- package/src/feedback/alert-dialog.meta.ts +19 -0
- package/src/feedback/alert-dialog.story.ts +61 -0
- package/src/feedback/preferences-dialog.meta.ts +13 -0
- package/src/feedback/preferences-dialog.story.ts +92 -0
- package/src/feedback/toast.meta.ts +23 -0
- package/src/feedback/toast.story.ts +66 -0
- package/src/layout/clamp.meta.ts +29 -0
- package/src/layout/clamp.story.ts +59 -0
- package/src/layout/header-bar.meta.ts +14 -0
- package/src/layout/header-bar.story.ts +66 -0
- package/src/layout/toolbar-view.meta.ts +18 -0
- package/src/layout/toolbar-view.story.ts +71 -0
- package/src/layout/wrap-box.meta.ts +29 -0
- package/src/layout/wrap-box.story.ts +52 -0
- package/src/metas.ts +41 -0
- package/src/navigation/bottom-sheet.meta.ts +13 -0
- package/src/navigation/bottom-sheet.story.ts +90 -0
- package/src/navigation/navigation-split-view.meta.ts +13 -0
- package/src/navigation/navigation-split-view.story.ts +83 -0
- package/src/navigation/navigation-view.meta.ts +19 -0
- package/src/navigation/navigation-view.story.ts +77 -0
- package/src/navigation/overlay-split-view.meta.ts +13 -0
- package/src/navigation/overlay-split-view.story.ts +82 -0
- package/src/navigation/sidebar.meta.ts +30 -0
- package/src/navigation/sidebar.story.ts +85 -0
- package/src/presentation/avatar.meta.ts +24 -0
- package/src/presentation/avatar.story.ts +46 -0
- package/src/presentation/banner.meta.ts +19 -0
- package/src/presentation/banner.story.ts +53 -0
- package/src/presentation/spinner.meta.ts +9 -0
- package/src/presentation/spinner.story.ts +47 -0
- package/src/presentation/status-page.meta.ts +29 -0
- package/src/presentation/status-page.story.ts +55 -0
- package/src/presentation/window-title.meta.ts +12 -0
- package/src/presentation/window-title.story.ts +42 -0
- package/src/rows/action-row.meta.ts +30 -0
- package/src/rows/action-row.story.ts +62 -0
- package/src/rows/button-row.meta.ts +34 -0
- package/src/rows/button-row.story.ts +61 -0
- package/src/rows/combo-row.meta.ts +29 -0
- package/src/rows/combo-row.story.ts +51 -0
- package/src/rows/entry-row.meta.ts +13 -0
- package/src/rows/entry-row.story.ts +49 -0
- package/src/rows/expander-row.meta.ts +25 -0
- package/src/rows/expander-row.story.ts +54 -0
- package/src/rows/password-entry-row.meta.ts +12 -0
- package/src/rows/password-entry-row.story.ts +47 -0
- package/src/rows/preferences-group.meta.ts +18 -0
- package/src/rows/preferences-group.story.ts +67 -0
- package/src/rows/spin-row.meta.ts +29 -0
- package/src/rows/spin-row.story.ts +56 -0
- package/src/rows/switch-row.meta.ts +18 -0
- package/src/rows/switch-row.story.ts +49 -0
- package/src/view-switching/carousel.meta.ts +35 -0
- package/src/view-switching/carousel.story.ts +131 -0
- package/src/view-switching/inline-view-switcher.meta.ts +25 -0
- package/src/view-switching/inline-view-switcher.story.ts +88 -0
- package/src/view-switching/tab-view.meta.ts +12 -0
- package/src/view-switching/tab-view.story.ts +72 -0
- package/src/view-switching/view-switcher.meta.ts +23 -0
- package/src/view-switching/view-switcher.story.ts +87 -0
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/example-gtk-adwaita-storybook",
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "Interactive component browser for Libadwaita widgets — a native Adwaita storybook built on @gjsify/storybook, debuggable over DBus/MCP via @gjsify/devtools",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/gjs.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
"./browser": "./src/browser/embed.ts",
|
|
13
|
+
"./metas": "./src/metas.ts",
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"clear": "rm -rf dist tsconfig.tsbuildinfo node_modules/.cache/gjsify-storybook",
|
|
18
|
+
"check": "gjsify tsc --noEmit",
|
|
19
|
+
"start": "gjsify storybook",
|
|
20
|
+
"start:watch": "gjsify storybook --watch",
|
|
21
|
+
"start:browser": "http-server dist -o -c-1",
|
|
22
|
+
"build": "gjsify storybook --build-only --out dist/gjs.js",
|
|
23
|
+
"build:browser": "gjsify build src/browser/main.ts --app browser --outfile dist/browser-main.js",
|
|
24
|
+
"build:assets": "mkdir -p dist && cp -f src/browser/index.html dist/index.html",
|
|
25
|
+
"build:web": "gjsify run build:browser && gjsify run build:assets",
|
|
26
|
+
"debug": "gjsify debug --profile storybook"
|
|
27
|
+
},
|
|
28
|
+
"gjsify": {
|
|
29
|
+
"main": "dist/gjs.js",
|
|
30
|
+
"storybook": {
|
|
31
|
+
"applicationId": "org.gjsify.AdwaitaStorybook",
|
|
32
|
+
"title": "Adwaita Storybook",
|
|
33
|
+
"stories": "src",
|
|
34
|
+
"globals": "auto"
|
|
35
|
+
},
|
|
36
|
+
"tier": 2
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@girs/adw-1": "^4.1.0",
|
|
40
|
+
"@girs/gio-2.0": "^4.1.0",
|
|
41
|
+
"@girs/gjs": "^4.1.0",
|
|
42
|
+
"@girs/glib-2.0": "^4.1.0",
|
|
43
|
+
"@girs/gobject-2.0": "^4.1.0",
|
|
44
|
+
"@girs/gtk-4.0": "^4.1.0",
|
|
45
|
+
"@gjsify/adwaita-web": "^0.19.0",
|
|
46
|
+
"@gjsify/cli": "^0.19.0",
|
|
47
|
+
"@gjsify/node-gi": "file:../../../packages/node-gi/node-gi",
|
|
48
|
+
"@gjsify/storybook": "^0.19.0",
|
|
49
|
+
"@gjsify/adwaita-storybook": "^0.19.0",
|
|
50
|
+
"@types/node": "^25.9.2",
|
|
51
|
+
"typescript": "^6.0.3"
|
|
52
|
+
},
|
|
53
|
+
"author": "Pascal Garber <pascal@artandcode.studio>",
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "git+https://github.com/gjsify/gjsify.git",
|
|
58
|
+
"directory": "showcases/gtk/adwaita-storybook"
|
|
59
|
+
},
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/gjsify/gjsify/issues"
|
|
62
|
+
},
|
|
63
|
+
"homepage": "https://github.com/gjsify/gjsify/tree/main/showcases/gtk/adwaita-storybook#readme"
|
|
64
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Browser port of the Button Content story — an <adw-button-content> (icon +
|
|
2
|
+
// label) inside a suggested-action pill <adw-button>, mirroring the GTK twin
|
|
3
|
+
// (button-content.story.ts) which places an Adw.ButtonContent inside a
|
|
4
|
+
// Gtk.Button with the .suggested-action + .pill style classes.
|
|
5
|
+
|
|
6
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
7
|
+
import { buttonContentMeta } from '../../buttons/button-content.meta.js';
|
|
8
|
+
|
|
9
|
+
/** GTK symbolic name (e.g. "folder-download-symbolic") → adwaita-web icon-name. */
|
|
10
|
+
function iconName(gtkName: string): string {
|
|
11
|
+
return gtkName.replace(/-symbolic$/, '');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class ButtonContentWebStory extends StoryElement {
|
|
15
|
+
private _content: HTMLElement | null = null;
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super(ButtonContentWebStory.getMetadata(), 'Default');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static getMetadata(): StoryMeta {
|
|
22
|
+
return buttonContentMeta;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
initialize(): void {
|
|
26
|
+
this._content = document.createElement('adw-button-content');
|
|
27
|
+
this._syncContent();
|
|
28
|
+
|
|
29
|
+
// The native story wraps the content in a suggested-action pill button.
|
|
30
|
+
// Use a plain button carrying the adw-button style classes (the
|
|
31
|
+
// <adw-button> custom element rebuilds its subtree from attributes and
|
|
32
|
+
// would discard the nested <adw-button-content>).
|
|
33
|
+
const button = document.createElement('button');
|
|
34
|
+
button.className = 'adw-button suggested-action pill';
|
|
35
|
+
button.append(this._content);
|
|
36
|
+
|
|
37
|
+
this.addContent(button);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
updateArgs(_args: StoryArgs): void {
|
|
41
|
+
this._syncContent();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
private _syncContent(): void {
|
|
45
|
+
if (!this._content) return;
|
|
46
|
+
|
|
47
|
+
this._content.setAttribute('label', this.args.label as string);
|
|
48
|
+
this._content.setAttribute('icon-name', iconName(this.args.iconName as string));
|
|
49
|
+
|
|
50
|
+
if (this.args.canShrink) this._content.setAttribute('can-shrink', '');
|
|
51
|
+
else this._content.removeAttribute('can-shrink');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const ButtonContentWebStories: WebStoryModule = { stories: [ButtonContentWebStory] };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Browser port of the Button Styles story — adw-button with Adwaita style
|
|
2
|
+
// variants. Shares its metadata/controls with the GTK twin (button-styles.story.ts).
|
|
3
|
+
|
|
4
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
5
|
+
import { buttonStylesMeta } from '../../buttons/button-styles.meta.js';
|
|
6
|
+
|
|
7
|
+
const VARIANT_ATTRS = ['pill', 'circular', 'suggested', 'destructive', 'flat'] as const;
|
|
8
|
+
|
|
9
|
+
/** Map the storybook `style` value to an <adw-button> variant attribute. */
|
|
10
|
+
const VARIANT_BY_STYLE: Record<string, (typeof VARIANT_ATTRS)[number]> = {
|
|
11
|
+
pill: 'pill',
|
|
12
|
+
circular: 'circular',
|
|
13
|
+
'suggested-action': 'suggested',
|
|
14
|
+
'destructive-action': 'destructive',
|
|
15
|
+
flat: 'flat',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function button(opts: { label?: string; icon?: string; variant?: (typeof VARIANT_ATTRS)[number] }): HTMLElement {
|
|
19
|
+
const btn = document.createElement('adw-button');
|
|
20
|
+
if (opts.label) btn.setAttribute('label', opts.label);
|
|
21
|
+
if (opts.icon) btn.setAttribute('icon', opts.icon);
|
|
22
|
+
if (opts.variant) btn.setAttribute(opts.variant, '');
|
|
23
|
+
return btn;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ButtonStylesWebStory extends StoryElement {
|
|
27
|
+
private _demo: HTMLElement | null = null;
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
super(ButtonStylesWebStory.getMetadata(), 'Default');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static getMetadata(): StoryMeta {
|
|
34
|
+
return buttonStylesMeta;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
initialize(): void {
|
|
38
|
+
// A flex-wrap row mirrors the native Adw.WrapBox so the demo reflows
|
|
39
|
+
// instead of overflowing in a narrow preview.
|
|
40
|
+
const box = document.createElement('div');
|
|
41
|
+
box.style.cssText = 'display:flex;flex-wrap:wrap;gap:12px;justify-content:center;align-items:center;';
|
|
42
|
+
|
|
43
|
+
box.append(
|
|
44
|
+
button({ label: 'Pill', variant: 'pill' }),
|
|
45
|
+
button({ icon: 'list-add', variant: 'circular' }),
|
|
46
|
+
button({ label: 'Suggested', variant: 'suggested' }),
|
|
47
|
+
button({ label: 'Delete', variant: 'destructive' }),
|
|
48
|
+
button({ label: 'Flat', variant: 'flat' }),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
this._demo = button({ label: this.args.label as string });
|
|
52
|
+
this._applyStyle(this.args.style as string);
|
|
53
|
+
box.append(this._demo);
|
|
54
|
+
|
|
55
|
+
this.addContent(box);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
updateArgs(_args: StoryArgs): void {
|
|
59
|
+
if (!this._demo) return;
|
|
60
|
+
this._applyStyle(this.args.style as string);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private _applyStyle(style: string): void {
|
|
64
|
+
if (!this._demo) return;
|
|
65
|
+
for (const attr of VARIANT_ATTRS) this._demo.removeAttribute(attr);
|
|
66
|
+
const variant = VARIANT_BY_STYLE[style];
|
|
67
|
+
if (variant) this._demo.setAttribute(variant, '');
|
|
68
|
+
// The circular variant is icon-only (no label), like the native demo.
|
|
69
|
+
if (variant === 'circular') {
|
|
70
|
+
this._demo.setAttribute('icon', 'list-add');
|
|
71
|
+
this._demo.removeAttribute('label');
|
|
72
|
+
} else {
|
|
73
|
+
this._demo.removeAttribute('icon');
|
|
74
|
+
this._demo.setAttribute('label', this.args.label as string);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const ButtonStylesWebStories: WebStoryModule = { stories: [ButtonStylesWebStory] };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Browser port of the Split Button stories — adw-split-button, a primary action
|
|
2
|
+
// paired with a dropdown menu. Shares its metadata/controls with the GTK twins
|
|
3
|
+
// (split-button.story.ts) so the two render identically.
|
|
4
|
+
|
|
5
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
6
|
+
import { splitButtonFlatMeta, splitButtonMeta } from '../../buttons/split-button.meta.js';
|
|
7
|
+
|
|
8
|
+
// The shared dropdown menu model used by both split-button variants — the web
|
|
9
|
+
// twin of buildMenu() in the GTK story.
|
|
10
|
+
const MENU = JSON.stringify([
|
|
11
|
+
{ label: 'Save as…', action: 'app.save-as' },
|
|
12
|
+
{ label: 'Export', action: 'app.export' },
|
|
13
|
+
{ label: 'Print', action: 'app.print' },
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
/** Strip the libadwaita `-symbolic` suffix to the adwaita-web base icon name. */
|
|
17
|
+
function iconBaseName(symbolic: string): string {
|
|
18
|
+
return symbolic.replace(/-symbolic$/, '');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Build an <adw-split-button> from the current story args. */
|
|
22
|
+
function splitButton(args: StoryArgs, opts: { flat?: boolean } = {}): HTMLElement {
|
|
23
|
+
const el = document.createElement('adw-split-button');
|
|
24
|
+
el.setAttribute('menu', MENU);
|
|
25
|
+
el.setAttribute('label', args.label as string);
|
|
26
|
+
const iconName = args.iconName as string;
|
|
27
|
+
if (iconName) el.setAttribute('icon-name', iconBaseName(iconName));
|
|
28
|
+
if (opts.flat) el.setAttribute('flat', '');
|
|
29
|
+
return el;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
abstract class SplitButtonWebStoryBase extends StoryElement {
|
|
33
|
+
protected _widget: HTMLElement | null = null;
|
|
34
|
+
protected abstract get flat(): boolean;
|
|
35
|
+
|
|
36
|
+
initialize(): void {
|
|
37
|
+
this._widget = splitButton(this.args, { flat: this.flat });
|
|
38
|
+
this.addContent(this._widget);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
updateArgs(_args: StoryArgs): void {
|
|
42
|
+
if (!this._widget) return;
|
|
43
|
+
const iconName = this.args.iconName as string;
|
|
44
|
+
// Mirror the GTK story: with an icon set, only the icon is shown; without
|
|
45
|
+
// one, the label drives the action half.
|
|
46
|
+
if (iconName) {
|
|
47
|
+
this._widget.setAttribute('icon-name', iconBaseName(iconName));
|
|
48
|
+
this._widget.removeAttribute('label');
|
|
49
|
+
} else {
|
|
50
|
+
this._widget.removeAttribute('icon-name');
|
|
51
|
+
this._widget.setAttribute('label', this.args.label as string);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Story: Adw.SplitButton with a primary action and a dropdown menu. */
|
|
57
|
+
export class SplitButtonWebStory extends SplitButtonWebStoryBase {
|
|
58
|
+
protected get flat(): boolean {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
constructor() {
|
|
63
|
+
super(SplitButtonWebStory.getMetadata(), 'Default');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static getMetadata(): StoryMeta {
|
|
67
|
+
return splitButtonMeta;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Story: a flat-styled Adw.SplitButton, suited to header bars and toolbars. */
|
|
72
|
+
export class SplitButtonFlatWebStory extends SplitButtonWebStoryBase {
|
|
73
|
+
protected get flat(): boolean {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
constructor() {
|
|
78
|
+
super(SplitButtonFlatWebStory.getMetadata(), 'Default');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
static getMetadata(): StoryMeta {
|
|
82
|
+
return splitButtonFlatMeta;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const SplitButtonWebStories: WebStoryModule = { stories: [SplitButtonWebStory, SplitButtonFlatWebStory] };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Browser port of the Toggle Group story — adw-toggle-group with three
|
|
2
|
+
// adw-toggle children and the Adwaita style variants. Shares its
|
|
3
|
+
// metadata/controls with the GTK twin (toggle-group.story.ts).
|
|
4
|
+
|
|
5
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
6
|
+
import { toggleGroupMeta } from '../../buttons/toggle-group.meta.js';
|
|
7
|
+
|
|
8
|
+
const STYLE_CLASSES = ['flat', 'round'] as const;
|
|
9
|
+
|
|
10
|
+
// The three toggles, mirroring the native demo. (`view-dual` stands in for the
|
|
11
|
+
// native `view-columns-symbolic` — the closest icon in @gjsify/adwaita-icons.)
|
|
12
|
+
const TOGGLES: ReadonlyArray<{ label: string; icon: string }> = [
|
|
13
|
+
{ label: 'List', icon: 'view-list' },
|
|
14
|
+
{ label: 'Grid', icon: 'view-grid' },
|
|
15
|
+
{ label: 'Columns', icon: 'view-dual' },
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export class ToggleGroupWebStory extends StoryElement {
|
|
19
|
+
private _group: HTMLElement | null = null;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
super(ToggleGroupWebStory.getMetadata(), 'Default');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static getMetadata(): StoryMeta {
|
|
26
|
+
return toggleGroupMeta;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
initialize(): void {
|
|
30
|
+
const group = document.createElement('adw-toggle-group');
|
|
31
|
+
for (const { label, icon } of TOGGLES) {
|
|
32
|
+
const toggle = document.createElement('adw-toggle');
|
|
33
|
+
toggle.setAttribute('label', label);
|
|
34
|
+
toggle.setAttribute('icon-name', icon);
|
|
35
|
+
group.appendChild(toggle);
|
|
36
|
+
}
|
|
37
|
+
group.setAttribute('active', String(this.args.active as number));
|
|
38
|
+
this._group = group;
|
|
39
|
+
this._applyStyle(this.args.style as string);
|
|
40
|
+
this.addContent(group);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
updateArgs(_args: StoryArgs): void {
|
|
44
|
+
if (!this._group) return;
|
|
45
|
+
this._group.setAttribute('active', String(this.args.active as number));
|
|
46
|
+
this._applyStyle(this.args.style as string);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private _applyStyle(style: string): void {
|
|
50
|
+
if (!this._group) return;
|
|
51
|
+
for (const cls of STYLE_CLASSES) this._group.classList.remove(cls);
|
|
52
|
+
if (style !== 'default') this._group.classList.add(style);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const ToggleGroupWebStories: WebStoryModule = { stories: [ToggleGroupWebStory] };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Embeddable browser target — `mount(container)` renders the Adwaita storybook
|
|
2
|
+
// into any DOM element, mirroring the `mount()` contract the DOM showcases
|
|
3
|
+
// expose (e.g. `@gjsify/example-dom-minimalist-browser/browser`). The gjsify
|
|
4
|
+
// website uses it to embed the storybook live in its showcase pages and the
|
|
5
|
+
// home-page slideshow.
|
|
6
|
+
//
|
|
7
|
+
// The storybook's root is an `<adw-window class="sb-window">`; the website's
|
|
8
|
+
// embed CSS sizes any `adw-window` to fill its slot, so the storybook fills the
|
|
9
|
+
// container the same way the native window fills its toplevel. The renderer is
|
|
10
|
+
// responsive (it collapses to single-pane navigation below 720px, like the
|
|
11
|
+
// native `Adw.Breakpoint`), so it adapts to both the wide doc-page embed and
|
|
12
|
+
// narrow mobile layouts.
|
|
13
|
+
|
|
14
|
+
import { mountStorybook } from '@gjsify/adwaita-storybook';
|
|
15
|
+
import { stories } from './stories.js';
|
|
16
|
+
|
|
17
|
+
export interface ShowcaseHandle {
|
|
18
|
+
/** No-op: the storybook renders no animation, so there's nothing to pause. */
|
|
19
|
+
pause(): void;
|
|
20
|
+
resume(): void;
|
|
21
|
+
readonly isPaused: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MountOptions {
|
|
25
|
+
/** Window title shown in the sidebar header (default "Adwaita Storybook"). */
|
|
26
|
+
title?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function mount(container: HTMLElement, options?: MountOptions): ShowcaseHandle {
|
|
30
|
+
mountStorybook(container, { title: options?.title ?? 'Adwaita Storybook', stories });
|
|
31
|
+
|
|
32
|
+
// The storybook is static (no rAF loop), so pause/resume are inert — they
|
|
33
|
+
// exist only to satisfy the slideshow's optional ShowcaseHandle contract.
|
|
34
|
+
let paused = false;
|
|
35
|
+
return {
|
|
36
|
+
pause(): void {
|
|
37
|
+
paused = true;
|
|
38
|
+
},
|
|
39
|
+
resume(): void {
|
|
40
|
+
paused = false;
|
|
41
|
+
},
|
|
42
|
+
get isPaused(): boolean {
|
|
43
|
+
return paused;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Browser port of the About Dialog story. Shares metadata with about-dialog.story.ts.
|
|
2
|
+
|
|
3
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
4
|
+
import { aboutDialogMeta } from '../../feedback/about-dialog.meta.js';
|
|
5
|
+
|
|
6
|
+
/** Imperatively-set list/scalar properties on the <adw-about-dialog> element. */
|
|
7
|
+
interface AboutDialogElement extends HTMLElement {
|
|
8
|
+
developers: string[];
|
|
9
|
+
designers: string[];
|
|
10
|
+
present(): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class AboutDialogWebStory extends StoryElement {
|
|
14
|
+
private _dialog: AboutDialogElement | null = null;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
super(AboutDialogWebStory.getMetadata(), 'Default');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static getMetadata(): StoryMeta {
|
|
21
|
+
return aboutDialogMeta;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
initialize(): void {
|
|
25
|
+
// The persistent content — a centered pill button that presents the
|
|
26
|
+
// dialog (mirrors the native story's "Show dialog" button).
|
|
27
|
+
const center = document.createElement('div');
|
|
28
|
+
center.style.display = 'flex';
|
|
29
|
+
center.style.alignItems = 'center';
|
|
30
|
+
center.style.justifyContent = 'center';
|
|
31
|
+
center.style.minHeight = '160px';
|
|
32
|
+
|
|
33
|
+
const button = document.createElement('adw-button');
|
|
34
|
+
button.textContent = 'Show dialog';
|
|
35
|
+
button.setAttribute('pill', '');
|
|
36
|
+
button.setAttribute('suggested', '');
|
|
37
|
+
button.addEventListener('click', () => this._present());
|
|
38
|
+
center.append(button);
|
|
39
|
+
|
|
40
|
+
// The dialog is a fixed full-cover overlay — mount it on the body so the
|
|
41
|
+
// scrim covers the whole viewport, not just the clamped story stage.
|
|
42
|
+
this._dialog = document.createElement('adw-about-dialog') as AboutDialogElement;
|
|
43
|
+
document.body.appendChild(this._dialog);
|
|
44
|
+
|
|
45
|
+
this.addContent(center);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private _present(): void {
|
|
49
|
+
if (!this._dialog) return;
|
|
50
|
+
const dialog = this._dialog;
|
|
51
|
+
dialog.setAttribute('application-name', (this.args.applicationName as string) ?? 'Adwaita Storybook');
|
|
52
|
+
// The native story uses `application-x-executable-symbolic`; that glyph
|
|
53
|
+
// does not ship in the web icon set, so leave it unset to fall back to
|
|
54
|
+
// the component's generic application icon (emblem-system).
|
|
55
|
+
dialog.setAttribute('developer-name', 'A GJSify Project');
|
|
56
|
+
dialog.setAttribute('version', (this.args.version as string) ?? '0.11.0');
|
|
57
|
+
dialog.setAttribute('comments', 'A live catalogue of native GTK and Adwaita widgets, rendered under GJS.');
|
|
58
|
+
dialog.setAttribute('website', 'https://github.com/gjsify/gjsify');
|
|
59
|
+
dialog.setAttribute('issue-url', 'https://github.com/gjsify/gjsify/issues');
|
|
60
|
+
dialog.setAttribute('license', 'The MIT License (MIT)');
|
|
61
|
+
dialog.setAttribute('license-url', 'https://opensource.org/licenses/mit-license.php');
|
|
62
|
+
dialog.setAttribute('copyright', '© 2026 The GJSify Project');
|
|
63
|
+
dialog.developers = ['Ada Lovelace', 'Grace Hopper'];
|
|
64
|
+
dialog.designers = ['Margaret Hamilton'];
|
|
65
|
+
dialog.present();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
updateArgs(_args: StoryArgs): void {
|
|
69
|
+
// The dialog reads the latest args each time it is presented; nothing to mutate live.
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
teardown(): void {
|
|
73
|
+
// The dialog lives on the body, not the story stage — remove it when the
|
|
74
|
+
// story is deselected so it does not leak across navigations.
|
|
75
|
+
if (this._dialog && this._dialog.parentNode) {
|
|
76
|
+
this._dialog.parentNode.removeChild(this._dialog);
|
|
77
|
+
}
|
|
78
|
+
this._dialog = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const AboutDialogWebStories: WebStoryModule = { stories: [AboutDialogWebStory] };
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Browser port of the Alert Dialog story. Shares metadata with alert-dialog.story.ts.
|
|
2
|
+
|
|
3
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
4
|
+
import { alertDialogMeta } from '../../feedback/alert-dialog.meta.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Story: adw-alert-dialog presented from a button, with cancel/destructive
|
|
8
|
+
* responses — a 1:1 port of the GTK AlertDialogStory.
|
|
9
|
+
*/
|
|
10
|
+
export class AlertDialogWebStory extends StoryElement {
|
|
11
|
+
constructor() {
|
|
12
|
+
super(AlertDialogWebStory.getMetadata(), 'Default');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static getMetadata(): StoryMeta {
|
|
16
|
+
return alertDialogMeta;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
initialize(): void {
|
|
20
|
+
const button = document.createElement('adw-button');
|
|
21
|
+
button.textContent = 'Show dialog';
|
|
22
|
+
button.setAttribute('pill', '');
|
|
23
|
+
button.setAttribute('suggested', '');
|
|
24
|
+
button.addEventListener('click', () => this._present());
|
|
25
|
+
|
|
26
|
+
// Centre the trigger button so the preview matches the GTK story's
|
|
27
|
+
// centred halign/valign placement.
|
|
28
|
+
const center = document.createElement('div');
|
|
29
|
+
center.style.display = 'flex';
|
|
30
|
+
center.style.alignItems = 'center';
|
|
31
|
+
center.style.justifyContent = 'center';
|
|
32
|
+
center.style.minHeight = '160px';
|
|
33
|
+
center.append(button);
|
|
34
|
+
|
|
35
|
+
this.addContent(center);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Build + present a fresh dialog, reading the latest args (as the GTK story does). */
|
|
39
|
+
private _present(): void {
|
|
40
|
+
const dialog = document.createElement('adw-alert-dialog') as HTMLElement & {
|
|
41
|
+
addResponse(id: string, label: string): void;
|
|
42
|
+
setResponseAppearance(id: string, appearance: 'default' | 'suggested' | 'destructive'): void;
|
|
43
|
+
setDefaultResponse(id: string | null): void;
|
|
44
|
+
setCloseResponse(id: string): void;
|
|
45
|
+
present(): void;
|
|
46
|
+
};
|
|
47
|
+
dialog.setAttribute('heading', this.args.heading as string);
|
|
48
|
+
dialog.setAttribute('body', this.args.body as string);
|
|
49
|
+
|
|
50
|
+
// The custom element only wires up its responses once connected, so add
|
|
51
|
+
// it to the document before driving the imperative API.
|
|
52
|
+
document.body.appendChild(dialog);
|
|
53
|
+
|
|
54
|
+
dialog.addResponse('cancel', 'Cancel');
|
|
55
|
+
dialog.addResponse('delete', 'Delete');
|
|
56
|
+
dialog.setResponseAppearance('delete', 'destructive');
|
|
57
|
+
dialog.setDefaultResponse('cancel');
|
|
58
|
+
dialog.setCloseResponse('cancel');
|
|
59
|
+
|
|
60
|
+
// Remove the dialog from the DOM once the user responds — the GTK dialog
|
|
61
|
+
// is likewise destroyed on response.
|
|
62
|
+
dialog.addEventListener('response', () => dialog.remove(), { once: true });
|
|
63
|
+
|
|
64
|
+
dialog.present();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
updateArgs(_args: StoryArgs): void {
|
|
68
|
+
// The dialog reads the latest args each time it is presented; nothing to mutate live.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const AlertDialogWebStories: WebStoryModule = { stories: [AlertDialogWebStory] };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Browser port of the Preferences Dialog story. Shares metadata with
|
|
2
|
+
// preferences-dialog.story.ts and presents the same single-page dialog from a
|
|
3
|
+
// "Show dialog" pill button.
|
|
4
|
+
|
|
5
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
6
|
+
import { preferencesDialogMeta } from '../../feedback/preferences-dialog.meta.js';
|
|
7
|
+
|
|
8
|
+
export class PreferencesDialogWebStory extends StoryElement {
|
|
9
|
+
private _dialog: HTMLElement | null = null;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
super(PreferencesDialogWebStory.getMetadata(), 'Default');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static getMetadata(): StoryMeta {
|
|
16
|
+
return preferencesDialogMeta;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Build the single page of grouped rows, mirroring the native story. */
|
|
20
|
+
private _buildDialog(): HTMLElement {
|
|
21
|
+
const dialog = document.createElement('adw-preferences-dialog');
|
|
22
|
+
dialog.setAttribute('title', 'Preferences');
|
|
23
|
+
|
|
24
|
+
const page = document.createElement('adw-preferences-page');
|
|
25
|
+
page.setAttribute('title', (this.args.pageTitle as string) ?? 'General');
|
|
26
|
+
page.setAttribute('icon-name', 'preferences-system-symbolic');
|
|
27
|
+
|
|
28
|
+
const group = document.createElement('adw-preferences-group');
|
|
29
|
+
group.setAttribute('title', (this.args.groupTitle as string) ?? 'Appearance');
|
|
30
|
+
group.setAttribute('description', 'Control how the application looks and behaves.');
|
|
31
|
+
|
|
32
|
+
// Dark style — a switch row, on by default (matches the native SwitchRow).
|
|
33
|
+
const darkStyle = document.createElement('adw-switch-row');
|
|
34
|
+
darkStyle.setAttribute('title', 'Dark style');
|
|
35
|
+
darkStyle.setAttribute('subtitle', 'Use a dark colour scheme');
|
|
36
|
+
darkStyle.setAttribute('active', '');
|
|
37
|
+
group.append(darkStyle);
|
|
38
|
+
|
|
39
|
+
// Accent colour — a combo row over the same five options.
|
|
40
|
+
const accent = document.createElement('adw-combo-row');
|
|
41
|
+
accent.setAttribute('title', 'Accent colour');
|
|
42
|
+
accent.setAttribute('items', JSON.stringify(['Blue', 'Teal', 'Green', 'Orange', 'Purple']));
|
|
43
|
+
accent.setAttribute('selected', '0');
|
|
44
|
+
group.append(accent);
|
|
45
|
+
|
|
46
|
+
// Font size — a spin row bounded 8–24, defaulting to 12.
|
|
47
|
+
const fontSize = document.createElement('adw-spin-row');
|
|
48
|
+
fontSize.setAttribute('title', 'Font size');
|
|
49
|
+
fontSize.setAttribute('min', '8');
|
|
50
|
+
fontSize.setAttribute('max', '24');
|
|
51
|
+
fontSize.setAttribute('step', '1');
|
|
52
|
+
fontSize.setAttribute('value', '12');
|
|
53
|
+
group.append(fontSize);
|
|
54
|
+
|
|
55
|
+
page.append(group);
|
|
56
|
+
dialog.append(page);
|
|
57
|
+
return dialog;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
initialize(): void {
|
|
61
|
+
// The persistent story content: a centered pill button that presents the
|
|
62
|
+
// dialog (the native story's "Show dialog" trigger).
|
|
63
|
+
const center = document.createElement('div');
|
|
64
|
+
center.style.display = 'flex';
|
|
65
|
+
center.style.alignItems = 'center';
|
|
66
|
+
center.style.justifyContent = 'center';
|
|
67
|
+
center.style.width = '100%';
|
|
68
|
+
center.style.height = '100%';
|
|
69
|
+
center.style.minHeight = '200px';
|
|
70
|
+
|
|
71
|
+
const button = document.createElement('adw-button');
|
|
72
|
+
button.textContent = 'Show dialog';
|
|
73
|
+
button.setAttribute('pill', '');
|
|
74
|
+
button.setAttribute('suggested', '');
|
|
75
|
+
button.addEventListener('click', () => this._present());
|
|
76
|
+
|
|
77
|
+
center.append(button);
|
|
78
|
+
this.addContent(center);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private _present(): void {
|
|
82
|
+
// Rebuild the dialog from the latest args each time it is presented (the
|
|
83
|
+
// native story does the same), then reveal it. The dialog appends itself
|
|
84
|
+
// to <body> so its fixed scrim covers the whole viewport.
|
|
85
|
+
if (this._dialog) this._dialog.remove();
|
|
86
|
+
this._dialog = this._buildDialog();
|
|
87
|
+
this._dialog.addEventListener('closed', () => {
|
|
88
|
+
this._dialog?.remove();
|
|
89
|
+
this._dialog = null;
|
|
90
|
+
});
|
|
91
|
+
document.body.appendChild(this._dialog);
|
|
92
|
+
// Reveal after connect so the element is upgraded + initialized.
|
|
93
|
+
requestAnimationFrame(() => this._dialog?.setAttribute('open', ''));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
updateArgs(_args: StoryArgs): void {
|
|
97
|
+
// The dialog is rebuilt from the latest args each time it is presented.
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
teardown(): void {
|
|
101
|
+
this._dialog?.remove();
|
|
102
|
+
this._dialog = null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const PreferencesDialogWebStories: WebStoryModule = { stories: [PreferencesDialogWebStory] };
|