create-rezi 0.1.0-alpha.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.
@@ -0,0 +1,223 @@
1
+ import { createApp, rgb, ui } from "@rezi-ui/core";
2
+ import { createNodeBackend } from "@rezi-ui/node";
3
+
4
+ type Plan = "starter" | "growth" | "enterprise";
5
+
6
+ type State = {
7
+ section: number;
8
+ name: string;
9
+ email: string;
10
+ company: string;
11
+ plan: Plan;
12
+ seats: string;
13
+ newsletter: boolean;
14
+ notes: string;
15
+ status: string;
16
+ };
17
+
18
+ const sections = ["Profile", "Plan", "Review"] as const;
19
+
20
+ const initialState: State = {
21
+ section: 0,
22
+ name: "",
23
+ email: "",
24
+ company: "",
25
+ plan: "growth",
26
+ seats: "5",
27
+ newsletter: true,
28
+ notes: "",
29
+ status: "Draft",
30
+ };
31
+
32
+ const app = createApp<State>({
33
+ backend: createNodeBackend(),
34
+ initialState,
35
+ });
36
+
37
+ const colors = {
38
+ accent: rgb(116, 200, 255),
39
+ muted: rgb(140, 150, 170),
40
+ panel: rgb(18, 22, 34),
41
+ panelAlt: rgb(22, 28, 44),
42
+ ok: rgb(130, 220, 170),
43
+ ink: rgb(10, 14, 24),
44
+ };
45
+
46
+ function clamp(value: number, min: number, max: number) {
47
+ return Math.max(min, Math.min(max, value));
48
+ }
49
+
50
+ function panel(title: string, children: ReturnType<typeof ui.column>[], flex = 1) {
51
+ return ui.box(
52
+ {
53
+ title,
54
+ flex,
55
+ border: "rounded",
56
+ px: 1,
57
+ py: 0,
58
+ style: { bg: colors.panel, fg: colors.muted },
59
+ },
60
+ children,
61
+ );
62
+ }
63
+
64
+ app.view((state) => {
65
+ return ui.column({ flex: 1, p: 1, gap: 1, items: "stretch" }, [
66
+ ui.row({ justify: "between", items: "center" }, [
67
+ ui.text("__APP_NAME__", { fg: colors.accent, bold: true }),
68
+ ui.text(`Status: ${state.status}`, { fg: colors.ok, bold: true }),
69
+ ]),
70
+
71
+ ui.row({ flex: 1, gap: 1, items: "stretch" }, [
72
+ panel(
73
+ "Sections",
74
+ [
75
+ ui.column(
76
+ { gap: 0 },
77
+ sections.map((label, index) => {
78
+ const active = index === state.section;
79
+ return ui.text(`${active ? ">" : " "} ${label}`, {
80
+ key: label,
81
+ fg: active ? colors.accent : colors.muted,
82
+ bold: active,
83
+ });
84
+ }),
85
+ ),
86
+ ],
87
+ 1,
88
+ ),
89
+
90
+ panel(
91
+ "Customer Details",
92
+ [
93
+ ui.column({ gap: 1 }, [
94
+ ui.field({
95
+ label: "Name",
96
+ required: true,
97
+ children: ui.input({
98
+ id: "name",
99
+ value: state.name,
100
+ placeholder: "Ada Lovelace",
101
+ onInput: (value) => app.update((s) => ({ ...s, name: value })),
102
+ }),
103
+ }),
104
+ ui.field({
105
+ label: "Email",
106
+ required: true,
107
+ children: ui.input({
108
+ id: "email",
109
+ value: state.email,
110
+ placeholder: "ada@lovelace.io",
111
+ onInput: (value) => app.update((s) => ({ ...s, email: value })),
112
+ }),
113
+ }),
114
+ ui.field({
115
+ label: "Company",
116
+ children: ui.input({
117
+ id: "company",
118
+ value: state.company,
119
+ placeholder: "Analytical Engines Ltd",
120
+ onInput: (value) => app.update((s) => ({ ...s, company: value })),
121
+ }),
122
+ }),
123
+ ui.field({
124
+ label: "Plan",
125
+ children: ui.select({
126
+ id: "plan",
127
+ value: state.plan,
128
+ options: [
129
+ { value: "starter", label: "Starter" },
130
+ { value: "growth", label: "Growth" },
131
+ { value: "enterprise", label: "Enterprise" },
132
+ ],
133
+ onChange: (value) => app.update((s) => ({ ...s, plan: value as Plan })),
134
+ }),
135
+ }),
136
+ ui.field({
137
+ label: "Seats",
138
+ children: ui.input({
139
+ id: "seats",
140
+ value: state.seats,
141
+ onInput: (value) => app.update((s) => ({ ...s, seats: value })),
142
+ }),
143
+ }),
144
+ ui.checkbox({
145
+ id: "newsletter",
146
+ label: "Subscribe to release notes",
147
+ checked: state.newsletter,
148
+ onChange: (checked) => app.update((s) => ({ ...s, newsletter: checked })),
149
+ }),
150
+ ]),
151
+ ],
152
+ 2,
153
+ ),
154
+
155
+ panel(
156
+ "Preview",
157
+ [
158
+ ui.column({ gap: 1 }, [
159
+ ui.text("Summary", { fg: colors.accent, bold: true }),
160
+ ui.text(`Name: ${state.name || "-"}`),
161
+ ui.text(`Email: ${state.email || "-"}`),
162
+ ui.text(`Company: ${state.company || "-"}`),
163
+ ui.text(`Plan: ${state.plan}`),
164
+ ui.text(`Seats: ${state.seats || "-"}`),
165
+ ui.text(`Newsletter: ${state.newsletter ? "Yes" : "No"}`),
166
+ ui.divider({ char: "-" }),
167
+ ui.text("Notes"),
168
+ ui.text(state.notes || "Add internal notes in Review."),
169
+ ui.button({
170
+ id: "save",
171
+ label: "Save draft",
172
+ onPress: () =>
173
+ app.update((s) => ({
174
+ ...s,
175
+ status: `Saved at ${new Date().toLocaleTimeString()}`,
176
+ })),
177
+ }),
178
+ ]),
179
+ ],
180
+ 1,
181
+ ),
182
+ ]),
183
+
184
+ ui.box({ px: 1, py: 0, style: { bg: colors.ink, fg: colors.muted } }, [
185
+ ui.row({ justify: "between", items: "center" }, [
186
+ ui.text("Form flow ready"),
187
+ ui.row({ gap: 1 }, [
188
+ ui.kbd("tab"),
189
+ ui.text("Focus"),
190
+ ui.kbd("ctrl+s"),
191
+ ui.text("Save"),
192
+ ui.kbd("ctrl+r"),
193
+ ui.text("Reset"),
194
+ ui.kbd("q"),
195
+ ui.text("Quit"),
196
+ ]),
197
+ ]),
198
+ ]),
199
+ ]);
200
+ });
201
+
202
+ app.keys({
203
+ q: () => app.stop(),
204
+ "ctrl+c": () => app.stop(),
205
+ "ctrl+s": () =>
206
+ app.update((s) => ({
207
+ ...s,
208
+ status: `Saved at ${new Date().toLocaleTimeString()}`,
209
+ })),
210
+ "ctrl+r": () => app.update(() => ({ ...initialState })),
211
+ "ctrl+up": () =>
212
+ app.update((s) => ({
213
+ ...s,
214
+ section: clamp(s.section - 1, 0, sections.length - 1),
215
+ })),
216
+ "ctrl+down": () =>
217
+ app.update((s) => ({
218
+ ...s,
219
+ section: clamp(s.section + 1, 0, sections.length - 1),
220
+ })),
221
+ });
222
+
223
+ await app.start();
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "types": ["node"],
10
+ "noUncheckedIndexedAccess": true,
11
+ "exactOptionalPropertyTypes": true
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }
@@ -0,0 +1,17 @@
1
+ # __APP_NAME__
2
+
3
+ Scaffolded with `npm create rezi` using the **__TEMPLATE_LABEL__** template.
4
+
5
+ ## Quickstart
6
+
7
+ ```bash
8
+ npm install
9
+ npm start
10
+ ```
11
+
12
+ ## Keybindings
13
+
14
+ - `up` / `down` or `j` / `k`: Switch stream
15
+ - `space`: Toggle pause
16
+ - `f`: Follow mode
17
+ - `q`: Quit
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "tsx src/main.ts",
8
+ "dev": "tsx watch src/main.ts",
9
+ "typecheck": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@rezi-ui/core": "^0.1.0-alpha.0",
13
+ "@rezi-ui/node": "^0.1.0-alpha.0"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^22.13.1",
17
+ "tsx": "^4.20.0",
18
+ "typescript": "^5.6.3"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ }
23
+ }
@@ -0,0 +1,174 @@
1
+ import { createApp, rgb, ui } from "@rezi-ui/core";
2
+ import { createNodeBackend } from "@rezi-ui/node";
3
+
4
+ type Stream = {
5
+ name: string;
6
+ category: string;
7
+ viewers: number;
8
+ bitrate: number;
9
+ health: "good" | "warning";
10
+ };
11
+
12
+ const streams: readonly Stream[] = [
13
+ { name: "city-cam-01", category: "Urban", viewers: 1280, bitrate: 6.4, health: "good" },
14
+ { name: "forest-node", category: "Nature", viewers: 860, bitrate: 4.8, health: "good" },
15
+ { name: "harbor-live", category: "Maritime", viewers: 420, bitrate: 3.9, health: "warning" },
16
+ { name: "lab-monitor", category: "Science", viewers: 96, bitrate: 2.1, health: "good" },
17
+ ];
18
+
19
+ const chatByStream: Record<string, string[]> = {
20
+ "city-cam-01": ["switch to night mode?", "pan left a bit", "traffic spike @ 18:00"],
21
+ "forest-node": ["birdsong level: high", "new fox spotted", "wind noise up"],
22
+ "harbor-live": ["dock 3 unloading", "signal jitter", "fog incoming"],
23
+ "lab-monitor": ["temperature stable", "note: calibrate sensor", "airflow normal"],
24
+ };
25
+
26
+ type State = {
27
+ selected: number;
28
+ paused: boolean;
29
+ follow: boolean;
30
+ };
31
+
32
+ const app = createApp<State>({
33
+ backend: createNodeBackend(),
34
+ initialState: {
35
+ selected: 0,
36
+ paused: false,
37
+ follow: true,
38
+ },
39
+ });
40
+
41
+ const colors = {
42
+ accent: rgb(116, 200, 255),
43
+ muted: rgb(140, 150, 170),
44
+ panel: rgb(18, 22, 34),
45
+ panelAlt: rgb(22, 28, 44),
46
+ ok: rgb(130, 220, 170),
47
+ warn: rgb(255, 180, 120),
48
+ ink: rgb(10, 14, 24),
49
+ };
50
+
51
+ function clamp(value: number, min: number, max: number) {
52
+ return Math.max(min, Math.min(max, value));
53
+ }
54
+
55
+ function panel(title: string, children: ReturnType<typeof ui.column>[], flex = 1) {
56
+ return ui.box(
57
+ { title, flex, border: "rounded", px: 1, py: 0, style: { bg: colors.panel, fg: colors.muted } },
58
+ children,
59
+ );
60
+ }
61
+
62
+ app.view((state) => {
63
+ const current = streams[state.selected] ?? streams[0];
64
+ const chat = chatByStream[current?.name ?? ""] ?? [];
65
+ const bufferValue = state.paused ? 0.2 : current ? Math.min(1, current.bitrate / 7) : 0;
66
+
67
+ return ui.column({ flex: 1, p: 1, gap: 1, items: "stretch" }, [
68
+ ui.row({ justify: "between", items: "center" }, [
69
+ ui.text("__APP_NAME__", { fg: colors.accent, bold: true }),
70
+ ui.row({ gap: 2 }, [
71
+ ui.text(`Mode: ${state.paused ? "Paused" : "Live"}`, {
72
+ fg: state.paused ? colors.warn : colors.ok,
73
+ bold: true,
74
+ }),
75
+ ui.text(`Follow: ${state.follow ? "On" : "Off"}`, { fg: colors.muted }),
76
+ ]),
77
+ ]),
78
+
79
+ ui.row({ flex: 1, gap: 1, items: "stretch" }, [
80
+ panel(
81
+ "Streams",
82
+ [
83
+ ui.column(
84
+ { gap: 0 },
85
+ streams.map((stream, index) => {
86
+ const active = index === state.selected;
87
+ const prefix = active ? ">" : " ";
88
+ return ui.text(`${prefix} ${stream.name}`, {
89
+ key: stream.name,
90
+ fg: active ? colors.accent : colors.muted,
91
+ bold: active,
92
+ });
93
+ }),
94
+ ),
95
+ ],
96
+ 1,
97
+ ),
98
+
99
+ ui.column({ flex: 2, gap: 1, items: "stretch" }, [
100
+ panel(
101
+ "Viewer",
102
+ [
103
+ ui.column({ gap: 1 }, [
104
+ ui.text(current ? current.name : "-", { fg: colors.accent, bold: true }),
105
+ ui.text(`Category: ${current?.category ?? "-"}`),
106
+ ui.text(`Viewers: ${current?.viewers ?? 0}`),
107
+ ui.text(`Health: ${current?.health ?? "-"}`, {
108
+ fg: current?.health === "warning" ? colors.warn : colors.ok,
109
+ }),
110
+ ui.text(`Bitrate: ${current?.bitrate ?? 0} Mbps`),
111
+ ui.progress(bufferValue, { label: "Buffer", showPercent: true }),
112
+ ]),
113
+ ],
114
+ 1,
115
+ ),
116
+ panel(
117
+ "Chat",
118
+ [
119
+ ui.column({ gap: 1 }, [
120
+ ui.text("Live chat", { fg: colors.muted }),
121
+ ...chat.map((line) => ui.text(`- ${line}`)),
122
+ ]),
123
+ ],
124
+ 1,
125
+ ),
126
+ ]),
127
+ ]),
128
+
129
+ ui.box({ px: 1, py: 0, style: { bg: colors.ink, fg: colors.muted } }, [
130
+ ui.row({ justify: "between", items: "center" }, [
131
+ ui.text("Streaming console ready"),
132
+ ui.row({ gap: 1 }, [
133
+ ui.kbd("up"),
134
+ ui.text("Stream"),
135
+ ui.kbd("space"),
136
+ ui.text("Pause"),
137
+ ui.kbd("f"),
138
+ ui.text("Follow"),
139
+ ui.kbd("q"),
140
+ ui.text("Quit"),
141
+ ]),
142
+ ]),
143
+ ]),
144
+ ]);
145
+ });
146
+
147
+ app.keys({
148
+ q: () => app.stop(),
149
+ "ctrl+c": () => app.stop(),
150
+ up: () =>
151
+ app.update((s) => ({
152
+ ...s,
153
+ selected: clamp(s.selected - 1, 0, streams.length - 1),
154
+ })),
155
+ down: () =>
156
+ app.update((s) => ({
157
+ ...s,
158
+ selected: clamp(s.selected + 1, 0, streams.length - 1),
159
+ })),
160
+ k: () =>
161
+ app.update((s) => ({
162
+ ...s,
163
+ selected: clamp(s.selected - 1, 0, streams.length - 1),
164
+ })),
165
+ j: () =>
166
+ app.update((s) => ({
167
+ ...s,
168
+ selected: clamp(s.selected + 1, 0, streams.length - 1),
169
+ })),
170
+ space: () => app.update((s) => ({ ...s, paused: !s.paused })),
171
+ f: () => app.update((s) => ({ ...s, follow: !s.follow })),
172
+ });
173
+
174
+ await app.start();
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "types": ["node"],
10
+ "noUncheckedIndexedAccess": true,
11
+ "exactOptionalPropertyTypes": true
12
+ },
13
+ "include": ["src/**/*.ts"]
14
+ }