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.
- package/BRAND.md +3 -3
- package/README.md +7 -4
- package/USAGE.md +49 -49
- package/astryx-dracula.d.ts +2 -2
- package/astryx-dracula.js +25 -9
- package/astryx-dracula.variants.d.ts +2 -2
- package/astryx.integration.mjs +1 -1
- package/icons.mjs +21 -17
- package/icons.tsx +69 -62
- package/package.json +125 -120
- package/shared/auth-chrome-config.ts +6 -6
- package/shared/auth-chrome.tsx +143 -144
- package/shared/auth-copy.ts +14 -14
- package/shared/chaptered-doc-config.ts +48 -51
- package/shared/chaptered-doc.tsx +267 -289
- package/shared/chart-hues.ts +25 -10
- package/shared/chart-labels.tsx +31 -30
- package/shared/chart-legend.tsx +55 -56
- package/shared/chart-panel-style.ts +3 -3
- package/shared/data-bar.tsx +97 -102
- package/shared/format-money.ts +1 -1
- package/shared/gallery-image.ts +6 -6
- package/shared/login-demo.ts +1 -1
- package/shared/metric-card.tsx +101 -105
- package/shared/metric-delta.tsx +28 -27
- package/shared/order-desk.tsx +205 -216
- package/shared/revenue-chart.tsx +145 -165
- package/shared/scene-castle.tsx +206 -291
- package/shared/scene-frame.tsx +33 -32
- package/shared/scene-hues.ts +17 -17
- package/shared/scene-tile.tsx +88 -92
- package/shared/settings-data.ts +119 -125
- package/shared/settings-rows.tsx +122 -159
- package/shared/sparkline.tsx +75 -88
- package/shared/sso-icons.tsx +10 -22
- package/shared/stars.tsx +16 -25
- package/templates/ai-chat-landing.template.mjs +5 -4
- package/templates/ai-chat-landing.tsx +473 -460
- package/templates/ai-chat.template.mjs +5 -4
- package/templates/ai-chat.tsx +543 -612
- package/templates/blank.template.mjs +5 -4
- package/templates/blank.tsx +18 -14
- package/templates/centered-hero.template.mjs +5 -4
- package/templates/centered-hero.tsx +55 -68
- package/templates/classic-gallery.template.mjs +5 -4
- package/templates/classic-gallery.tsx +97 -104
- package/templates/contact-form.template.mjs +5 -4
- package/templates/contact-form.tsx +247 -282
- package/templates/dashboard-portfolio.template.mjs +5 -4
- package/templates/dashboard-portfolio.tsx +717 -548
- package/templates/dashboard.template.mjs +5 -4
- package/templates/dashboard.tsx +596 -567
- package/templates/detail-page.template.mjs +5 -4
- package/templates/detail-page.tsx +546 -603
- package/templates/documentation-design.template.mjs +5 -4
- package/templates/documentation-design.tsx +231 -243
- package/templates/documentation-technical.template.mjs +5 -4
- package/templates/documentation-technical.tsx +236 -239
- package/templates/documentation.template.mjs +5 -4
- package/templates/documentation.tsx +267 -279
- package/templates/editor.template.mjs +5 -4
- package/templates/editor.tsx +844 -877
- package/templates/file-explorer.template.mjs +5 -4
- package/templates/file-explorer.tsx +566 -590
- package/templates/form-two-column.template.mjs +5 -4
- package/templates/form-two-column.tsx +195 -210
- package/templates/gallery-hero.template.mjs +5 -4
- package/templates/gallery-hero.tsx +93 -114
- package/templates/ide.template.mjs +5 -4
- package/templates/ide.tsx +407 -453
- package/templates/incident-console.template.mjs +5 -4
- package/templates/incident-console.tsx +593 -575
- package/templates/kanban-board.template.mjs +5 -4
- package/templates/kanban-board.tsx +665 -683
- package/templates/library.template.mjs +5 -4
- package/templates/library.tsx +409 -430
- package/templates/login-card.template.mjs +5 -4
- package/templates/login-card.tsx +63 -72
- package/templates/login-split.template.mjs +5 -4
- package/templates/login-split.tsx +192 -193
- package/templates/login-sso.template.mjs +5 -4
- package/templates/login-sso.tsx +257 -258
- package/templates/login.template.mjs +5 -4
- package/templates/login.tsx +53 -49
- package/templates/messaging-shell.template.mjs +5 -4
- package/templates/messaging-shell.tsx +556 -574
- package/templates/mixed-gallery.template.mjs +5 -4
- package/templates/mixed-gallery.tsx +60 -58
- package/templates/payment-form.template.mjs +5 -4
- package/templates/payment-form.tsx +882 -991
- package/templates/product-detail.template.mjs +5 -4
- package/templates/product-detail.tsx +261 -272
- package/templates/product-gallery.template.mjs +5 -4
- package/templates/product-gallery.tsx +126 -137
- package/templates/product-tour.template.mjs +5 -4
- package/templates/product-tour.tsx +741 -773
- package/templates/settings-dialog.template.mjs +5 -4
- package/templates/settings-dialog.tsx +688 -717
- package/templates/settings-sidebar.template.mjs +5 -4
- package/templates/settings-sidebar.tsx +762 -807
- package/templates/settings.template.mjs +5 -4
- package/templates/settings.tsx +289 -304
- package/templates/shell-nav.template.mjs +5 -4
- package/templates/shell-nav.tsx +212 -219
- package/templates/shell-side-nav.template.mjs +5 -4
- package/templates/shell-side-nav.tsx +135 -157
- package/templates/shell-top-nav.template.mjs +5 -4
- package/templates/shell-top-nav.tsx +182 -203
- package/templates/side-gallery.template.mjs +5 -4
- package/templates/side-gallery.tsx +85 -90
- package/templates/table-grouped.template.mjs +5 -4
- package/templates/table-grouped.tsx +1000 -1053
- package/templates/table-page-chart.template.mjs +5 -4
- package/templates/table-page-chart.tsx +400 -398
- package/templates/table-page-heatmap-status.template.mjs +5 -4
- package/templates/table-page-heatmap-status.tsx +483 -512
- package/templates/table-page-shoe-store-heatmap.template.mjs +5 -4
- package/templates/table-page-shoe-store-heatmap.tsx +748 -746
- package/templates/table-page.template.mjs +5 -4
- package/templates/table-page.tsx +396 -405
- package/templates/table.template.mjs +5 -4
- package/templates/table.tsx +92 -93
- package/templates/tech-report.template.mjs +5 -4
- package/templates/tech-report.tsx +1316 -1304
- package/templates/theme-showcase.template.mjs +5 -4
- package/templates/theme-showcase.tsx +871 -951
- package/theme.css +35 -77
- package/theme.css.d.ts +9 -0
- package/tokens.css +373 -380
package/templates/library.tsx
CHANGED
|
@@ -1,462 +1,441 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
// XLE (canonical structure
|
|
3
|
-
//
|
|
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 {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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 = [
|
|
50
|
+
const CATEGORIES = ["All", "Layout", "Forms", "Navigation", "Feedback", "Data"];
|
|
49
51
|
|
|
50
52
|
const ITEMS: LibraryItem[] = [
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
289
|
+
const [activeTab, setActiveTab] = useState("All");
|
|
290
|
+
const [search, setSearch] = useState("");
|
|
291
|
+
const [sortOrder, setSortOrder] = useState("A-Z");
|
|
308
292
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
}
|