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
@@ -1,462 +1,441 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
- // XLE (canonical structure, validated with `bunx astryx layout check`):
3
- // L > (LH[divider] > (V[g=1] > Hd"Library"[level=1] + Tx"30 components across 5 categories"[t=body])) + (LC[p=6] > V[g=8] > (V[g=4] > TI"Search the stacks…" + (H[a=center g=3] > (SI[fill] > TgG"Filter" > OFL > Tg"All"! + Tg"Layout" + Tg"Forms" + Tg"Navigation" + Tg"Feedback" + Tg"Data") + DM"Sort")) + D + (V[g=8] > (H[j=between a=center] > Hd"Layout"[level=2] + Tx"6 items"[t=body]) + (G[c={min:280} g=4] > (C[p=0] > AR + (S.transparent[p=4] > V[g=1] > Hd"Card"[level=3] + Tx"Description"[t=supporting]))*4)))
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] > (LH[divider p=6] > (V[g=1] > Hd"Library"[level=1] + Tx"30 components across 5 categories"[t=body])) + (LC[p=6] > V[g=8] > (V[g=4] > TI"Search" + (H[g=3 a=center] > (SI[fill] > V > TgG"Filter" > OFL > Tg"All"! + Tg"Layout" + Tg"Forms" + Tg"Navigation" + Tg"Feedback" + Tg"Data") + DD"A-Z")) + (V[g=8] > (D + (V[g=8] > (H[j=between a=center] > Hd"Layout"[level=2] + Tx"6 items"[t=body]) + (G[c={min:280} g=4] > (C[p=0] > AR[ratio=16/9] > Scene{SceneTile}) + (S.transparent[p=4] > V[g=1] > Hd"Card"[level=3] + Tx"Description"[t=body]))*6))*5))
4
6
 
5
7
  /**
6
8
  * Library — a browsable grid of design-system entries grouped by category. Frame: page header (title) | content column (search, filter row, s (Frame/responsive/container: see XLE header above.)
7
9
  */
8
10
 
9
- import {useState} from 'react';
10
- import {Layout, LayoutHeader, LayoutContent} from '@astryxdesign/core/Layout';
11
- import {Text, Heading} from '@astryxdesign/core/Text';
12
- import {Card} from '@astryxdesign/core/Card';
13
- import {ToggleButton, ToggleButtonGroup} from '@astryxdesign/core/ToggleButton';
14
- import {TextInput} from '@astryxdesign/core/TextInput';
15
- import {Divider} from '@astryxdesign/core/Divider';
16
- import {Section} from '@astryxdesign/core/Section';
17
- import {Grid} from '@astryxdesign/core/Grid';
18
- import {HStack, VStack, StackItem} from '@astryxdesign/core/Stack';
19
- import {DropdownMenu} from '@astryxdesign/core/DropdownMenu';
20
- import {OverflowList} from '@astryxdesign/core/OverflowList';
21
- import {AspectRatio} from '@astryxdesign/core/AspectRatio';
22
- import {Button} from '@astryxdesign/core/Button';
23
- import {EmptyState} from '@astryxdesign/core/EmptyState';
24
- import {Icon} from '@astryxdesign/core/Icon';
25
- import {Search} from 'lucide-react';
26
- import {SceneTile} from 'astryx-dracula/shared/scene-tile';
27
- import type {SceneHue} from 'astryx-dracula/shared/scene-hues';
11
+ import { AspectRatio } from "@astryxdesign/core/AspectRatio";
12
+ import { Button } from "@astryxdesign/core/Button";
13
+ import { Card } from "@astryxdesign/core/Card";
14
+ import { Divider } from "@astryxdesign/core/Divider";
15
+ import { DropdownMenu } from "@astryxdesign/core/DropdownMenu";
16
+ import { EmptyState } from "@astryxdesign/core/EmptyState";
17
+ import { Grid } from "@astryxdesign/core/Grid";
18
+ import { Icon } from "@astryxdesign/core/Icon";
19
+ import { Layout, LayoutContent, LayoutHeader } from "@astryxdesign/core/Layout";
20
+ import { OverflowList } from "@astryxdesign/core/OverflowList";
21
+ import { Section } from "@astryxdesign/core/Section";
22
+ import { HStack, StackItem, VStack } from "@astryxdesign/core/Stack";
23
+ import { Heading, Text } from "@astryxdesign/core/Text";
24
+ import { TextInput } from "@astryxdesign/core/TextInput";
25
+ import { ToggleButton, ToggleButtonGroup } from "@astryxdesign/core/ToggleButton";
26
+ import type { SceneHue } from "astryx-dracula/shared/scene-hues";
27
+ import { SceneTile } from "astryx-dracula/shared/scene-tile";
28
+ import { Search } from "lucide-react";
29
+ import { useState } from "react";
28
30
 
29
31
  interface LibraryItem {
30
- id: string;
31
- name: string;
32
- description: string;
33
- category: string;
34
- type: 'Component' | 'Pattern' | 'Utility';
32
+ id: string;
33
+ name: string;
34
+ description: string;
35
+ category: string;
36
+ type: "Component" | "Pattern" | "Utility";
35
37
  }
36
38
 
37
39
  // One Dracula accent per category shelf. Purple stays out of the map: it is
38
40
  // reserved for interactive elements, and these hues only tint the thumbnail
39
41
  // dot, so each shelf takes a non-purple accent instead.
40
42
  const CATEGORY_HUES: Record<string, SceneHue> = {
41
- Layout: 'var(--dracula-orange)',
42
- Forms: 'var(--dracula-cyan)',
43
- Navigation: 'var(--dracula-pink)',
44
- Feedback: 'var(--dracula-yellow)',
45
- Data: 'var(--dracula-green)',
43
+ Layout: "var(--dracula-orange)",
44
+ Forms: "var(--dracula-cyan)",
45
+ Navigation: "var(--dracula-pink)",
46
+ Feedback: "var(--dracula-yellow)",
47
+ Data: "var(--dracula-green)",
46
48
  };
47
49
 
48
- const CATEGORIES = ['All', 'Layout', 'Forms', 'Navigation', 'Feedback', 'Data'];
50
+ const CATEGORIES = ["All", "Layout", "Forms", "Navigation", "Feedback", "Data"];
49
51
 
50
52
  const ITEMS: LibraryItem[] = [
51
- {
52
- id: '1',
53
- name: 'Stack',
54
- description:
55
- 'Vertical and horizontal stack layouts with configurable gap and alignment.',
56
- category: 'Layout',
57
- type: 'Component',
58
- },
59
- {
60
- id: '2',
61
- name: 'Grid',
62
- description:
63
- 'Responsive grid container with auto-fit columns and gap control.',
64
- category: 'Layout',
65
- type: 'Component',
66
- },
67
- {
68
- id: '3',
69
- name: 'Card',
70
- description:
71
- 'Surface container with optional padding, border, and shadow variants.',
72
- category: 'Layout',
73
- type: 'Component',
74
- },
75
- {
76
- id: '4',
77
- name: 'Center',
78
- description: 'Centers its child both horizontally and vertically.',
79
- category: 'Layout',
80
- type: 'Utility',
81
- },
82
- {
83
- id: '5',
84
- name: 'Section',
85
- description: 'Semantic page section with optional heading and divider.',
86
- category: 'Layout',
87
- type: 'Pattern',
88
- },
89
- {
90
- id: '6',
91
- name: 'Collapsible',
92
- description: 'Expandable region with animated height transition.',
93
- category: 'Layout',
94
- type: 'Component',
95
- },
96
- {
97
- id: '7',
98
- name: 'TextInput',
99
- description:
100
- 'Single-line text field with label, placeholder, and validation states.',
101
- category: 'Forms',
102
- type: 'Component',
103
- },
104
- {
105
- id: '8',
106
- name: 'TextArea',
107
- description: 'Multi-line text field with auto-resize and character count.',
108
- category: 'Forms',
109
- type: 'Component',
110
- },
111
- {
112
- id: '9',
113
- name: 'CheckboxInput',
114
- description: 'Checkbox with label, indeterminate state, and group support.',
115
- category: 'Forms',
116
- type: 'Component',
117
- },
118
- {
119
- id: '10',
120
- name: 'RadioList',
121
- description: 'Group of radio buttons with accessible fieldset wrapper.',
122
- category: 'Forms',
123
- type: 'Component',
124
- },
125
- {
126
- id: '11',
127
- name: 'Switch',
128
- description: 'Toggle switch for binary on/off settings.',
129
- category: 'Forms',
130
- type: 'Component',
131
- },
132
- {
133
- id: '12',
134
- name: 'Selector',
135
- description:
136
- 'Dropdown or inline option selector with single and multi-select modes.',
137
- category: 'Forms',
138
- type: 'Component',
139
- },
140
- {
141
- id: '13',
142
- name: 'TabList',
143
- description:
144
- 'Horizontal tab navigation with underline indicator and keyboard support.',
145
- category: 'Navigation',
146
- type: 'Component',
147
- },
148
- {
149
- id: '14',
150
- name: 'TopNav',
151
- description: 'Application top bar with logo, nav links, and action slots.',
152
- category: 'Navigation',
153
- type: 'Pattern',
154
- },
155
- {
156
- id: '15',
157
- name: 'SideNav',
158
- description:
159
- 'Vertical sidebar navigation with collapsible groups and active states.',
160
- category: 'Navigation',
161
- type: 'Pattern',
162
- },
163
- {
164
- id: '16',
165
- name: 'Breadcrumbs',
166
- description: 'Path trail navigation with separator and truncation support.',
167
- category: 'Navigation',
168
- type: 'Component',
169
- },
170
- {
171
- id: '17',
172
- name: 'Pagination',
173
- description:
174
- 'Page navigation with prev/next controls and page count display.',
175
- category: 'Navigation',
176
- type: 'Component',
177
- },
178
- {
179
- id: '18',
180
- name: 'MobileNav',
181
- description:
182
- 'Bottom tab bar for mobile viewports with icon and label slots.',
183
- category: 'Navigation',
184
- type: 'Pattern',
185
- },
186
- {
187
- id: '19',
188
- name: 'Badge',
189
- description:
190
- 'Compact label for status, count, or category with semantic color variants.',
191
- category: 'Feedback',
192
- type: 'Component',
193
- },
194
- {
195
- id: '20',
196
- name: 'Banner',
197
- description:
198
- 'Full-width alert bar for info, success, warning, and error messages.',
199
- category: 'Feedback',
200
- type: 'Component',
201
- },
202
- {
203
- id: '21',
204
- name: 'Spinner',
205
- description: 'Animated loading indicator with size and color variants.',
206
- category: 'Feedback',
207
- type: 'Component',
208
- },
209
- {
210
- id: '22',
211
- name: 'ProgressBar',
212
- description: 'Horizontal bar indicating task completion percentage.',
213
- category: 'Feedback',
214
- type: 'Component',
215
- },
216
- {
217
- id: '23',
218
- name: 'StatusDot',
219
- description:
220
- 'Small dot indicator for presence, health, or pipeline status.',
221
- category: 'Feedback',
222
- type: 'Component',
223
- },
224
- {
225
- id: '24',
226
- name: 'Tooltip',
227
- description:
228
- 'Contextual label that appears on hover with configurable placement.',
229
- category: 'Feedback',
230
- type: 'Component',
231
- },
232
- {
233
- id: '25',
234
- name: 'Table',
235
- description:
236
- 'Feature-rich data table with sorting, selection, and column resizing.',
237
- category: 'Data',
238
- type: 'Component',
239
- },
240
- {
241
- id: '26',
242
- name: 'Avatar',
243
- description:
244
- 'User profile image with fallback initials and status dot support.',
245
- category: 'Data',
246
- type: 'Component',
247
- },
248
- {
249
- id: '27',
250
- name: 'Skeleton',
251
- description:
252
- 'Placeholder shimmer for loading states matching content shapes.',
253
- category: 'Data',
254
- type: 'Utility',
255
- },
256
- {
257
- id: '28',
258
- name: 'HoverCard',
259
- description: 'Rich popover that appears on hover with arbitrary content.',
260
- category: 'Data',
261
- type: 'Component',
262
- },
263
- {
264
- id: '29',
265
- name: 'PowerSearch',
266
- description:
267
- 'Command-palette style search with grouped results and keyboard nav.',
268
- category: 'Data',
269
- type: 'Pattern',
270
- },
271
- {
272
- id: '30',
273
- name: 'Typeahead',
274
- description:
275
- 'Autocomplete input with async suggestion loading and selection.',
276
- category: 'Data',
277
- type: 'Component',
278
- },
53
+ {
54
+ id: "1",
55
+ name: "Stack",
56
+ description: "Vertical and horizontal stack layouts with configurable gap and alignment.",
57
+ category: "Layout",
58
+ type: "Component",
59
+ },
60
+ {
61
+ id: "2",
62
+ name: "Grid",
63
+ description: "Responsive grid container with auto-fit columns and gap control.",
64
+ category: "Layout",
65
+ type: "Component",
66
+ },
67
+ {
68
+ id: "3",
69
+ name: "Card",
70
+ description: "Surface container with optional padding, border, and shadow variants.",
71
+ category: "Layout",
72
+ type: "Component",
73
+ },
74
+ {
75
+ id: "4",
76
+ name: "Center",
77
+ description: "Centers its child both horizontally and vertically.",
78
+ category: "Layout",
79
+ type: "Utility",
80
+ },
81
+ {
82
+ id: "5",
83
+ name: "Section",
84
+ description: "Semantic page section with optional heading and divider.",
85
+ category: "Layout",
86
+ type: "Pattern",
87
+ },
88
+ {
89
+ id: "6",
90
+ name: "Collapsible",
91
+ description: "Expandable region with animated height transition.",
92
+ category: "Layout",
93
+ type: "Component",
94
+ },
95
+ {
96
+ id: "7",
97
+ name: "TextInput",
98
+ description: "Single-line text field with label, placeholder, and validation states.",
99
+ category: "Forms",
100
+ type: "Component",
101
+ },
102
+ {
103
+ id: "8",
104
+ name: "TextArea",
105
+ description: "Multi-line text field with auto-resize and character count.",
106
+ category: "Forms",
107
+ type: "Component",
108
+ },
109
+ {
110
+ id: "9",
111
+ name: "CheckboxInput",
112
+ description: "Checkbox with label, indeterminate state, and group support.",
113
+ category: "Forms",
114
+ type: "Component",
115
+ },
116
+ {
117
+ id: "10",
118
+ name: "RadioList",
119
+ description: "Group of radio buttons with accessible fieldset wrapper.",
120
+ category: "Forms",
121
+ type: "Component",
122
+ },
123
+ {
124
+ id: "11",
125
+ name: "Switch",
126
+ description: "Toggle switch for binary on/off settings.",
127
+ category: "Forms",
128
+ type: "Component",
129
+ },
130
+ {
131
+ id: "12",
132
+ name: "Selector",
133
+ description: "Dropdown or inline option selector with single and multi-select modes.",
134
+ category: "Forms",
135
+ type: "Component",
136
+ },
137
+ {
138
+ id: "13",
139
+ name: "TabList",
140
+ description: "Horizontal tab navigation with underline indicator and keyboard support.",
141
+ category: "Navigation",
142
+ type: "Component",
143
+ },
144
+ {
145
+ id: "14",
146
+ name: "TopNav",
147
+ description: "Application top bar with logo, nav links, and action slots.",
148
+ category: "Navigation",
149
+ type: "Pattern",
150
+ },
151
+ {
152
+ id: "15",
153
+ name: "SideNav",
154
+ description: "Vertical sidebar navigation with collapsible groups and active states.",
155
+ category: "Navigation",
156
+ type: "Pattern",
157
+ },
158
+ {
159
+ id: "16",
160
+ name: "Breadcrumbs",
161
+ description: "Path trail navigation with separator and truncation support.",
162
+ category: "Navigation",
163
+ type: "Component",
164
+ },
165
+ {
166
+ id: "17",
167
+ name: "Pagination",
168
+ description: "Page navigation with prev/next controls and page count display.",
169
+ category: "Navigation",
170
+ type: "Component",
171
+ },
172
+ {
173
+ id: "18",
174
+ name: "MobileNav",
175
+ description: "Bottom tab bar for mobile viewports with icon and label slots.",
176
+ category: "Navigation",
177
+ type: "Pattern",
178
+ },
179
+ {
180
+ id: "19",
181
+ name: "Badge",
182
+ description: "Compact label for status, count, or category with semantic color variants.",
183
+ category: "Feedback",
184
+ type: "Component",
185
+ },
186
+ {
187
+ id: "20",
188
+ name: "Banner",
189
+ description: "Full-width alert bar for info, success, warning, and error messages.",
190
+ category: "Feedback",
191
+ type: "Component",
192
+ },
193
+ {
194
+ id: "21",
195
+ name: "Spinner",
196
+ description: "Animated loading indicator with size and color variants.",
197
+ category: "Feedback",
198
+ type: "Component",
199
+ },
200
+ {
201
+ id: "22",
202
+ name: "ProgressBar",
203
+ description: "Horizontal bar indicating task completion percentage.",
204
+ category: "Feedback",
205
+ type: "Component",
206
+ },
207
+ {
208
+ id: "23",
209
+ name: "StatusDot",
210
+ description: "Small dot indicator for presence, health, or pipeline status.",
211
+ category: "Feedback",
212
+ type: "Component",
213
+ },
214
+ {
215
+ id: "24",
216
+ name: "Tooltip",
217
+ description: "Contextual label that appears on hover with configurable placement.",
218
+ category: "Feedback",
219
+ type: "Component",
220
+ },
221
+ {
222
+ id: "25",
223
+ name: "Table",
224
+ description: "Feature-rich data table with sorting, selection, and column resizing.",
225
+ category: "Data",
226
+ type: "Component",
227
+ },
228
+ {
229
+ id: "26",
230
+ name: "Avatar",
231
+ description: "User profile image with fallback initials and status dot support.",
232
+ category: "Data",
233
+ type: "Component",
234
+ },
235
+ {
236
+ id: "27",
237
+ name: "Skeleton",
238
+ description: "Placeholder shimmer for loading states matching content shapes.",
239
+ category: "Data",
240
+ type: "Utility",
241
+ },
242
+ {
243
+ id: "28",
244
+ name: "HoverCard",
245
+ description: "Rich popover that appears on hover with arbitrary content.",
246
+ category: "Data",
247
+ type: "Component",
248
+ },
249
+ {
250
+ id: "29",
251
+ name: "PowerSearch",
252
+ description: "Command-palette style search with grouped results and keyboard nav.",
253
+ category: "Data",
254
+ type: "Pattern",
255
+ },
256
+ {
257
+ id: "30",
258
+ name: "Typeahead",
259
+ description: "Autocomplete input with async suggestion loading and selection.",
260
+ category: "Data",
261
+ type: "Component",
262
+ },
279
263
  ];
280
264
 
281
265
  // =============================================================================
282
266
  // Side Nav
283
267
  // =============================================================================
284
268
 
285
- function LibraryCard({item}: {item: LibraryItem}) {
286
- const hue = CATEGORY_HUES[item.category] ?? 'var(--dracula-comment)';
287
- return (
288
- <Card padding={0}>
289
- <AspectRatio ratio={16 / 9}>
290
- <SceneTile label={`${item.name} thumbnail`} hue={hue} size="lg" />
291
- </AspectRatio>
292
- <Section variant="transparent" padding={4}>
293
- <VStack gap={1}>
294
- <Heading level={3}>{item.name}</Heading>
295
- <Text type="body" color="secondary">
296
- {item.description}
297
- </Text>
298
- </VStack>
299
- </Section>
300
- </Card>
301
- );
269
+ function LibraryCard({ item }: { item: LibraryItem }) {
270
+ const hue = CATEGORY_HUES[item.category] ?? "var(--dracula-comment)";
271
+ return (
272
+ <Card padding={0}>
273
+ <AspectRatio ratio={16 / 9}>
274
+ <SceneTile label={`${item.name} thumbnail`} hue={hue} size="lg" />
275
+ </AspectRatio>
276
+ <Section variant="transparent" padding={4}>
277
+ <VStack gap={1}>
278
+ <Heading level={3}>{item.name}</Heading>
279
+ <Text type="body" color="secondary">
280
+ {item.description}
281
+ </Text>
282
+ </VStack>
283
+ </Section>
284
+ </Card>
285
+ );
302
286
  }
303
287
 
304
288
  export default function LibraryGrid() {
305
- const [activeTab, setActiveTab] = useState('All');
306
- const [search, setSearch] = useState('');
307
- const [sortOrder, setSortOrder] = useState('A-Z');
289
+ const [activeTab, setActiveTab] = useState("All");
290
+ const [search, setSearch] = useState("");
291
+ const [sortOrder, setSortOrder] = useState("A-Z");
308
292
 
309
- // ITEMS is module-static (only .filter over it and .sort over copies below),
310
- // so plain consts recompute cheaply per render with no memo needed.
311
- const query = search.trim().toLowerCase();
312
- const visible =
313
- activeTab === 'All' ? ITEMS : ITEMS.filter(i => i.category === activeTab);
314
- const searched = query
315
- ? visible.filter(
316
- i =>
317
- i.name.toLowerCase().includes(query) ||
318
- i.description.toLowerCase().includes(query),
319
- )
320
- : visible;
321
- const filtered = [...searched];
322
- if (sortOrder === 'A-Z') {
323
- filtered.sort((a, b) => a.name.localeCompare(b.name));
324
- } else if (sortOrder === 'Z-A') {
325
- filtered.sort((a, b) => b.name.localeCompare(a.name));
326
- } else if (sortOrder === 'Newest') {
327
- filtered.sort((a, b) => Number(b.id) - Number(a.id));
328
- }
293
+ // ITEMS is module-static (only .filter over it and .sort over copies below),
294
+ // so plain consts recompute cheaply per render with no memo needed.
295
+ const query = search.trim().toLowerCase();
296
+ const visible = activeTab === "All" ? ITEMS : ITEMS.filter((i) => i.category === activeTab);
297
+ const searched = query
298
+ ? visible.filter(
299
+ (i) => i.name.toLowerCase().includes(query) || i.description.toLowerCase().includes(query),
300
+ )
301
+ : visible;
302
+ const filtered = [...searched];
303
+ if (sortOrder === "A-Z") {
304
+ filtered.sort((a, b) => a.name.localeCompare(b.name));
305
+ } else if (sortOrder === "Z-A") {
306
+ filtered.sort((a, b) => b.name.localeCompare(a.name));
307
+ } else if (sortOrder === "Newest") {
308
+ filtered.sort((a, b) => Number(b.id) - Number(a.id));
309
+ }
329
310
 
330
- let groupedSections: Array<{category: string; items: LibraryItem[]}> | null =
331
- null;
332
- if (activeTab === 'All') {
333
- const order = CATEGORIES.filter(c => c !== 'All');
334
- const byCategory: Record<string, LibraryItem[]> = {};
335
- for (const item of filtered) {
336
- (byCategory[item.category] ??= []).push(item);
337
- }
338
- groupedSections = [];
339
- for (const cat of order) {
340
- const items = byCategory[cat];
341
- if (items) {
342
- groupedSections.push({category: cat, items});
343
- }
344
- }
345
- }
311
+ let groupedSections: Array<{ category: string; items: LibraryItem[] }> | null = null;
312
+ if (activeTab === "All") {
313
+ const order = CATEGORIES.filter((c) => c !== "All");
314
+ const byCategory: Record<string, LibraryItem[]> = {};
315
+ for (const item of filtered) {
316
+ const group = byCategory[item.category] ?? [];
317
+ group.push(item);
318
+ byCategory[item.category] = group;
319
+ }
320
+ groupedSections = [];
321
+ for (const cat of order) {
322
+ const items = byCategory[cat];
323
+ if (items) {
324
+ groupedSections.push({ category: cat, items });
325
+ }
326
+ }
327
+ }
346
328
 
347
- return (
348
- <Layout
349
- height="fill"
350
- header={
351
- <LayoutHeader hasDivider padding={6}>
352
- <VStack gap={1}>
353
- <Heading level={1}>Library</Heading>
354
- <Text type="body" color="secondary">
355
- 30 components across 5 categories
356
- </Text>
357
- </VStack>
358
- </LayoutHeader>
359
- }
360
- content={
361
- <LayoutContent padding={6}>
362
- <VStack gap={8}>
363
- <VStack gap={4}>
364
- <TextInput
365
- label="Search"
366
- isLabelHidden
367
- placeholder="Search the stacks…"
368
- value={search}
369
- onChange={setSearch}
370
- startIcon={Search}
371
- size="lg"
372
- />
373
- <HStack vAlign="center" gap={3}>
374
- <StackItem size="fill">
375
- <VStack>
376
- <ToggleButtonGroup
377
- label="Filter by category"
378
- value={activeTab}
379
- onChange={v => setActiveTab(v ?? 'All')}>
380
- <OverflowList
381
- gap={1}
382
- behavior="observeParent"
383
- overflowRenderer={overflowItems => (
384
- <DropdownMenu
385
- button={{
386
- label: `+${overflowItems.length}`,
387
- variant: 'ghost',
388
- size: 'lg',
389
- }}
390
- items={overflowItems.map(({index}) => ({
391
- label: CATEGORIES[index],
392
- onClick: () => setActiveTab(CATEGORIES[index]),
393
- }))}
394
- />
395
- )}>
396
- {CATEGORIES.map(cat => (
397
- <ToggleButton
398
- key={cat}
399
- label={cat}
400
- value={cat}
401
- size="lg"
402
- />
403
- ))}
404
- </OverflowList>
405
- </ToggleButtonGroup>
406
- </VStack>
407
- </StackItem>
408
- <DropdownMenu
409
- button={{label: sortOrder, size: 'lg'}}
410
- items={[
411
- {label: 'A-Z', onClick: () => setSortOrder('A-Z')},
412
- {label: 'Z-A', onClick: () => setSortOrder('Z-A')},
413
- {label: 'Newest', onClick: () => setSortOrder('Newest')},
414
- ]}
415
- />
416
- </HStack>
417
- </VStack>
329
+ return (
330
+ <Layout
331
+ height="fill"
332
+ header={
333
+ <LayoutHeader hasDivider padding={6}>
334
+ <VStack gap={1}>
335
+ <Heading level={1}>Library</Heading>
336
+ <Text type="body" color="secondary">
337
+ 30 components across 5 categories
338
+ </Text>
339
+ </VStack>
340
+ </LayoutHeader>
341
+ }
342
+ content={
343
+ <LayoutContent padding={6}>
344
+ <VStack gap={8}>
345
+ <VStack gap={4}>
346
+ <TextInput
347
+ label="Search"
348
+ isLabelHidden
349
+ placeholder="Search the stacks…"
350
+ value={search}
351
+ onChange={setSearch}
352
+ startIcon={Search}
353
+ size="lg"
354
+ />
355
+ <HStack vAlign="center" gap={3}>
356
+ <StackItem size="fill">
357
+ <VStack>
358
+ <ToggleButtonGroup
359
+ label="Filter by category"
360
+ value={activeTab}
361
+ onChange={(v) => setActiveTab(v ?? "All")}
362
+ >
363
+ <OverflowList
364
+ gap={1}
365
+ behavior="observeParent"
366
+ overflowRenderer={(overflowItems) => (
367
+ <DropdownMenu
368
+ button={{
369
+ label: `+${overflowItems.length}`,
370
+ variant: "ghost",
371
+ size: "lg",
372
+ }}
373
+ items={overflowItems.map(({ index }) => ({
374
+ label: CATEGORIES[index],
375
+ onClick: () => setActiveTab(CATEGORIES[index]),
376
+ }))}
377
+ />
378
+ )}
379
+ >
380
+ {CATEGORIES.map((cat) => (
381
+ <ToggleButton key={cat} label={cat} value={cat} size="lg" />
382
+ ))}
383
+ </OverflowList>
384
+ </ToggleButtonGroup>
385
+ </VStack>
386
+ </StackItem>
387
+ <DropdownMenu
388
+ button={{ label: sortOrder, size: "lg" }}
389
+ items={[
390
+ { label: "A-Z", onClick: () => setSortOrder("A-Z") },
391
+ { label: "Z-A", onClick: () => setSortOrder("Z-A") },
392
+ { label: "Newest", onClick: () => setSortOrder("Newest") },
393
+ ]}
394
+ />
395
+ </HStack>
396
+ </VStack>
418
397
 
419
- {filtered.length === 0 ? (
420
- <EmptyState
421
- icon={<Icon icon={Search} size="lg" color="secondary" />}
422
- title="Nothing stirs in the stacks"
423
- description="No entries match this search and filter. Clear them to browse the full library."
424
- actions={
425
- <Button
426
- label="Clear search & filters"
427
- variant="primary"
428
- onClick={() => {
429
- setSearch('');
430
- setActiveTab('All');
431
- }}
432
- />
433
- }
434
- />
435
- ) : (
436
- <VStack gap={8}>
437
- {(
438
- groupedSections ?? [{category: activeTab, items: filtered}]
439
- ).flatMap(section => [
440
- <Divider key={`d-${section.category}`} />,
441
- <VStack key={section.category} gap={8}>
442
- <HStack justify="between" vAlign="center">
443
- <Heading level={2}>{section.category}</Heading>
444
- <Text type="body" color="secondary" hasTabularNumbers>
445
- {section.items.length} {section.items.length === 1 ? 'item' : 'items'}
446
- </Text>
447
- </HStack>
448
- <Grid columns={{minWidth: 280}} gap={4}>
449
- {section.items.map(item => (
450
- <LibraryCard key={item.id} item={item} />
451
- ))}
452
- </Grid>
453
- </VStack>,
454
- ])}
455
- </VStack>
456
- )}
457
- </VStack>
458
- </LayoutContent>
459
- }
460
- />
461
- );
398
+ {filtered.length === 0 ? (
399
+ <EmptyState
400
+ icon={<Icon icon={Search} size="lg" color="secondary" />}
401
+ title="Nothing stirs in the stacks"
402
+ description="No entries match this search and filter. Clear them to browse the full library."
403
+ actions={
404
+ <Button
405
+ label="Clear search & filters"
406
+ variant="primary"
407
+ onClick={() => {
408
+ setSearch("");
409
+ setActiveTab("All");
410
+ }}
411
+ />
412
+ }
413
+ />
414
+ ) : (
415
+ <VStack gap={8}>
416
+ {(groupedSections ?? [{ category: activeTab, items: filtered }]).flatMap(
417
+ (section) => [
418
+ <Divider key={`d-${section.category}`} />,
419
+ <VStack key={section.category} gap={8}>
420
+ <HStack justify="between" vAlign="center">
421
+ <Heading level={2}>{section.category}</Heading>
422
+ <Text type="body" color="secondary" hasTabularNumbers>
423
+ {section.items.length} {section.items.length === 1 ? "item" : "items"}
424
+ </Text>
425
+ </HStack>
426
+ <Grid columns={{ minWidth: 280 }} gap={4}>
427
+ {section.items.map((item) => (
428
+ <LibraryCard key={item.id} item={item} />
429
+ ))}
430
+ </Grid>
431
+ </VStack>,
432
+ ],
433
+ )}
434
+ </VStack>
435
+ )}
436
+ </VStack>
437
+ </LayoutContent>
438
+ }
439
+ />
440
+ );
462
441
  }