create-gtkx 0.21.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 (55) hide show
  1. package/LICENSE +373 -0
  2. package/bin/create-gtkx.js +5 -0
  3. package/dist/cli.d.ts +24 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +19 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/command.d.ts +65 -0
  8. package/dist/command.d.ts.map +1 -0
  9. package/dist/command.js +69 -0
  10. package/dist/command.js.map +1 -0
  11. package/dist/create.d.ts +3 -0
  12. package/dist/create.d.ts.map +1 -0
  13. package/dist/create.js +7 -0
  14. package/dist/create.js.map +1 -0
  15. package/dist/deps.d.ts +3 -0
  16. package/dist/deps.d.ts.map +1 -0
  17. package/dist/deps.js +51 -0
  18. package/dist/deps.js.map +1 -0
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +4 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/options.d.ts +25 -0
  24. package/dist/options.d.ts.map +1 -0
  25. package/dist/options.js +13 -0
  26. package/dist/options.js.map +1 -0
  27. package/dist/scaffolder.d.ts +51 -0
  28. package/dist/scaffolder.d.ts.map +1 -0
  29. package/dist/scaffolder.js +191 -0
  30. package/dist/scaffolder.js.map +1 -0
  31. package/dist/templates.d.ts +11 -0
  32. package/dist/templates.d.ts.map +1 -0
  33. package/dist/templates.js +25 -0
  34. package/dist/templates.js.map +1 -0
  35. package/package.json +65 -0
  36. package/src/cli.ts +21 -0
  37. package/src/command.ts +87 -0
  38. package/src/create.ts +7 -0
  39. package/src/deps.ts +51 -0
  40. package/src/index.ts +4 -0
  41. package/src/options.ts +24 -0
  42. package/src/scaffolder.ts +306 -0
  43. package/src/templates.ts +42 -0
  44. package/templates/claude/EXAMPLES.md.ejs +790 -0
  45. package/templates/claude/SKILL.md.ejs +126 -0
  46. package/templates/claude/WIDGETS.md.ejs +934 -0
  47. package/templates/config/vitest.config.ts.ejs +11 -0
  48. package/templates/gitignore.ejs +4 -0
  49. package/templates/gtkx.config.ts.ejs +6 -0
  50. package/templates/package.json.ejs +16 -0
  51. package/templates/src/app.tsx.ejs +48 -0
  52. package/templates/src/gtkx-env.d.ts.ejs +2 -0
  53. package/templates/src/index.tsx.ejs +4 -0
  54. package/templates/tests/app.test.tsx.ejs +16 -0
  55. package/templates/tsconfig.json.ejs +14 -0
@@ -0,0 +1,934 @@
1
+ # GTKX Widget Reference
2
+
3
+ ## Imports
4
+
5
+ Every element-like component is imported from its namespace module: `@gtkx/jsx/gtk` for GTK widgets (`GtkBox`, `GtkListView`, `GtkColumnView`, `GtkDrawingArea`, …), `@gtkx/jsx/adw` for Adwaita (`AdwHeaderBar`, `AdwComboRow`, …), and `@gtkx/jsx/gio` for Gio elements (`GMenu`, `GSimpleAction`). `@gtkx/react` provides `render`, `quit`, hooks (`useApplication`, `useProperty`, `useSetting`, `useSignal`, `useTickCallback`, `useAdjustment`), and shared types such as `MenuEntry` and `ListItem`. `@gtkx/animate` provides the animation components. Enums and classes come from `@gtkx/gi/<ns>` (e.g. `import * as Gtk from "@gtkx/gi/gtk"`).
6
+
7
+ ## Common Props (All Widgets)
8
+
9
+ | Prop | Type | Description |
10
+ |------|------|-------------|
11
+ | `hexpand` / `vexpand` | boolean | Expand to fill available space |
12
+ | `halign` / `valign` | `Gtk.Align.START \| CENTER \| END \| FILL` | Alignment |
13
+ | `marginStart/End/Top/Bottom` | number | Margins in pixels |
14
+ | `sensitive` | boolean | Enabled/disabled state |
15
+ | `visible` | boolean | Visibility |
16
+ | `cssClasses` | string[] | CSS classes for styling |
17
+ | `widthRequest` / `heightRequest` | number | Minimum size |
18
+
19
+ ---
20
+
21
+ ## Containers
22
+
23
+ ### GtkBox
24
+ Linear layout (horizontal or vertical).
25
+
26
+ ```tsx
27
+ <GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
28
+ {children}
29
+ </GtkBox>
30
+ ```
31
+
32
+ ### GtkGrid
33
+ 2D grid with explicit positioning using `GtkGridChild`.
34
+
35
+ ```tsx
36
+ <GtkGrid rowSpacing={8} columnSpacing={12}>
37
+ <GtkGridChild column={0} row={0}><GtkLabel label="Name:" /></GtkGridChild>
38
+ <GtkGridChild column={1} row={0}><GtkEntry hexpand /></GtkGridChild>
39
+ <GtkGridChild column={0} row={1} columnSpan={2}><GtkButton label="Submit" /></GtkGridChild>
40
+ </GtkGrid>
41
+ ```
42
+
43
+ **GtkGridChild props:** `column`, `row`, `columnSpan`, `rowSpan`
44
+
45
+ ### GtkStack
46
+ Page container, shows one child at a time. Drive the visible page with `visibleChildName`, matching a page's `id`.
47
+
48
+ ```tsx
49
+ <GtkStack visibleChildName="page1" transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT}>
50
+ <GtkStackPage id="page1" title="First" iconName="document-new">
51
+ <Content1 />
52
+ </GtkStackPage>
53
+ <GtkStackPage id="page2" title="Second">
54
+ <Content2 />
55
+ </GtkStackPage>
56
+ </GtkStack>
57
+ ```
58
+
59
+ **GtkStackPage props:** `id` (matches the stack's `visibleChildName`), `title`, `iconName`, `needsAttention`, `badgeNumber` (AdwViewStack only). `AdwViewStack` pairs with `AdwViewStackPage` the same way.
60
+
61
+ ### GtkNotebook
62
+ Tabbed container with visible tabs.
63
+
64
+ ```tsx
65
+ <GtkNotebook>
66
+ <GtkNotebookPage label="Tab 1"><Content1 /></GtkNotebookPage>
67
+ <GtkNotebookPage label="Tab 2" tabExpand tabFill><Content2 /></GtkNotebookPage>
68
+ </GtkNotebook>
69
+ ```
70
+
71
+ Custom tab widget via the `tabLabel` prop:
72
+ ```tsx
73
+ <GtkNotebookPage
74
+ tabLabel={
75
+ <GtkBox spacing={4}>
76
+ <GtkImage iconName="folder-symbolic" />
77
+ <GtkLabel label="Files" />
78
+ </GtkBox>
79
+ }
80
+ >
81
+ <Content />
82
+ </GtkNotebookPage>
83
+ ```
84
+
85
+ **GtkNotebookPage props:** `label`, `tabLabel`, `tabExpand`, `tabFill`
86
+
87
+ ### GtkPaned
88
+ Resizable split with draggable divider. Pass the two panes through the `startChild` and `endChild` props.
89
+
90
+ ```tsx
91
+ <GtkPaned position={280} shrinkStartChild={false} startChild={<Sidebar />} endChild={<MainContent />} />
92
+ ```
93
+
94
+ ### GtkOverlay
95
+ Stack widgets on top of each other. First child is base layer, additional children need a `GtkOverlayChild` wrapper. Multiple children supported per overlay.
96
+
97
+ ```tsx
98
+ <GtkOverlay>
99
+ <GtkButton label="Notifications" />
100
+ <GtkOverlayChild>
101
+ <GtkLabel label="3" cssClasses={["badge"]} halign={Gtk.Align.END} valign={Gtk.Align.START} />
102
+ </GtkOverlayChild>
103
+ </GtkOverlay>
104
+ ```
105
+
106
+ ### GtkFixed
107
+ Absolute positioning with optional 3D transforms. Use a `GtkFixedChild` wrapper for children.
108
+
109
+ ```tsx
110
+ <GtkFixed>
111
+ <GtkFixedChild x={20} y={30}>
112
+ <GtkLabel label="Top Left" />
113
+ </GtkFixedChild>
114
+ <GtkFixedChild x={200} y={100} transform={someGskTransform}>
115
+ <GtkLabel label="Transformed" />
116
+ </GtkFixedChild>
117
+ </GtkFixed>
118
+ ```
119
+
120
+ **GtkFixedChild props:** `x`, `y` (pixel coordinates), `transform` (optional `Gsk.Transform`)
121
+
122
+ ### GtkScrolledWindow
123
+ Scrollable container.
124
+
125
+ ```tsx
126
+ <GtkScrolledWindow vexpand hscrollbarPolicy={Gtk.PolicyType.NEVER}>
127
+ <Content />
128
+ </GtkScrolledWindow>
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Virtual Lists
134
+
135
+ All virtual list widgets use an `items` data prop and a `renderItem` function. Items are `{ id: string, value: T }` objects.
136
+
137
+ ### GtkListView
138
+ High-performance scrollable list with selection.
139
+
140
+ ```tsx
141
+ <GtkListView
142
+ estimatedItemHeight={48}
143
+ vexpand
144
+ selected={selectedId ? [selectedId] : []}
145
+ selectionMode={Gtk.SelectionMode.SINGLE}
146
+ onSelectionChanged={(ids) => setSelectedId(ids[0])}
147
+ items={items.map(item => ({ id: item.id, value: item }))}
148
+ renderItem={(item: Item) => <GtkLabel label={item.name} />}
149
+ />
150
+ ```
151
+
152
+ ### GtkGridView
153
+ Grid-based virtual scrolling.
154
+
155
+ ```tsx
156
+ <GtkGridView
157
+ estimatedItemHeight={100}
158
+ minColumns={2}
159
+ maxColumns={4}
160
+ items={items.map(item => ({ id: item.id, value: item }))}
161
+ renderItem={(item: Item) => (
162
+ <GtkBox orientation={Gtk.Orientation.VERTICAL}>
163
+ <GtkImage iconName={item.icon} />
164
+ <GtkLabel label={item.name} />
165
+ </GtkBox>
166
+ )}
167
+ />
168
+ ```
169
+
170
+ ### GtkColumnView
171
+ Table with sortable columns.
172
+
173
+ ```tsx
174
+ <GtkColumnView
175
+ estimatedRowHeight={48}
176
+ sortColumn="name"
177
+ sortOrder={Gtk.SortType.ASCENDING}
178
+ onSortChanged={handleSort}
179
+ items={items.map(item => ({ id: item.id, value: item }))}
180
+ >
181
+ <GtkColumnViewColumn
182
+ title="Name"
183
+ id="name"
184
+ expand
185
+ resizable
186
+ sortable
187
+ renderCell={(item: Item) => <GtkLabel label={item.name} />}
188
+ />
189
+ <GtkColumnViewColumn
190
+ title="Size"
191
+ id="size"
192
+ fixedWidth={100}
193
+ renderCell={(item: Item) => <GtkLabel label={`${item.size} KB`} />}
194
+ />
195
+ </GtkColumnView>
196
+ ```
197
+
198
+ ### GtkDropDown
199
+ Selection dropdown.
200
+
201
+ ```tsx
202
+ <GtkDropDown
203
+ selectedId={selectedId}
204
+ onSelectionChanged={setSelectedId}
205
+ items={options.map(opt => ({ id: opt.id, value: opt.label }))}
206
+ />
207
+ ```
208
+
209
+ ### GtkListView (tree mode)
210
+ Hierarchical tree with expand/collapse. Items with nested `children` arrays trigger tree behavior.
211
+
212
+ ```tsx
213
+ <GtkListView
214
+ estimatedItemHeight={48}
215
+ vexpand
216
+ autoexpand={false}
217
+ selectionMode={Gtk.SelectionMode.SINGLE}
218
+ selected={selectedId ? [selectedId] : []}
219
+ onSelectionChanged={(ids) => setSelectedId(ids[0])}
220
+ items={files.map(file => ({
221
+ id: file.id,
222
+ value: file,
223
+ children: file.children?.map(child => ({ id: child.id, value: child })),
224
+ }))}
225
+ renderItem={(item: FileNode, row) => (
226
+ <GtkBox spacing={8}>
227
+ <GtkImage iconName={item.isDirectory ? "folder-symbolic" : "text-x-generic-symbolic"} />
228
+ <GtkLabel label={item.name} />
229
+ </GtkBox>
230
+ )}
231
+ />
232
+ ```
233
+
234
+ **ListItem data props:** `id`, `value`, `children` (nested items for tree mode), `hideExpander`, `indentForDepth`, `indentForIcon`, `section` (for sectioned lists)
235
+
236
+ ---
237
+
238
+ ## Inputs
239
+
240
+ ### GtkEntry
241
+ Single-line text input. **Requires two-way binding.**
242
+
243
+ ```tsx
244
+ const [text, setText] = useState("");
245
+ <GtkEntry text={text} onChanged={(e) => setText(e.getText())} placeholderText="Enter text..." />
246
+ ```
247
+
248
+ ### GtkToggleButton
249
+ Toggle button. Auto-prevents signal feedback loops.
250
+
251
+ ```tsx
252
+ <GtkToggleButton active={isActive} onToggled={() => setIsActive(!isActive)} label="Toggle" />
253
+ ```
254
+
255
+ ### GtkCheckButton
256
+ Checkbox.
257
+
258
+ ```tsx
259
+ <GtkCheckButton active={checked} onToggled={() => setChecked(!checked)} label="Option" />
260
+ ```
261
+
262
+ ### GtkSwitch
263
+ On/off switch.
264
+
265
+ ```tsx
266
+ <GtkSwitch active={enabled} onStateSet={() => { setEnabled(!enabled); return true; }} />
267
+ ```
268
+
269
+ ### GtkSpinButton
270
+ Numeric input with increment/decrement. Build the `adjustment` with the `useAdjustment` hook from `@gtkx/react`.
271
+
272
+ ```tsx
273
+ const adjustment = useAdjustment({ value: count, lower: 0, upper: 100, stepIncrement: 1 });
274
+
275
+ <GtkSpinButton adjustment={adjustment} onValueChanged={(self) => setCount(self.getValue())} />
276
+ ```
277
+
278
+ ### GtkScale
279
+ Slider with an adjustment and optional marks.
280
+
281
+ ```tsx
282
+ const adjustment = useAdjustment({ value: volume, lower: 0, upper: 100, stepIncrement: 1 });
283
+
284
+ <GtkScale
285
+ drawValue
286
+ valuePos={Gtk.PositionType.TOP}
287
+ adjustment={adjustment}
288
+ onValueChanged={(self) => setVolume(self.getValue())}
289
+ marks={[
290
+ { value: 0, label: "Min", position: Gtk.PositionType.BOTTOM },
291
+ { value: 50, position: Gtk.PositionType.BOTTOM },
292
+ { value: 100, label: "Max", position: Gtk.PositionType.BOTTOM },
293
+ ]}
294
+ />
295
+ ```
296
+
297
+ **`useAdjustment` config:** `value`, `lower`, `upper`, `stepIncrement`, `pageIncrement`, `pageSize`
298
+ **ScaleMark type:** `{ value: number, position?: Gtk.PositionType, label?: string }`
299
+
300
+ ### GtkCalendar
301
+ Date picker with markable days.
302
+
303
+ ```tsx
304
+ <GtkCalendar
305
+ onDaySelected={(cal) => setDate(cal.getDate())}
306
+ markedDays={[15, 20, 25]}
307
+ />
308
+ ```
309
+
310
+ ### GtkLevelBar
311
+ Progress/level indicator with customizable thresholds.
312
+
313
+ ```tsx
314
+ <GtkLevelBar
315
+ value={0.6}
316
+ offsets={[
317
+ { id: "low", value: 0.25 },
318
+ { id: "high", value: 0.75 },
319
+ { id: "full", value: 1.0 },
320
+ ]}
321
+ />
322
+ ```
323
+
324
+ **LevelBarOffset type:** `{ id: string, value: number }`
325
+
326
+ ---
327
+
328
+ ## Display
329
+
330
+ ### GtkLabel
331
+ ```tsx
332
+ <GtkLabel label="Text" halign={Gtk.Align.START} wrap useMarkup />
333
+ ```
334
+
335
+ ### GtkButton
336
+ ```tsx
337
+ <GtkButton label="Click" onClicked={handleClick} iconName="document-new-symbolic" />
338
+ ```
339
+
340
+ ### GtkImage
341
+ ```tsx
342
+ <GtkImage iconName="folder-symbolic" pixelSize={48} />
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Header & Action Bars
348
+
349
+ ### GtkHeaderBar
350
+ Title bar with packed widgets. Pass packed widgets through the `packStart`/`packEnd` props and the title through `titleWidget`.
351
+
352
+ ```tsx
353
+ <GtkHeaderBar
354
+ packStart={<GtkButton iconName="go-previous-symbolic" />}
355
+ titleWidget={<GtkLabel label="Title" cssClasses={["title"]} />}
356
+ packEnd={<GtkMenuButton iconName="open-menu-symbolic" />}
357
+ />
358
+ ```
359
+
360
+ ### GtkActionBar
361
+ Bottom action bar.
362
+
363
+ ```tsx
364
+ <GtkActionBar
365
+ packStart={<GtkButton label="Cancel" />}
366
+ packEnd={<GtkButton label="Save" cssClasses={["suggested-action"]} />}
367
+ />
368
+ ```
369
+
370
+ ---
371
+
372
+ ## Menus
373
+
374
+ Menus are data. `<GMenu>` (from `@gtkx/jsx/gio`) takes an `items` array of `MenuEntry` objects (`{ label?, action?, submenu?, section? }`, exported by `@gtkx/react`). Each leaf entry triggers a named action; declare the actions as `<GSimpleAction>` elements (also from `@gtkx/jsx/gio`) through the window's `addAction` prop (scope `win.`) or as children of the application (scope `app.`).
375
+
376
+ ### GtkMenuButton with a menu model
377
+
378
+ ```tsx
379
+ <GtkMenuButton
380
+ iconName="open-menu-symbolic"
381
+ menuModel={
382
+ <GMenu
383
+ items={[
384
+ { label: "_New", action: "win.new" },
385
+ { label: "_Open", action: "win.open" },
386
+ {
387
+ label: "Export",
388
+ submenu: [
389
+ { label: "PDF", action: "win.export-pdf" },
390
+ { label: "CSV", action: "win.export-csv" },
391
+ ],
392
+ },
393
+ { section: [{ label: "_Quit", action: "app.quit" }] },
394
+ ]}
395
+ />
396
+ }
397
+ />
398
+ ```
399
+
400
+ ### Declaring the actions
401
+
402
+ ```tsx
403
+ <GtkApplicationWindow
404
+ addAction={
405
+ <>
406
+ <GSimpleAction name="new" onActivate={handleNew} accels="<Control>n" />
407
+ <GSimpleAction name="open" onActivate={handleOpen} accels="<Control>o" />
408
+ <GSimpleAction name="export-pdf" onActivate={exportPdf} />
409
+ <GSimpleAction name="export-csv" onActivate={exportCsv} />
410
+ </>
411
+ }
412
+ >
413
+ {/* ... */}
414
+ </GtkApplicationWindow>
415
+ ```
416
+
417
+ **MenuEntry fields:** `label` (underscore marks the mnemonic), `action` (detailed action name, e.g. `"win.open"`), `submenu` (nested `MenuEntry[]`), `section` (inline group of `MenuEntry[]`, with `label` as the heading)
418
+
419
+ **GSimpleAction props:** `name` (required), `onActivate`, `accels` (e.g., `"<Control>n"`), `enabled`
420
+
421
+ ---
422
+
423
+ ## Windows
424
+
425
+ ### GtkApplicationWindow
426
+
427
+ Handle `onCloseRequest` and return `true` to veto GTK's native close so React state unmounts the window.
428
+
429
+ ```tsx
430
+ <GtkApplicationWindow
431
+ title="App"
432
+ defaultWidth={800}
433
+ defaultHeight={600}
434
+ onCloseRequest={() => {
435
+ quit();
436
+ return true;
437
+ }}
438
+ >
439
+ <Content />
440
+ </GtkApplicationWindow>
441
+ ```
442
+
443
+ Custom titlebar:
444
+ ```tsx
445
+ <GtkApplicationWindow ... titlebar={<GtkHeaderBar />}>
446
+ <Content />
447
+ </GtkApplicationWindow>
448
+ ```
449
+
450
+ ---
451
+
452
+ ## Adwaita (Libadwaita)
453
+
454
+ Import: `import * as Adw from "@gtkx/gi/adw";`
455
+
456
+ ### AdwApplicationWindow + AdwToolbarView
457
+ Modern app structure.
458
+
459
+ ```tsx
460
+ <AdwApplicationWindow
461
+ title="App"
462
+ defaultWidth={800}
463
+ defaultHeight={600}
464
+ onCloseRequest={() => {
465
+ quit();
466
+ return true;
467
+ }}
468
+ >
469
+ <AdwToolbarView
470
+ addTopBar={
471
+ <AdwHeaderBar titleWidget={<AdwWindowTitle title="App" subtitle="Description" />} />
472
+ }
473
+ addBottomBar={<GtkActionBar />}
474
+ >
475
+ <MainContent />
476
+ </AdwToolbarView>
477
+ </AdwApplicationWindow>
478
+ ```
479
+
480
+ ### AdwStatusPage
481
+ Welcome, error, or empty state.
482
+
483
+ ```tsx
484
+ <AdwStatusPage iconName="applications-system-symbolic" title="Welcome" description="Get started" vexpand>
485
+ <GtkButton label="Start" cssClasses={["suggested-action", "pill"]} halign={Gtk.Align.CENTER} />
486
+ </AdwStatusPage>
487
+ ```
488
+
489
+ ### AdwBanner
490
+ Dismissable notification.
491
+
492
+ ```tsx
493
+ <AdwBanner title="Update available" buttonLabel="Dismiss" revealed={show} onButtonClicked={() => setShow(false)} />
494
+ ```
495
+
496
+ ### AdwPreferencesPage / AdwPreferencesGroup
497
+ Settings UI.
498
+
499
+ ```tsx
500
+ <AdwPreferencesPage title="Settings">
501
+ <AdwPreferencesGroup title="Appearance" description="Customize look">
502
+ <AdwSwitchRow title="Dark Mode" active={dark} onNotifyActive={(active) => setDark(active ?? false)} />
503
+ <AdwActionRow
504
+ title="Theme"
505
+ subtitle="Select color"
506
+ addPrefix={<GtkImage iconName="preferences-color-symbolic" />}
507
+ addSuffix={<GtkImage iconName="go-next-symbolic" valign={Gtk.Align.CENTER} />}
508
+ />
509
+ </AdwPreferencesGroup>
510
+ </AdwPreferencesPage>
511
+ ```
512
+
513
+ **ActionRow children:** Use the `addPrefix` prop for left widgets, `addSuffix` for right widgets, or the `activatableWidget` prop for a clickable suffix.
514
+
515
+ ### AdwExpanderRow
516
+ Expandable settings row with optional action widget.
517
+
518
+ ```tsx
519
+ <AdwExpanderRow
520
+ title="Advanced"
521
+ subtitle="More options"
522
+ addAction={<GtkButton iconName="emblem-system-symbolic" cssClasses={["flat"]} />}
523
+ addRow={
524
+ <>
525
+ <AdwSwitchRow title="Option 1" active />
526
+ <AdwSwitchRow title="Option 2" />
527
+ </>
528
+ }
529
+ />
530
+ ```
531
+
532
+ **ExpanderRow slots:** the `addRow` prop for nested rows, the `addAction` prop for the header action widget. Direct children also work for simple cases.
533
+
534
+ ### AdwEntryRow / AdwPasswordEntryRow
535
+ Input in list row.
536
+
537
+ ```tsx
538
+ <AdwEntryRow title="Username" text={username} onChanged={(e) => setUsername(e.getText())} />
539
+ <AdwPasswordEntryRow title="Password" />
540
+ ```
541
+
542
+ ### AdwToggleGroup
543
+ Segmented button group for mutually exclusive options, built from `AdwToggle` children.
544
+
545
+ ```tsx
546
+ const [mode, setMode] = useState("list");
547
+
548
+ <AdwToggleGroup activeName={mode} onNotifyActiveName={(name) => setMode(name ?? "list")}>
549
+ <AdwToggle name="list" iconName="view-list-symbolic" tooltip="List View" />
550
+ <AdwToggle name="grid" iconName="view-grid-symbolic" tooltip="Grid View" />
551
+ <AdwToggle name="flow" label="Flow" />
552
+ </AdwToggleGroup>
553
+ ```
554
+
555
+ **ToggleGroup props:** `activeName`, `active` (index), `onNotifyActiveName`
556
+
557
+ **AdwToggle props:** `name`, `label`, `iconName`, `tooltip`, `enabled`
558
+
559
+ ### AdwNavigationView
560
+ Push/pop navigation. Each `AdwNavigationPage` carries a `tag`; the view pushes a page when it mounts and pops it when it unmounts. Drive the active page with `visiblePageTag` and react to back navigation through `onPopped`.
561
+
562
+ ```tsx
563
+ const [detail, setDetail] = useState(false);
564
+
565
+ <AdwNavigationView visiblePageTag={detail ? "details" : "home"} onPopped={() => setDetail(false)}>
566
+ <AdwNavigationPage tag="home" title="Home">
567
+ <GtkButton label="Go to Details" onClicked={() => setDetail(true)} />
568
+ </AdwNavigationPage>
569
+ {detail && (
570
+ <AdwNavigationPage tag="details" title="Details">
571
+ <GtkLabel label="Details content" />
572
+ </AdwNavigationPage>
573
+ )}
574
+ </AdwNavigationView>
575
+ ```
576
+
577
+ **AdwNavigationPage props:** `tag`, `title`, `canPop`. The header bar inside a page shows a back button automatically when there is a page to pop.
578
+
579
+ ### AdwNavigationSplitView
580
+ Sidebar/content split layout for master-detail interfaces. Each pane is an `AdwNavigationPage` passed through the `sidebar` and `content` slot props.
581
+
582
+ ```tsx
583
+ const [selected, setSelected] = useState(items[0]);
584
+
585
+ <AdwNavigationSplitView
586
+ sidebarWidthFraction={0.33}
587
+ minSidebarWidth={200}
588
+ maxSidebarWidth={300}
589
+ sidebar={
590
+ <AdwNavigationPage title="Sidebar">
591
+ <AdwToolbarView addTopBar={<AdwHeaderBar />}>
592
+ <GtkListBox cssClasses={["navigation-sidebar"]} onRowSelected={(row) => {
593
+ if (!row) return;
594
+ const item = items[row.getIndex()];
595
+ if (item) setSelected(item);
596
+ }}>
597
+ {items.map((item) => <AdwActionRow key={item.id} title={item.title} />)}
598
+ </GtkListBox>
599
+ </AdwToolbarView>
600
+ </AdwNavigationPage>
601
+ }
602
+ content={
603
+ <AdwNavigationPage title={selected?.title ?? ""}>
604
+ <AdwToolbarView addTopBar={<AdwHeaderBar />}>
605
+ <GtkLabel label={selected?.title ?? ""} />
606
+ </AdwToolbarView>
607
+ </AdwNavigationPage>
608
+ }
609
+ />
610
+ ```
611
+
612
+ **Props:** `sidebarWidthFraction`, `minSidebarWidth`, `maxSidebarWidth`, `collapsed`, `showContent`, `sidebar`, `content`.
613
+ **Selection:** Use `GtkListBox` with `onRowSelected` (single click) not `onRowActivated` (double click).
614
+
615
+ ### AdwAlertDialog
616
+ Modern modal alert dialogs with response buttons.
617
+
618
+ ```tsx
619
+ const [showDialog, setShowDialog] = useState(false);
620
+
621
+ {showDialog && (
622
+ <AdwAlertDialog
623
+ heading="Delete File?"
624
+ body="This action cannot be undone."
625
+ onResponse={(id) => {
626
+ if (id === "delete") handleDelete();
627
+ setShowDialog(false);
628
+ }}
629
+ responses={[
630
+ { id: "cancel", label: "Cancel" },
631
+ { id: "delete", label: "Delete", appearance: Adw.ResponseAppearance.DESTRUCTIVE },
632
+ ]}
633
+ />
634
+ )}
635
+ ```
636
+
637
+ **Response descriptor:** `{ id, label, appearance? (SUGGESTED, DESTRUCTIVE), enabled? }`
638
+
639
+ ### GtkColorDialogButton / GtkFontDialogButton
640
+ Color and font picker buttons. The dialog is declared as a slot element (`GtkColorDialog`/`GtkFontDialog`, from `@gtkx/jsx/gtk`); observe the picked value through the `notify::` prop.
641
+
642
+ ```tsx
643
+ <GtkColorDialogButton
644
+ rgba={color}
645
+ dialog={<GtkColorDialog title="Select Color" modal withAlpha />}
646
+ onNotifyRgba={(value) => value && setColor(value)}
647
+ />
648
+ <GtkFontDialogButton
649
+ fontDesc={font}
650
+ useFont
651
+ useSize
652
+ dialog={<GtkFontDialog title="Select Font" modal />}
653
+ onNotifyFontDesc={(value) => value && setFont(value)}
654
+ />
655
+ ```
656
+
657
+ ### Other Adwaita Widgets
658
+
659
+ | Widget | Description |
660
+ |--------|-------------|
661
+ | `AdwClamp` | Limits content width (`maximumSize={600}`) |
662
+ | `AdwAvatar` | User avatar (`size={48} text="Name" showInitials`) |
663
+ | `AdwSpinner` | Loading indicator |
664
+ | `AdwWindowTitle` | Title + subtitle for header bars |
665
+ | `AdwButtonRow` | Button styled as list row |
666
+
667
+ ---
668
+
669
+ ## Animations
670
+
671
+ Wrap widgets in `AdwTimedAnimation` (duration + easing) or `AdwSpringAnimation`
672
+ (physics: damping, stiffness, mass) for declarative animations.
673
+
674
+ Spring animation:
675
+ ```tsx
676
+ <AdwSpringAnimation
677
+ initial={{ opacity: 0, scale: 0.8 }}
678
+ animate={{ opacity: 1, scale: 1 }}
679
+ damping={0.8}
680
+ stiffness={200}
681
+ mass={1}
682
+ animateOnMount
683
+ onAnimationComplete={() => console.log("done")}
684
+ >
685
+ <GtkBox>...</GtkBox>
686
+ </AdwSpringAnimation>
687
+ ```
688
+
689
+ Timed animation:
690
+ ```tsx
691
+ <AdwTimedAnimation
692
+ initial={{ opacity: 0 }}
693
+ animate={{ opacity: 1 }}
694
+ duration={300}
695
+ easing={Adw.Easing.EASE_OUT_CUBIC}
696
+ animateOnMount
697
+ >
698
+ <GtkLabel label="Fade in" />
699
+ </AdwTimedAnimation>
700
+ ```
701
+
702
+ **Shared props:** `initial`, `animate`, `exit`, `animateOnMount`, `onAnimationStart`, `onAnimationComplete`
703
+
704
+ **AdwSpringAnimation props:** `damping`, `stiffness`, `mass`, `initialVelocity`, `clamp`
705
+
706
+ **AdwTimedAnimation props:** `duration`, `easing` (from `Adw.Easing`), `repeat`, `reverse`, `alternate`
707
+
708
+ ---
709
+
710
+ ## Drag and Drop
711
+
712
+ Drag and drop uses the `GtkDragSource` and `GtkDropTarget` event controllers, attached through a widget's `addController` prop. `GtkDragSource` provides content from `onPrepare` and takes its drag icon through the `icon` object prop; `GtkDropTarget` declares accepted `types` and handles `onDrop`.
713
+
714
+ ```tsx
715
+ import * as Gdk from "@gtkx/gi/gdk";
716
+ import { Type, Value } from "@gtkx/gi/gobject";
717
+ import { GtkBox, GtkButton, GtkDragSource, GtkDropTarget, GtkLabel } from "@gtkx/jsx/gtk";
718
+
719
+ const DraggableButton = ({ label }: { label: string }) => (
720
+ <GtkButton
721
+ label={label}
722
+ addController={
723
+ <GtkDragSource
724
+ actions={Gdk.DragAction.COPY}
725
+ onPrepare={() => Gdk.ContentProvider.newForValue(Value.newFromString(label))}
726
+ icon={{ paintable: someTexture, hotX: 16, hotY: 16 }}
727
+ />
728
+ }
729
+ />
730
+ );
731
+
732
+ const DropZone = () => {
733
+ const [dropped, setDropped] = useState<string | null>(null);
734
+ return (
735
+ <GtkBox
736
+ addController={
737
+ <GtkDropTarget
738
+ types={[Type.STRING]}
739
+ actions={Gdk.DragAction.COPY}
740
+ onDrop={(value: Value) => { setDropped(value.getString()); return true; }}
741
+ />
742
+ }
743
+ >
744
+ <GtkLabel label={dropped ?? "Drop here"} />
745
+ </GtkBox>
746
+ );
747
+ };
748
+ ```
749
+
750
+ **GtkDragSource props:** `actions`, `content`, `onPrepare`, `onDragBegin`, `onDragEnd`, `onDragCancel`, `icon` (`{ paintable, hotX?, hotY? }`)
751
+
752
+ **GtkDropTarget props:** `types`, `actions`, `preload`, `onAccept`, `onEnter`, `onMotion`, `onLeave`, `onDrop`
753
+
754
+ ## GValue Factories
755
+
756
+ Create typed values for drag-and-drop and signal emission:
757
+
758
+ | Factory | Description |
759
+ | ------------------------------ | ----------------------------- |
760
+ | `Value.newFromString(str)` | String values |
761
+ | `Value.newFromDouble(num)` | 64-bit floating point |
762
+ | `Value.newFromInt(num)` | 32-bit signed integer |
763
+ | `Value.newFromBoolean(bool)` | Boolean values |
764
+ | `Value.newFromObject(obj)` | GObject instances |
765
+ | `Value.newFromBoxed(boxed)` | Boxed types (Gdk.RGBA, etc.) |
766
+ | `Value.newFromEnum(gtype, n)` | Enum values (requires GType) |
767
+ | `Value.newFromFlags(gtype, n)` | Flags values (requires GType) |
768
+
769
+ Type constants for `types`: `Type.STRING`, `Type.INT`, `Type.DOUBLE`, `Type.BOOLEAN`, `Type.OBJECT`.
770
+
771
+ ## Custom Drawing
772
+
773
+ Render custom graphics with `GtkDrawingArea` (from `@gtkx/jsx/gtk`) using the `drawFunc` prop, with the GIR signature `(self, cr, width, height)`:
774
+
775
+ ```tsx
776
+ import type * as Gtk from "@gtkx/gi/gtk";
777
+ import type { Context } from "@gtkx/gi/cairo";
778
+ import { GtkDrawingArea } from "@gtkx/jsx/gtk";
779
+
780
+ const Canvas = () => {
781
+ const handleDraw = (self: Gtk.DrawingArea, cr: Context, width: number, height: number) => {
782
+ cr.setSourceRgb(0.2, 0.4, 0.8);
783
+ cr.rectangle(10, 10, width - 20, height - 20);
784
+ cr.fill();
785
+ };
786
+
787
+ return <GtkDrawingArea contentWidth={400} contentHeight={300} drawFunc={handleDraw} />;
788
+ };
789
+ ```
790
+
791
+ Changing the `drawFunc` callback identity automatically queues a redraw. Attach a `GtkGestureDrag` through the `addController` prop for interactive drawing.
792
+
793
+ ## Event Controllers
794
+
795
+ Event controllers attach through any widget's `addController` prop (wrap several in a fragment). They are auto-generated from GTK's introspection data.
796
+
797
+ ```tsx
798
+ <GtkBox
799
+ focusable
800
+ addController={
801
+ <>
802
+ <GtkEventControllerMotion
803
+ onEnter={(x, y) => console.log("Entered at", x, y)}
804
+ onMotion={(x, y) => setPosition({ x, y })}
805
+ onLeave={() => console.log("Left")}
806
+ />
807
+ <GtkEventControllerKey
808
+ onKeyPressed={(keyval, keycode, state) => {
809
+ console.log("Key pressed:", keyval);
810
+ return false;
811
+ }}
812
+ />
813
+ <GtkGestureClick onPressed={(nPress, x, y) => console.log("Clicked")} />
814
+ </>
815
+ }
816
+ >
817
+ <GtkLabel label="Hover or type here" />
818
+ </GtkBox>
819
+ ```
820
+
821
+ **Input controllers:** `GtkEventControllerMotion`, `GtkEventControllerKey`, `GtkEventControllerScroll`, `GtkEventControllerFocus`
822
+
823
+ **Gesture controllers:** `GtkGestureClick`, `GtkGestureDrag`, `GtkGestureLongPress`, `GtkGestureZoom`, `GtkGestureRotate`, `GtkGestureSwipe`, `GtkGestureStylus`, `GtkGesturePan`
824
+
825
+ **Drag-and-drop:** `GtkDragSource`, `GtkDropTarget`, `GtkDropControllerMotion`
826
+
827
+ ## SearchBar
828
+
829
+ ```tsx
830
+ const [searchActive, setSearchActive] = useState(false);
831
+
832
+ <GtkSearchBar searchModeEnabled={searchActive} onNotifySearchModeEnabled={(enabled) => setSearchActive(enabled ?? false)}>
833
+ <GtkSearchEntry text={query} onSearchChanged={(entry) => setQuery(entry.getText())} />
834
+ </GtkSearchBar>
835
+ ```
836
+
837
+ The `onNotifySearchModeEnabled` callback fires when search mode changes (e.g., user presses Escape).
838
+
839
+ ## TextView / SourceView
840
+
841
+ Text content lives in an explicit `GtkTextBuffer` element passed through the view's `buffer` prop. Text nodes are allowed only inside `GtkLabel` and buffer elements. Use `GtkTextTag` for formatting and `GtkTextAnchor` for embedded widgets.
842
+
843
+ ```tsx
844
+ <GtkTextView
845
+ wrapMode={Gtk.WrapMode.WORD}
846
+ buffer={
847
+ <GtkTextBuffer
848
+ enableUndo
849
+ onChanged={(buffer) => {
850
+ const text = buffer.getText(buffer.getStartIter(), buffer.getEndIter(), false);
851
+ console.log(text);
852
+ }}
853
+ >
854
+ Normal text, <GtkTextTag id="bold" weight={Pango.Weight.BOLD}>bold</GtkTextTag>, and
855
+ <GtkTextAnchor><GtkButton label="Click" /></GtkTextAnchor> inline.
856
+ </GtkTextBuffer>
857
+ }
858
+ />
859
+ ```
860
+
861
+ **GtkTextBuffer props:** `enableUndo`, `onChanged`, `onInsertText`, `onDeleteRange`, `onNotifyCanUndo`, `onNotifyCanRedo`
862
+
863
+ **GtkTextTag props:** `id` (required), `priority`, `foreground`, `background`, `weight`, `style`, `underline`, `strikethrough`, `family`, `size`, `sizePoints`, `scale`, `justification`, `leftMargin`, `rightMargin`, `indent`, `editable`, `invisible`
864
+
865
+ **GtkTextAnchor:** Embeds widgets inline with `children`
866
+
867
+ **GtkTextPaintable:** Embeds images inline with the `paintable` prop
868
+
869
+ `GtkSourceView` (from `@gtkx/jsx/gtksource`) pairs with `GtkSourceBuffer` the same way; language and style scheme objects come from `@gtkx/gi/gtksource`:
870
+
871
+ ```tsx
872
+ import * as GtkSource from "@gtkx/gi/gtksource";
873
+ import { GtkSourceBuffer, GtkSourceView } from "@gtkx/jsx/gtksource";
874
+
875
+ <GtkSourceView
876
+ showLineNumbers
877
+ highlightCurrentLine
878
+ monospace
879
+ buffer={
880
+ <GtkSourceBuffer
881
+ language={GtkSource.LanguageManager.getDefault().getLanguage("typescript-jsx")}
882
+ styleScheme={GtkSource.StyleSchemeManager.getDefault().getScheme("Adwaita-dark")}
883
+ onChanged={(buffer) => setCode(buffer.getText(buffer.getStartIter(), buffer.getEndIter(), false))}
884
+ >
885
+ {code}
886
+ </GtkSourceBuffer>
887
+ }
888
+ />
889
+ ```
890
+
891
+ **GtkSourceBuffer additional props:** `language`, `styleScheme`, `highlightSyntax`, `highlightMatchingBrackets`, `implicitTrailingNewline`, `onCursorMoved`, `onHighlightUpdated`
892
+
893
+ ## Keyboard Shortcuts
894
+
895
+ Pass `<GtkShortcut>` elements through a `<GtkShortcutController>`'s `addShortcut` prop, and attach the controller through the widget's `addController` prop. Each shortcut pairs a `trigger` (`Gtk.ShortcutTrigger.parseString`) with an `action` (`Gtk.CallbackAction.new`, returning `true` when handled):
896
+
897
+ ```tsx
898
+ <GtkBox
899
+ orientation={Gtk.Orientation.VERTICAL}
900
+ spacing={12}
901
+ focusable
902
+ addController={
903
+ <GtkShortcutController
904
+ scope={Gtk.ShortcutScope.LOCAL}
905
+ addShortcut={
906
+ <>
907
+ <GtkShortcut
908
+ trigger={Gtk.ShortcutTrigger.parseString("<Control>equal")}
909
+ action={Gtk.CallbackAction.new(() => {
910
+ setCount((c) => c + 1);
911
+ return true;
912
+ })}
913
+ />
914
+ <GtkShortcut
915
+ trigger={Gtk.ShortcutTrigger.parseString("<Control>minus")}
916
+ action={Gtk.CallbackAction.new(() => {
917
+ setCount((c) => c - 1);
918
+ return true;
919
+ })}
920
+ />
921
+ </>
922
+ }
923
+ />
924
+ }
925
+ >
926
+ <GtkLabel label={`Count: ${count}`} />
927
+ </GtkBox>
928
+ ```
929
+
930
+ **Scopes:** `LOCAL` (attached widget focus), `MANAGED` (parent managed), `GLOBAL` (window-wide)
931
+
932
+ **Trigger strings for `parseString`:** `<Control>s`, `<Control><Shift>s`, `<Alt>F4`, `<Primary>q`, `F5`
933
+
934
+ **Multiple triggers:** combine with `Gtk.AlternativeTrigger.new(triggerA, triggerB)`