@gjsify/example-gtk-adwaita-storybook 0.38.1 → 0.40.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/example-gtk-adwaita-storybook",
3
- "version": "0.38.1",
3
+ "version": "0.40.0",
4
4
  "description": "Interactive component browser for Libadwaita widgets — a native Adwaita storybook built on @gjsify/storybook, debuggable over DBus/MCP via @gjsify/devtools",
5
5
  "type": "module",
6
6
  "main": "dist/gjs.js",
@@ -54,11 +54,11 @@
54
54
  "@girs/glib-2.0": "^4.1.0",
55
55
  "@girs/gobject-2.0": "^4.1.0",
56
56
  "@girs/gtk-4.0": "^4.1.0",
57
- "@gjsify/adwaita-web": "^0.38.1",
58
- "@gjsify/cli": "^0.38.1",
57
+ "@gjsify/adwaita-web": "^0.40.0",
58
+ "@gjsify/cli": "^0.40.0",
59
59
  "@gjsify/node-gi": "file:../../../packages/node-gi/node-gi",
60
- "@gjsify/storybook": "^0.38.1",
61
- "@gjsify/adwaita-storybook": "^0.38.1",
60
+ "@gjsify/storybook": "^0.40.0",
61
+ "@gjsify/adwaita-storybook": "^0.40.0",
62
62
  "@types/node": "^25.9.2",
63
63
  "typescript": "^6.0.3"
64
64
  },
@@ -0,0 +1,37 @@
1
+ // Browser port of the Menu Button story. Shares metadata with menu-button.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { MENU_BUTTON_ITEMS, menuButtonMeta } from '../../buttons/menu-button.meta.js';
5
+
6
+ export class MenuButtonWebStory extends StoryElement {
7
+ private _widget: HTMLElement | null = null;
8
+
9
+ constructor() {
10
+ super(MenuButtonWebStory.getMetadata(), 'Default');
11
+ }
12
+
13
+ static getMetadata(): StoryMeta {
14
+ return menuButtonMeta;
15
+ }
16
+
17
+ initialize(): void {
18
+ this._widget = document.createElement('adw-menu-button');
19
+ this._widget.setAttribute('menu', JSON.stringify(MENU_BUTTON_ITEMS.map((label) => ({ label }))));
20
+ this._apply();
21
+ this.addContent(this._widget);
22
+ }
23
+
24
+ updateArgs(_args: StoryArgs): void {
25
+ this._apply();
26
+ }
27
+
28
+ private _apply(): void {
29
+ if (!this._widget) return;
30
+ // The element takes symbolic names WITHOUT the `-symbolic` suffix — its icon
31
+ // set is Adwaita's symbolic one, so the suffix would be part of every name.
32
+ this._widget.setAttribute('icon-name', (this.args.iconName as string).replace(/-symbolic$/, ''));
33
+ this._widget.setAttribute('menu-title', this.args.menuTitle as string);
34
+ }
35
+ }
36
+
37
+ export const MenuButtonWebStories: WebStoryModule = { stories: [MenuButtonWebStory] };
@@ -0,0 +1,36 @@
1
+ // Browser port of the Drop Down story. Shares metadata with drop-down.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { DROP_DOWN_OPTIONS, dropDownMeta } from '../../controls/drop-down.meta.js';
5
+
6
+ export class DropDownWebStory extends StoryElement {
7
+ private _dropDown: HTMLElement | null = null;
8
+
9
+ constructor() {
10
+ super(DropDownWebStory.getMetadata(), 'Default');
11
+ }
12
+
13
+ static getMetadata(): StoryMeta {
14
+ return dropDownMeta;
15
+ }
16
+
17
+ initialize(): void {
18
+ this._dropDown = document.createElement('adw-drop-down');
19
+ this._dropDown.setAttribute('options', JSON.stringify(DROP_DOWN_OPTIONS));
20
+ this._apply();
21
+ this.addContent(this._dropDown);
22
+ }
23
+
24
+ updateArgs(_args: StoryArgs): void {
25
+ this._apply();
26
+ }
27
+
28
+ private _apply(): void {
29
+ if (!this._dropDown) return;
30
+ this._dropDown.setAttribute('selected', String(this.args.selected as number));
31
+ if (this.args.enableSearch as boolean) this._dropDown.setAttribute('enable-search', '');
32
+ else this._dropDown.removeAttribute('enable-search');
33
+ }
34
+ }
35
+
36
+ export const DropDownWebStories: WebStoryModule = { stories: [DropDownWebStory] };
@@ -0,0 +1,40 @@
1
+ // Browser port of the Entry story. Shares metadata with entry.story.ts.
2
+
3
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
4
+ import { entryMeta } from '../../controls/entry.meta.js';
5
+
6
+ export class EntryWebStory extends StoryElement {
7
+ private _entry: HTMLElement | null = null;
8
+
9
+ constructor() {
10
+ super(EntryWebStory.getMetadata(), 'Default');
11
+ }
12
+
13
+ static getMetadata(): StoryMeta {
14
+ return entryMeta;
15
+ }
16
+
17
+ initialize(): void {
18
+ this._entry = document.createElement('adw-entry');
19
+ this._entry.style.minWidth = '280px';
20
+ this._apply();
21
+ this.addContent(this._entry);
22
+ }
23
+
24
+ updateArgs(_args: StoryArgs): void {
25
+ this._apply();
26
+ }
27
+
28
+ private _apply(): void {
29
+ if (!this._entry) return;
30
+ this._entry.setAttribute('value', this.args.text as string);
31
+ this._entry.setAttribute('placeholder', this.args.placeholder as string);
32
+ // `<adw-entry>` has no `editable`: the browser spelling of a field you may
33
+ // read but not change is `disabled`, which also greys it — the one place
34
+ // this rendering cannot match GtkEditable:editable exactly.
35
+ if (this.args.editable as boolean) this._entry.removeAttribute('disabled');
36
+ else this._entry.setAttribute('disabled', '');
37
+ }
38
+ }
39
+
40
+ export const EntryWebStories: WebStoryModule = { stories: [EntryWebStory] };
@@ -5,7 +5,10 @@ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } fro
5
5
  import {
6
6
  OVERVIEW_ACCENT_OPTIONS,
7
7
  OVERVIEW_ADVANCED_ROWS,
8
+ OVERVIEW_DEVICES,
9
+ OVERVIEW_GROUP_TITLES,
8
10
  OVERVIEW_SHORTCUTS,
11
+ OVERVIEW_TEXT,
9
12
  overviewWidgetsMeta,
10
13
  } from '../../overview/widgets.meta.js';
11
14
 
@@ -33,7 +36,10 @@ export class OverviewWidgetsWebStory extends StoryElement {
33
36
  host.style.flexDirection = 'column';
34
37
  host.style.width = '100%';
35
38
 
36
- this._banner = el('adw-banner', { title: 'You have unsaved changes', 'button-label': 'Save' });
39
+ this._banner = el('adw-banner', {
40
+ title: OVERVIEW_TEXT.bannerTitle,
41
+ 'button-label': OVERVIEW_TEXT.bannerButton,
42
+ });
37
43
  this._syncBanner();
38
44
  host.append(this._banner);
39
45
 
@@ -42,12 +48,16 @@ export class OverviewWidgetsWebStory extends StoryElement {
42
48
  // --- Appearance: a widget demo only. The storybook's OWN appearance is in
43
49
  // the chrome, and wiring these to it would give one setting two controls
44
50
  // that disagree.
45
- const appearance = el('adw-preferences-group', { title: 'Appearance' });
51
+ const appearance = el('adw-preferences-group', { title: OVERVIEW_GROUP_TITLES.appearance });
46
52
  appearance.append(
47
- el('adw-switch-row', { title: 'Dark mode', subtitle: 'Use the dark Adwaita palette' }),
48
- el('adw-switch-row', { title: 'Notifications', subtitle: 'Show toasts for events', active: '' }),
53
+ el('adw-switch-row', { title: OVERVIEW_TEXT.darkMode, subtitle: OVERVIEW_TEXT.darkModeSubtitle }),
54
+ el('adw-switch-row', {
55
+ title: OVERVIEW_TEXT.notifications,
56
+ subtitle: OVERVIEW_TEXT.notificationsSubtitle,
57
+ active: '',
58
+ }),
49
59
  el('adw-combo-row', {
50
- title: 'Accent color',
60
+ title: OVERVIEW_TEXT.accentColor,
51
61
  // `items` is JSON, not a comma list — the element parses it.
52
62
  items: JSON.stringify([...OVERVIEW_ACCENT_OPTIONS]),
53
63
  selected: '0',
@@ -56,14 +66,23 @@ export class OverviewWidgetsWebStory extends StoryElement {
56
66
  page.append(appearance);
57
67
 
58
68
  // --- Account ---
59
- const account = el('adw-preferences-group', { title: 'Account' });
69
+ const account = el('adw-preferences-group', { title: OVERVIEW_GROUP_TITLES.account });
60
70
  account.append(
61
- el('adw-entry-row', { title: 'Name', text: 'Ada Lovelace' }),
62
- el('adw-entry-row', { title: 'Email', text: 'ada@example.com' }),
63
- el('adw-spin-row', { title: 'Devices', min: '1', max: '10', step: '1', value: '3' }),
71
+ el('adw-entry-row', { title: OVERVIEW_TEXT.name, text: OVERVIEW_TEXT.nameValue }),
72
+ el('adw-entry-row', { title: OVERVIEW_TEXT.email, text: OVERVIEW_TEXT.emailValue }),
73
+ el('adw-spin-row', {
74
+ title: OVERVIEW_TEXT.devices,
75
+ min: String(OVERVIEW_DEVICES.lower),
76
+ max: String(OVERVIEW_DEVICES.upper),
77
+ step: String(OVERVIEW_DEVICES.step),
78
+ value: String(OVERVIEW_DEVICES.value),
79
+ }),
64
80
  );
65
81
 
66
- const advanced = el('adw-expander-row', { title: 'Advanced', subtitle: 'More options' });
82
+ const advanced = el('adw-expander-row', {
83
+ title: OVERVIEW_TEXT.advanced,
84
+ subtitle: OVERVIEW_TEXT.advancedSubtitle,
85
+ });
67
86
  advanced.toggleAttribute('expanded', true);
68
87
  for (const row of OVERVIEW_ADVANCED_ROWS) {
69
88
  advanced.append(
@@ -76,7 +95,7 @@ export class OverviewWidgetsWebStory extends StoryElement {
76
95
  page.append(account);
77
96
 
78
97
  // --- Shortcuts: the newest widget, in context ---
79
- const shortcuts = el('adw-preferences-group', { title: 'Shortcuts' });
98
+ const shortcuts = el('adw-preferences-group', { title: OVERVIEW_GROUP_TITLES.shortcuts });
80
99
  for (const shortcut of OVERVIEW_SHORTCUTS) {
81
100
  const row = el('adw-action-row', { title: shortcut.title });
82
101
  const label = el('adw-shortcut-label', { accelerator: shortcut.accelerator, slot: 'suffix' });
@@ -86,7 +105,7 @@ export class OverviewWidgetsWebStory extends StoryElement {
86
105
  page.append(shortcuts);
87
106
 
88
107
  // --- Actions ---
89
- const actions = el('adw-preferences-group', { title: 'Actions' });
108
+ const actions = el('adw-preferences-group', { title: OVERVIEW_GROUP_TITLES.actions });
90
109
  const buttons = document.createElement('div');
91
110
  buttons.style.display = 'flex';
92
111
  buttons.style.gap = '12px';
@@ -94,10 +113,10 @@ export class OverviewWidgetsWebStory extends StoryElement {
94
113
 
95
114
  const save = document.createElement('button');
96
115
  save.className = 'adw-button suggested-action';
97
- save.textContent = 'Save changes';
116
+ save.textContent = OVERVIEW_TEXT.save;
98
117
  const remove = document.createElement('button');
99
118
  remove.className = 'adw-button destructive-action';
100
- remove.textContent = 'Delete account';
119
+ remove.textContent = OVERVIEW_TEXT.delete;
101
120
  buttons.append(save, remove);
102
121
  actions.append(buttons);
103
122
  page.append(actions);
@@ -3,8 +3,17 @@
3
3
  // embeddable `mount(container)` (`embed.ts`, used by the gjsify website) draw
4
4
  // from this single list so the two stay in lockstep.
5
5
  //
6
- // Order mirrors the native sidebar's category order (Presentation, Boxed
7
- // Lists, Buttons, …).
6
+ // THE ORDER OF THIS ARRAY NO LONGER DECIDES THE SIDEBAR. Categories are ordered
7
+ // by STORYBOOK_CATEGORY_ORDER in @gjsify/storybook-core, which every target's
8
+ // controller applies, so this list only decides which stories exist and how they
9
+ // sit WITHIN their category.
10
+ //
11
+ // It used to claim it mirrored the native sidebar's order. It measurably did not:
12
+ // the GTK and NativeScript storybooks took their order from a path glob
13
+ // (alphabetical by directory), so `Overview` led here and sat fifth there. One
14
+ // sentence was the only thing holding an invariant across three files, and the
15
+ // sentence was wrong — which is why the order moved somewhere a build gate can
16
+ // check it.
8
17
 
9
18
  import type { WebStoryModule } from '@gjsify/adwaita-storybook';
10
19
  import { OverviewWidgetsWebStories } from './overview/widgets.web.js';
@@ -23,8 +32,11 @@ import { PasswordEntryRowWebStories } from './rows/password-entry-row.web.js';
23
32
  import { PreferencesGroupWebStories } from './rows/preferences-group.web.js';
24
33
  import { SpinRowWebStories } from './rows/spin-row.web.js';
25
34
  import { SwitchRowWebStories } from './rows/switch-row.web.js';
35
+ import { DropDownWebStories } from './controls/drop-down.web.js';
36
+ import { EntryWebStories } from './controls/entry.web.js';
26
37
  import { ButtonContentWebStories } from './buttons/button-content.web.js';
27
38
  import { ButtonStylesWebStories } from './buttons/button-styles.web.js';
39
+ import { MenuButtonWebStories } from './buttons/menu-button.web.js';
28
40
  import { SplitButtonWebStories } from './buttons/split-button.web.js';
29
41
  import { ToggleGroupWebStories } from './buttons/toggle-group.web.js';
30
42
  import { ClampWebStories } from './layout/clamp.web.js';
@@ -35,6 +47,7 @@ import { CarouselWebStories } from './view-switching/carousel.web.js';
35
47
  import { InlineViewSwitcherWebStories } from './view-switching/inline-view-switcher.web.js';
36
48
  import { TabViewWebStories } from './view-switching/tab-view.web.js';
37
49
  import { ViewSwitcherWebStories } from './view-switching/view-switcher.web.js';
50
+ import { ViewSwitcherBarWebStories } from './view-switching/view-switcher-bar.web.js';
38
51
  import { BottomSheetWebStories } from './navigation/bottom-sheet.web.js';
39
52
  import { NavigationSplitViewWebStories } from './navigation/navigation-split-view.web.js';
40
53
  import { NavigationViewWebStories } from './navigation/navigation-view.web.js';
@@ -62,8 +75,11 @@ export const stories: WebStoryModule[] = [
62
75
  PreferencesGroupWebStories,
63
76
  SpinRowWebStories,
64
77
  SwitchRowWebStories,
78
+ EntryWebStories,
79
+ DropDownWebStories,
65
80
  ButtonContentWebStories,
66
81
  ButtonStylesWebStories,
82
+ MenuButtonWebStories,
67
83
  SplitButtonWebStories,
68
84
  ToggleGroupWebStories,
69
85
  ClampWebStories,
@@ -74,6 +90,7 @@ export const stories: WebStoryModule[] = [
74
90
  InlineViewSwitcherWebStories,
75
91
  TabViewWebStories,
76
92
  ViewSwitcherWebStories,
93
+ ViewSwitcherBarWebStories,
77
94
  BottomSheetWebStories,
78
95
  NavigationSplitViewWebStories,
79
96
  NavigationViewWebStories,
@@ -0,0 +1,67 @@
1
+ // Browser port of the View Switcher Bar story. Shares metadata with
2
+ // view-switcher-bar.story.ts.
3
+
4
+ import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
5
+ // TYPE-ONLY: the bar is driven through `setStack()`/`reveal`, not attributes, and a
6
+ // hand-written structural interface here would be a second copy of that API that
7
+ // nothing keeps in step. The import erases, so the bundle keeps no edge to the package.
8
+ import type { AdwViewStack, AdwViewSwitcherBar } from '@gjsify/adwaita-web';
9
+ import { VIEW_SWITCHER_BAR_PAGES, viewSwitcherBarMeta } from '../../view-switching/view-switcher-bar.meta.js';
10
+
11
+ export class ViewSwitcherBarWebStory extends StoryElement {
12
+ private _bar: AdwViewSwitcherBar | null = null;
13
+
14
+ constructor() {
15
+ super(ViewSwitcherBarWebStory.getMetadata(), 'Default');
16
+ }
17
+
18
+ static getMetadata(): StoryMeta {
19
+ return viewSwitcherBarMeta;
20
+ }
21
+
22
+ initialize(): void {
23
+ const stack = document.createElement('adw-view-stack') as AdwViewStack;
24
+ stack.style.flex = '1';
25
+ for (const page of VIEW_SWITCHER_BAR_PAGES) {
26
+ const status = document.createElement('adw-status-page');
27
+ status.setAttribute('icon', page.icon.replace(/-symbolic$/, ''));
28
+ status.setAttribute('title', page.title);
29
+ status.setAttribute('description', `The ${page.title.toLowerCase()} page.`);
30
+ const child = document.createElement('adw-view-stack-page');
31
+ child.setAttribute('name', page.name);
32
+ child.setAttribute('title', page.title);
33
+ child.setAttribute('icon-name', page.icon.replace(/-symbolic$/, ''));
34
+ child.appendChild(status);
35
+ stack.appendChild(child);
36
+ }
37
+
38
+ const bar = document.createElement('adw-view-switcher-bar') as AdwViewSwitcherBar;
39
+ bar.setAttribute('slot', 'bottom');
40
+
41
+ // A toolbar view, so the bar sits where libadwaita puts it — at the bottom
42
+ // edge — rather than floating in the middle of the preview.
43
+ const view = document.createElement('adw-toolbar-view');
44
+ view.style.width = '360px';
45
+ view.style.height = '360px';
46
+ view.append(stack, bar);
47
+ this.addContent(view);
48
+
49
+ // Bound AFTER the stack is in the document: the bar derives its buttons from
50
+ // the stack's pages, and those only exist once the `<adw-view-stack-page>`
51
+ // children have upgraded.
52
+ bar.setStack(stack);
53
+ this._bar = bar;
54
+ this._apply();
55
+ }
56
+
57
+ updateArgs(_args: StoryArgs): void {
58
+ this._apply();
59
+ }
60
+
61
+ private _apply(): void {
62
+ if (!this._bar) return;
63
+ this._bar.reveal = this.args.reveal as boolean;
64
+ }
65
+ }
66
+
67
+ export const ViewSwitcherBarWebStories: WebStoryModule = { stories: [ViewSwitcherBarWebStory] };
@@ -0,0 +1,29 @@
1
+ // Shared, renderer-agnostic metadata for the Menu Button story. Imported by the GTK
2
+ // renderer (menu-button.story.ts), the browser renderer
3
+ // (browser/buttons/menu-button.web.ts) and the NativeScript one.
4
+
5
+ import { ControlType, type StoryMeta } from '@gjsify/stories';
6
+
7
+ /** The menu all three renderings build, so the popover holds the same items everywhere. */
8
+ export const MENU_BUTTON_ITEMS = ['Save as…', 'Export', 'Print'] as const;
9
+
10
+ export const menuButtonMeta: StoryMeta = {
11
+ title: 'Buttons/Menu Button',
12
+ description:
13
+ 'Gtk.MenuButton — a button whose only action is to open a menu. Where a Split Button has a primary ' +
14
+ 'action beside its arrow, this one has none: every choice lives in the popover.',
15
+ controls: [
16
+ {
17
+ name: 'iconName',
18
+ label: 'Icon',
19
+ type: ControlType.SELECT,
20
+ options: [
21
+ { label: 'Hamburger', value: 'open-menu-symbolic' },
22
+ { label: 'More', value: 'view-more-symbolic' },
23
+ { label: 'Open', value: 'document-open-symbolic' },
24
+ ],
25
+ defaultValue: 'open-menu-symbolic',
26
+ },
27
+ { name: 'menuTitle', label: 'Menu title', type: ControlType.TEXT, defaultValue: 'Document' },
28
+ ],
29
+ };
@@ -0,0 +1,60 @@
1
+ // Gtk.MenuButton — a button whose only job is to open a menu.
2
+ // original implementation.
3
+
4
+ import Gio from '@girs/gio-2.0';
5
+ import Gtk from '@girs/gtk-4.0';
6
+ import GObject from '@girs/gobject-2.0';
7
+ import { type StoryArgs, type StoryMeta, type StoryModule, StoryWidget } from '@gjsify/storybook';
8
+ import { MENU_BUTTON_ITEMS, menuButtonMeta } from './menu-button.meta.js';
9
+
10
+ /** Story: a Gtk.MenuButton over a Gio.Menu whose section carries the title. */
11
+ export class MenuButtonStory extends StoryWidget {
12
+ private _widget: Gtk.MenuButton | null = null;
13
+
14
+ static {
15
+ GObject.registerClass({ GTypeName: 'AdwStorybookMenuButton' }, MenuButtonStory);
16
+ }
17
+
18
+ constructor() {
19
+ super(StoryWidget.fromMeta(MenuButtonStory.getMetadata(), 'Default'));
20
+ }
21
+
22
+ static getMetadata(): StoryMeta {
23
+ return { ...menuButtonMeta, component: Gtk.MenuButton.$gtype };
24
+ }
25
+
26
+ initialize(): void {
27
+ this._widget = new Gtk.MenuButton({ halign: Gtk.Align.CENTER });
28
+ this._apply();
29
+ this.addContent(this._widget);
30
+ }
31
+
32
+ updateArgs(_args: StoryArgs): void {
33
+ this._apply();
34
+ }
35
+
36
+ /**
37
+ * A Gio.Menu is immutable to the popover once shown, so the model is rebuilt
38
+ * rather than mutated — the title lives on the SECTION, which is how GTK
39
+ * spells a labelled group of items.
40
+ */
41
+ private _buildMenu(title: string): Gio.Menu {
42
+ const items = new Gio.Menu();
43
+ for (const label of MENU_BUTTON_ITEMS) {
44
+ items.append(label, `app.${label.toLowerCase().replace(/[^a-z]+/g, '-')}`);
45
+ }
46
+ const menu = new Gio.Menu();
47
+ menu.append_section(title.length > 0 ? title : null, items);
48
+ return menu;
49
+ }
50
+
51
+ private _apply(): void {
52
+ if (!this._widget) return;
53
+ this._widget.iconName = this.args.iconName as string;
54
+ this._widget.menuModel = this._buildMenu(this.args.menuTitle as string);
55
+ }
56
+ }
57
+
58
+ GObject.type_ensure(MenuButtonStory.$gtype);
59
+
60
+ export const MenuButtonStories: StoryModule = { stories: [MenuButtonStory] };
@@ -0,0 +1,25 @@
1
+ // Shared, renderer-agnostic metadata for the Drop Down story. Imported by the GTK
2
+ // renderer (drop-down.story.ts), the browser renderer
3
+ // (browser/controls/drop-down.web.ts) and the NativeScript one.
4
+
5
+ import { ControlType, type StoryMeta } from '@gjsify/stories';
6
+
7
+ /** The one option list all three renderings drive, so a pick means the same thing everywhere. */
8
+ export const DROP_DOWN_OPTIONS = ['Automatic', 'Always', 'Never', 'When busy'] as const;
9
+
10
+ export const dropDownMeta: StoryMeta = {
11
+ title: 'Controls/Drop Down',
12
+ description:
13
+ 'Gtk.DropDown — pick one value from a list. The button shows the selection; the list opens in a ' +
14
+ 'popover. Adw.ComboRow is the same choice presented as a boxed-list row.',
15
+ controls: [
16
+ {
17
+ name: 'selected',
18
+ label: 'Selected',
19
+ type: ControlType.SELECT,
20
+ options: DROP_DOWN_OPTIONS.map((label, index) => ({ label, value: index })),
21
+ defaultValue: 0,
22
+ },
23
+ { name: 'enableSearch', label: 'Search field', type: ControlType.BOOLEAN, defaultValue: false },
24
+ ],
25
+ };
@@ -0,0 +1,49 @@
1
+ // Gtk.DropDown — pick one value from a list, shown in a popover.
2
+ // original implementation.
3
+
4
+ import Gtk from '@girs/gtk-4.0';
5
+ import GObject from '@girs/gobject-2.0';
6
+ import { type StoryArgs, type StoryMeta, type StoryModule, StoryWidget } from '@gjsify/storybook';
7
+ import { DROP_DOWN_OPTIONS, dropDownMeta } from './drop-down.meta.js';
8
+
9
+ /** Story: a Gtk.DropDown over a Gtk.StringList, with the search field toggled by an arg. */
10
+ export class DropDownStory extends StoryWidget {
11
+ private _dropDown: Gtk.DropDown | null = null;
12
+
13
+ static {
14
+ GObject.registerClass({ GTypeName: 'AdwStorybookDropDown' }, DropDownStory);
15
+ }
16
+
17
+ constructor() {
18
+ super(StoryWidget.fromMeta(DropDownStory.getMetadata(), 'Default'));
19
+ }
20
+
21
+ static getMetadata(): StoryMeta {
22
+ return { ...dropDownMeta, component: Gtk.DropDown.$gtype };
23
+ }
24
+
25
+ initialize(): void {
26
+ this._dropDown = Gtk.DropDown.new_from_strings([...DROP_DOWN_OPTIONS]);
27
+ // Search needs to know WHICH string to match: a GtkDropDown over a
28
+ // GtkStringList holds GtkStringObjects, and without an expression the
29
+ // search field appears and matches nothing.
30
+ this._dropDown.expression = Gtk.PropertyExpression.new(Gtk.StringObject.$gtype, null, 'string');
31
+ this._dropDown.halign = Gtk.Align.CENTER;
32
+ this._apply();
33
+ this.addContent(this._dropDown);
34
+ }
35
+
36
+ updateArgs(_args: StoryArgs): void {
37
+ this._apply();
38
+ }
39
+
40
+ private _apply(): void {
41
+ if (!this._dropDown) return;
42
+ this._dropDown.selected = this.args.selected as number;
43
+ this._dropDown.enableSearch = this.args.enableSearch as boolean;
44
+ }
45
+ }
46
+
47
+ GObject.type_ensure(DropDownStory.$gtype);
48
+
49
+ export const DropDownStories: StoryModule = { stories: [DropDownStory] };
@@ -0,0 +1,18 @@
1
+ // Shared, renderer-agnostic metadata for the Entry story. Imported by the GTK
2
+ // renderer (entry.story.ts), the browser renderer (browser/controls/entry.web.ts)
3
+ // and the NativeScript one, so all three expose identical controls.
4
+
5
+ import { ControlType, type StoryMeta } from '@gjsify/stories';
6
+
7
+ export const entryMeta: StoryMeta = {
8
+ title: 'Controls/Entry',
9
+ description:
10
+ 'Gtk.Entry — a single-line text field. Libadwaita ships no AdwEntry; the Adwaita look is the ' +
11
+ 'GTK entry styled by the stylesheet, which is why the row variants (Entry Row, Password Entry Row) ' +
12
+ 'are the widgets with an Adw prefix.',
13
+ controls: [
14
+ { name: 'text', label: 'Text', type: ControlType.TEXT, defaultValue: '' },
15
+ { name: 'placeholder', label: 'Placeholder', type: ControlType.TEXT, defaultValue: 'Search files…' },
16
+ { name: 'editable', label: 'Editable', type: ControlType.BOOLEAN, defaultValue: true },
17
+ ],
18
+ };
@@ -0,0 +1,47 @@
1
+ // Gtk.Entry — the single-line text field the Adwaita stylesheet dresses.
2
+ // original implementation.
3
+
4
+ import Gtk from '@girs/gtk-4.0';
5
+ import GObject from '@girs/gobject-2.0';
6
+ import { type StoryArgs, type StoryMeta, type StoryModule, StoryWidget } from '@gjsify/storybook';
7
+ import { entryMeta } from './entry.meta.js';
8
+
9
+ /** Story: a Gtk.Entry with placeholder text and an editable toggle. */
10
+ export class EntryStory extends StoryWidget {
11
+ private _entry: Gtk.Entry | null = null;
12
+
13
+ static {
14
+ GObject.registerClass({ GTypeName: 'AdwStorybookEntry' }, EntryStory);
15
+ }
16
+
17
+ constructor() {
18
+ super(StoryWidget.fromMeta(EntryStory.getMetadata(), 'Default'));
19
+ }
20
+
21
+ static getMetadata(): StoryMeta {
22
+ return { ...entryMeta, component: Gtk.Entry.$gtype };
23
+ }
24
+
25
+ initialize(): void {
26
+ this._entry = new Gtk.Entry({ widthRequest: 280, halign: Gtk.Align.CENTER });
27
+ this._apply();
28
+ this.addContent(this._entry);
29
+ }
30
+
31
+ updateArgs(_args: StoryArgs): void {
32
+ this._apply();
33
+ }
34
+
35
+ private _apply(): void {
36
+ if (!this._entry) return;
37
+ this._entry.text = this.args.text as string;
38
+ this._entry.placeholderText = this.args.placeholder as string;
39
+ // `editable` false keeps the entry focusable and selectable, which is what
40
+ // GtkEditable:editable means — NOT `sensitive`, which would grey it out.
41
+ this._entry.editable = this.args.editable as boolean;
42
+ }
43
+ }
44
+
45
+ GObject.type_ensure(EntryStory.$gtype);
46
+
47
+ export const EntryStories: StoryModule = { stories: [EntryStory] };
package/src/metas.ts CHANGED
@@ -6,8 +6,11 @@
6
6
 
7
7
  export * from './buttons/button-content.meta.js';
8
8
  export * from './buttons/button-styles.meta.js';
9
+ export * from './buttons/menu-button.meta.js';
9
10
  export * from './buttons/split-button.meta.js';
10
11
  export * from './buttons/toggle-group.meta.js';
12
+ export * from './controls/drop-down.meta.js';
13
+ export * from './controls/entry.meta.js';
11
14
  export * from './feedback/about-dialog.meta.js';
12
15
  export * from './feedback/alert-dialog.meta.js';
13
16
  export * from './feedback/preferences-dialog.meta.js';
@@ -40,4 +43,5 @@ export * from './rows/switch-row.meta.js';
40
43
  export * from './view-switching/carousel.meta.js';
41
44
  export * from './view-switching/inline-view-switcher.meta.js';
42
45
  export * from './view-switching/tab-view.meta.js';
46
+ export * from './view-switching/view-switcher-bar.meta.js';
43
47
  export * from './view-switching/view-switcher.meta.js';