astryx-dracula 0.1.3 → 0.2.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 (94) hide show
  1. package/BRAND.md +1 -1
  2. package/README.md +2 -0
  3. package/USAGE.md +13 -0
  4. package/astryx-dracula.js +4 -4
  5. package/astryx.integration.mjs +3 -0
  6. package/package.json +4 -2
  7. package/templates/ai-chat-landing.template.mjs +6 -0
  8. package/templates/ai-chat-landing.tsx +476 -0
  9. package/templates/ai-chat.template.mjs +6 -0
  10. package/templates/ai-chat.tsx +762 -0
  11. package/templates/blank.template.mjs +6 -0
  12. package/templates/blank.tsx +16 -0
  13. package/templates/centered-hero.template.mjs +6 -0
  14. package/templates/centered-hero.tsx +188 -0
  15. package/templates/classic-gallery.template.mjs +6 -0
  16. package/templates/classic-gallery.tsx +181 -0
  17. package/templates/contact-form.template.mjs +6 -0
  18. package/templates/contact-form.tsx +292 -0
  19. package/templates/dashboard-portfolio.template.mjs +6 -0
  20. package/templates/dashboard-portfolio.tsx +800 -0
  21. package/templates/dashboard.template.mjs +6 -0
  22. package/templates/dashboard.tsx +774 -0
  23. package/templates/detail-page.template.mjs +6 -0
  24. package/templates/detail-page.tsx +695 -0
  25. package/templates/documentation-design.template.mjs +6 -0
  26. package/templates/documentation-design.tsx +751 -0
  27. package/templates/documentation-technical.template.mjs +6 -0
  28. package/templates/documentation-technical.tsx +292 -0
  29. package/templates/documentation.template.mjs +6 -0
  30. package/templates/documentation.tsx +347 -0
  31. package/templates/editor.template.mjs +6 -0
  32. package/templates/editor.tsx +959 -0
  33. package/templates/file-explorer.template.mjs +6 -0
  34. package/templates/file-explorer.tsx +577 -0
  35. package/templates/form-two-column.template.mjs +6 -0
  36. package/templates/form-two-column.tsx +314 -0
  37. package/templates/gallery-hero.template.mjs +6 -0
  38. package/templates/gallery-hero.tsx +287 -0
  39. package/templates/ide.template.mjs +6 -0
  40. package/templates/ide.tsx +533 -0
  41. package/templates/incident-console.template.mjs +6 -0
  42. package/templates/incident-console.tsx +584 -0
  43. package/templates/kanban-board.template.mjs +6 -0
  44. package/templates/kanban-board.tsx +746 -0
  45. package/templates/library.template.mjs +6 -0
  46. package/templates/library.tsx +489 -0
  47. package/templates/login-card.template.mjs +6 -0
  48. package/templates/login-card.tsx +200 -0
  49. package/templates/login-split.template.mjs +6 -0
  50. package/templates/login-split.tsx +318 -0
  51. package/templates/login-sso.template.mjs +6 -0
  52. package/templates/login-sso.tsx +277 -0
  53. package/templates/login.template.mjs +6 -0
  54. package/templates/login.tsx +98 -0
  55. package/templates/messaging-shell.template.mjs +6 -0
  56. package/templates/messaging-shell.tsx +675 -0
  57. package/templates/mixed-gallery.template.mjs +6 -0
  58. package/templates/mixed-gallery.tsx +190 -0
  59. package/templates/payment-form.template.mjs +6 -0
  60. package/templates/payment-form.tsx +990 -0
  61. package/templates/product-detail.template.mjs +6 -0
  62. package/templates/product-detail.tsx +366 -0
  63. package/templates/product-gallery.template.mjs +6 -0
  64. package/templates/product-gallery.tsx +173 -0
  65. package/templates/settings-dialog.template.mjs +6 -0
  66. package/templates/settings-dialog.tsx +920 -0
  67. package/templates/settings-sidebar.template.mjs +6 -0
  68. package/templates/settings-sidebar.tsx +952 -0
  69. package/templates/settings.template.mjs +6 -0
  70. package/templates/settings.tsx +235 -0
  71. package/templates/shell-nav.template.mjs +6 -0
  72. package/templates/shell-nav.tsx +306 -0
  73. package/templates/shell-side-nav.template.mjs +6 -0
  74. package/templates/shell-side-nav.tsx +238 -0
  75. package/templates/shell-top-nav.template.mjs +6 -0
  76. package/templates/shell-top-nav.tsx +243 -0
  77. package/templates/side-gallery.template.mjs +6 -0
  78. package/templates/side-gallery.tsx +179 -0
  79. package/templates/table-grouped.template.mjs +6 -0
  80. package/templates/table-grouped.tsx +1174 -0
  81. package/templates/table-page-chart.template.mjs +6 -0
  82. package/templates/table-page-chart.tsx +693 -0
  83. package/templates/table-page-heatmap-status.template.mjs +6 -0
  84. package/templates/table-page-heatmap-status.tsx +547 -0
  85. package/templates/table-page-shoe-store-heatmap.template.mjs +6 -0
  86. package/templates/table-page-shoe-store-heatmap.tsx +1038 -0
  87. package/templates/table-page.template.mjs +6 -0
  88. package/templates/table-page.tsx +445 -0
  89. package/templates/table.template.mjs +6 -0
  90. package/templates/table.tsx +84 -0
  91. package/templates/theme-showcase.template.mjs +6 -0
  92. package/templates/theme-showcase.tsx +1282 -0
  93. package/theme.css +4 -4
  94. package/tokens.css +4 -4
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'File Explorer',
4
+ description: 'Column-based file browser with preview',
5
+ category: 'tooling',
6
+ };
@@ -0,0 +1,577 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {useState, useMemo, type CSSProperties} from 'react';
4
+ import {Layout, LayoutContent} from '@astryxdesign/core/Layout';
5
+ import {Toolbar} from '@astryxdesign/core/Toolbar';
6
+ import {List, ListItem} from '@astryxdesign/core/List';
7
+ import {HStack, VStack} from '@astryxdesign/core/Layout';
8
+ import {useMediaQuery} from '@astryxdesign/core/hooks';
9
+ import {Text} from '@astryxdesign/core/Text';
10
+ import {Icon} from '@astryxdesign/core/Icon';
11
+ import {IconButton} from '@astryxdesign/core/IconButton';
12
+ import {Section} from '@astryxdesign/core/Section';
13
+ import {Avatar} from '@astryxdesign/core/Avatar';
14
+ import {MetadataList, MetadataListItem} from '@astryxdesign/core/MetadataList';
15
+ import {
16
+ SegmentedControl,
17
+ SegmentedControlItem,
18
+ } from '@astryxdesign/core/SegmentedControl';
19
+ import {
20
+ ChevronLeft,
21
+ ChevronRight,
22
+ Search,
23
+ Share2,
24
+ Tag,
25
+ Ellipsis,
26
+ Columns,
27
+ LayoutGrid,
28
+ Rows3,
29
+ Table,
30
+ SlidersHorizontal,
31
+ FileText,
32
+ Folder,
33
+ } from 'lucide-react';
34
+
35
+ interface FileSystemItem {
36
+ id: string;
37
+ name: string;
38
+ type: 'file' | 'folder';
39
+ children?: FileSystemItem[];
40
+ }
41
+
42
+ const FILESYSTEM: FileSystemItem[] = [
43
+ {
44
+ id: 'applications',
45
+ name: 'Applications',
46
+ type: 'folder',
47
+ children: [
48
+ {
49
+ id: 'chrome-apps',
50
+ name: 'Chrome Apps',
51
+ type: 'folder',
52
+ children: [
53
+ {id: 'component-lab', name: 'Palette Lab.app', type: 'file'},
54
+ {id: 'google-chat', name: 'Night Chat.app', type: 'file'},
55
+ {id: 'workchat', name: 'Coven.app', type: 'file'},
56
+ ],
57
+ },
58
+ {id: 'figma', name: 'Figma.app', type: 'file'},
59
+ {id: 'safari', name: 'Safari.app', type: 'file'},
60
+ {id: 'slack', name: 'Slack.app', type: 'file'},
61
+ {id: 'terminal', name: 'Terminal.app', type: 'file'},
62
+ {id: 'vscode', name: 'Visual Studio Code.app', type: 'file'},
63
+ {id: 'xcode', name: 'Xcode.app', type: 'file'},
64
+ ],
65
+ },
66
+ {id: 'debug-log', name: 'debug-nocturne.log', type: 'file'},
67
+ {
68
+ id: 'desktop',
69
+ name: 'Desktop',
70
+ type: 'folder',
71
+ children: [
72
+ {id: 'screenshot1', name: 'Screenshot 2026-10-31.png', type: 'file'},
73
+ {id: 'notes-txt', name: 'midnight-notes.txt', type: 'file'},
74
+ {
75
+ id: 'projects',
76
+ name: 'Projects',
77
+ type: 'folder',
78
+ children: [
79
+ {id: 'readme-proj', name: 'README.md', type: 'file'},
80
+ {
81
+ id: 'src-folder',
82
+ name: 'src',
83
+ type: 'folder',
84
+ children: [
85
+ {id: 'index-ts', name: 'index.ts', type: 'file'},
86
+ {id: 'app-tsx', name: 'App.tsx', type: 'file'},
87
+ ],
88
+ },
89
+ ],
90
+ },
91
+ ],
92
+ },
93
+ {
94
+ id: 'documents',
95
+ name: 'Documents',
96
+ type: 'folder',
97
+ children: [
98
+ {id: 'design-spec', name: 'dracula-spec.pdf', type: 'file'},
99
+ {id: 'resume', name: 'resume.docx', type: 'file'},
100
+ {
101
+ id: 'work',
102
+ name: 'Work',
103
+ type: 'folder',
104
+ children: [
105
+ {id: 'q1-report', name: 'Q1-report.xlsx', type: 'file'},
106
+ {id: 'presentation', name: 'team-presentation.pptx', type: 'file'},
107
+ ],
108
+ },
109
+ ],
110
+ },
111
+ {
112
+ id: 'downloads',
113
+ name: 'Downloads',
114
+ type: 'folder',
115
+ children: [
116
+ {id: 'archive', name: 'archive.zip', type: 'file'},
117
+ {id: 'installer', name: 'installer.dmg', type: 'file'},
118
+ {id: 'photo', name: 'castle-2026.jpg', type: 'file'},
119
+ ],
120
+ },
121
+ {id: 'login-screenshot', name: 'login-midnight-screenshot.png', type: 'file'},
122
+ {
123
+ id: 'movies',
124
+ name: 'Movies',
125
+ type: 'folder',
126
+ children: [{id: 'recording', name: 'screen-recording.mov', type: 'file'}],
127
+ },
128
+ {
129
+ id: 'music',
130
+ name: 'Music',
131
+ type: 'folder',
132
+ children: [{id: 'playlist', name: 'favorites.m3u', type: 'file'}],
133
+ },
134
+ {
135
+ id: 'node-modules',
136
+ name: 'node_modules',
137
+ type: 'folder',
138
+ children: [
139
+ {
140
+ id: 'react',
141
+ name: 'react',
142
+ type: 'folder',
143
+ children: [{id: 'react-index', name: 'index.js', type: 'file'}],
144
+ },
145
+ {
146
+ id: 'react-dom',
147
+ name: 'react-dom',
148
+ type: 'folder',
149
+ children: [{id: 'react-dom-index', name: 'index.js', type: 'file'}],
150
+ },
151
+ ],
152
+ },
153
+ {
154
+ id: 'pictures',
155
+ name: 'Pictures',
156
+ type: 'folder',
157
+ children: [
158
+ {
159
+ id: 'vacation',
160
+ name: 'transylvania-2026',
161
+ type: 'folder',
162
+ children: [
163
+ {id: 'img1', name: 'IMG_0001.jpg', type: 'file'},
164
+ {id: 'img2', name: 'IMG_0002.jpg', type: 'file'},
165
+ {id: 'img3', name: 'IMG_0003.jpg', type: 'file'},
166
+ ],
167
+ },
168
+ {
169
+ id: 'screenshots-folder',
170
+ name: 'Screenshots',
171
+ type: 'folder',
172
+ children: [
173
+ {id: 'ss1', name: 'Screen Shot 1.png', type: 'file'},
174
+ {id: 'ss2', name: 'Screen Shot 2.png', type: 'file'},
175
+ ],
176
+ },
177
+ ],
178
+ },
179
+ {
180
+ id: 'public',
181
+ name: 'Public',
182
+ type: 'folder',
183
+ children: [
184
+ {id: 'drop-box', name: 'Drop Box', type: 'folder', children: []},
185
+ ],
186
+ },
187
+ {
188
+ id: 'astryx',
189
+ name: 'astryx',
190
+ type: 'folder',
191
+ children: [
192
+ {id: 'astryx-readme', name: 'README.md', type: 'file'},
193
+ {id: 'astryx-pkg', name: 'package.json', type: 'file'},
194
+ {
195
+ id: 'astryx-packages',
196
+ name: 'packages',
197
+ type: 'folder',
198
+ children: [
199
+ {
200
+ id: 'astryx-core',
201
+ name: 'core',
202
+ type: 'folder',
203
+ children: [
204
+ {
205
+ id: 'core-src',
206
+ name: 'src',
207
+ type: 'folder',
208
+ children: [
209
+ {id: 'button-tsx', name: 'Button.tsx', type: 'file'},
210
+ {id: 'card-tsx', name: 'Card.tsx', type: 'file'},
211
+ {id: 'text-tsx', name: 'Text.tsx', type: 'file'},
212
+ ],
213
+ },
214
+ ],
215
+ },
216
+ {
217
+ id: 'astryx-cli',
218
+ name: 'cli',
219
+ type: 'folder',
220
+ children: [{id: 'cli-index', name: 'index.ts', type: 'file'}],
221
+ },
222
+ ],
223
+ },
224
+ {
225
+ id: 'astryx-apps',
226
+ name: 'apps',
227
+ type: 'folder',
228
+ children: [
229
+ {
230
+ id: 'storybook',
231
+ name: 'storybook',
232
+ type: 'folder',
233
+ children: [
234
+ {
235
+ id: 'sb-config',
236
+ name: '.storybook',
237
+ type: 'folder',
238
+ children: [],
239
+ },
240
+ ],
241
+ },
242
+ {
243
+ id: 'sandbox-app',
244
+ name: 'sandbox',
245
+ type: 'folder',
246
+ children: [
247
+ {
248
+ id: 'sandbox-src',
249
+ name: 'src',
250
+ type: 'folder',
251
+ children: [],
252
+ },
253
+ ],
254
+ },
255
+ ],
256
+ },
257
+ ],
258
+ },
259
+ ];
260
+
261
+ const page: CSSProperties = {height: '100dvh'};
262
+ const columnRow: CSSProperties = {overflowX: 'auto', overflowY: 'hidden'};
263
+ const scrollable: CSSProperties = {overflowY: 'auto'};
264
+ const fixedColumn: CSSProperties = {flexShrink: 0};
265
+ const detailColumn: CSSProperties = {
266
+ flexGrow: 1,
267
+ flexShrink: 0,
268
+ flexBasis: 320,
269
+ };
270
+ const controlsScroll: CSSProperties = {overflowX: 'auto'};
271
+
272
+ function findItem(items: FileSystemItem[], id: string): FileSystemItem | null {
273
+ for (const item of items) {
274
+ if (item.id === id) {
275
+ return item;
276
+ }
277
+ if (item.children) {
278
+ const found = findItem(item.children, id);
279
+ if (found) {
280
+ return found;
281
+ }
282
+ }
283
+ }
284
+ return null;
285
+ }
286
+
287
+ function getFileExtension(name: string): string {
288
+ const dot = name.lastIndexOf('.');
289
+ return dot > 0 ? name.substring(dot + 1).toUpperCase() : 'File';
290
+ }
291
+
292
+ export default function FileExplorer() {
293
+ const [selectedPath, setSelectedPath] = useState<string[]>([
294
+ 'applications',
295
+ 'chrome-apps',
296
+ 'component-lab',
297
+ ]);
298
+
299
+ const isNarrow = useMediaQuery('(max-width: 768px)');
300
+ const isPhone = useMediaQuery('(max-width: 767px)');
301
+
302
+ const columns = useMemo(() => {
303
+ const cols: {items: FileSystemItem[]; selectedId: string | null}[] = [];
304
+ cols.push({items: FILESYSTEM, selectedId: selectedPath[0] ?? null});
305
+ let currentItems: FileSystemItem[] = FILESYSTEM;
306
+ for (let i = 0; i < selectedPath.length; i++) {
307
+ const selected = currentItems.find(item => item.id === selectedPath[i]);
308
+ if (selected?.children && selected.children.length > 0) {
309
+ cols.push({
310
+ items: selected.children,
311
+ selectedId: selectedPath[i + 1] ?? null,
312
+ });
313
+ currentItems = selected.children;
314
+ } else {
315
+ break;
316
+ }
317
+ }
318
+ return cols;
319
+ }, [selectedPath]);
320
+
321
+ const columnOffset = isNarrow ? Math.max(columns.length - 1, 0) : 0;
322
+ const visibleColumns = isNarrow ? columns.slice(-1) : columns;
323
+
324
+ const currentFolderName = useMemo(() => {
325
+ if (selectedPath.length === 0) {
326
+ return 'Home';
327
+ }
328
+ const lastId = selectedPath[selectedPath.length - 1];
329
+ const item = findItem(FILESYSTEM, lastId);
330
+ if (item?.type === 'folder') {
331
+ return item.name;
332
+ }
333
+ if (selectedPath.length >= 2) {
334
+ const parent = findItem(
335
+ FILESYSTEM,
336
+ selectedPath[selectedPath.length - 2],
337
+ );
338
+ return parent?.name ?? 'Home';
339
+ }
340
+ return 'Home';
341
+ }, [selectedPath]);
342
+
343
+ const selectedFile = useMemo(() => {
344
+ if (selectedPath.length === 0) {
345
+ return null;
346
+ }
347
+ const lastId = selectedPath[selectedPath.length - 1];
348
+ const item = findItem(FILESYSTEM, lastId);
349
+ return item?.type === 'file' ? item : null;
350
+ }, [selectedPath]);
351
+
352
+ const handleSelect = (columnIndex: number, itemId: string) => {
353
+ setSelectedPath([...selectedPath.slice(0, columnIndex), itemId]);
354
+ };
355
+
356
+ const titleContent = (
357
+ <>
358
+ <IconButton
359
+ variant="ghost"
360
+ size="sm"
361
+ icon={<Icon icon={ChevronLeft} size="sm" />}
362
+ onClick={() => {
363
+ if (selectedPath.length > 0) {
364
+ setSelectedPath(selectedPath.slice(0, -1));
365
+ }
366
+ }}
367
+ isDisabled={selectedPath.length === 0}
368
+ label="Go back"
369
+ tooltip="Go back"
370
+ />
371
+ <IconButton
372
+ variant="ghost"
373
+ size="sm"
374
+ icon={<Icon icon={ChevronRight} size="sm" />}
375
+ isDisabled
376
+ label="Go forward"
377
+ tooltip="Go forward"
378
+ />
379
+ <Text type="label" maxLines={1}>
380
+ {currentFolderName}
381
+ </Text>
382
+ </>
383
+ );
384
+ const viewSwitcher = (
385
+ <SegmentedControl
386
+ value="column"
387
+ onChange={() => {}}
388
+ label="View mode">
389
+ <SegmentedControlItem
390
+ value="grid"
391
+ label="Grid"
392
+ icon={<Icon icon={LayoutGrid} size="sm" />}
393
+ isLabelHidden
394
+ />
395
+ <SegmentedControlItem
396
+ value="list"
397
+ label="List"
398
+ icon={<Icon icon={Rows3} size="sm" />}
399
+ isLabelHidden
400
+ />
401
+ <SegmentedControlItem
402
+ value="column"
403
+ label="Column"
404
+ icon={<Icon icon={Columns} size="sm" />}
405
+ isLabelHidden
406
+ />
407
+ <SegmentedControlItem
408
+ value="gallery"
409
+ label="Gallery"
410
+ icon={<Icon icon={Table} size="sm" />}
411
+ isLabelHidden
412
+ />
413
+ </SegmentedControl>
414
+ );
415
+ const fileActions = (
416
+ <>
417
+ <IconButton
418
+ variant="ghost"
419
+ size="sm"
420
+ icon={<Icon icon={SlidersHorizontal} size="sm" />}
421
+ label="Group"
422
+ tooltip="Group"
423
+ />
424
+ <IconButton
425
+ variant="ghost"
426
+ size="sm"
427
+ icon={<Icon icon={Share2} size="sm" />}
428
+ label="Share"
429
+ tooltip="Share"
430
+ />
431
+ <IconButton
432
+ variant="ghost"
433
+ size="sm"
434
+ icon={<Icon icon={Tag} size="sm" />}
435
+ label="Tags"
436
+ tooltip="Tags"
437
+ />
438
+ <IconButton
439
+ variant="ghost"
440
+ size="sm"
441
+ icon={<Icon icon={Ellipsis} size="sm" />}
442
+ label="More"
443
+ tooltip="More"
444
+ />
445
+ <IconButton
446
+ variant="ghost"
447
+ size="sm"
448
+ icon={<Icon icon={Search} size="sm" />}
449
+ label="Search"
450
+ tooltip="Search"
451
+ />
452
+ </>
453
+ );
454
+
455
+ return (
456
+ <Layout
457
+ style={page}
458
+ height="fill"
459
+ header={
460
+ <>
461
+ <Toolbar
462
+ label="File Explorer"
463
+ size="sm"
464
+ dividers={isPhone ? undefined : ['bottom']}
465
+ startContent={titleContent}
466
+ centerContent={isPhone ? undefined : viewSwitcher}
467
+ endContent={isPhone ? undefined : fileActions}
468
+ />
469
+ {isPhone && (
470
+ <Section
471
+ variant="transparent"
472
+ padding={2}
473
+ dividers={['bottom']}>
474
+ <HStack gap={2} vAlign="center" style={controlsScroll}>
475
+ {viewSwitcher}
476
+ {fileActions}
477
+ </HStack>
478
+ </Section>
479
+ )}
480
+ </>
481
+ }
482
+ content={
483
+ <LayoutContent padding={0} isScrollable={false}>
484
+ <HStack height="100%" style={columnRow}>
485
+ {visibleColumns.map((col, colIndex) => {
486
+ const trueIndex = columnOffset + colIndex;
487
+ const showDivider =
488
+ trueIndex < columns.length - 1 || selectedFile != null;
489
+ return (
490
+ <Section
491
+ key={trueIndex}
492
+ width={240}
493
+ padding={2}
494
+ variant="transparent"
495
+ dividers={showDivider ? ['end'] : undefined}
496
+ style={{...scrollable, ...fixedColumn}}>
497
+ <List density="compact" hasDividers={false}>
498
+ {col.items.map(item => {
499
+ const isSelected = col.selectedId === item.id;
500
+ const hasChildren =
501
+ item.type === 'folder' &&
502
+ item.children != null &&
503
+ item.children.length > 0;
504
+ return (
505
+ <ListItem
506
+ key={item.id}
507
+ label={
508
+ <Text type="body" maxLines={1}>
509
+ {item.name}
510
+ </Text>
511
+ }
512
+ startContent={
513
+ <Icon
514
+ icon={
515
+ item.type === 'folder'
516
+ ? Folder
517
+ : FileText
518
+ }
519
+ color={
520
+ item.type === 'folder' ? 'accent' : 'secondary'
521
+ }
522
+ size="sm"
523
+ />
524
+ }
525
+ endContent={
526
+ hasChildren ? (
527
+ <Icon
528
+ icon={ChevronRight}
529
+ size="xsm"
530
+ color="secondary"
531
+ />
532
+ ) : undefined
533
+ }
534
+ onClick={() => handleSelect(trueIndex, item.id)}
535
+ isSelected={isSelected}
536
+ />
537
+ );
538
+ })}
539
+ </List>
540
+ </Section>
541
+ );
542
+ })}
543
+ {selectedFile && (
544
+ <Section
545
+ padding={6}
546
+ variant="transparent"
547
+ style={{...scrollable, ...detailColumn}}>
548
+ <VStack gap={4} hAlign="center">
549
+ <Avatar name={selectedFile.name} size={96} />
550
+ <VStack gap={1} hAlign="center">
551
+ <Text type="label" maxLines={1}>
552
+ {selectedFile.name}
553
+ </Text>
554
+ <Text type="supporting">
555
+ {getFileExtension(selectedFile.name)} Document
556
+ </Text>
557
+ </VStack>
558
+ <MetadataList title="Information">
559
+ <MetadataListItem label="Created">
560
+ March 28, 2026 at 2:15 PM
561
+ </MetadataListItem>
562
+ <MetadataListItem label="Modified">
563
+ Yesterday, 10:27 PM
564
+ </MetadataListItem>
565
+ <MetadataListItem label="Kind">
566
+ {getFileExtension(selectedFile.name)} Document
567
+ </MetadataListItem>
568
+ </MetadataList>
569
+ </VStack>
570
+ </Section>
571
+ )}
572
+ </HStack>
573
+ </LayoutContent>
574
+ }
575
+ />
576
+ );
577
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ type: 'page',
3
+ name: 'Two-column Form',
4
+ description: 'Contact form with illustration and info strip',
5
+ category: 'forms',
6
+ };