@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.
Files changed (119) hide show
  1. package/README.md +120 -0
  2. package/dist/adw-runasync.mjs +1 -0
  3. package/dist/gjs.js +91 -0
  4. package/dist/interact-probe.node.mjs +87 -0
  5. package/dist/probe.node.mjs +87 -0
  6. package/dist/render-probe.node.mjs +87 -0
  7. package/dist/runasync-probe.mjs +1 -0
  8. package/dist/storybook.node.mjs +87 -0
  9. package/package.json +64 -0
  10. package/src/browser/buttons/button-content.web.ts +55 -0
  11. package/src/browser/buttons/button-styles.web.ts +79 -0
  12. package/src/browser/buttons/split-button.web.ts +86 -0
  13. package/src/browser/buttons/toggle-group.web.ts +56 -0
  14. package/src/browser/embed.ts +46 -0
  15. package/src/browser/feedback/about-dialog.web.ts +82 -0
  16. package/src/browser/feedback/alert-dialog.web.ts +72 -0
  17. package/src/browser/feedback/preferences-dialog.web.ts +106 -0
  18. package/src/browser/feedback/toast.web.ts +62 -0
  19. package/src/browser/index.html +23 -0
  20. package/src/browser/layout/clamp.web.ts +45 -0
  21. package/src/browser/layout/header-bar.web.ts +56 -0
  22. package/src/browser/layout/toolbar-view.web.ts +95 -0
  23. package/src/browser/layout/wrap-box.web.ts +45 -0
  24. package/src/browser/main.ts +14 -0
  25. package/src/browser/navigation/bottom-sheet.web.ts +119 -0
  26. package/src/browser/navigation/navigation-split-view.web.ts +99 -0
  27. package/src/browser/navigation/navigation-view.web.ts +104 -0
  28. package/src/browser/navigation/overlay-split-view.web.ts +106 -0
  29. package/src/browser/navigation/sidebar.web.ts +84 -0
  30. package/src/browser/presentation/avatar.web.ts +36 -0
  31. package/src/browser/presentation/banner.web.ts +41 -0
  32. package/src/browser/presentation/spinner.web.ts +28 -0
  33. package/src/browser/presentation/status-page.web.ts +43 -0
  34. package/src/browser/presentation/window-title.web.ts +34 -0
  35. package/src/browser/rows/action-row.web.ts +61 -0
  36. package/src/browser/rows/button-row.web.ts +64 -0
  37. package/src/browser/rows/combo-row.web.ts +44 -0
  38. package/src/browser/rows/entry-row.web.ts +42 -0
  39. package/src/browser/rows/expander-row.web.ts +53 -0
  40. package/src/browser/rows/password-entry-row.web.ts +43 -0
  41. package/src/browser/rows/preferences-group.web.ts +68 -0
  42. package/src/browser/rows/spin-row.web.ts +47 -0
  43. package/src/browser/rows/switch-row.web.ts +43 -0
  44. package/src/browser/stories.ts +82 -0
  45. package/src/browser/view-switching/carousel.web.ts +106 -0
  46. package/src/browser/view-switching/inline-view-switcher.web.ts +55 -0
  47. package/src/browser/view-switching/tab-view.web.ts +64 -0
  48. package/src/browser/view-switching/view-switcher.web.ts +55 -0
  49. package/src/buttons/button-content.meta.ts +26 -0
  50. package/src/buttons/button-content.story.ts +49 -0
  51. package/src/buttons/button-styles.meta.ts +26 -0
  52. package/src/buttons/button-styles.story.ts +77 -0
  53. package/src/buttons/split-button.meta.ts +45 -0
  54. package/src/buttons/split-button.story.ts +102 -0
  55. package/src/buttons/toggle-group.meta.ts +34 -0
  56. package/src/buttons/toggle-group.story.ts +56 -0
  57. package/src/feedback/about-dialog.meta.ts +13 -0
  58. package/src/feedback/about-dialog.story.ts +64 -0
  59. package/src/feedback/alert-dialog.meta.ts +19 -0
  60. package/src/feedback/alert-dialog.story.ts +61 -0
  61. package/src/feedback/preferences-dialog.meta.ts +13 -0
  62. package/src/feedback/preferences-dialog.story.ts +92 -0
  63. package/src/feedback/toast.meta.ts +23 -0
  64. package/src/feedback/toast.story.ts +66 -0
  65. package/src/layout/clamp.meta.ts +29 -0
  66. package/src/layout/clamp.story.ts +59 -0
  67. package/src/layout/header-bar.meta.ts +14 -0
  68. package/src/layout/header-bar.story.ts +66 -0
  69. package/src/layout/toolbar-view.meta.ts +18 -0
  70. package/src/layout/toolbar-view.story.ts +71 -0
  71. package/src/layout/wrap-box.meta.ts +29 -0
  72. package/src/layout/wrap-box.story.ts +52 -0
  73. package/src/metas.ts +41 -0
  74. package/src/navigation/bottom-sheet.meta.ts +13 -0
  75. package/src/navigation/bottom-sheet.story.ts +90 -0
  76. package/src/navigation/navigation-split-view.meta.ts +13 -0
  77. package/src/navigation/navigation-split-view.story.ts +83 -0
  78. package/src/navigation/navigation-view.meta.ts +19 -0
  79. package/src/navigation/navigation-view.story.ts +77 -0
  80. package/src/navigation/overlay-split-view.meta.ts +13 -0
  81. package/src/navigation/overlay-split-view.story.ts +82 -0
  82. package/src/navigation/sidebar.meta.ts +30 -0
  83. package/src/navigation/sidebar.story.ts +85 -0
  84. package/src/presentation/avatar.meta.ts +24 -0
  85. package/src/presentation/avatar.story.ts +46 -0
  86. package/src/presentation/banner.meta.ts +19 -0
  87. package/src/presentation/banner.story.ts +53 -0
  88. package/src/presentation/spinner.meta.ts +9 -0
  89. package/src/presentation/spinner.story.ts +47 -0
  90. package/src/presentation/status-page.meta.ts +29 -0
  91. package/src/presentation/status-page.story.ts +55 -0
  92. package/src/presentation/window-title.meta.ts +12 -0
  93. package/src/presentation/window-title.story.ts +42 -0
  94. package/src/rows/action-row.meta.ts +30 -0
  95. package/src/rows/action-row.story.ts +62 -0
  96. package/src/rows/button-row.meta.ts +34 -0
  97. package/src/rows/button-row.story.ts +61 -0
  98. package/src/rows/combo-row.meta.ts +29 -0
  99. package/src/rows/combo-row.story.ts +51 -0
  100. package/src/rows/entry-row.meta.ts +13 -0
  101. package/src/rows/entry-row.story.ts +49 -0
  102. package/src/rows/expander-row.meta.ts +25 -0
  103. package/src/rows/expander-row.story.ts +54 -0
  104. package/src/rows/password-entry-row.meta.ts +12 -0
  105. package/src/rows/password-entry-row.story.ts +47 -0
  106. package/src/rows/preferences-group.meta.ts +18 -0
  107. package/src/rows/preferences-group.story.ts +67 -0
  108. package/src/rows/spin-row.meta.ts +29 -0
  109. package/src/rows/spin-row.story.ts +56 -0
  110. package/src/rows/switch-row.meta.ts +18 -0
  111. package/src/rows/switch-row.story.ts +49 -0
  112. package/src/view-switching/carousel.meta.ts +35 -0
  113. package/src/view-switching/carousel.story.ts +131 -0
  114. package/src/view-switching/inline-view-switcher.meta.ts +25 -0
  115. package/src/view-switching/inline-view-switcher.story.ts +88 -0
  116. package/src/view-switching/tab-view.meta.ts +12 -0
  117. package/src/view-switching/tab-view.story.ts +72 -0
  118. package/src/view-switching/view-switcher.meta.ts +23 -0
  119. package/src/view-switching/view-switcher.story.ts +87 -0
@@ -0,0 +1,62 @@
1
+ // Browser port of the Toast story. Shares metadata with toast.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { toastMeta } from '../../feedback/toast.meta.js';
5
+
6
+ /** The <adw-toast-overlay> exposes addToast(title, opts) — see @gjsify/adwaita-web. */
7
+ interface ToastOverlayElement extends HTMLElement {
8
+ addToast(title: string, options?: { timeout?: number; buttonLabel?: string; onAction?: () => void }): void;
9
+ }
10
+
11
+ /** Story: an Adw.Toast presented through an adw-toast-overlay on button press. */
12
+ export class ToastWebStory extends StoryElement {
13
+ private _overlay: ToastOverlayElement | null = null;
14
+
15
+ constructor() {
16
+ super(ToastWebStory.getMetadata(), 'Default');
17
+ }
18
+
19
+ static getMetadata(): StoryMeta {
20
+ return toastMeta;
21
+ }
22
+
23
+ initialize(): void {
24
+ this._overlay = document.createElement('adw-toast-overlay') as ToastOverlayElement;
25
+ // Match the native story's fixed 360×220 viewport so the toast has a
26
+ // bounded overlay area to float over.
27
+ this._overlay.style.width = '360px';
28
+ this._overlay.style.height = '220px';
29
+
30
+ const center = document.createElement('div');
31
+ center.style.display = 'flex';
32
+ center.style.alignItems = 'center';
33
+ center.style.justifyContent = 'center';
34
+ center.style.width = '100%';
35
+ center.style.height = '100%';
36
+
37
+ const button = document.createElement('adw-button');
38
+ button.textContent = 'Show toast';
39
+ button.setAttribute('pill', '');
40
+ button.setAttribute('suggested', '');
41
+ button.addEventListener('click', () => this._showToast());
42
+ center.append(button);
43
+
44
+ this._overlay.append(center);
45
+ this.addContent(this._overlay);
46
+ }
47
+
48
+ private _showToast(): void {
49
+ if (!this._overlay) return;
50
+ const hasButton = this.args.hasButton as boolean;
51
+ this._overlay.addToast(this.args.title as string, {
52
+ timeout: this.args.timeout as number,
53
+ buttonLabel: hasButton ? (this.args.buttonLabel as string) : undefined,
54
+ });
55
+ }
56
+
57
+ updateArgs(_args: StoryArgs): void {
58
+ // Toasts are built on demand in _showToast(); nothing to re-apply to the overlay.
59
+ }
60
+ }
61
+
62
+ export const ToastWebStories: WebStoryModule = { stories: [ToastWebStory] };
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Adwaita Storybook — gjsify showcase</title>
7
+ <style>
8
+ html, body { margin: 0; height: 100%; }
9
+ body {
10
+ box-sizing: border-box;
11
+ display: grid;
12
+ padding: 28px;
13
+ /* A neutral "desktop" so the Adwaita window's rounded corners +
14
+ shadow read the way a GNOME app does on a session. */
15
+ background: radial-gradient(120% 120% at 25% 15%, #5b6b87 0%, #353a4a 55%, #20242e 100%);
16
+ }
17
+ body > adw-window { min-height: 0; min-width: 0; }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <script type="module" src="browser-main.js"></script>
22
+ </body>
23
+ </html>
@@ -0,0 +1,45 @@
1
+ // Browser port of the Clamp story. Shares metadata with clamp.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { clampMeta } from '../../layout/clamp.meta.js';
5
+
6
+ export class ClampWebStory extends StoryElement {
7
+ private _clamp: HTMLElement | null = null;
8
+
9
+ constructor() {
10
+ super(ClampWebStory.getMetadata(), 'Default');
11
+ }
12
+
13
+ static getMetadata(): StoryMeta {
14
+ return clampMeta;
15
+ }
16
+
17
+ initialize(): void {
18
+ // The card fills the clamp, which caps its width at maximum-size and
19
+ // centres it — so the text wraps within the clamped width (a fixed
20
+ // width wider than the clamp would overflow and clip instead).
21
+ const inner = document.createElement('adw-card');
22
+ inner.style.width = '100%';
23
+ inner.style.boxSizing = 'border-box';
24
+ inner.style.padding = '18px';
25
+ inner.textContent =
26
+ 'This content is clamped — it stops growing past the maximum size and stays centred.';
27
+
28
+ this._clamp = document.createElement('adw-clamp');
29
+ this._clamp.append(inner);
30
+ this._syncClamp();
31
+
32
+ this.addContent(this._clamp);
33
+ }
34
+
35
+ updateArgs(_args: StoryArgs): void {
36
+ this._syncClamp();
37
+ }
38
+
39
+ private _syncClamp(): void {
40
+ if (!this._clamp) return;
41
+ this._clamp.setAttribute('maximum-size', String(this.args.maximumSize as number));
42
+ }
43
+ }
44
+
45
+ export const ClampWebStories: WebStoryModule = { stories: [ClampWebStory] };
@@ -0,0 +1,56 @@
1
+ // Browser port of the Header Bar story. Shares metadata with header-bar.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { headerBarMeta } from '../../layout/header-bar.meta.js';
5
+
6
+ export class HeaderBarWebStory extends StoryElement {
7
+ private _headerBar: HTMLElement | null = null;
8
+ private _title: HTMLElement | null = null;
9
+
10
+ constructor() {
11
+ super(HeaderBarWebStory.getMetadata(), 'Default');
12
+ }
13
+
14
+ static getMetadata(): StoryMeta {
15
+ return headerBarMeta;
16
+ }
17
+
18
+ initialize(): void {
19
+ // Center title widget — the equivalent of Adw.HeaderBar's title-widget.
20
+ this._title = document.createElement('adw-window-title');
21
+ this._title.setAttribute('slot', 'center');
22
+
23
+ // Start control — a flat back button.
24
+ const backButton = document.createElement('adw-button');
25
+ backButton.setAttribute('slot', 'start');
26
+ backButton.setAttribute('icon', 'go-previous');
27
+ backButton.setAttribute('flat', '');
28
+
29
+ // End control — a flat menu button.
30
+ const menuButton = document.createElement('adw-button');
31
+ menuButton.setAttribute('slot', 'end');
32
+ menuButton.setAttribute('icon', 'open-menu');
33
+ menuButton.setAttribute('flat', '');
34
+
35
+ this._headerBar = document.createElement('adw-header-bar');
36
+ this._headerBar.style.width = '460px';
37
+ this._headerBar.append(backButton, this._title, menuButton);
38
+
39
+ this._sync();
40
+ this.addContent(this._headerBar);
41
+ }
42
+
43
+ updateArgs(_args: StoryArgs): void {
44
+ this._sync();
45
+ }
46
+
47
+ private _sync(): void {
48
+ if (!this._title) return;
49
+ this._title.setAttribute('title', this.args.title as string);
50
+ this._title.setAttribute('subtitle', this.args.subtitle as string);
51
+ // Adw.HeaderBar.showTitle toggles visibility of the title widget.
52
+ this._title.hidden = !(this.args.showTitle as boolean);
53
+ }
54
+ }
55
+
56
+ export const HeaderBarWebStories: WebStoryModule = { stories: [HeaderBarWebStory] };
@@ -0,0 +1,95 @@
1
+ // Browser port of the Toolbar View story. Shares metadata with toolbar-view.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { toolbarViewMeta } from '../../layout/toolbar-view.meta.js';
5
+
6
+ export class ToolbarViewWebStory extends StoryElement {
7
+ private _topBar: HTMLElement | null = null;
8
+ private _bottomBar: HTMLElement | null = null;
9
+
10
+ constructor() {
11
+ super(ToolbarViewWebStory.getMetadata(), 'Default');
12
+ }
13
+
14
+ static getMetadata(): StoryMeta {
15
+ return toolbarViewMeta;
16
+ }
17
+
18
+ initialize(): void {
19
+ const view = document.createElement('adw-toolbar-view');
20
+ view.style.width = '460px';
21
+ view.style.height = '320px';
22
+
23
+ // Top header bar with a window title (matches Adw.HeaderBar + WindowTitle).
24
+ const header = document.createElement('adw-header-bar');
25
+ header.setAttribute('slot', 'top');
26
+ const title = document.createElement('adw-window-title');
27
+ title.setAttribute('slot', 'center');
28
+ title.setAttribute('title', 'Documents');
29
+ title.setAttribute('subtitle', '12 items');
30
+ header.appendChild(title);
31
+ view.appendChild(header);
32
+ this._topBar = header;
33
+
34
+ // Scrollable content — a status page sits between the toolbars.
35
+ const content = document.createElement('adw-status-page');
36
+ content.setAttribute('icon', 'folder');
37
+ content.setAttribute('title', 'Your library');
38
+ content.setAttribute(
39
+ 'description',
40
+ 'Content sits between the toolbars and scrolls independently of them.',
41
+ );
42
+ view.appendChild(content);
43
+
44
+ // Bottom action bar — flat start buttons, a centered label and an end button
45
+ // (mirrors the native Gtk.ActionBar with pack_start / center / pack_end).
46
+ const bottomBar = document.createElement('div');
47
+ bottomBar.setAttribute('slot', 'bottom');
48
+ bottomBar.className = 'adw-action-bar';
49
+ bottomBar.style.display = 'flex';
50
+ bottomBar.style.alignItems = 'center';
51
+ bottomBar.style.gap = 'var(--spacing-xs, 6px)';
52
+ bottomBar.style.padding = 'var(--spacing-xs, 6px)';
53
+ bottomBar.style.borderTop = '1px solid var(--separator-color, rgba(0, 0, 0, 0.15))';
54
+
55
+ const addButton = document.createElement('adw-button');
56
+ addButton.setAttribute('icon', 'list-add');
57
+ addButton.setAttribute('flat', '');
58
+ addButton.setAttribute('tooltip', 'Add');
59
+
60
+ const removeButton = document.createElement('adw-button');
61
+ removeButton.setAttribute('icon', 'list-remove');
62
+ removeButton.setAttribute('flat', '');
63
+ removeButton.setAttribute('tooltip', 'Remove');
64
+
65
+ const selectionLabel = document.createElement('span');
66
+ selectionLabel.textContent = 'Selection: none';
67
+ selectionLabel.style.flex = '1 1 auto';
68
+ selectionLabel.style.textAlign = 'center';
69
+
70
+ const shareButton = document.createElement('adw-button');
71
+ shareButton.setAttribute('icon', 'send-to');
72
+ shareButton.setAttribute('flat', '');
73
+ shareButton.setAttribute('tooltip', 'Share');
74
+
75
+ bottomBar.append(addButton, removeButton, selectionLabel, shareButton);
76
+ view.appendChild(bottomBar);
77
+ this._bottomBar = bottomBar;
78
+
79
+ this._sync();
80
+ this.addContent(view);
81
+ }
82
+
83
+ updateArgs(_args: StoryArgs): void {
84
+ this._sync();
85
+ }
86
+
87
+ private _sync(): void {
88
+ // The web toolbar view has no reveal properties; mirror revealTopBars /
89
+ // revealBottomBars by collapsing the top/bottom bars themselves.
90
+ if (this._topBar) this._topBar.hidden = !(this.args.revealTopBars as boolean);
91
+ if (this._bottomBar) this._bottomBar.hidden = !(this.args.revealBottomBars as boolean);
92
+ }
93
+ }
94
+
95
+ export const ToolbarViewWebStories: WebStoryModule = { stories: [ToolbarViewWebStory] };
@@ -0,0 +1,45 @@
1
+ // Browser port of the Wrap Box story. Shares metadata with wrap-box.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { wrapBoxMeta } from '../../layout/wrap-box.meta.js';
5
+
6
+ const TAGS = ['Design', 'Adwaita', 'GNOME', 'GTK', 'Typescript', 'Storybook', 'Wrapping', 'Layout'];
7
+
8
+ export class WrapBoxWebStory extends StoryElement {
9
+ private _wrap: HTMLElement | null = null;
10
+
11
+ constructor() {
12
+ super(WrapBoxWebStory.getMetadata(), 'Default');
13
+ }
14
+
15
+ static getMetadata(): StoryMeta {
16
+ return wrapBoxMeta;
17
+ }
18
+
19
+ initialize(): void {
20
+ this._wrap = document.createElement('adw-wrap-box');
21
+ this._wrap.style.width = '460px';
22
+
23
+ for (const tag of TAGS) {
24
+ const chip = document.createElement('adw-button');
25
+ chip.setAttribute('label', tag);
26
+ chip.setAttribute('pill', '');
27
+ this._wrap.append(chip);
28
+ }
29
+
30
+ this._syncWrap();
31
+ this.addContent(this._wrap);
32
+ }
33
+
34
+ updateArgs(_args: StoryArgs): void {
35
+ this._syncWrap();
36
+ }
37
+
38
+ private _syncWrap(): void {
39
+ if (!this._wrap) return;
40
+ this._wrap.setAttribute('child-spacing', String(this.args.childSpacing as number));
41
+ this._wrap.setAttribute('line-spacing', String(this.args.lineSpacing as number));
42
+ }
43
+ }
44
+
45
+ export const WrapBoxWebStories: WebStoryModule = { stories: [WrapBoxWebStory] };
@@ -0,0 +1,14 @@
1
+ // Browser entry — mounts the Adwaita storybook as a web app built from
2
+ // @gjsify/adwaita-web custom elements, mirroring the native GTK storybook
3
+ // (launched via `gjsify storybook`). Each *.web.ts story shares its metadata
4
+ // with the GTK *.story.ts twin, so the two targets expose identical controls
5
+ // and can be compared 1:1 via screenshots.
6
+ //
7
+ // The story list lives in `./stories.ts` so the standalone entry here and the
8
+ // embeddable `mount(container)` (`./embed.ts`, used by the gjsify website) stay
9
+ // in lockstep.
10
+
11
+ import { mountStorybook } from '@gjsify/adwaita-storybook';
12
+ import { stories } from './stories.js';
13
+
14
+ mountStorybook(document.body, { title: 'Adwaita Storybook', stories });
@@ -0,0 +1,119 @@
1
+ // Browser port of the Bottom Sheet story. Shares metadata with bottom-sheet.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { bottomSheetMeta } from '../../navigation/bottom-sheet.meta.js';
5
+
6
+ /** GTK symbolic name (e.g. "list-add-symbolic") → adwaita-web icon class. */
7
+ function iconClass(gtkName: string): string {
8
+ return `adw-icon adw-icon--${gtkName.replace(/-symbolic$/, '')}`;
9
+ }
10
+
11
+ export class BottomSheetWebStory extends StoryElement {
12
+ private _sheet: HTMLElement | null = null;
13
+
14
+ constructor() {
15
+ super(BottomSheetWebStory.getMetadata(), 'Default');
16
+ }
17
+
18
+ static getMetadata(): StoryMeta {
19
+ return bottomSheetMeta;
20
+ }
21
+
22
+ /** The persistent content — a centered pill button that toggles the sheet. */
23
+ private _buildContent(): HTMLElement {
24
+ const content = document.createElement('adw-bottom-sheet-content');
25
+
26
+ const center = document.createElement('div');
27
+ center.style.display = 'flex';
28
+ center.style.alignItems = 'center';
29
+ center.style.justifyContent = 'center';
30
+ center.style.width = '100%';
31
+ center.style.height = '100%';
32
+
33
+ const toggle = document.createElement('adw-button');
34
+ toggle.textContent = 'Toggle sheet';
35
+ toggle.setAttribute('pill', '');
36
+ toggle.setAttribute('suggested', '');
37
+ toggle.addEventListener('click', () => {
38
+ if (this._sheet) this._sheet.toggleAttribute('open');
39
+ });
40
+
41
+ center.append(toggle);
42
+ content.append(center);
43
+ return content;
44
+ }
45
+
46
+ /** The sheet — a title above a preferences group of share actions. */
47
+ private _buildSheet(): HTMLElement {
48
+ const sheet = document.createElement('adw-bottom-sheet-sheet');
49
+
50
+ const box = document.createElement('div');
51
+ box.style.display = 'flex';
52
+ box.style.flexDirection = 'column';
53
+ box.style.gap = '12px';
54
+ box.style.margin = '18px 18px 24px';
55
+
56
+ // Match the GTK .title-2 typography style (bold heading) inline — the
57
+ // adwaita-web skin has no typography utility class for it.
58
+ const title = document.createElement('span');
59
+ title.textContent = 'Share track';
60
+ title.style.fontSize = '15pt';
61
+ title.style.fontWeight = '800';
62
+ box.append(title);
63
+
64
+ const group = document.createElement('adw-preferences-group');
65
+ for (const [rowTitle, icon] of [
66
+ ['Copy link', 'edit-copy-symbolic'],
67
+ ['Send to a friend', 'mail-send-symbolic'],
68
+ ['Add to playlist', 'list-add-symbolic'],
69
+ ] as const) {
70
+ const row = document.createElement('adw-action-row');
71
+ row.setAttribute('title', rowTitle);
72
+ row.setAttribute('activatable', '');
73
+
74
+ const prefix = document.createElement('span');
75
+ prefix.setAttribute('slot', 'prefix');
76
+ prefix.className = iconClass(icon);
77
+ row.append(prefix);
78
+
79
+ group.append(row);
80
+ }
81
+ box.append(group);
82
+
83
+ sheet.append(box);
84
+ return sheet;
85
+ }
86
+
87
+ initialize(): void {
88
+ this._sheet = document.createElement('adw-bottom-sheet');
89
+ // Match the native story's fixed 480×340 viewport so the sheet has a
90
+ // bounded content area to slide over.
91
+ this._sheet.style.width = '480px';
92
+ this._sheet.style.height = '340px';
93
+
94
+ this._sheet.append(this._buildContent(), this._buildSheet());
95
+
96
+ this._sync();
97
+ this.addContent(this._sheet);
98
+ }
99
+
100
+ updateArgs(_args: StoryArgs): void {
101
+ this._sync();
102
+ }
103
+
104
+ private _sync(): void {
105
+ if (!this._sheet) return;
106
+ this._sheet.toggleAttribute('open', this.args.open as boolean);
107
+ this._setBool('modal', this.args.modal as boolean);
108
+ this._setBool('can-close', this.args.canClose as boolean);
109
+ }
110
+
111
+ /** Boolean attributes that default on are encoded as `="false"` when off. */
112
+ private _setBool(name: string, value: boolean): void {
113
+ if (!this._sheet) return;
114
+ if (value) this._sheet.setAttribute(name, '');
115
+ else this._sheet.setAttribute(name, 'false');
116
+ }
117
+ }
118
+
119
+ export const BottomSheetWebStories: WebStoryModule = { stories: [BottomSheetWebStory] };
@@ -0,0 +1,99 @@
1
+ // Browser port of the Navigation Split View story. Shares metadata with
2
+ // navigation-split-view.story.ts.
3
+
4
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
5
+ import { navigationSplitViewMeta } from '../../navigation/navigation-split-view.meta.js';
6
+
7
+ /** GTK symbolic name (e.g. "folder-symbolic") → adwaita-web icon class. */
8
+ function iconClass(gtkName: string): string {
9
+ return `adw-icon adw-icon--${gtkName.replace(/-symbolic$/, '')}`;
10
+ }
11
+
12
+ export class NavigationSplitViewWebStory extends StoryElement {
13
+ private _view: HTMLElement | null = null;
14
+
15
+ constructor() {
16
+ super(NavigationSplitViewWebStory.getMetadata(), 'Default');
17
+ }
18
+
19
+ static getMetadata(): StoryMeta {
20
+ return navigationSplitViewMeta;
21
+ }
22
+
23
+ /** A boxed-list of mailboxes inside a toolbar view — the sidebar page. */
24
+ private buildSidebar(): HTMLElement {
25
+ const group = document.createElement('adw-preferences-group');
26
+ for (const [title, subtitle, icon] of [
27
+ ['All Mail', '128 messages', 'mail-unread-symbolic'],
28
+ ['Starred', '6 messages', 'starred-symbolic'],
29
+ ['Drafts', '2 messages', 'document-edit-symbolic'],
30
+ ['Archive', '512 messages', 'folder-symbolic'],
31
+ ] as const) {
32
+ const row = document.createElement('adw-action-row');
33
+ row.setAttribute('title', title);
34
+ row.setAttribute('subtitle', subtitle);
35
+ row.setAttribute('activatable', '');
36
+
37
+ const image = document.createElement('span');
38
+ image.setAttribute('slot', 'prefix');
39
+ image.className = iconClass(icon);
40
+ row.appendChild(image);
41
+
42
+ group.appendChild(row);
43
+ }
44
+
45
+ const header = document.createElement('adw-header-bar');
46
+ header.setAttribute('slot', 'top');
47
+ const title = document.createElement('adw-window-title');
48
+ title.setAttribute('slot', 'center');
49
+ title.setAttribute('title', 'Mailboxes');
50
+ header.appendChild(title);
51
+
52
+ const toolbarView = document.createElement('adw-toolbar-view');
53
+ toolbarView.setAttribute('slot', 'sidebar');
54
+ toolbarView.append(header, group);
55
+ return toolbarView;
56
+ }
57
+
58
+ /** A status page inside a toolbar view — the content page. */
59
+ private buildContent(): HTMLElement {
60
+ const status = document.createElement('adw-status-page');
61
+ status.setAttribute('icon', 'mail-unread-symbolic');
62
+ status.setAttribute('title', 'All Mail');
63
+ status.setAttribute('description', 'Select a conversation from the list to read it here.');
64
+
65
+ const header = document.createElement('adw-header-bar');
66
+ header.setAttribute('slot', 'top');
67
+ const title = document.createElement('adw-window-title');
68
+ title.setAttribute('slot', 'center');
69
+ title.setAttribute('title', 'All Mail');
70
+ header.appendChild(title);
71
+
72
+ const toolbarView = document.createElement('adw-toolbar-view');
73
+ toolbarView.setAttribute('slot', 'content');
74
+ toolbarView.append(header, status);
75
+ return toolbarView;
76
+ }
77
+
78
+ initialize(): void {
79
+ this._view = document.createElement('adw-navigation-split-view');
80
+ this._view.style.width = '480px';
81
+ this._view.style.height = '340px';
82
+ this._view.append(this.buildSidebar(), this.buildContent());
83
+
84
+ this._sync();
85
+ this.addContent(this._view);
86
+ }
87
+
88
+ updateArgs(_args: StoryArgs): void {
89
+ this._sync();
90
+ }
91
+
92
+ private _sync(): void {
93
+ if (!this._view) return;
94
+ this._view.toggleAttribute('show-content', this.args.showContent as boolean);
95
+ this._view.toggleAttribute('collapsed', this.args.collapsed as boolean);
96
+ }
97
+ }
98
+
99
+ export const NavigationSplitViewWebStories: WebStoryModule = { stories: [NavigationSplitViewWebStory] };
@@ -0,0 +1,104 @@
1
+ // Browser port of the Navigation View story. Shares metadata with
2
+ // navigation-view.story.ts.
3
+
4
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
5
+ import { navigationViewMeta } from '../../navigation/navigation-view.meta.js';
6
+
7
+ interface AdwNavigationViewEl extends HTMLElement {
8
+ push(pageOrTag: HTMLElement | string): void;
9
+ }
10
+
11
+ export class NavigationViewWebStory extends StoryElement {
12
+ private _view: AdwNavigationViewEl | null = null;
13
+ private _rootTitle: HTMLElement | null = null;
14
+ private _detailPage: HTMLElement | null = null;
15
+ private _detailTitle: HTMLElement | null = null;
16
+ private _detailStatus: HTMLElement | null = null;
17
+
18
+ constructor() {
19
+ super(NavigationViewWebStory.getMetadata(), 'Default');
20
+ }
21
+
22
+ static getMetadata(): StoryMeta {
23
+ return navigationViewMeta;
24
+ }
25
+
26
+ initialize(): void {
27
+ this._view = document.createElement('adw-navigation-view') as AdwNavigationViewEl;
28
+ this._view.style.width = '480px';
29
+ this._view.style.height = '340px';
30
+ this._view.setAttribute(
31
+ 'animate-transitions',
32
+ (this.args.animateTransitions as boolean) ? 'true' : 'false',
33
+ );
34
+
35
+ // --- Detail page: a status page describing the contact. ---
36
+ this._detailTitle = document.createElement('adw-window-title');
37
+ this._detailTitle.setAttribute('title', this.args.detailTitle as string);
38
+ this._detailTitle.setAttribute('slot', 'center');
39
+
40
+ this._detailStatus = document.createElement('adw-status-page');
41
+ this._detailStatus.setAttribute('icon', 'avatar-default');
42
+ this._detailStatus.setAttribute('title', this.args.detailTitle as string);
43
+ this._detailStatus.setAttribute('description', 'Mathematician and writer, the first computer programmer.');
44
+
45
+ const detailHeader = document.createElement('adw-header-bar');
46
+ detailHeader.setAttribute('slot', 'top');
47
+ detailHeader.appendChild(this._detailTitle);
48
+
49
+ const detailToolbar = document.createElement('adw-toolbar-view');
50
+ detailToolbar.append(detailHeader, this._detailStatus);
51
+
52
+ this._detailPage = document.createElement('adw-navigation-page');
53
+ this._detailPage.setAttribute('tag', 'detail');
54
+ this._detailPage.setAttribute('title', this.args.detailTitle as string);
55
+ this._detailPage.appendChild(detailToolbar);
56
+
57
+ // --- Root page: an "Open contact" pill button that pushes the detail. ---
58
+ this._rootTitle = document.createElement('adw-window-title');
59
+ this._rootTitle.setAttribute('title', this.args.rootTitle as string);
60
+ this._rootTitle.setAttribute('slot', 'center');
61
+
62
+ const openButton = document.createElement('adw-button');
63
+ openButton.setAttribute('label', 'Open contact');
64
+ openButton.setAttribute('pill', '');
65
+ openButton.setAttribute('suggested', '');
66
+ openButton.classList.add('adw-navigation-view-root-action');
67
+ openButton.addEventListener('click', () => {
68
+ this._view?.push('detail');
69
+ });
70
+
71
+ const rootHeader = document.createElement('adw-header-bar');
72
+ rootHeader.setAttribute('slot', 'top');
73
+ rootHeader.appendChild(this._rootTitle);
74
+
75
+ const rootToolbar = document.createElement('adw-toolbar-view');
76
+ rootToolbar.append(rootHeader, openButton);
77
+
78
+ const rootPage = document.createElement('adw-navigation-page');
79
+ rootPage.setAttribute('tag', 'root');
80
+ rootPage.setAttribute('title', this.args.rootTitle as string);
81
+ rootPage.appendChild(rootToolbar);
82
+
83
+ // Root is the first child (auto-pushed); the detail is a static page made
84
+ // reachable via push-by-tag.
85
+ this._view.append(rootPage, this._detailPage);
86
+
87
+ this.addContent(this._view);
88
+ }
89
+
90
+ updateArgs(_args: StoryArgs): void {
91
+ if (!this._view) return;
92
+ this._view.setAttribute(
93
+ 'animate-transitions',
94
+ (this.args.animateTransitions as boolean) ? 'true' : 'false',
95
+ );
96
+ this._rootTitle?.setAttribute('title', this.args.rootTitle as string);
97
+ const detailTitle = this.args.detailTitle as string;
98
+ this._detailTitle?.setAttribute('title', detailTitle);
99
+ this._detailStatus?.setAttribute('title', detailTitle);
100
+ this._detailPage?.setAttribute('title', detailTitle);
101
+ }
102
+ }
103
+
104
+ export const NavigationViewWebStories: WebStoryModule = { stories: [NavigationViewWebStory] };