@gjsify/example-gtk-adwaita-storybook 0.37.0 → 0.38.1
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/dist/browser-main.js +156 -6
- package/dist/gjs.js +80 -4
- package/dist/gjs.node.mjs +75 -4
- package/package.json +6 -6
- package/src/browser/overview/widgets.web.ts +118 -0
- package/src/browser/presentation/shortcut-label.web.ts +88 -0
- package/src/browser/stories.ts +4 -0
- package/src/metas.ts +2 -0
- package/src/overview/widgets.meta.ts +56 -0
- package/src/overview/widgets.story.ts +129 -0
- package/src/presentation/shortcut-label.meta.ts +90 -0
- package/src/presentation/shortcut-label.story.ts +92 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Browser port of the Overview story — the widget gallery. Shares metadata with
|
|
2
|
+
// widgets.story.ts.
|
|
3
|
+
|
|
4
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
5
|
+
import {
|
|
6
|
+
OVERVIEW_ACCENT_OPTIONS,
|
|
7
|
+
OVERVIEW_ADVANCED_ROWS,
|
|
8
|
+
OVERVIEW_SHORTCUTS,
|
|
9
|
+
overviewWidgetsMeta,
|
|
10
|
+
} from '../../overview/widgets.meta.js';
|
|
11
|
+
|
|
12
|
+
/** A custom element with attributes set, since every row here is one. */
|
|
13
|
+
function el(tag: string, attributes: Record<string, string> = {}): HTMLElement {
|
|
14
|
+
const node = document.createElement(tag);
|
|
15
|
+
for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, value);
|
|
16
|
+
return node;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class OverviewWidgetsWebStory extends StoryElement {
|
|
20
|
+
private _banner: HTMLElement | null = null;
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super(OverviewWidgetsWebStory.getMetadata(), 'Default');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
static getMetadata(): StoryMeta {
|
|
27
|
+
return overviewWidgetsMeta;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
initialize(): void {
|
|
31
|
+
const host = document.createElement('div');
|
|
32
|
+
host.style.display = 'flex';
|
|
33
|
+
host.style.flexDirection = 'column';
|
|
34
|
+
host.style.width = '100%';
|
|
35
|
+
|
|
36
|
+
this._banner = el('adw-banner', { title: 'You have unsaved changes', 'button-label': 'Save' });
|
|
37
|
+
this._syncBanner();
|
|
38
|
+
host.append(this._banner);
|
|
39
|
+
|
|
40
|
+
const page = el('adw-preferences-page');
|
|
41
|
+
|
|
42
|
+
// --- Appearance: a widget demo only. The storybook's OWN appearance is in
|
|
43
|
+
// the chrome, and wiring these to it would give one setting two controls
|
|
44
|
+
// that disagree.
|
|
45
|
+
const appearance = el('adw-preferences-group', { title: 'Appearance' });
|
|
46
|
+
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: '' }),
|
|
49
|
+
el('adw-combo-row', {
|
|
50
|
+
title: 'Accent color',
|
|
51
|
+
// `items` is JSON, not a comma list — the element parses it.
|
|
52
|
+
items: JSON.stringify([...OVERVIEW_ACCENT_OPTIONS]),
|
|
53
|
+
selected: '0',
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
page.append(appearance);
|
|
57
|
+
|
|
58
|
+
// --- Account ---
|
|
59
|
+
const account = el('adw-preferences-group', { title: 'Account' });
|
|
60
|
+
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' }),
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const advanced = el('adw-expander-row', { title: 'Advanced', subtitle: 'More options' });
|
|
67
|
+
advanced.toggleAttribute('expanded', true);
|
|
68
|
+
for (const row of OVERVIEW_ADVANCED_ROWS) {
|
|
69
|
+
advanced.append(
|
|
70
|
+
row.kind === 'switch'
|
|
71
|
+
? el('adw-switch-row', row.active ? { title: row.title, active: '' } : { title: row.title })
|
|
72
|
+
: el('adw-action-row', { title: row.title }),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
account.append(advanced);
|
|
76
|
+
page.append(account);
|
|
77
|
+
|
|
78
|
+
// --- Shortcuts: the newest widget, in context ---
|
|
79
|
+
const shortcuts = el('adw-preferences-group', { title: 'Shortcuts' });
|
|
80
|
+
for (const shortcut of OVERVIEW_SHORTCUTS) {
|
|
81
|
+
const row = el('adw-action-row', { title: shortcut.title });
|
|
82
|
+
const label = el('adw-shortcut-label', { accelerator: shortcut.accelerator, slot: 'suffix' });
|
|
83
|
+
row.append(label);
|
|
84
|
+
shortcuts.append(row);
|
|
85
|
+
}
|
|
86
|
+
page.append(shortcuts);
|
|
87
|
+
|
|
88
|
+
// --- Actions ---
|
|
89
|
+
const actions = el('adw-preferences-group', { title: 'Actions' });
|
|
90
|
+
const buttons = document.createElement('div');
|
|
91
|
+
buttons.style.display = 'flex';
|
|
92
|
+
buttons.style.gap = '12px';
|
|
93
|
+
buttons.style.margin = '6px 0';
|
|
94
|
+
|
|
95
|
+
const save = document.createElement('button');
|
|
96
|
+
save.className = 'adw-button suggested-action';
|
|
97
|
+
save.textContent = 'Save changes';
|
|
98
|
+
const remove = document.createElement('button');
|
|
99
|
+
remove.className = 'adw-button destructive-action';
|
|
100
|
+
remove.textContent = 'Delete account';
|
|
101
|
+
buttons.append(save, remove);
|
|
102
|
+
actions.append(buttons);
|
|
103
|
+
page.append(actions);
|
|
104
|
+
|
|
105
|
+
host.append(page);
|
|
106
|
+
this.addContent(host);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
updateArgs(_args: StoryArgs): void {
|
|
110
|
+
this._syncBanner();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private _syncBanner(): void {
|
|
114
|
+
this._banner?.toggleAttribute('revealed', this.args.revealed as boolean);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export const OverviewWidgetsWebStories: WebStoryModule = { stories: [OverviewWidgetsWebStory] };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Browser port of the Shortcut Label story. Shares metadata with shortcut-label.story.ts.
|
|
2
|
+
|
|
3
|
+
import { StoryElement, type StoryArgs, type StoryMeta, type WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
4
|
+
import {
|
|
5
|
+
SHORTCUT_LABEL_LEVELS,
|
|
6
|
+
shortcutLabelLevelsMeta,
|
|
7
|
+
shortcutLabelMeta,
|
|
8
|
+
} from '../../presentation/shortcut-label.meta.js';
|
|
9
|
+
|
|
10
|
+
export class ShortcutLabelWebStory extends StoryElement {
|
|
11
|
+
private _widget: HTMLElement | null = null;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super(ShortcutLabelWebStory.getMetadata(), 'Default');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static getMetadata(): StoryMeta {
|
|
18
|
+
return shortcutLabelMeta;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
initialize(): void {
|
|
22
|
+
this._widget = document.createElement('adw-shortcut-label');
|
|
23
|
+
this._sync();
|
|
24
|
+
this.addContent(this._widget);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
updateArgs(_args: StoryArgs): void {
|
|
28
|
+
this._sync();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `disabled-text` first: an empty accelerator renders the placeholder, so the
|
|
32
|
+
* placeholder has to be in place before the accelerator triggers the render. */
|
|
33
|
+
private _sync(): void {
|
|
34
|
+
if (!this._widget) return;
|
|
35
|
+
this._widget.setAttribute('disabled-text', this.args.disabledText as string);
|
|
36
|
+
this._widget.setAttribute('accelerator', this.args.accelerator as string);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Story: every grammar level stacked, so the rules are legible side by side. */
|
|
41
|
+
export class ShortcutLabelLevelsWebStory extends StoryElement {
|
|
42
|
+
private _widgets: HTMLElement[] = [];
|
|
43
|
+
|
|
44
|
+
constructor() {
|
|
45
|
+
super(ShortcutLabelLevelsWebStory.getMetadata(), 'Default');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static getMetadata(): StoryMeta {
|
|
49
|
+
return shortcutLabelLevelsMeta;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
initialize(): void {
|
|
53
|
+
// A two-column grid, so the shortcuts share one column and line up — the
|
|
54
|
+
// comparison is the point of this story.
|
|
55
|
+
const grid = document.createElement('div');
|
|
56
|
+
grid.style.display = 'grid';
|
|
57
|
+
grid.style.gridTemplateColumns = 'auto auto';
|
|
58
|
+
grid.style.rowGap = '12px';
|
|
59
|
+
grid.style.columnGap = '24px';
|
|
60
|
+
grid.style.alignItems = 'center';
|
|
61
|
+
|
|
62
|
+
for (const level of SHORTCUT_LABEL_LEVELS) {
|
|
63
|
+
const name = document.createElement('span');
|
|
64
|
+
name.className = 'dimmed';
|
|
65
|
+
name.textContent = level.label;
|
|
66
|
+
grid.append(name);
|
|
67
|
+
|
|
68
|
+
const widget = document.createElement('adw-shortcut-label');
|
|
69
|
+
widget.style.justifySelf = 'end';
|
|
70
|
+
widget.setAttribute('disabled-text', this.args.disabledText as string);
|
|
71
|
+
widget.setAttribute('accelerator', level.accelerator);
|
|
72
|
+
grid.append(widget);
|
|
73
|
+
this._widgets.push(widget);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.addContent(grid);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
updateArgs(_args: StoryArgs): void {
|
|
80
|
+
for (const widget of this._widgets) {
|
|
81
|
+
widget.setAttribute('disabled-text', this.args.disabledText as string);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export const ShortcutLabelWebStories: WebStoryModule = {
|
|
87
|
+
stories: [ShortcutLabelWebStory, ShortcutLabelLevelsWebStory],
|
|
88
|
+
};
|
package/src/browser/stories.ts
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
// Lists, Buttons, …).
|
|
8
8
|
|
|
9
9
|
import type { WebStoryModule } from '@gjsify/adwaita-storybook';
|
|
10
|
+
import { OverviewWidgetsWebStories } from './overview/widgets.web.js';
|
|
10
11
|
import { AvatarWebStories } from './presentation/avatar.web.js';
|
|
11
12
|
import { BannerWebStories } from './presentation/banner.web.js';
|
|
13
|
+
import { ShortcutLabelWebStories } from './presentation/shortcut-label.web.js';
|
|
12
14
|
import { SpinnerWebStories } from './presentation/spinner.web.js';
|
|
13
15
|
import { StatusPageWebStories } from './presentation/status-page.web.js';
|
|
14
16
|
import { WindowTitleWebStories } from './presentation/window-title.web.js';
|
|
@@ -44,8 +46,10 @@ import { PreferencesDialogWebStories } from './feedback/preferences-dialog.web.j
|
|
|
44
46
|
import { ToastWebStories } from './feedback/toast.web.js';
|
|
45
47
|
|
|
46
48
|
export const stories: WebStoryModule[] = [
|
|
49
|
+
OverviewWidgetsWebStories,
|
|
47
50
|
AvatarWebStories,
|
|
48
51
|
BannerWebStories,
|
|
52
|
+
ShortcutLabelWebStories,
|
|
49
53
|
SpinnerWebStories,
|
|
50
54
|
StatusPageWebStories,
|
|
51
55
|
WindowTitleWebStories,
|
package/src/metas.ts
CHANGED
|
@@ -21,8 +21,10 @@ export * from './navigation/navigation-split-view.meta.js';
|
|
|
21
21
|
export * from './navigation/navigation-view.meta.js';
|
|
22
22
|
export * from './navigation/overlay-split-view.meta.js';
|
|
23
23
|
export * from './navigation/sidebar.meta.js';
|
|
24
|
+
export * from './overview/widgets.meta.js';
|
|
24
25
|
export * from './presentation/avatar.meta.js';
|
|
25
26
|
export * from './presentation/banner.meta.js';
|
|
27
|
+
export * from './presentation/shortcut-label.meta.js';
|
|
26
28
|
export * from './presentation/spinner.meta.js';
|
|
27
29
|
export * from './presentation/status-page.meta.js';
|
|
28
30
|
export * from './presentation/window-title.meta.js';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Shared metadata for the Overview story — the widget gallery.
|
|
2
|
+
//
|
|
3
|
+
// WHAT THIS REPLACES. The gallery used to be two standalone apps rendering the
|
|
4
|
+
// same page: `examples/gtk/adwaita-reference` and
|
|
5
|
+
// `showcases/dom/adwaita-widgets-nativescript`. Both existed to answer "do these
|
|
6
|
+
// widgets look right TOGETHER", which is a question the storybook already hosts —
|
|
7
|
+
// it just had no story that composed more than one widget. Two apps meant two
|
|
8
|
+
// toolchains, two copies of the content, and (measured) a theme copy in one of
|
|
9
|
+
// them that had drifted to a quarter of the package's stylesheet.
|
|
10
|
+
//
|
|
11
|
+
// So it is one story on all three targets instead, and the two apps are gone.
|
|
12
|
+
// Story-set parity is machine-checked, which the two apps never were.
|
|
13
|
+
//
|
|
14
|
+
// The `revealed` control is the one thing here that varies meaningfully: it is
|
|
15
|
+
// `Adw.Banner:revealed`, a real property with a real animation, rather than a knob
|
|
16
|
+
// invented so the panel is not empty.
|
|
17
|
+
|
|
18
|
+
import { ControlType, type StoryMeta } from '@gjsify/stories';
|
|
19
|
+
|
|
20
|
+
/** One row of the Account group's expander. */
|
|
21
|
+
export interface OverviewExpanderRow {
|
|
22
|
+
readonly title: string;
|
|
23
|
+
readonly kind: 'switch' | 'action';
|
|
24
|
+
readonly active?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The Account group's expander contents, shared so the three targets agree. */
|
|
28
|
+
export const OVERVIEW_ADVANCED_ROWS: ReadonlyArray<OverviewExpanderRow> = [
|
|
29
|
+
{ title: 'Sync', kind: 'switch', active: true },
|
|
30
|
+
{ title: 'Export', kind: 'action' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** The accent names the gallery's combo row offers — a plain widget demo, not the real setting. */
|
|
34
|
+
export const OVERVIEW_ACCENT_OPTIONS: readonly string[] = ['Blue', 'Teal', 'Green', 'Orange'];
|
|
35
|
+
|
|
36
|
+
/** The shortcut rows, so the newest widget appears in context and not only alone. */
|
|
37
|
+
export const OVERVIEW_SHORTCUTS: ReadonlyArray<{ title: string; accelerator: string }> = [
|
|
38
|
+
{ title: 'Copy', accelerator: '<Control>C' },
|
|
39
|
+
{ title: 'Select all', accelerator: '<Shift><Control>a' },
|
|
40
|
+
{ title: 'Switch tab', accelerator: '<Alt>1...9' },
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export const overviewWidgetsMeta: StoryMeta = {
|
|
44
|
+
title: 'Overview/Widgets',
|
|
45
|
+
description:
|
|
46
|
+
'Many widgets at once, the way an app composes them: a banner, preference groups of rows, an expander, buttons and shortcut labels. Use it to judge spacing, alignment and the theme as a whole rather than one widget in isolation.',
|
|
47
|
+
controls: [
|
|
48
|
+
{
|
|
49
|
+
name: 'revealed',
|
|
50
|
+
label: 'Show banner',
|
|
51
|
+
type: ControlType.BOOLEAN,
|
|
52
|
+
description: 'Adw.Banner:revealed — animates in and out',
|
|
53
|
+
defaultValue: true,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// The Overview story on GTK — the widget gallery that used to be
|
|
2
|
+
// `examples/gtk/adwaita-reference`. original implementation.
|
|
3
|
+
|
|
4
|
+
import Adw from '@girs/adw-1';
|
|
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 {
|
|
9
|
+
OVERVIEW_ACCENT_OPTIONS,
|
|
10
|
+
OVERVIEW_ADVANCED_ROWS,
|
|
11
|
+
OVERVIEW_SHORTCUTS,
|
|
12
|
+
overviewWidgetsMeta,
|
|
13
|
+
} from './widgets.meta.js';
|
|
14
|
+
|
|
15
|
+
/** Story: many widgets at once, composed the way an app composes them. */
|
|
16
|
+
export class OverviewWidgetsStory extends StoryWidget {
|
|
17
|
+
private _banner: Adw.Banner | null = null;
|
|
18
|
+
|
|
19
|
+
static {
|
|
20
|
+
GObject.registerClass({ GTypeName: 'AdwStorybookOverviewWidgets' }, OverviewWidgetsStory);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
super(StoryWidget.fromMeta(OverviewWidgetsStory.getMetadata(), 'Default'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static getMetadata(): StoryMeta {
|
|
28
|
+
return overviewWidgetsMeta;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
initialize(): void {
|
|
32
|
+
const box = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, hexpand: true });
|
|
33
|
+
|
|
34
|
+
this._banner = new Adw.Banner({
|
|
35
|
+
title: 'You have unsaved changes',
|
|
36
|
+
button_label: 'Save',
|
|
37
|
+
revealed: this.args.revealed as boolean,
|
|
38
|
+
});
|
|
39
|
+
box.append(this._banner);
|
|
40
|
+
|
|
41
|
+
const page = new Adw.PreferencesPage({ hexpand: true });
|
|
42
|
+
|
|
43
|
+
// --- Appearance: switches and a combo. A widget demo only — the
|
|
44
|
+
// storybook's OWN appearance lives in the header bar's dialog, and wiring
|
|
45
|
+
// these to it would give one setting two controls that disagree.
|
|
46
|
+
const appearance = new Adw.PreferencesGroup({ title: 'Appearance' });
|
|
47
|
+
appearance.add(
|
|
48
|
+
new Adw.SwitchRow({ title: 'Dark mode', subtitle: 'Use the dark Adwaita palette', active: false }),
|
|
49
|
+
);
|
|
50
|
+
appearance.add(new Adw.SwitchRow({ title: 'Notifications', subtitle: 'Show toasts for events', active: true }));
|
|
51
|
+
appearance.add(
|
|
52
|
+
new Adw.ComboRow({
|
|
53
|
+
title: 'Accent color',
|
|
54
|
+
model: Gtk.StringList.new([...OVERVIEW_ACCENT_OPTIONS]),
|
|
55
|
+
selected: 0,
|
|
56
|
+
}),
|
|
57
|
+
);
|
|
58
|
+
page.add(appearance);
|
|
59
|
+
|
|
60
|
+
// --- Account: entries, a spin row, and a nested expander ---
|
|
61
|
+
const account = new Adw.PreferencesGroup({ title: 'Account' });
|
|
62
|
+
|
|
63
|
+
const name = new Adw.EntryRow({ title: 'Name' });
|
|
64
|
+
name.set_text('Ada Lovelace');
|
|
65
|
+
account.add(name);
|
|
66
|
+
|
|
67
|
+
const email = new Adw.EntryRow({ title: 'Email' });
|
|
68
|
+
email.set_text('ada@example.com');
|
|
69
|
+
account.add(email);
|
|
70
|
+
|
|
71
|
+
const devices = new Adw.SpinRow({
|
|
72
|
+
title: 'Devices',
|
|
73
|
+
adjustment: new Gtk.Adjustment({ value: 3, lower: 1, upper: 10, step_increment: 1, page_increment: 1 }),
|
|
74
|
+
});
|
|
75
|
+
// The displayed value does not always follow the adjustment through
|
|
76
|
+
// property-init ordering, so it is set explicitly.
|
|
77
|
+
devices.set_value(3);
|
|
78
|
+
account.add(devices);
|
|
79
|
+
|
|
80
|
+
const advanced = new Adw.ExpanderRow({ title: 'Advanced', subtitle: 'More options', expanded: true });
|
|
81
|
+
for (const row of OVERVIEW_ADVANCED_ROWS) {
|
|
82
|
+
advanced.add_row(
|
|
83
|
+
row.kind === 'switch'
|
|
84
|
+
? new Adw.SwitchRow({ title: row.title, active: row.active ?? false })
|
|
85
|
+
: new Adw.ActionRow({ title: row.title }),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
account.add(advanced);
|
|
89
|
+
page.add(account);
|
|
90
|
+
|
|
91
|
+
// --- Shortcuts: the newest widget, in context ---
|
|
92
|
+
const shortcuts = new Adw.PreferencesGroup({ title: 'Shortcuts' });
|
|
93
|
+
for (const shortcut of OVERVIEW_SHORTCUTS) {
|
|
94
|
+
const row = new Adw.ActionRow({ title: shortcut.title });
|
|
95
|
+
row.add_suffix(new Adw.ShortcutLabel({ accelerator: shortcut.accelerator, valign: Gtk.Align.CENTER }));
|
|
96
|
+
shortcuts.add(row);
|
|
97
|
+
}
|
|
98
|
+
page.add(shortcuts);
|
|
99
|
+
|
|
100
|
+
// --- Actions: the two button styles side by side ---
|
|
101
|
+
const actions = new Adw.PreferencesGroup({ title: 'Actions' });
|
|
102
|
+
const buttons = new Gtk.Box({
|
|
103
|
+
orientation: Gtk.Orientation.HORIZONTAL,
|
|
104
|
+
spacing: 12,
|
|
105
|
+
homogeneous: true,
|
|
106
|
+
margin_top: 6,
|
|
107
|
+
margin_bottom: 6,
|
|
108
|
+
});
|
|
109
|
+
const save = new Gtk.Button({ label: 'Save changes' });
|
|
110
|
+
save.add_css_class('suggested-action');
|
|
111
|
+
buttons.append(save);
|
|
112
|
+
const remove = new Gtk.Button({ label: 'Delete account' });
|
|
113
|
+
remove.add_css_class('destructive-action');
|
|
114
|
+
buttons.append(remove);
|
|
115
|
+
actions.add(buttons);
|
|
116
|
+
page.add(actions);
|
|
117
|
+
|
|
118
|
+
box.append(page);
|
|
119
|
+
this.addContent(box);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
updateArgs(_args: StoryArgs): void {
|
|
123
|
+
this._banner?.set_revealed(this.args.revealed as boolean);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
GObject.type_ensure(OverviewWidgetsStory.$gtype);
|
|
128
|
+
|
|
129
|
+
export const OverviewWidgetsStories: StoryModule = { stories: [OverviewWidgetsStory] };
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Shared, renderer-agnostic metadata for the Shortcut Label story. Imported by
|
|
2
|
+
// the GTK renderer (shortcut-label.story.ts), the browser renderer
|
|
3
|
+
// (browser/presentation/shortcut-label.web.ts) and the NativeScript one, so all
|
|
4
|
+
// three expose identical controls.
|
|
5
|
+
//
|
|
6
|
+
// `accelerator` is a SELECT rather than free text: the interesting thing about
|
|
7
|
+
// this widget is its four-level grammar (` ` → `...` → `+` → `&`), and the
|
|
8
|
+
// presets put each level one tap away — which matters most on the NativeScript
|
|
9
|
+
// target, where typing `<Control>C` means fighting a phone keyboard.
|
|
10
|
+
|
|
11
|
+
import { ControlType, type StoryMeta } from '@gjsify/stories';
|
|
12
|
+
|
|
13
|
+
/** One row of the All Levels story. */
|
|
14
|
+
export interface ShortcutLabelLevel {
|
|
15
|
+
readonly label: string;
|
|
16
|
+
readonly accelerator: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The rows the All Levels story stacks, shared by all three renderers.
|
|
21
|
+
*
|
|
22
|
+
* It lives in the meta because the whole value of that story is the COMPARISON:
|
|
23
|
+
* three targets showing a different set of accelerators could not be compared at
|
|
24
|
+
* all, which is the same reason the story SET is gated
|
|
25
|
+
* (scripts/check-storybook-story-parity.mjs).
|
|
26
|
+
*/
|
|
27
|
+
export const SHORTCUT_LABEL_LEVELS: ReadonlyArray<ShortcutLabelLevel> = [
|
|
28
|
+
{ label: 'Single', accelerator: '<Control>C' },
|
|
29
|
+
{ label: 'Modifier order', accelerator: '<Shift><Control>a' },
|
|
30
|
+
{ label: 'Alternatives', accelerator: '<Shift>A Home' },
|
|
31
|
+
{ label: 'Range', accelerator: '<Alt>1...9' },
|
|
32
|
+
{ label: 'In sequence', accelerator: '<Control>C+<Control>X' },
|
|
33
|
+
{ label: 'Together', accelerator: 'Control_L&Control_R' },
|
|
34
|
+
{ label: 'Nested', accelerator: '<Alt>1...9 <Alt>0' },
|
|
35
|
+
{ label: 'Unset', accelerator: '' },
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
export const shortcutLabelMeta: StoryMeta = {
|
|
39
|
+
title: 'Presentation/Shortcut Label',
|
|
40
|
+
description:
|
|
41
|
+
'Adw.ShortcutLabel — an accelerator drawn as keycaps. The four grammar levels: alternatives (space), a range (...), pressed in sequence (+), and pressed together (&).',
|
|
42
|
+
controls: [
|
|
43
|
+
{
|
|
44
|
+
name: 'accelerator',
|
|
45
|
+
label: 'Accelerator',
|
|
46
|
+
type: ControlType.SELECT,
|
|
47
|
+
options: [
|
|
48
|
+
{ label: 'Ctrl+C', value: '<Control>C' },
|
|
49
|
+
{ label: 'Modifier order (Shift+Ctrl+A)', value: '<Shift><Control>a' },
|
|
50
|
+
{ label: 'Alternatives (Shift+A / Home)', value: '<Shift>A Home' },
|
|
51
|
+
{ label: 'Range (Alt+1…9)', value: '<Alt>1...9' },
|
|
52
|
+
{ label: 'In sequence (Ctrl+C → Ctrl+X)', value: '<Control>C+<Control>X' },
|
|
53
|
+
{ label: 'Together (Ctrl L + Ctrl R)', value: 'Control_L&Control_R' },
|
|
54
|
+
{ label: 'Nested (Alt+1…9 / Alt+0)', value: '<Alt>1...9 <Alt>0' },
|
|
55
|
+
{ label: 'Glyph key (Ctrl+Space)', value: '<Control>space' },
|
|
56
|
+
{ label: 'Arrow key (Left)', value: 'Left' },
|
|
57
|
+
{ label: 'Unset — shows the placeholder', value: '' },
|
|
58
|
+
],
|
|
59
|
+
defaultValue: '<Control>C',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'disabledText',
|
|
63
|
+
label: 'Placeholder',
|
|
64
|
+
type: ControlType.TEXT,
|
|
65
|
+
defaultValue: 'Disabled',
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Every grammar level at once.
|
|
72
|
+
*
|
|
73
|
+
* The interactive story above shows ONE accelerator per view, and the four
|
|
74
|
+
* nesting levels are only legible side by side — `Modifier order` renders
|
|
75
|
+
* `Ctrl Shift A` from `<Shift><Control>a`, and `Range` ends without a modifier,
|
|
76
|
+
* neither of which reads as a rule until there is a neighbour to compare against.
|
|
77
|
+
*/
|
|
78
|
+
export const shortcutLabelLevelsMeta: StoryMeta = {
|
|
79
|
+
title: 'Presentation/Shortcut Label (All Levels)',
|
|
80
|
+
description:
|
|
81
|
+
"Every Adw.ShortcutLabel grammar level at once: alternatives (space), a range (...), pressed in sequence (+), pressed together (&) — plus GTK's fixed keycap order and the disabled placeholder.",
|
|
82
|
+
controls: [
|
|
83
|
+
{
|
|
84
|
+
name: 'disabledText',
|
|
85
|
+
label: 'Placeholder',
|
|
86
|
+
type: ControlType.TEXT,
|
|
87
|
+
defaultValue: 'Disabled',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Adw.ShortcutLabel — an accelerator drawn as keycaps. original implementation.
|
|
2
|
+
|
|
3
|
+
import Adw from '@girs/adw-1';
|
|
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 { SHORTCUT_LABEL_LEVELS, shortcutLabelLevelsMeta, shortcutLabelMeta } from './shortcut-label.meta.js';
|
|
8
|
+
|
|
9
|
+
/** Story: Adw.ShortcutLabel across the four levels of its accelerator grammar. */
|
|
10
|
+
export class ShortcutLabelStory extends StoryWidget {
|
|
11
|
+
private _widget: Adw.ShortcutLabel | null = null;
|
|
12
|
+
|
|
13
|
+
static {
|
|
14
|
+
GObject.registerClass({ GTypeName: 'AdwStorybookShortcutLabel' }, ShortcutLabelStory);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super(StoryWidget.fromMeta(ShortcutLabelStory.getMetadata(), 'Default'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static getMetadata(): StoryMeta {
|
|
22
|
+
return { ...shortcutLabelMeta, component: Adw.ShortcutLabel.$gtype };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
initialize(): void {
|
|
26
|
+
this._widget = new Adw.ShortcutLabel({
|
|
27
|
+
accelerator: this.args.accelerator as string,
|
|
28
|
+
disabledText: this.args.disabledText as string,
|
|
29
|
+
halign: Gtk.Align.CENTER,
|
|
30
|
+
valign: Gtk.Align.CENTER,
|
|
31
|
+
});
|
|
32
|
+
this.addContent(this._widget);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
updateArgs(_args: StoryArgs): void {
|
|
36
|
+
if (!this._widget) return;
|
|
37
|
+
this._widget.disabledText = this.args.disabledText as string;
|
|
38
|
+
this._widget.accelerator = this.args.accelerator as string;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
GObject.type_ensure(ShortcutLabelStory.$gtype);
|
|
43
|
+
|
|
44
|
+
/** Story: every grammar level stacked, so the rules are legible side by side. */
|
|
45
|
+
export class ShortcutLabelLevelsStory extends StoryWidget {
|
|
46
|
+
private _widgets: Adw.ShortcutLabel[] = [];
|
|
47
|
+
|
|
48
|
+
static {
|
|
49
|
+
GObject.registerClass({ GTypeName: 'AdwStorybookShortcutLabelLevels' }, ShortcutLabelLevelsStory);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
constructor() {
|
|
53
|
+
super(StoryWidget.fromMeta(ShortcutLabelLevelsStory.getMetadata(), 'Default'));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static getMetadata(): StoryMeta {
|
|
57
|
+
return { ...shortcutLabelLevelsMeta, component: Adw.ShortcutLabel.$gtype };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
initialize(): void {
|
|
61
|
+
// A grid rather than a box per row: the shortcuts line up in one column
|
|
62
|
+
// only if they share it, and the comparison is the point of this story.
|
|
63
|
+
const grid = new Gtk.Grid({ row_spacing: 12, column_spacing: 24 });
|
|
64
|
+
|
|
65
|
+
for (const [row, level] of SHORTCUT_LABEL_LEVELS.entries()) {
|
|
66
|
+
const name = new Gtk.Label({ label: level.label, halign: Gtk.Align.START });
|
|
67
|
+
name.add_css_class('dimmed');
|
|
68
|
+
grid.attach(name, 0, row, 1, 1);
|
|
69
|
+
|
|
70
|
+
const widget = new Adw.ShortcutLabel({
|
|
71
|
+
accelerator: level.accelerator,
|
|
72
|
+
disabledText: this.args.disabledText as string,
|
|
73
|
+
halign: Gtk.Align.END,
|
|
74
|
+
hexpand: true,
|
|
75
|
+
});
|
|
76
|
+
grid.attach(widget, 1, row, 1, 1);
|
|
77
|
+
this._widgets.push(widget);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.addContent(grid);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
updateArgs(_args: StoryArgs): void {
|
|
84
|
+
for (const widget of this._widgets) {
|
|
85
|
+
widget.disabledText = this.args.disabledText as string;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
GObject.type_ensure(ShortcutLabelLevelsStory.$gtype);
|
|
91
|
+
|
|
92
|
+
export const ShortcutLabelStories: StoryModule = { stories: [ShortcutLabelStory, ShortcutLabelLevelsStory] };
|