astryx-dracula 0.5.0 → 0.6.2

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 (129) hide show
  1. package/BRAND.md +3 -3
  2. package/README.md +7 -4
  3. package/USAGE.md +49 -49
  4. package/astryx-dracula.d.ts +2 -2
  5. package/astryx-dracula.js +25 -9
  6. package/astryx-dracula.variants.d.ts +2 -2
  7. package/astryx.integration.mjs +1 -1
  8. package/icons.mjs +21 -17
  9. package/icons.tsx +69 -62
  10. package/package.json +125 -120
  11. package/shared/auth-chrome-config.ts +6 -6
  12. package/shared/auth-chrome.tsx +143 -144
  13. package/shared/auth-copy.ts +14 -14
  14. package/shared/chaptered-doc-config.ts +48 -51
  15. package/shared/chaptered-doc.tsx +267 -289
  16. package/shared/chart-hues.ts +25 -10
  17. package/shared/chart-labels.tsx +31 -30
  18. package/shared/chart-legend.tsx +55 -56
  19. package/shared/chart-panel-style.ts +3 -3
  20. package/shared/data-bar.tsx +97 -102
  21. package/shared/format-money.ts +1 -1
  22. package/shared/gallery-image.ts +6 -6
  23. package/shared/login-demo.ts +1 -1
  24. package/shared/metric-card.tsx +101 -105
  25. package/shared/metric-delta.tsx +28 -27
  26. package/shared/order-desk.tsx +205 -216
  27. package/shared/revenue-chart.tsx +145 -165
  28. package/shared/scene-castle.tsx +206 -291
  29. package/shared/scene-frame.tsx +33 -32
  30. package/shared/scene-hues.ts +17 -17
  31. package/shared/scene-tile.tsx +88 -92
  32. package/shared/settings-data.ts +119 -125
  33. package/shared/settings-rows.tsx +122 -159
  34. package/shared/sparkline.tsx +75 -88
  35. package/shared/sso-icons.tsx +10 -22
  36. package/shared/stars.tsx +16 -25
  37. package/templates/ai-chat-landing.template.mjs +5 -4
  38. package/templates/ai-chat-landing.tsx +473 -460
  39. package/templates/ai-chat.template.mjs +5 -4
  40. package/templates/ai-chat.tsx +543 -612
  41. package/templates/blank.template.mjs +5 -4
  42. package/templates/blank.tsx +18 -14
  43. package/templates/centered-hero.template.mjs +5 -4
  44. package/templates/centered-hero.tsx +55 -68
  45. package/templates/classic-gallery.template.mjs +5 -4
  46. package/templates/classic-gallery.tsx +97 -104
  47. package/templates/contact-form.template.mjs +5 -4
  48. package/templates/contact-form.tsx +247 -282
  49. package/templates/dashboard-portfolio.template.mjs +5 -4
  50. package/templates/dashboard-portfolio.tsx +717 -548
  51. package/templates/dashboard.template.mjs +5 -4
  52. package/templates/dashboard.tsx +596 -567
  53. package/templates/detail-page.template.mjs +5 -4
  54. package/templates/detail-page.tsx +546 -603
  55. package/templates/documentation-design.template.mjs +5 -4
  56. package/templates/documentation-design.tsx +231 -243
  57. package/templates/documentation-technical.template.mjs +5 -4
  58. package/templates/documentation-technical.tsx +236 -239
  59. package/templates/documentation.template.mjs +5 -4
  60. package/templates/documentation.tsx +267 -279
  61. package/templates/editor.template.mjs +5 -4
  62. package/templates/editor.tsx +844 -877
  63. package/templates/file-explorer.template.mjs +5 -4
  64. package/templates/file-explorer.tsx +566 -590
  65. package/templates/form-two-column.template.mjs +5 -4
  66. package/templates/form-two-column.tsx +195 -210
  67. package/templates/gallery-hero.template.mjs +5 -4
  68. package/templates/gallery-hero.tsx +93 -114
  69. package/templates/ide.template.mjs +5 -4
  70. package/templates/ide.tsx +407 -453
  71. package/templates/incident-console.template.mjs +5 -4
  72. package/templates/incident-console.tsx +593 -575
  73. package/templates/kanban-board.template.mjs +5 -4
  74. package/templates/kanban-board.tsx +665 -683
  75. package/templates/library.template.mjs +5 -4
  76. package/templates/library.tsx +409 -430
  77. package/templates/login-card.template.mjs +5 -4
  78. package/templates/login-card.tsx +63 -72
  79. package/templates/login-split.template.mjs +5 -4
  80. package/templates/login-split.tsx +192 -193
  81. package/templates/login-sso.template.mjs +5 -4
  82. package/templates/login-sso.tsx +257 -258
  83. package/templates/login.template.mjs +5 -4
  84. package/templates/login.tsx +53 -49
  85. package/templates/messaging-shell.template.mjs +5 -4
  86. package/templates/messaging-shell.tsx +556 -574
  87. package/templates/mixed-gallery.template.mjs +5 -4
  88. package/templates/mixed-gallery.tsx +60 -58
  89. package/templates/payment-form.template.mjs +5 -4
  90. package/templates/payment-form.tsx +882 -991
  91. package/templates/product-detail.template.mjs +5 -4
  92. package/templates/product-detail.tsx +261 -272
  93. package/templates/product-gallery.template.mjs +5 -4
  94. package/templates/product-gallery.tsx +126 -137
  95. package/templates/product-tour.template.mjs +5 -4
  96. package/templates/product-tour.tsx +741 -773
  97. package/templates/settings-dialog.template.mjs +5 -4
  98. package/templates/settings-dialog.tsx +688 -717
  99. package/templates/settings-sidebar.template.mjs +5 -4
  100. package/templates/settings-sidebar.tsx +762 -807
  101. package/templates/settings.template.mjs +5 -4
  102. package/templates/settings.tsx +289 -304
  103. package/templates/shell-nav.template.mjs +5 -4
  104. package/templates/shell-nav.tsx +212 -219
  105. package/templates/shell-side-nav.template.mjs +5 -4
  106. package/templates/shell-side-nav.tsx +135 -157
  107. package/templates/shell-top-nav.template.mjs +5 -4
  108. package/templates/shell-top-nav.tsx +182 -203
  109. package/templates/side-gallery.template.mjs +5 -4
  110. package/templates/side-gallery.tsx +85 -90
  111. package/templates/table-grouped.template.mjs +5 -4
  112. package/templates/table-grouped.tsx +1000 -1053
  113. package/templates/table-page-chart.template.mjs +5 -4
  114. package/templates/table-page-chart.tsx +400 -398
  115. package/templates/table-page-heatmap-status.template.mjs +5 -4
  116. package/templates/table-page-heatmap-status.tsx +483 -512
  117. package/templates/table-page-shoe-store-heatmap.template.mjs +5 -4
  118. package/templates/table-page-shoe-store-heatmap.tsx +748 -746
  119. package/templates/table-page.template.mjs +5 -4
  120. package/templates/table-page.tsx +396 -405
  121. package/templates/table.template.mjs +5 -4
  122. package/templates/table.tsx +92 -93
  123. package/templates/tech-report.template.mjs +5 -4
  124. package/templates/tech-report.tsx +1316 -1304
  125. package/templates/theme-showcase.template.mjs +5 -4
  126. package/templates/theme-showcase.tsx +871 -951
  127. package/theme.css +35 -77
  128. package/theme.css.d.ts +9 -0
  129. package/tokens.css +373 -380
package/templates/ide.tsx CHANGED
@@ -1,87 +1,87 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
- // XLE (canonical structure, validated with `bunx astryx layout check`):
3
- // L > (LP[p=4] > V[g=2] > TI"Search files" + TreeList) + (LC[p=0] > (Hd"Code editor"[level=1] + V > (SI[fill] > Cd) + (V > (TL > Tab"Terminal"! + Tab"Problems" + Tab"Output" + Tab"Debug") + Cd))) + (LP[p=4] > V[g=3] > (SG > SGI"Properties"! + SGI"History") + (V[g=3] > (V[g=1] > Hd"NightCounter.tsx"[level=3] + Tx"src/components/NightCounter.tsx"[t=supporting]) + ML + (V[g=2] > B.secondary"Format Document" + B.secondary"Go to Definition" + B.secondary"Find References")))
2
+ // XLE (canonical structure of the tree below; the CLI had no XLE validator
3
+ // after `astryx layout` was removed in 0.7.0, so this is a reading aid and a
4
+ // human drift check — update it with the file, never alone):
5
+ // L[h=fill] > LC[p=0] > (VH"Code editor" + L[h=fill] > (LP[w=256 p=4] > V[g=2] > TI"Search the night" + TrL) + RH + (LC[p=0] > L[h=fill] > (LC[p=0] > V > (SI[fill] > Cd) + RH + (V[h=300] > (TL[size=sm] > Tab"Terminal"! + Tab"Problems" + Tab"Output" + Tab"Debug") + (SI[fill] > Cd))) + (RH + (LP[w=320 p=4] > V[g=3] > (SG > SGI"Properties"! + SGI"History") + (V[g=3] > (V[g=1] > Hd"NightCounter.tsx"[level=3] + Tx"src/components/NightCounter.tsx"[t=supporting]) + ML + (V[g=2] > B.secondary"Format Document" + B.secondary"Go to Definition" + B.secondary"Find References")) + (V[g=1] > UL > LI*3)))))
4
6
 
5
- import {useState, useMemo, type CSSProperties} from 'react';
6
-
7
- import {Layout, LayoutContent, LayoutPanel} from '@astryxdesign/core/Layout';
8
- import {StatusDot} from '@astryxdesign/core/StatusDot';
9
- import {ResizeHandle, useResizable} from '@astryxdesign/core/Resizable';
10
- import {Text, Heading} from '@astryxdesign/core/Text';
11
- import {VisuallyHidden} from '@astryxdesign/core/VisuallyHidden';
12
- import {Center} from '@astryxdesign/core/Center';
13
- import {CodeBlock} from '@astryxdesign/core/CodeBlock';
14
- import {EmptyState} from '@astryxdesign/core/EmptyState';
15
- import {Stack, StackItem} from '@astryxdesign/core/Layout';
16
- import {useMediaQuery} from '@astryxdesign/core/hooks';
17
- import {TabList, Tab} from '@astryxdesign/core/TabList';
18
- import {
19
- SegmentedControl,
20
- SegmentedControlItem,
21
- } from '@astryxdesign/core/SegmentedControl';
22
- import {Button} from '@astryxdesign/core/Button';
23
- import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
24
- import {List, ListItem} from '@astryxdesign/core/List';
25
- import {Icon} from '@astryxdesign/core/Icon';
26
- import {TextInput} from '@astryxdesign/core/TextInput';
27
- import {TreeList} from '@astryxdesign/core/TreeList';
28
- import type {TreeListItemData} from '@astryxdesign/core/TreeList';
29
- import {Folder, FileText, Search} from 'lucide-react';
7
+ import { Button } from "@astryxdesign/core/Button";
8
+ import { Center } from "@astryxdesign/core/Center";
9
+ import { CodeBlock } from "@astryxdesign/core/CodeBlock";
10
+ import { EmptyState } from "@astryxdesign/core/EmptyState";
11
+ import { useMediaQuery } from "@astryxdesign/core/hooks";
12
+ import { Icon } from "@astryxdesign/core/Icon";
13
+ import { Layout, LayoutContent, LayoutPanel, Stack, StackItem } from "@astryxdesign/core/Layout";
14
+ import { List, ListItem } from "@astryxdesign/core/List";
15
+ import { MetadataList, MetadataListItem } from "@astryxdesign/core/MetadataList";
16
+ import { ResizeHandle, useResizable } from "@astryxdesign/core/Resizable";
17
+ import { SegmentedControl, SegmentedControlItem } from "@astryxdesign/core/SegmentedControl";
18
+ import { StatusDot } from "@astryxdesign/core/StatusDot";
19
+ import { Tab, TabList } from "@astryxdesign/core/TabList";
20
+ import { Heading, Text } from "@astryxdesign/core/Text";
21
+ import { TextInput } from "@astryxdesign/core/TextInput";
22
+ import type { TreeListItemData } from "@astryxdesign/core/TreeList";
23
+ import { TreeList } from "@astryxdesign/core/TreeList";
24
+ import { VisuallyHidden } from "@astryxdesign/core/VisuallyHidden";
25
+ import { FileText, Folder, Search } from "lucide-react";
26
+ import { type CSSProperties, useMemo, useState } from "react";
30
27
 
31
28
  const styles: Record<string, CSSProperties> = {
32
- contentFill: {
33
- height: '100%',
34
- },
35
- terminalWrapper: {
36
- minHeight: 0,
37
- overflow: 'hidden',
38
- display: 'grid',
39
- },
40
- tabListPadding: {
41
- paddingTop: 'var(--spacing-2)',
42
- // 4px inline = offset 2 + ring thickness 2, so the focus ring lands INSIDE
43
- // this box's clip instead of on its edge being cut in half. Deliberately
44
- // NOT applied to `terminalPanel`: that would inset the CodeBlock itself,
45
- // moving the code rather than giving the ring somewhere to live.
46
- paddingInline: 'var(--spacing-1)',
47
- },
48
- metadataCompact: {
49
- gap: 'var(--spacing-1) var(--spacing-3)',
50
- },
51
- // No `overflow` here, deliberately. The only child is a `CodeBlock` at
52
- // `height: 100%` whose own root is `overflow: hidden`, so this box could
53
- // never overflow and the scroller never fired — it only clipped. An
54
- // `overflow: auto` on a parent whose sole child is a fixed-height
55
- // `overflow: hidden` box is inert, which is what the deleted line was.
56
- editorArea: {
57
- minHeight: 0,
58
- },
59
- fileExplorer: {
60
- minWidth: 0,
61
- },
62
- propertiesPanel: {
63
- height: '100%',
64
- },
65
- propertiesContent: {
66
- flex: 1,
67
- minHeight: 0,
68
- },
69
- propertyActions: {
70
- marginTop: 'auto',
71
- },
72
- terminalPanel: {
73
- flexShrink: 0,
74
- overflow: 'hidden',
75
- },
29
+ contentFill: {
30
+ height: "100%",
31
+ },
32
+ terminalWrapper: {
33
+ minHeight: 0,
34
+ overflow: "hidden",
35
+ display: "grid",
36
+ },
37
+ tabListPadding: {
38
+ paddingTop: "var(--spacing-2)",
39
+ // The focus ring is a fixed 2px outline (core's defineTheme.ts), and the
40
+ // ring lands INSIDE this box's clip rather than on its edge being cut in
41
+ // half. The 2px of that is a PIXEL relationship, so it is written as
42
+ // pixels: --spacing-1 is rem since core 0.7.0, and at a 20px root it
43
+ // is 5px, which would clip the ring by a pixel. Deliberately NOT
44
+ // applied to `terminalPanel`: that would inset the CodeBlock itself,
45
+ // moving the code rather than giving the ring somewhere to live.
46
+ paddingInline: "calc(var(--spacing-1) + 2px)",
47
+ },
48
+ metadataCompact: {
49
+ gap: "var(--spacing-1) var(--spacing-3)",
50
+ },
51
+ // No `overflow` here, deliberately. The only child is a `CodeBlock` at
52
+ // `height: 100%` whose own root is `overflow: hidden`, so this box could
53
+ // never overflow and the scroller never fired — it only clipped. An
54
+ // `overflow: auto` on a parent whose sole child is a fixed-height
55
+ // `overflow: hidden` box is inert, which is what the deleted line was.
56
+ editorArea: {
57
+ minHeight: 0,
58
+ },
59
+ fileExplorer: {
60
+ minWidth: 0,
61
+ },
62
+ propertiesPanel: {
63
+ height: "100%",
64
+ },
65
+ propertiesContent: {
66
+ flex: 1,
67
+ minHeight: 0,
68
+ },
69
+ propertyActions: {
70
+ marginTop: "auto",
71
+ },
72
+ terminalPanel: {
73
+ flexShrink: 0,
74
+ overflow: "hidden",
75
+ },
76
76
  };
77
77
 
78
78
  // Full-bleed CodeBlock inside a panel: the panel owns the chrome, so the
79
79
  // block drops its own border and radius rather than drawing a box in a box.
80
80
  const codeBlockFlush: CSSProperties = {
81
- width: '100%',
82
- height: '100%',
83
- borderWidth: 0,
84
- borderRadius: 0,
81
+ width: "100%",
82
+ height: "100%",
83
+ borderWidth: 0,
84
+ borderRadius: 0,
85
85
  };
86
86
 
87
87
  const EDITOR_CODE = `import {useState, useCallback} from 'react';
@@ -136,398 +136,352 @@ const OUTPUT_LOG = `[@astryxdesign/core] theme tokens resolved in 38ms
136
136
  [HMR] connected, watching src/components/NightCounter.tsx
137
137
  [HMR] updated src/tokens.ts in 122ms`;
138
138
 
139
- function buildFileTree(
140
- onFileClick: (name: string) => void,
141
- ): TreeListItemData[] {
142
- const label = (text: string) => <Text maxLines={1}>{text}</Text>;
143
- const file = (id: string): TreeListItemData => ({
144
- id,
145
- label: label(id),
146
- startContent: <Icon icon={FileText} size="xsm" />,
147
- onClick: () => onFileClick(id),
148
- });
149
- return [
150
- {
151
- id: 'src',
152
- label: label('src'),
153
- startContent: <Icon icon={Folder} size="xsm" />,
154
- isExpanded: true,
155
- children: [
156
- {
157
- id: 'components',
158
- label: label('components'),
159
- startContent: <Icon icon={Folder} size="xsm" />,
160
- isExpanded: true,
161
- children: [
162
- file('NightCounter.tsx'),
163
- file('Header.tsx'),
164
- file('Layout.tsx'),
165
- ],
166
- },
167
- {
168
- id: 'pages',
169
- label: label('pages'),
170
- startContent: <Icon icon={Folder} size="xsm" />,
171
- isExpanded: true,
172
- children: [file('index.tsx'), file('about.tsx')],
173
- },
174
- {
175
- id: 'styles',
176
- label: label('styles'),
177
- startContent: <Icon icon={Folder} size="xsm" />,
178
- isExpanded: true,
179
- children: [file('tokens.ts'), file('theme.ts')],
180
- },
181
- ],
182
- },
183
- file('package.json'),
184
- file('tsconfig.json'),
185
- file('next.config.mjs'),
186
- ];
139
+ function buildFileTree(onFileClick: (name: string) => void): TreeListItemData[] {
140
+ const label = (text: string) => <Text maxLines={1}>{text}</Text>;
141
+ const file = (id: string): TreeListItemData => ({
142
+ id,
143
+ label: label(id),
144
+ startContent: <Icon icon={FileText} size="xsm" />,
145
+ onClick: () => onFileClick(id),
146
+ });
147
+ return [
148
+ {
149
+ id: "src",
150
+ label: label("src"),
151
+ startContent: <Icon icon={Folder} size="xsm" />,
152
+ isExpanded: true,
153
+ children: [
154
+ {
155
+ id: "components",
156
+ label: label("components"),
157
+ startContent: <Icon icon={Folder} size="xsm" />,
158
+ isExpanded: true,
159
+ children: [file("NightCounter.tsx"), file("Header.tsx"), file("Layout.tsx")],
160
+ },
161
+ {
162
+ id: "pages",
163
+ label: label("pages"),
164
+ startContent: <Icon icon={Folder} size="xsm" />,
165
+ isExpanded: true,
166
+ children: [file("index.tsx"), file("about.tsx")],
167
+ },
168
+ {
169
+ id: "styles",
170
+ label: label("styles"),
171
+ startContent: <Icon icon={Folder} size="xsm" />,
172
+ isExpanded: true,
173
+ children: [file("tokens.ts"), file("theme.ts")],
174
+ },
175
+ ],
176
+ },
177
+ file("package.json"),
178
+ file("tsconfig.json"),
179
+ file("next.config.mjs"),
180
+ ];
187
181
  }
188
182
 
189
- function filterFileTree(
190
- items: TreeListItemData[],
191
- query: string,
192
- ): TreeListItemData[] {
193
- const out: TreeListItemData[] = [];
194
- for (const item of items) {
195
- const children = item.children
196
- ? filterFileTree(item.children, query)
197
- : undefined;
198
- if (
199
- item.id.toLowerCase().includes(query) ||
200
- (children && children.length > 0)
201
- ) {
202
- out.push({...item, ...(children ? {children, isExpanded: true} : null)});
203
- }
204
- }
205
- return out;
183
+ function filterFileTree(items: TreeListItemData[], query: string): TreeListItemData[] {
184
+ const out: TreeListItemData[] = [];
185
+ for (const item of items) {
186
+ const children = item.children ? filterFileTree(item.children, query) : undefined;
187
+ if (item.id.toLowerCase().includes(query) || (children && children.length > 0)) {
188
+ out.push({ ...item, ...(children ? { children, isExpanded: true } : null) });
189
+ }
190
+ }
191
+ return out;
206
192
  }
207
193
 
208
194
  const PROPERTIES = [
209
- {label: 'Type', value: 'React Component'},
210
- {label: 'Language', value: 'TypeScript'},
211
- {label: 'Lines', value: '42'},
212
- {label: 'Size', value: '1.2 KB'},
213
- {label: 'Last modified', value: '2 hours ago'},
214
- {label: 'Imports', value: '4 modules'},
215
- {label: 'Exports', value: '1 default'},
216
- {label: 'Hooks', value: 'useState, useCallback'},
195
+ { label: "Type", value: "React Component" },
196
+ { label: "Language", value: "TypeScript" },
197
+ { label: "Lines", value: "42" },
198
+ { label: "Size", value: "1.2 KB" },
199
+ { label: "Last modified", value: "2 hours ago" },
200
+ { label: "Imports", value: "4 modules" },
201
+ { label: "Exports", value: "1 default" },
202
+ { label: "Hooks", value: "useState, useCallback" },
217
203
  ];
218
204
 
219
205
  const HISTORY_ITEMS = [
220
- {label: 'Opened NightCounter.tsx', time: '2 min ago'},
221
- {label: 'Opened Layout.tsx', time: '6 min ago'},
222
- {label: 'Viewed tokens.ts', time: '11 min ago'},
206
+ { label: "Opened NightCounter.tsx", time: "2 min ago" },
207
+ { label: "Opened Layout.tsx", time: "6 min ago" },
208
+ { label: "Viewed tokens.ts", time: "11 min ago" },
223
209
  ];
224
210
 
225
211
  export default function IdeWorkspace() {
226
- const [activeFile, setActiveFile] = useState('NightCounter.tsx');
227
- const [activeTermTab, setActiveTermTab] = useState('terminal');
228
- const [activePropertiesTab, setActivePropertiesTab] = useState('properties');
229
- const [query, setQuery] = useState('');
230
- const fileTree = useMemo(() => buildFileTree(setActiveFile), []);
231
- const visibleTree = useMemo(() => {
232
- const q = query.trim().toLowerCase();
233
- return q ? filterFileTree(fileTree, q) : fileTree;
234
- }, [fileTree, query]);
212
+ const [activeFile, setActiveFile] = useState("NightCounter.tsx");
213
+ const [activeTermTab, setActiveTermTab] = useState("terminal");
214
+ const [activePropertiesTab, setActivePropertiesTab] = useState("properties");
215
+ const [query, setQuery] = useState("");
216
+ const fileTree = useMemo(() => buildFileTree(setActiveFile), []);
217
+ const visibleTree = useMemo(() => {
218
+ const q = query.trim().toLowerCase();
219
+ return q ? filterFileTree(fileTree, q) : fileTree;
220
+ }, [fileTree, query]);
235
221
 
236
- const startPanel = useResizable({
237
- defaultSize: 256,
238
- minSize: 160,
239
- maxSize: 400,
240
- collapsible: true,
241
- collapsedSize: 50,
242
- });
222
+ const startPanel = useResizable({
223
+ defaultSize: 256,
224
+ minSize: 160,
225
+ maxSize: 400,
226
+ collapsible: true,
227
+ collapsedSize: 50,
228
+ });
243
229
 
244
- const endPanel = useResizable({
245
- defaultSize: 320,
246
- minSize: 180,
247
- maxSize: 500,
248
- collapsible: true,
249
- collapsedSize: 50,
250
- });
230
+ const endPanel = useResizable({
231
+ defaultSize: 320,
232
+ minSize: 180,
233
+ maxSize: 500,
234
+ collapsible: true,
235
+ collapsedSize: 50,
236
+ });
251
237
 
252
- // Capped so the terminal cannot swallow the canvas: the editor stack above
253
- // has no minimum of its own, and an unbounded drag leaves nothing to grab.
254
- const bottomPanel = useResizable({
255
- defaultSize: 300,
256
- minSize: 80,
257
- maxSize: 520,
258
- collapsible: true,
259
- collapsedSize: 40,
260
- });
238
+ // Capped so the terminal cannot swallow the canvas: the editor stack above
239
+ // has no minimum of its own, and an unbounded drag leaves nothing to grab.
240
+ const bottomPanel = useResizable({
241
+ defaultSize: 300,
242
+ minSize: 80,
243
+ maxSize: 520,
244
+ collapsible: true,
245
+ collapsedSize: 40,
246
+ });
261
247
 
262
- const isMobile = useMediaQuery('(max-width: 768px)');
248
+ const isMobile = useMediaQuery("(max-width: 768px)");
263
249
 
264
- return (
265
- <Layout
266
- height="fill"
267
- content={
268
- <LayoutContent padding={0}>
269
- <VisuallyHidden as="h1">
270
- Code editor
271
- </VisuallyHidden>
272
- <Layout
273
- height="fill"
274
- start={
275
- isMobile ? undefined : (
276
- <>
277
- {!startPanel.isCollapsed && (
278
- <LayoutPanel
279
- width={startPanel.size}
280
- hasDivider={false}
281
- padding={4}>
282
- <Stack
283
- direction="vertical"
284
- style={styles.fileExplorer}
285
- gap={2}>
286
- <TextInput
287
- label="Search files"
288
- isLabelHidden
289
- value={query}
290
- onChange={setQuery}
291
- placeholder="Search the night"
292
- size="md"
293
- startIcon={Search}
294
- hasClear
295
- />
296
- {visibleTree.length > 0 ? (
297
- <TreeList items={visibleTree} density="compact" />
298
- ) : (
299
- <EmptyState
300
- title="No files match"
301
- description={`Nothing in the night answers to "${query.trim()}".`}
302
- isCompact
303
- />
304
- )}
305
- </Stack>
306
- </LayoutPanel>
307
- )}
308
- <ResizeHandle
309
- direction="horizontal"
310
- hasDivider
311
- isAlwaysVisible={false}
312
- resizable={startPanel.props}
313
- label="Resize file explorer"
314
- />
315
- </>
316
- )
317
- }
318
- content={
319
- <LayoutContent padding={0}>
320
- <Layout
321
- height="fill"
322
- content={
323
- <LayoutContent padding={0}>
324
- <Stack direction="vertical" style={styles.contentFill}>
325
- <StackItem size="fill" style={styles.editorArea}>
326
- <CodeBlock
327
- code={EDITOR_CODE}
328
- language="typescript"
329
- container="section"
330
- hasLanguageLabel={false}
331
- hasLineNumbers
332
- highlightLines={[21]}
333
- hasCopyButton={false}
334
- size="sm"
335
- style={codeBlockFlush}
336
- />
337
- </StackItem>
338
- <ResizeHandle
339
- direction="vertical"
340
- hasDivider
341
- isReversed
342
- isAlwaysVisible={false}
343
- resizable={bottomPanel.props}
344
- label="Resize terminal"
345
- />
346
- {!bottomPanel.isCollapsed && (
347
- <Stack
348
- direction="vertical"
349
- height={bottomPanel.size}
350
- style={styles.terminalPanel}>
351
- <TabList
352
- value={activeTermTab}
353
- onChange={val => setActiveTermTab(val)}
354
- size="sm"
355
- hasDivider={false}
356
- style={styles.tabListPadding}>
357
- <Tab label="Terminal" value="terminal" />
358
- <Tab label="Problems" value="problems" />
359
- <Tab label="Output" value="output" />
360
- <Tab label="Debug" value="debug" />
361
- </TabList>
362
- <StackItem
363
- size="fill"
364
- style={styles.terminalWrapper}>
365
- {activeTermTab === 'terminal' && (
366
- <CodeBlock
367
- code={TERMINAL_OUTPUT}
368
- language="bash"
369
- container="section"
370
- hasLanguageLabel={false}
371
- hasCopyButton={false}
372
- size="sm"
373
- style={codeBlockFlush}
374
- />
375
- )}
376
- {activeTermTab === 'output' && (
377
- <CodeBlock
378
- code={OUTPUT_LOG}
379
- language="bash"
380
- container="section"
381
- hasLanguageLabel={false}
382
- hasCopyButton={false}
383
- size="sm"
384
- style={codeBlockFlush}
385
- />
386
- )}
387
- {activeTermTab === 'problems' && (
388
- <Center style={{height: '100%'}}>
389
- <EmptyState
390
- title="No problems"
391
- description="The night watch reports no errors or warnings."
392
- isCompact
393
- />
394
- </Center>
395
- )}
396
- {activeTermTab === 'debug' && (
397
- <Center style={{height: '100%'}}>
398
- <EmptyState
399
- title="No debug session"
400
- description="Start debugging to inspect the night shift."
401
- isCompact
402
- />
403
- </Center>
404
- )}
405
- </StackItem>
406
- </Stack>
407
- )}
408
- </Stack>
409
- </LayoutContent>
410
- }
411
- end={
412
- isMobile ? undefined : (
413
- <>
414
- <ResizeHandle
415
- direction="horizontal"
416
- hasDivider
417
- isReversed
418
- isAlwaysVisible={false}
419
- resizable={endPanel.props}
420
- label="Resize properties panel"
421
- />
422
- {!endPanel.isCollapsed && (
423
- <LayoutPanel
424
- width={endPanel.size}
425
- hasDivider={false}
426
- padding={4}>
427
- <Stack
428
- direction="vertical"
429
- gap={3}
430
- style={styles.propertiesPanel}>
431
- <SegmentedControl
432
- label="Properties panel sections"
433
- value={activePropertiesTab}
434
- onChange={setActivePropertiesTab}
435
- size="sm"
436
- layout="fill">
437
- <SegmentedControlItem
438
- label="Properties"
439
- value="properties"
440
- />
441
- <SegmentedControlItem
442
- label="History"
443
- value="history"
444
- />
445
- </SegmentedControl>
446
- {activePropertiesTab === 'properties' ? (
447
- <Stack
448
- direction="vertical"
449
- gap={3}
450
- style={styles.propertiesContent}>
451
- <Stack direction="vertical" gap={1}>
452
- <Heading level={3} maxLines={1}>
453
- {activeFile}
454
- </Heading>
455
- <Text
456
- color="secondary"
457
- type="supporting"
458
- maxLines={1}>
459
- src/components/{activeFile}
460
- </Text>
461
- </Stack>
462
- <MetadataList style={styles.metadataCompact}>
463
- {PROPERTIES.map(prop => (
464
- <MetadataListItem
465
- key={prop.label}
466
- label={prop.label}>
467
- {prop.value}
468
- </MetadataListItem>
469
- ))}
470
- </MetadataList>
471
- <Stack
472
- direction="vertical"
473
- gap={2}
474
- style={styles.propertyActions}>
475
- <Button
476
- label="Format Document"
477
- size="sm"
478
- variant="secondary"
479
- />
480
- <Button
481
- label="Go to Definition"
482
- size="sm"
483
- variant="secondary"
484
- />
485
- <Button
486
- label="Find References"
487
- size="sm"
488
- variant="secondary"
489
- />
490
- </Stack>
491
- </Stack>
492
- ) : (
493
- <Stack direction="vertical" gap={1}>
494
- <List>
495
- {HISTORY_ITEMS.map(item => (
496
- <ListItem
497
- key={item.label}
498
- label={item.label}
499
- endContent={
500
- <Text
501
- type="supporting"
502
- color="secondary"
503
- maxLines={1}
504
- hasTabularNumbers>
505
- {item.time}
506
- </Text>
507
- }
508
- startContent={
509
- <StatusDot
510
- variant="neutral"
511
- label="Timeline marker"
512
- />
513
- }
514
- />
515
- ))}
516
- </List>
517
- </Stack>
518
- )}
519
- </Stack>
520
- </LayoutPanel>
521
- )}
522
- </>
523
- )
524
- }
525
- />
526
- </LayoutContent>
527
- }
528
- />
529
- </LayoutContent>
530
- }
531
- />
532
- );
250
+ return (
251
+ <Layout
252
+ height="fill"
253
+ content={
254
+ <LayoutContent padding={0}>
255
+ <VisuallyHidden as="h1">Code editor</VisuallyHidden>
256
+ <Layout
257
+ height="fill"
258
+ start={
259
+ isMobile ? undefined : (
260
+ <>
261
+ {!startPanel.isCollapsed && (
262
+ <LayoutPanel width={startPanel.size} hasDivider={false} padding={4}>
263
+ <Stack direction="vertical" style={styles.fileExplorer} gap={2}>
264
+ <TextInput
265
+ label="Search files"
266
+ isLabelHidden
267
+ value={query}
268
+ onChange={setQuery}
269
+ placeholder="Search the night"
270
+ size="md"
271
+ startIcon={Search}
272
+ hasClear
273
+ />
274
+ {visibleTree.length > 0 ? (
275
+ <TreeList items={visibleTree} density="compact" />
276
+ ) : (
277
+ <EmptyState
278
+ title="No files match"
279
+ description={`Nothing in the night answers to "${query.trim()}".`}
280
+ isCompact
281
+ />
282
+ )}
283
+ </Stack>
284
+ </LayoutPanel>
285
+ )}
286
+ <ResizeHandle
287
+ direction="horizontal"
288
+ hasDivider
289
+ isAlwaysVisible={false}
290
+ resizable={startPanel.props}
291
+ label="Resize file explorer"
292
+ />
293
+ </>
294
+ )
295
+ }
296
+ content={
297
+ <LayoutContent padding={0}>
298
+ <Layout
299
+ height="fill"
300
+ content={
301
+ <LayoutContent padding={0}>
302
+ <Stack direction="vertical" style={styles.contentFill}>
303
+ <StackItem size="fill" style={styles.editorArea}>
304
+ <CodeBlock
305
+ code={EDITOR_CODE}
306
+ language="typescript"
307
+ container="section"
308
+ hasLanguageLabel={false}
309
+ hasLineNumbers
310
+ highlightLines={[21]}
311
+ hasCopyButton={false}
312
+ size="sm"
313
+ style={codeBlockFlush}
314
+ />
315
+ </StackItem>
316
+ <ResizeHandle
317
+ direction="vertical"
318
+ hasDivider
319
+ isReversed
320
+ isAlwaysVisible={false}
321
+ resizable={bottomPanel.props}
322
+ label="Resize terminal"
323
+ />
324
+ {!bottomPanel.isCollapsed && (
325
+ <Stack
326
+ direction="vertical"
327
+ height={bottomPanel.size}
328
+ style={styles.terminalPanel}
329
+ >
330
+ <TabList
331
+ value={activeTermTab}
332
+ onChange={(val) => setActiveTermTab(val)}
333
+ size="sm"
334
+ hasDivider={false}
335
+ style={styles.tabListPadding}
336
+ >
337
+ <Tab label="Terminal" value="terminal" />
338
+ <Tab label="Problems" value="problems" />
339
+ <Tab label="Output" value="output" />
340
+ <Tab label="Debug" value="debug" />
341
+ </TabList>
342
+ <StackItem size="fill" style={styles.terminalWrapper}>
343
+ {activeTermTab === "terminal" && (
344
+ <CodeBlock
345
+ code={TERMINAL_OUTPUT}
346
+ language="bash"
347
+ container="section"
348
+ hasLanguageLabel={false}
349
+ hasCopyButton={false}
350
+ size="sm"
351
+ style={codeBlockFlush}
352
+ />
353
+ )}
354
+ {activeTermTab === "output" && (
355
+ <CodeBlock
356
+ code={OUTPUT_LOG}
357
+ language="bash"
358
+ container="section"
359
+ hasLanguageLabel={false}
360
+ hasCopyButton={false}
361
+ size="sm"
362
+ style={codeBlockFlush}
363
+ />
364
+ )}
365
+ {activeTermTab === "problems" && (
366
+ <Center style={{ height: "100%" }}>
367
+ <EmptyState
368
+ title="No problems"
369
+ description="The night watch reports no errors or warnings."
370
+ isCompact
371
+ />
372
+ </Center>
373
+ )}
374
+ {activeTermTab === "debug" && (
375
+ <Center style={{ height: "100%" }}>
376
+ <EmptyState
377
+ title="No debug session"
378
+ description="Start debugging to inspect the night shift."
379
+ isCompact
380
+ />
381
+ </Center>
382
+ )}
383
+ </StackItem>
384
+ </Stack>
385
+ )}
386
+ </Stack>
387
+ </LayoutContent>
388
+ }
389
+ end={
390
+ isMobile ? undefined : (
391
+ <>
392
+ <ResizeHandle
393
+ direction="horizontal"
394
+ hasDivider
395
+ isReversed
396
+ isAlwaysVisible={false}
397
+ resizable={endPanel.props}
398
+ label="Resize properties panel"
399
+ />
400
+ {!endPanel.isCollapsed && (
401
+ <LayoutPanel width={endPanel.size} hasDivider={false} padding={4}>
402
+ <Stack direction="vertical" gap={3} style={styles.propertiesPanel}>
403
+ <SegmentedControl
404
+ label="Properties panel sections"
405
+ value={activePropertiesTab}
406
+ onChange={setActivePropertiesTab}
407
+ size="sm"
408
+ layout="fill"
409
+ >
410
+ <SegmentedControlItem label="Properties" value="properties" />
411
+ <SegmentedControlItem label="History" value="history" />
412
+ </SegmentedControl>
413
+ {activePropertiesTab === "properties" ? (
414
+ <Stack
415
+ direction="vertical"
416
+ gap={3}
417
+ style={styles.propertiesContent}
418
+ >
419
+ <Stack direction="vertical" gap={1}>
420
+ <Heading level={3} maxLines={1}>
421
+ {activeFile}
422
+ </Heading>
423
+ <Text color="secondary" type="supporting" maxLines={1}>
424
+ src/components/{activeFile}
425
+ </Text>
426
+ </Stack>
427
+ <MetadataList style={styles.metadataCompact}>
428
+ {PROPERTIES.map((prop) => (
429
+ <MetadataListItem key={prop.label} label={prop.label}>
430
+ {prop.value}
431
+ </MetadataListItem>
432
+ ))}
433
+ </MetadataList>
434
+ <Stack
435
+ direction="vertical"
436
+ gap={2}
437
+ style={styles.propertyActions}
438
+ >
439
+ <Button label="Format Document" size="sm" variant="secondary" />
440
+ <Button
441
+ label="Go to Definition"
442
+ size="sm"
443
+ variant="secondary"
444
+ />
445
+ <Button label="Find References" size="sm" variant="secondary" />
446
+ </Stack>
447
+ </Stack>
448
+ ) : (
449
+ <Stack direction="vertical" gap={1}>
450
+ <List>
451
+ {HISTORY_ITEMS.map((item) => (
452
+ <ListItem
453
+ key={item.label}
454
+ label={item.label}
455
+ endContent={
456
+ <Text
457
+ type="supporting"
458
+ color="secondary"
459
+ maxLines={1}
460
+ hasTabularNumbers
461
+ >
462
+ {item.time}
463
+ </Text>
464
+ }
465
+ startContent={
466
+ <StatusDot variant="neutral" label="Timeline marker" />
467
+ }
468
+ />
469
+ ))}
470
+ </List>
471
+ </Stack>
472
+ )}
473
+ </Stack>
474
+ </LayoutPanel>
475
+ )}
476
+ </>
477
+ )
478
+ }
479
+ />
480
+ </LayoutContent>
481
+ }
482
+ />
483
+ </LayoutContent>
484
+ }
485
+ />
486
+ );
533
487
  }