@gtkx/cli 0.9.3 → 0.10.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/bin/gtkx.js +2 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +1 -0
- package/dist/create.d.ts +37 -6
- package/dist/create.js +44 -844
- package/dist/dev-server.d.ts +26 -4
- package/dist/dev-server.js +64 -9
- package/dist/refresh-runtime.d.ts +9 -0
- package/dist/refresh-runtime.js +44 -0
- package/dist/templates.d.ts +8 -0
- package/dist/templates.js +18 -0
- package/dist/vite-plugin-gtkx-refresh.d.ts +7 -0
- package/dist/vite-plugin-gtkx-refresh.js +36 -0
- package/dist/vite-plugin-swc-ssr-refresh.d.ts +7 -0
- package/dist/vite-plugin-swc-ssr-refresh.js +45 -0
- package/package.json +14 -5
- package/templates/claude/EXAMPLES.md.ejs +364 -0
- package/templates/claude/SKILL.md.ejs +372 -0
- package/templates/claude/WIDGETS.md.ejs +531 -0
- package/templates/config/jest.config.js.ejs +19 -0
- package/templates/config/vitest.config.ts.ejs +13 -0
- package/templates/gitignore.ejs +4 -0
- package/templates/package.json.ejs +15 -0
- package/templates/src/app.tsx.ejs +17 -0
- package/templates/src/dev.tsx.ejs +5 -0
- package/templates/src/index.tsx.ejs +4 -0
- package/templates/tests/app.test.tsx.ejs +27 -0
- package/templates/tsconfig.json.ejs +14 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# GTKX Code Examples
|
|
2
|
+
|
|
3
|
+
## Application Structure
|
|
4
|
+
|
|
5
|
+
### Basic App with State
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
9
|
+
import { GtkApplicationWindow, GtkBox, GtkLabel, quit } from "@gtkx/react";
|
|
10
|
+
import { useCallback, useState } from "react";
|
|
11
|
+
|
|
12
|
+
interface Todo {
|
|
13
|
+
id: number;
|
|
14
|
+
text: string;
|
|
15
|
+
completed: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let nextId = 1;
|
|
19
|
+
|
|
20
|
+
export const App = () => {
|
|
21
|
+
const [todos, setTodos] = useState<Todo[]>([]);
|
|
22
|
+
|
|
23
|
+
const addTodo = useCallback((text: string) => {
|
|
24
|
+
setTodos((prev) => [...prev, { id: nextId++, text, completed: false }]);
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
const toggleTodo = useCallback((id: number) => {
|
|
28
|
+
setTodos((prev) =>
|
|
29
|
+
prev.map((todo) =>
|
|
30
|
+
todo.id === id ? { ...todo, completed: !todo.completed } : todo
|
|
31
|
+
)
|
|
32
|
+
);
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<GtkApplicationWindow title="Todo App" defaultWidth={400} defaultHeight={500} onCloseRequest={quit}>
|
|
37
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={16} marginTop={16} marginStart={16} marginEnd={16}>
|
|
38
|
+
Todo App
|
|
39
|
+
</GtkBox>
|
|
40
|
+
</GtkApplicationWindow>
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const appId = "com.gtkx.todo";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Layout Patterns
|
|
48
|
+
|
|
49
|
+
### Grid for Forms
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
53
|
+
import { GtkButton, GtkEntry, GtkGrid, GtkLabel, GridChild } from "@gtkx/react";
|
|
54
|
+
import { useState } from "react";
|
|
55
|
+
|
|
56
|
+
const FormLayout = () => {
|
|
57
|
+
const [name, setName] = useState("");
|
|
58
|
+
const [email, setEmail] = useState("");
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<GtkGrid rowSpacing={8} columnSpacing={12}>
|
|
62
|
+
<GridChild column={0} row={0}>
|
|
63
|
+
<GtkLabel label="Name:" halign={Gtk.Align.END} />
|
|
64
|
+
</GridChild>
|
|
65
|
+
<GridChild column={1} row={0}>
|
|
66
|
+
<GtkEntry text={name} onChanged={(e) => setName(e.getText())} hexpand />
|
|
67
|
+
</GridChild>
|
|
68
|
+
<GridChild column={0} row={1}>
|
|
69
|
+
<GtkLabel label="Email:" halign={Gtk.Align.END} />
|
|
70
|
+
</GridChild>
|
|
71
|
+
<GridChild column={1} row={1}>
|
|
72
|
+
<GtkEntry text={email} onChanged={(e) => setEmail(e.getText())} hexpand />
|
|
73
|
+
</GridChild>
|
|
74
|
+
<GridChild column={0} row={2} columnSpan={2}>
|
|
75
|
+
<GtkButton label="Submit" halign={Gtk.Align.END} marginTop={8} />
|
|
76
|
+
</GridChild>
|
|
77
|
+
</GtkGrid>
|
|
78
|
+
);
|
|
79
|
+
};
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Stack with StackSwitcher
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
86
|
+
import { GtkBox, GtkLabel, GtkStack, GtkStackSwitcher, StackPage } from "@gtkx/react";
|
|
87
|
+
|
|
88
|
+
const TabContainer = () => (
|
|
89
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={8}>
|
|
90
|
+
<GtkStackSwitcher
|
|
91
|
+
ref={(switcher: Gtk.StackSwitcher | null) => {
|
|
92
|
+
if (switcher) {
|
|
93
|
+
const stack = switcher.getParent()?.getLastChild() as Gtk.Stack | null;
|
|
94
|
+
if (stack) switcher.setStack(stack);
|
|
95
|
+
}
|
|
96
|
+
}}
|
|
97
|
+
/>
|
|
98
|
+
<GtkStack transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT} transitionDuration={200}>
|
|
99
|
+
<StackPage name="page1" title="First">First Page Content</StackPage>
|
|
100
|
+
<StackPage name="page2" title="Second">Second Page Content</StackPage>
|
|
101
|
+
</GtkStack>
|
|
102
|
+
</GtkBox>
|
|
103
|
+
);
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Virtual Scrolling Lists
|
|
107
|
+
|
|
108
|
+
### ListView with Selection
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
112
|
+
import { GtkBox, GtkLabel, GtkScrolledWindow, ListView, ListItem } from "@gtkx/react";
|
|
113
|
+
import { useState } from "react";
|
|
114
|
+
|
|
115
|
+
interface Task {
|
|
116
|
+
id: string;
|
|
117
|
+
title: string;
|
|
118
|
+
completed: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const tasks: Task[] = [
|
|
122
|
+
{ id: "1", title: "Learn GTK4", completed: true },
|
|
123
|
+
{ id: "2", title: "Build React app", completed: false },
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const TaskList = () => {
|
|
127
|
+
const [selectedId, setSelectedId] = useState<string | undefined>();
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<GtkBox cssClasses={["card"]} heightRequest={250}>
|
|
131
|
+
<GtkScrolledWindow vexpand>
|
|
132
|
+
<ListView<Task>
|
|
133
|
+
vexpand
|
|
134
|
+
selected={selectedId ? [selectedId] : []}
|
|
135
|
+
onSelectionChanged={(ids) => setSelectedId(ids[0])}
|
|
136
|
+
renderItem={(task) => (
|
|
137
|
+
<GtkLabel
|
|
138
|
+
label={task?.title ?? ""}
|
|
139
|
+
cssClasses={task?.completed ? ["dim-label"] : []}
|
|
140
|
+
halign={Gtk.Align.START}
|
|
141
|
+
marginStart={12}
|
|
142
|
+
marginTop={8}
|
|
143
|
+
marginBottom={8}
|
|
144
|
+
/>
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
{tasks.map((task) => (
|
|
148
|
+
<ListItem key={task.id} id={task.id} value={task} />
|
|
149
|
+
))}
|
|
150
|
+
</ListView>
|
|
151
|
+
</GtkScrolledWindow>
|
|
152
|
+
</GtkBox>
|
|
153
|
+
);
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### HeaderBar with Navigation
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
161
|
+
import { GtkApplicationWindow, GtkBox, GtkButton, GtkHeaderBar, GtkLabel, GtkWindow, Pack, Slot, quit } from "@gtkx/react";
|
|
162
|
+
import { useState } from "react";
|
|
163
|
+
|
|
164
|
+
const AppWithHeaderBar = () => {
|
|
165
|
+
const [page, setPage] = useState("home");
|
|
166
|
+
|
|
167
|
+
return (
|
|
168
|
+
<GtkApplicationWindow title="My App" defaultWidth={600} defaultHeight={400} onCloseRequest={quit}>
|
|
169
|
+
<Slot for={GtkWindow} id="titlebar">
|
|
170
|
+
<GtkHeaderBar>
|
|
171
|
+
<Pack.Start>
|
|
172
|
+
{page !== "home" && (
|
|
173
|
+
<GtkButton iconName="go-previous-symbolic" onClicked={() => setPage("home")} />
|
|
174
|
+
)}
|
|
175
|
+
</Pack.Start>
|
|
176
|
+
<Pack.End>
|
|
177
|
+
<GtkButton iconName="emblem-system-symbolic" onClicked={() => setPage("settings")} />
|
|
178
|
+
</Pack.End>
|
|
179
|
+
</GtkHeaderBar>
|
|
180
|
+
</Slot>
|
|
181
|
+
<GtkLabel label={page === "home" ? "Home Page" : "Settings Page"} vexpand />
|
|
182
|
+
</GtkApplicationWindow>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Menus
|
|
188
|
+
|
|
189
|
+
### MenuButton with PopoverMenu
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
193
|
+
import { GtkBox, GtkLabel, GtkMenuButton, GtkPopoverMenu, Menu, Slot } from "@gtkx/react";
|
|
194
|
+
import { useState } from "react";
|
|
195
|
+
|
|
196
|
+
const MenuDemo = () => {
|
|
197
|
+
const [lastAction, setLastAction] = useState<string | null>(null);
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<GtkBox orientation={Gtk.Orientation.VERTICAL} spacing={12}>
|
|
201
|
+
<GtkLabel label={`Last action: ${lastAction ?? "(none)"}`} />
|
|
202
|
+
<GtkMenuButton label="Actions">
|
|
203
|
+
<Slot for={GtkMenuButton} id="popover">
|
|
204
|
+
<GtkPopoverMenu>
|
|
205
|
+
<Menu.Item id="new" label="New" onActivate={() => setLastAction("New")} accels="<Control>n" />
|
|
206
|
+
<Menu.Item id="open" label="Open" onActivate={() => setLastAction("Open")} accels="<Control>o" />
|
|
207
|
+
<Menu.Item id="save" label="Save" onActivate={() => setLastAction("Save")} accels="<Control>s" />
|
|
208
|
+
</GtkPopoverMenu>
|
|
209
|
+
</Slot>
|
|
210
|
+
</GtkMenuButton>
|
|
211
|
+
</GtkBox>
|
|
212
|
+
);
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Component Props Pattern
|
|
217
|
+
|
|
218
|
+
### List Item Component
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
222
|
+
import { GtkBox, GtkButton, GtkCheckButton, GtkLabel } from "@gtkx/react";
|
|
223
|
+
|
|
224
|
+
interface Todo {
|
|
225
|
+
id: number;
|
|
226
|
+
text: string;
|
|
227
|
+
completed: boolean;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
interface TodoItemProps {
|
|
231
|
+
todo: Todo;
|
|
232
|
+
onToggle: (id: number) => void;
|
|
233
|
+
onDelete: (id: number) => void;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export const TodoItem = ({ todo, onToggle, onDelete }: TodoItemProps) => (
|
|
237
|
+
<GtkBox orientation={Gtk.Orientation.HORIZONTAL} spacing={8}>
|
|
238
|
+
<GtkCheckButton active={todo.completed} onToggled={() => onToggle(todo.id)} />
|
|
239
|
+
<GtkLabel label={todo.text} hexpand cssClasses={todo.completed ? ["dim-label"] : []} />
|
|
240
|
+
<GtkButton iconName="edit-delete-symbolic" onClicked={() => onDelete(todo.id)} cssClasses={["flat"]} />
|
|
241
|
+
</GtkBox>
|
|
242
|
+
);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Adwaita App Structure
|
|
246
|
+
|
|
247
|
+
### Modern Adwaita App
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
251
|
+
import {
|
|
252
|
+
AdwApplicationWindow,
|
|
253
|
+
AdwHeaderBar,
|
|
254
|
+
AdwToolbarView,
|
|
255
|
+
AdwWindowTitle,
|
|
256
|
+
AdwStatusPage,
|
|
257
|
+
AdwBanner,
|
|
258
|
+
GtkButton,
|
|
259
|
+
Slot,
|
|
260
|
+
Toolbar,
|
|
261
|
+
quit,
|
|
262
|
+
} from "@gtkx/react";
|
|
263
|
+
import { useState } from "react";
|
|
264
|
+
|
|
265
|
+
export const App = () => {
|
|
266
|
+
const [showBanner, setShowBanner] = useState(true);
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<AdwApplicationWindow title="My App" defaultWidth={800} defaultHeight={600} onCloseRequest={quit}>
|
|
270
|
+
<Slot for={AdwApplicationWindow} id="content">
|
|
271
|
+
<AdwToolbarView>
|
|
272
|
+
<Toolbar.Top>
|
|
273
|
+
<AdwHeaderBar>
|
|
274
|
+
<Slot for={AdwHeaderBar} id="titleWidget">
|
|
275
|
+
<AdwWindowTitle title="My App" subtitle="Welcome" />
|
|
276
|
+
</Slot>
|
|
277
|
+
</AdwHeaderBar>
|
|
278
|
+
</Toolbar.Top>
|
|
279
|
+
<AdwBanner
|
|
280
|
+
title="Welcome to the app!"
|
|
281
|
+
buttonLabel="Dismiss"
|
|
282
|
+
revealed={showBanner}
|
|
283
|
+
onButtonClicked={() => setShowBanner(false)}
|
|
284
|
+
/>
|
|
285
|
+
<AdwStatusPage
|
|
286
|
+
iconName="applications-system-symbolic"
|
|
287
|
+
title="Welcome"
|
|
288
|
+
description="Get started with your new GTKX app"
|
|
289
|
+
vexpand
|
|
290
|
+
>
|
|
291
|
+
<GtkButton
|
|
292
|
+
label="Get Started"
|
|
293
|
+
cssClasses={["suggested-action", "pill"]}
|
|
294
|
+
halign={Gtk.Align.CENTER}
|
|
295
|
+
/>
|
|
296
|
+
</AdwStatusPage>
|
|
297
|
+
</AdwToolbarView>
|
|
298
|
+
</Slot>
|
|
299
|
+
</AdwApplicationWindow>
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
export const appId = "com.example.myapp";
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Settings Page with Preferences
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
import * as Gtk from "@gtkx/ffi/gtk";
|
|
310
|
+
import {
|
|
311
|
+
AdwPreferencesPage,
|
|
312
|
+
AdwPreferencesGroup,
|
|
313
|
+
AdwActionRow,
|
|
314
|
+
AdwSwitchRow,
|
|
315
|
+
AdwExpanderRow,
|
|
316
|
+
AdwEntryRow,
|
|
317
|
+
GtkImage,
|
|
318
|
+
GtkScrolledWindow,
|
|
319
|
+
Slot,
|
|
320
|
+
} from "@gtkx/react";
|
|
321
|
+
import { useState } from "react";
|
|
322
|
+
|
|
323
|
+
const SettingsPage = () => {
|
|
324
|
+
const [darkMode, setDarkMode] = useState(false);
|
|
325
|
+
const [notifications, setNotifications] = useState(true);
|
|
326
|
+
const [username, setUsername] = useState("");
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<GtkScrolledWindow vexpand>
|
|
330
|
+
<AdwPreferencesPage title="Settings">
|
|
331
|
+
<AdwPreferencesGroup title="Appearance" description="Customize the look and feel">
|
|
332
|
+
<AdwSwitchRow
|
|
333
|
+
title="Dark Mode"
|
|
334
|
+
subtitle="Use dark color scheme"
|
|
335
|
+
active={darkMode}
|
|
336
|
+
onActivate={() => setDarkMode(!darkMode)}
|
|
337
|
+
/>
|
|
338
|
+
</AdwPreferencesGroup>
|
|
339
|
+
|
|
340
|
+
<AdwPreferencesGroup title="Account">
|
|
341
|
+
<AdwEntryRow
|
|
342
|
+
title="Username"
|
|
343
|
+
text={username}
|
|
344
|
+
onChanged={(e) => setUsername(e.getText())}
|
|
345
|
+
/>
|
|
346
|
+
<AdwActionRow title="Profile" subtitle="Manage your profile">
|
|
347
|
+
<Slot for={AdwActionRow} id="activatableWidget">
|
|
348
|
+
<GtkImage iconName="go-next-symbolic" valign={Gtk.Align.CENTER} />
|
|
349
|
+
</Slot>
|
|
350
|
+
</AdwActionRow>
|
|
351
|
+
</AdwPreferencesGroup>
|
|
352
|
+
|
|
353
|
+
<AdwPreferencesGroup title="Notifications">
|
|
354
|
+
<AdwExpanderRow title="Notification Settings" subtitle="Configure alerts">
|
|
355
|
+
<AdwSwitchRow title="Sound" active />
|
|
356
|
+
<AdwSwitchRow title="Badges" active />
|
|
357
|
+
<AdwSwitchRow title="Lock Screen" active={false} />
|
|
358
|
+
</AdwExpanderRow>
|
|
359
|
+
</AdwPreferencesGroup>
|
|
360
|
+
</AdwPreferencesPage>
|
|
361
|
+
</GtkScrolledWindow>
|
|
362
|
+
);
|
|
363
|
+
};
|
|
364
|
+
```
|