astryx-dracula 0.2.0 → 0.3.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.
- package/BRAND.md +30 -18
- package/README.md +22 -11
- package/USAGE.md +29 -10
- package/astryx-dracula.js +19 -13
- package/astryx-dracula.variants.d.ts +15 -0
- package/package.json +59 -17
- package/shared/auth-chrome-config.ts +18 -0
- package/shared/auth-chrome.tsx +25 -0
- package/shared/auth-copy.ts +22 -0
- package/shared/chaptered-doc-config.ts +92 -0
- package/shared/chaptered-doc.tsx +332 -0
- package/shared/chart-hues.ts +27 -0
- package/shared/chart-labels.tsx +44 -0
- package/shared/chart-legend.tsx +100 -0
- package/shared/chart-panel-style.ts +13 -0
- package/shared/data-bar.tsx +160 -0
- package/shared/gallery-image.ts +20 -0
- package/shared/login-demo.ts +7 -0
- package/shared/metric-delta.tsx +36 -0
- package/shared/order-desk.tsx +273 -0
- package/shared/revenue-chart.tsx +174 -0
- package/shared/scene-castle.tsx +322 -0
- package/shared/scene-frame.tsx +88 -0
- package/shared/scene-hues.ts +49 -0
- package/shared/scene-tile.tsx +128 -0
- package/shared/settings-data.ts +125 -0
- package/shared/settings-rows.tsx +179 -0
- package/shared/sparkline.tsx +94 -0
- package/shared/sso-icons.tsx +30 -0
- package/templates/ai-chat-landing.tsx +67 -32
- package/templates/ai-chat.tsx +41 -20
- package/templates/blank.tsx +5 -2
- package/templates/centered-hero.tsx +15 -124
- package/templates/classic-gallery.tsx +21 -65
- package/templates/contact-form.tsx +62 -40
- package/templates/dashboard-portfolio.tsx +146 -227
- package/templates/dashboard.tsx +123 -121
- package/templates/detail-page.tsx +101 -95
- package/templates/documentation-design.template.mjs +1 -1
- package/templates/documentation-design.tsx +85 -96
- package/templates/documentation-technical.template.mjs +1 -1
- package/templates/documentation-technical.tsx +33 -21
- package/templates/documentation.template.mjs +1 -1
- package/templates/documentation.tsx +54 -74
- package/templates/editor.tsx +119 -89
- package/templates/file-explorer.tsx +133 -84
- package/templates/form-two-column.tsx +62 -124
- package/templates/gallery-hero.tsx +17 -171
- package/templates/ide.tsx +27 -21
- package/templates/incident-console.tsx +105 -69
- package/templates/kanban-board.tsx +100 -41
- package/templates/library.template.mjs +2 -2
- package/templates/library.tsx +38 -41
- package/templates/login-card.tsx +59 -86
- package/templates/login-split.tsx +88 -128
- package/templates/login-sso.tsx +107 -68
- package/templates/login.tsx +108 -52
- package/templates/messaging-shell.tsx +172 -143
- package/templates/mixed-gallery.tsx +31 -130
- package/templates/payment-form.tsx +369 -289
- package/templates/product-detail.tsx +40 -57
- package/templates/product-gallery.tsx +31 -40
- package/templates/product-tour.template.mjs +6 -0
- package/templates/product-tour.tsx +916 -0
- package/templates/settings-dialog.tsx +145 -286
- package/templates/settings-sidebar.tsx +179 -267
- package/templates/settings.tsx +42 -13
- package/templates/shell-nav.tsx +32 -23
- package/templates/shell-side-nav.tsx +30 -19
- package/templates/shell-top-nav.tsx +81 -43
- package/templates/side-gallery.tsx +39 -93
- package/templates/table-grouped.tsx +63 -33
- package/templates/table-page-chart.tsx +24 -297
- package/templates/table-page-heatmap-status.tsx +157 -91
- package/templates/table-page-shoe-store-heatmap.tsx +19 -288
- package/templates/table-page.tsx +24 -6
- package/templates/table.tsx +24 -7
- package/templates/tech-report.template.mjs +6 -0
- package/templates/tech-report.tsx +1486 -0
- package/templates/theme-showcase.template.mjs +1 -1
- package/templates/theme-showcase.tsx +313 -249
- package/theme.css +76 -14
- package/tokens.css +425 -30
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
// XLE (canonical structure, validated with `bunx astryx layout check`):
|
|
3
|
+
// TN"Nocturne" + (S[p6] > V[g10] > (Ctr > V[g4 a=center] > Hd"Little haunts"[level=1 type=display-2] + Tx"We believe"[t=body]) + (G[c={min:200,max:3} g4] > (C[p0] > AR + V[g2 a=center] > Bd + Hd"Product"[level=2] + Tx"Description"[t=body] + (H[g2] > NI + B"Add to cart"))*3)) + (V[g8] > (G[c={min:200} g4] > (GS[c=1] > C > Hd"Checkout"[level=2]) + (GS[c=2] > C > Hd"Night Owl AI"[level=2])) + (G[c={min:200} g4] > (GS[c=3] > C > T) + (GS[c=1] > C > Hd"Revenue"[level=2])))
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Theme Showcase — the storefront page each theme is previewed against, and the only template that is deliberately shell-less: it paints its o (Frame/responsive/container: see XLE header above.)
|
|
7
|
+
*/
|
|
2
8
|
|
|
3
9
|
import {type CSSProperties, type ReactNode} from 'react';
|
|
4
10
|
import {
|
|
@@ -33,6 +39,10 @@ import {Divider} from '@astryxdesign/core/Divider';
|
|
|
33
39
|
import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
|
|
34
40
|
import {Item} from '@astryxdesign/core/Item';
|
|
35
41
|
import {Table, proportional, pixel} from '@astryxdesign/core/Table';
|
|
42
|
+
import type {TableColumn} from '@astryxdesign/core/Table';
|
|
43
|
+
import {StatusDot} from '@astryxdesign/core/StatusDot';
|
|
44
|
+
import {ProductSwatch} from 'astryx-dracula/shared/revenue-chart';
|
|
45
|
+
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
36
46
|
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
37
47
|
import {Selector} from '@astryxdesign/core/Selector';
|
|
38
48
|
import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
|
|
@@ -51,6 +61,7 @@ import {
|
|
|
51
61
|
ChatMessageList,
|
|
52
62
|
ChatSystemMessage,
|
|
53
63
|
} from '@astryxdesign/core/Chat';
|
|
64
|
+
import type {SceneHue} from 'astryx-dracula/shared/scene-hues';
|
|
54
65
|
|
|
55
66
|
// Styles passed to Astryx components via their `style` prop. Astryx components
|
|
56
67
|
// forward the DOM `style` prop, so these work with no CSS compiler — in
|
|
@@ -98,6 +109,10 @@ const styles: Record<string, CSSProperties> = {
|
|
|
98
109
|
minWidth: 0,
|
|
99
110
|
maxWidth: 240,
|
|
100
111
|
},
|
|
112
|
+
filterRowFill: {
|
|
113
|
+
flex: 1,
|
|
114
|
+
minWidth: 0,
|
|
115
|
+
},
|
|
101
116
|
activityCard: {
|
|
102
117
|
backgroundColor: 'var(--color-background-surface)',
|
|
103
118
|
color: 'var(--color-text-primary)',
|
|
@@ -119,6 +134,9 @@ const styles: Record<string, CSSProperties> = {
|
|
|
119
134
|
activityCardStack: {
|
|
120
135
|
height: '100%',
|
|
121
136
|
},
|
|
137
|
+
inventoryItemText: {
|
|
138
|
+
minWidth: 0,
|
|
139
|
+
},
|
|
122
140
|
activityListFade: {
|
|
123
141
|
flex: 1,
|
|
124
142
|
minHeight: 0,
|
|
@@ -129,13 +147,11 @@ const styles: Record<string, CSSProperties> = {
|
|
|
129
147
|
'linear-gradient(to bottom, black calc(100% - 48px), transparent)',
|
|
130
148
|
marginInline: 'calc(var(--spacing-2) * -1)',
|
|
131
149
|
},
|
|
132
|
-
|
|
133
|
-
maxWidth: 960,
|
|
134
|
-
marginInline: 'auto',
|
|
135
|
-
minWidth: 0,
|
|
136
|
-
},
|
|
150
|
+
// The hero column: centered, narrower than the grid below it.
|
|
137
151
|
contentFluid: {
|
|
138
152
|
maxWidth: 880,
|
|
153
|
+
marginInline: 'auto',
|
|
154
|
+
minWidth: 0,
|
|
139
155
|
},
|
|
140
156
|
heroText: {
|
|
141
157
|
textAlign: 'center' as const,
|
|
@@ -149,6 +165,17 @@ const styles: Record<string, CSSProperties> = {
|
|
|
149
165
|
},
|
|
150
166
|
cardDescription: {
|
|
151
167
|
flex: 1,
|
|
168
|
+
textAlign: 'center' as const,
|
|
169
|
+
},
|
|
170
|
+
// Store surfaces: the shell is intentionally absent, so the page paints its
|
|
171
|
+
// own body and card-showcase bands.
|
|
172
|
+
storeRoot: {
|
|
173
|
+
minHeight: '100%',
|
|
174
|
+
backgroundColor: 'var(--color-background-body)',
|
|
175
|
+
},
|
|
176
|
+
showcaseBand: {
|
|
177
|
+
padding: 'var(--spacing-6)',
|
|
178
|
+
backgroundColor: 'var(--color-background-surface)',
|
|
152
179
|
},
|
|
153
180
|
quantityInput: {
|
|
154
181
|
// minWidth (not a hard width) so the field grows to fit the digit + the
|
|
@@ -173,7 +200,10 @@ const inlineStyles: Record<string, CSSProperties> = {
|
|
|
173
200
|
chatBody: {
|
|
174
201
|
flex: 1,
|
|
175
202
|
minHeight: 0,
|
|
176
|
-
|
|
203
|
+
// The message region is the scroll owner: the card is stretched to the
|
|
204
|
+
// grid row height, so longer conversations must scroll here rather than
|
|
205
|
+
// clip against the card.
|
|
206
|
+
overflowY: 'auto' as const,
|
|
177
207
|
},
|
|
178
208
|
chatSuggestions: {
|
|
179
209
|
paddingInline: 'var(--spacing-4)',
|
|
@@ -183,22 +213,21 @@ const inlineStyles: Record<string, CSSProperties> = {
|
|
|
183
213
|
paddingInline: 'var(--spacing-4)',
|
|
184
214
|
paddingBottom: 'var(--spacing-4)',
|
|
185
215
|
},
|
|
216
|
+
// Center supplies the 32px box and the centering; only the paint stays here.
|
|
217
|
+
// Surface, not muted: --color-text-secondary #9AA1BC is 3.57:1 on
|
|
218
|
+
// --color-background-muted #44475A, under the 4.5:1 text floor. The chip is
|
|
219
|
+
// aria-hidden decorative art so it clears the 3:1 non-text bar as-is, but the
|
|
220
|
+
// surface fill carries the same 4.60:1 pairing at no cost and cannot drop
|
|
221
|
+
// under the floor if anything smaller is ever put in the box.
|
|
186
222
|
activityIcon: {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
borderRadius: 'var(--radius-full)',
|
|
190
|
-
backgroundColor: 'var(--color-background-muted)',
|
|
223
|
+
borderRadius: 'var(--radius-element)', // square chip: no circles except dots/avatars
|
|
224
|
+
backgroundColor: 'var(--color-background-surface)',
|
|
191
225
|
color: 'var(--color-text-secondary)',
|
|
192
|
-
display: 'flex',
|
|
193
|
-
alignItems: 'center',
|
|
194
|
-
justifyContent: 'center',
|
|
195
226
|
flexShrink: 0,
|
|
196
227
|
},
|
|
197
228
|
cardBody: {
|
|
198
229
|
padding: 'var(--spacing-4)',
|
|
199
230
|
flex: 1,
|
|
200
|
-
display: 'flex',
|
|
201
|
-
flexDirection: 'column',
|
|
202
231
|
},
|
|
203
232
|
};
|
|
204
233
|
|
|
@@ -209,20 +238,23 @@ const artImage: CSSProperties = {
|
|
|
209
238
|
height: '100%',
|
|
210
239
|
display: 'block',
|
|
211
240
|
};
|
|
212
|
-
// Rounded inventory swatch. No radius prop where the swatch renders, so the
|
|
213
|
-
// SVG carries its own rounded corners (rx=4).
|
|
214
|
-
const thumbSwatch: CSSProperties = {
|
|
215
|
-
flexShrink: 0,
|
|
216
|
-
display: 'block',
|
|
217
|
-
};
|
|
218
241
|
|
|
219
|
-
// One Dracula accent per hero product slot.
|
|
242
|
+
// One Dracula accent per hero product slot. Purple stays off decorative art:
|
|
243
|
+
// it reads as interactive, and nothing here is tappable.
|
|
220
244
|
const PRODUCT_HUES = [
|
|
221
|
-
'var(--dracula-
|
|
245
|
+
'var(--dracula-green)',
|
|
222
246
|
'var(--dracula-cyan)',
|
|
223
247
|
'var(--dracula-yellow)',
|
|
224
248
|
];
|
|
225
249
|
|
|
250
|
+
// Grid recipes the store renders at every width above the phone layout. Kept
|
|
251
|
+
// at module scope: an object literal rebuilt per render is a new value prop
|
|
252
|
+
// every time.
|
|
253
|
+
const SHOWCASE_COLUMNS = {minWidth: 200, repeat: 'fit'} as const;
|
|
254
|
+
const PRODUCT_COLUMNS = {minWidth: 200, max: 3} as const;
|
|
255
|
+
const PAYMENT_COLUMNS = {minWidth: 70, max: 3} as const;
|
|
256
|
+
const EXPIRY_COLUMNS = {minWidth: 90, max: 2} as const;
|
|
257
|
+
|
|
226
258
|
/** Categorical badge variants usable for showcase product/inventory tags. */
|
|
227
259
|
export type ShowcaseBadgeVariant =
|
|
228
260
|
| 'blue'
|
|
@@ -247,7 +279,7 @@ const DEFAULT_PRODUCTS: ProductSpec[] = [
|
|
|
247
279
|
name: 'Moonphase Watch',
|
|
248
280
|
description: 'Clean lines and lume that carry through the longest night.',
|
|
249
281
|
badge: 'New',
|
|
250
|
-
badgeVariant: '
|
|
282
|
+
badgeVariant: 'cyan',
|
|
251
283
|
},
|
|
252
284
|
{
|
|
253
285
|
name: 'Night-Owl Headphones',
|
|
@@ -305,31 +337,6 @@ function ProductArt({hue, label}: {hue: string; label: string}) {
|
|
|
305
337
|
);
|
|
306
338
|
}
|
|
307
339
|
|
|
308
|
-
// Rounded inventory swatch: Dracula surface with a per-row accent ring.
|
|
309
|
-
function ThumbSwatch({hue, label}: {hue: string; label: string}) {
|
|
310
|
-
return (
|
|
311
|
-
<svg
|
|
312
|
-
viewBox="0 0 40 40"
|
|
313
|
-
width={40}
|
|
314
|
-
height={40}
|
|
315
|
-
style={thumbSwatch}
|
|
316
|
-
role="img"
|
|
317
|
-
aria-label={`${label} swatch`}>
|
|
318
|
-
<rect width={40} height={40} rx={5} fill="var(--dracula-bg-light)" />
|
|
319
|
-
<rect
|
|
320
|
-
x={1}
|
|
321
|
-
y={1}
|
|
322
|
-
width={38}
|
|
323
|
-
height={38}
|
|
324
|
-
rx={4}
|
|
325
|
-
fill="none"
|
|
326
|
-
stroke="var(--color-widget-content-border)"
|
|
327
|
-
/>
|
|
328
|
-
<circle cx={20} cy={20} r={8} fill="none" stroke={hue} strokeWidth={3} />
|
|
329
|
-
</svg>
|
|
330
|
-
);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
340
|
export interface ThemeShowcaseProps {
|
|
334
341
|
/** The three hero product cards. Defaults to the nocturne store products. */
|
|
335
342
|
products?: ProductSpec[];
|
|
@@ -351,20 +358,12 @@ export function ThemeShowcaseStore({
|
|
|
351
358
|
}: ThemeShowcaseProps = {}) {
|
|
352
359
|
const {isMobile} = useAppShellMobile();
|
|
353
360
|
return (
|
|
354
|
-
<
|
|
355
|
-
style={{
|
|
356
|
-
minHeight: '100%',
|
|
357
|
-
backgroundColor: 'var(--color-background-body)',
|
|
358
|
-
}}>
|
|
361
|
+
<VStack gap={0} style={styles.storeRoot}>
|
|
359
362
|
<StorePreview products={products} isMobile={isMobile} />
|
|
360
|
-
<
|
|
361
|
-
style={{
|
|
362
|
-
padding: 'var(--spacing-6)',
|
|
363
|
-
backgroundColor: 'var(--color-background-surface)',
|
|
364
|
-
}}>
|
|
363
|
+
<VStack gap={0} style={styles.showcaseBand}>
|
|
365
364
|
<CardShowcase inventory={inventory} isMobile={isMobile} />
|
|
366
|
-
</
|
|
367
|
-
</
|
|
365
|
+
</VStack>
|
|
366
|
+
</VStack>
|
|
368
367
|
);
|
|
369
368
|
}
|
|
370
369
|
|
|
@@ -375,7 +374,7 @@ function CardShowcase({
|
|
|
375
374
|
inventory: InventoryRow[];
|
|
376
375
|
isMobile: boolean;
|
|
377
376
|
}) {
|
|
378
|
-
const columns = isMobile ? 1 :
|
|
377
|
+
const columns = isMobile ? 1 : SHOWCASE_COLUMNS;
|
|
379
378
|
|
|
380
379
|
return (
|
|
381
380
|
<VStack gap={8}>
|
|
@@ -406,14 +405,18 @@ function StorePreview({
|
|
|
406
405
|
products: ProductSpec[];
|
|
407
406
|
isMobile: boolean;
|
|
408
407
|
}) {
|
|
408
|
+
// Shell-less preview: no AppShell above means the host context always
|
|
409
|
+
// reports desktop, so the nav reads the real viewport instead.
|
|
410
|
+
const isNarrowViewport = useMediaQuery('(max-width: 640px)');
|
|
411
|
+
const collapseNav = isMobile || isNarrowViewport;
|
|
409
412
|
return (
|
|
410
|
-
<
|
|
413
|
+
<VStack gap={0} data-theme-preview="true">
|
|
411
414
|
<VStack gap={0}>
|
|
412
415
|
<TopNav
|
|
413
416
|
label="Theme preview navigation"
|
|
414
417
|
heading={<TopNavHeading heading="Nocturne" />}
|
|
415
418
|
centerContent={
|
|
416
|
-
|
|
419
|
+
collapseNav ? undefined : (
|
|
417
420
|
<>
|
|
418
421
|
<TopNavItem label="Shop" href="#/templates/theme-showcase" isSelected />
|
|
419
422
|
<TopNavItem label="New In" href="#/templates/theme-showcase" />
|
|
@@ -423,47 +426,58 @@ function StorePreview({
|
|
|
423
426
|
)
|
|
424
427
|
}
|
|
425
428
|
endContent={
|
|
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
|
-
|
|
429
|
+
collapseNav ? (
|
|
430
|
+
<Button
|
|
431
|
+
label="Cart"
|
|
432
|
+
tooltip="Cart"
|
|
433
|
+
variant="ghost"
|
|
434
|
+
isIconOnly
|
|
435
|
+
icon={<ShoppingBag size={20} />}
|
|
436
|
+
href="#/templates/theme-showcase"
|
|
437
|
+
/>
|
|
438
|
+
) : (
|
|
439
|
+
<HStack gap={2} vAlign="center">
|
|
440
|
+
<HStack gap={0.5}>
|
|
441
|
+
<Button
|
|
442
|
+
label="Search"
|
|
443
|
+
tooltip="Search"
|
|
444
|
+
variant="ghost"
|
|
445
|
+
isIconOnly
|
|
446
|
+
icon={<Search size={20} />}
|
|
447
|
+
href="#/templates/theme-showcase"
|
|
448
|
+
/>
|
|
449
|
+
<Button
|
|
450
|
+
label="Account"
|
|
451
|
+
tooltip="Account"
|
|
452
|
+
variant="ghost"
|
|
453
|
+
isIconOnly
|
|
454
|
+
icon={<User size={20} />}
|
|
455
|
+
href="#/templates/theme-showcase"
|
|
456
|
+
/>
|
|
457
|
+
<Button
|
|
458
|
+
label="Cart"
|
|
459
|
+
tooltip="Cart"
|
|
460
|
+
variant="ghost"
|
|
461
|
+
isIconOnly
|
|
462
|
+
icon={<ShoppingBag size={20} />}
|
|
463
|
+
href="#/templates/theme-showcase"
|
|
464
|
+
/>
|
|
465
|
+
</HStack>
|
|
466
|
+
<Button label="Sign in" variant="primary" href="#/templates/theme-showcase" />
|
|
452
467
|
</HStack>
|
|
453
|
-
|
|
454
|
-
</HStack>
|
|
468
|
+
)
|
|
455
469
|
}
|
|
456
470
|
/>
|
|
457
471
|
|
|
458
472
|
<Section padding={6} variant="transparent">
|
|
459
|
-
<VStack gap={10} style={
|
|
473
|
+
<VStack gap={10} style={styles.contentFluid}>
|
|
460
474
|
<Center>
|
|
461
475
|
<VStack gap={4} hAlign="center" style={styles.heroText}>
|
|
462
|
-
<
|
|
476
|
+
<Heading level={1} type="display-2">
|
|
463
477
|
Little haunts,
|
|
464
478
|
<br />
|
|
465
479
|
everywhere you roam
|
|
466
|
-
</
|
|
480
|
+
</Heading>
|
|
467
481
|
<Text type="body" color="secondary">
|
|
468
482
|
We believe the smallest shadows are the ones that matter most.
|
|
469
483
|
Turn an ordinary evening into something worth remembering.
|
|
@@ -471,7 +485,7 @@ function StorePreview({
|
|
|
471
485
|
</VStack>
|
|
472
486
|
</Center>
|
|
473
487
|
|
|
474
|
-
<Grid columns={isMobile ? 1 :
|
|
488
|
+
<Grid columns={isMobile ? 1 : PRODUCT_COLUMNS} gap={4}>
|
|
475
489
|
{products.map((p, i) => (
|
|
476
490
|
<Card key={p.name} padding={0} height="100%">
|
|
477
491
|
<VStack gap={0} style={styles.cardStack}>
|
|
@@ -481,44 +495,42 @@ function StorePreview({
|
|
|
481
495
|
label={p.name}
|
|
482
496
|
/>
|
|
483
497
|
</AspectRatio>
|
|
484
|
-
<
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
<
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
<
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
</VStack>
|
|
521
|
-
</div>
|
|
498
|
+
<VStack
|
|
499
|
+
gap={2}
|
|
500
|
+
hAlign="center"
|
|
501
|
+
style={inlineStyles.cardBody}>
|
|
502
|
+
<HStack>
|
|
503
|
+
<Badge label={p.badge} variant={p.badgeVariant} />
|
|
504
|
+
</HStack>
|
|
505
|
+
<Heading level={2} style={styles.centerText}>
|
|
506
|
+
{p.name}
|
|
507
|
+
</Heading>
|
|
508
|
+
<Text
|
|
509
|
+
type="body"
|
|
510
|
+
color="secondary"
|
|
511
|
+
style={styles.cardDescription}>
|
|
512
|
+
{p.description}
|
|
513
|
+
</Text>
|
|
514
|
+
<HStack gap={2} vAlign="center" hAlign="center">
|
|
515
|
+
<NumberInput
|
|
516
|
+
label="Quantity"
|
|
517
|
+
isLabelHidden
|
|
518
|
+
value={1}
|
|
519
|
+
onChange={() => {}}
|
|
520
|
+
min={1}
|
|
521
|
+
max={99}
|
|
522
|
+
size="sm"
|
|
523
|
+
style={styles.quantityInput}
|
|
524
|
+
/>
|
|
525
|
+
<Button
|
|
526
|
+
label="Add to cart"
|
|
527
|
+
variant="secondary"
|
|
528
|
+
size="sm"
|
|
529
|
+
href="#/templates/theme-showcase"
|
|
530
|
+
style={styles.cartButton}
|
|
531
|
+
/>
|
|
532
|
+
</HStack>
|
|
533
|
+
</VStack>
|
|
522
534
|
</VStack>
|
|
523
535
|
</Card>
|
|
524
536
|
))}
|
|
@@ -526,13 +538,13 @@ function StorePreview({
|
|
|
526
538
|
</VStack>
|
|
527
539
|
</Section>
|
|
528
540
|
</VStack>
|
|
529
|
-
</
|
|
541
|
+
</VStack>
|
|
530
542
|
);
|
|
531
543
|
}
|
|
532
544
|
|
|
533
545
|
function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
534
546
|
return (
|
|
535
|
-
<Card padding={
|
|
547
|
+
<Card padding={4} style={styles.card}>
|
|
536
548
|
<VStack gap={4} style={styles.checkoutStack}>
|
|
537
549
|
<Heading level={2}>Checkout</Heading>
|
|
538
550
|
|
|
@@ -555,7 +567,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
555
567
|
label="Economy Shipping"
|
|
556
568
|
description="Delivered in 5–7 business days"
|
|
557
569
|
endContent={
|
|
558
|
-
<Text type="body" weight="
|
|
570
|
+
<Text type="body" weight="semibold" hasTabularNumbers>
|
|
559
571
|
$12.00
|
|
560
572
|
</Text>
|
|
561
573
|
}
|
|
@@ -565,7 +577,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
565
577
|
label="Standard Shipping"
|
|
566
578
|
description="Delivered in 3–5 business days"
|
|
567
579
|
endContent={
|
|
568
|
-
<Text type="body" weight="
|
|
580
|
+
<Text type="body" weight="semibold" hasTabularNumbers>
|
|
569
581
|
$16.00
|
|
570
582
|
</Text>
|
|
571
583
|
}
|
|
@@ -575,7 +587,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
575
587
|
label="Express Shipping"
|
|
576
588
|
description="Delivered in 1–2 business days"
|
|
577
589
|
endContent={
|
|
578
|
-
<Text type="body" weight="
|
|
590
|
+
<Text type="body" weight="semibold" hasTabularNumbers>
|
|
579
591
|
$24.00
|
|
580
592
|
</Text>
|
|
581
593
|
}
|
|
@@ -583,10 +595,10 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
583
595
|
</RadioList>
|
|
584
596
|
|
|
585
597
|
<VStack gap={2} style={styles.checkoutStack}>
|
|
586
|
-
<Text type="label" weight="
|
|
598
|
+
<Text type="label" weight="semibold">
|
|
587
599
|
Payment method
|
|
588
600
|
</Text>
|
|
589
|
-
<Grid columns={isMobile ? 1 :
|
|
601
|
+
<Grid columns={isMobile ? 1 : PAYMENT_COLUMNS} gap={2}>
|
|
590
602
|
<SelectableCard
|
|
591
603
|
label="Pay with card"
|
|
592
604
|
isSelected={true}
|
|
@@ -597,7 +609,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
597
609
|
hAlign="center"
|
|
598
610
|
style={styles.paymentCardContent}>
|
|
599
611
|
<CreditCard size={20} />
|
|
600
|
-
<Text type="supporting" weight="
|
|
612
|
+
<Text type="supporting" weight="semibold">
|
|
601
613
|
Card
|
|
602
614
|
</Text>
|
|
603
615
|
</VStack>
|
|
@@ -612,7 +624,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
612
624
|
hAlign="center"
|
|
613
625
|
style={styles.paymentCardContent}>
|
|
614
626
|
<Smartphone size={20} />
|
|
615
|
-
<Text type="supporting" weight="
|
|
627
|
+
<Text type="supporting" weight="semibold">
|
|
616
628
|
Apple Pay
|
|
617
629
|
</Text>
|
|
618
630
|
</VStack>
|
|
@@ -627,7 +639,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
627
639
|
hAlign="center"
|
|
628
640
|
style={styles.paymentCardContent}>
|
|
629
641
|
<Wallet size={20} />
|
|
630
|
-
<Text type="supporting" weight="
|
|
642
|
+
<Text type="supporting" weight="semibold">
|
|
631
643
|
Google Pay
|
|
632
644
|
</Text>
|
|
633
645
|
</VStack>
|
|
@@ -644,7 +656,7 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
644
656
|
size="lg"
|
|
645
657
|
/>
|
|
646
658
|
|
|
647
|
-
<Grid columns={isMobile ? 1 :
|
|
659
|
+
<Grid columns={isMobile ? 1 : EXPIRY_COLUMNS} gap={2}>
|
|
648
660
|
<TextInput
|
|
649
661
|
label="Expiry"
|
|
650
662
|
placeholder="MM / YY"
|
|
@@ -733,26 +745,32 @@ function ChatCard() {
|
|
|
733
745
|
|
|
734
746
|
<Divider variant="subtle" />
|
|
735
747
|
|
|
736
|
-
<
|
|
748
|
+
<VStack
|
|
749
|
+
gap={0}
|
|
750
|
+
style={inlineStyles.chatBody}
|
|
751
|
+
isScrollable={false}
|
|
752
|
+
tabIndex={0}
|
|
753
|
+
role="region"
|
|
754
|
+
aria-label="Night Owl chat messages">
|
|
737
755
|
<ChatMessageList>
|
|
738
756
|
<ChatSystemMessage>Today</ChatSystemMessage>
|
|
739
757
|
|
|
740
758
|
<ChatMessage sender="user">
|
|
741
759
|
<ChatMessageBubble variant="filled">
|
|
742
|
-
Where
|
|
760
|
+
Where's my order?
|
|
743
761
|
</ChatMessageBubble>
|
|
744
762
|
</ChatMessage>
|
|
745
763
|
|
|
746
764
|
<ChatMessage sender="assistant">
|
|
747
765
|
<VStack gap={3}>
|
|
748
766
|
<Text type="body">
|
|
749
|
-
Your order #1043
|
|
750
|
-
shipped this morning from the
|
|
751
|
-
in transit with
|
|
767
|
+
Your order #1043 (the Moonphase Watch and Belfry Throw)
|
|
768
|
+
shipped this morning from the courier-raven roost and is currently
|
|
769
|
+
in transit with courier-raven. It's on track to arrive at your address by
|
|
752
770
|
end of day tomorrow.
|
|
753
771
|
</Text>
|
|
754
772
|
<Text type="body">
|
|
755
|
-
Let me know if you
|
|
773
|
+
Let me know if you'd like to reschedule the delivery, redirect
|
|
756
774
|
it to a pickup point, or start a return once it arrives.
|
|
757
775
|
</Text>
|
|
758
776
|
</VStack>
|
|
@@ -766,23 +784,23 @@ function ChatCard() {
|
|
|
766
784
|
|
|
767
785
|
<ChatMessage sender="assistant">
|
|
768
786
|
<VStack gap={3}>
|
|
769
|
-
<Text type="body">Here
|
|
787
|
+
<Text type="body">Here's everything I have on order #1043:</Text>
|
|
770
788
|
<Card padding={3}>
|
|
771
789
|
<VStack gap={1}>
|
|
772
790
|
<Item
|
|
773
791
|
label="Items"
|
|
774
792
|
description="Moonphase Watch · Belfry Throw"
|
|
775
793
|
endContent={
|
|
776
|
-
<Text type="body" weight="
|
|
794
|
+
<Text type="body" weight="semibold" hasTabularNumbers>
|
|
777
795
|
$248
|
|
778
796
|
</Text>
|
|
779
797
|
}
|
|
780
798
|
/>
|
|
781
799
|
<Item
|
|
782
800
|
label="Shipping"
|
|
783
|
-
description="
|
|
801
|
+
description="Courier-raven ground"
|
|
784
802
|
endContent={
|
|
785
|
-
<Text type="body" weight="
|
|
803
|
+
<Text type="body" weight="semibold" hasTabularNumbers>
|
|
786
804
|
$12
|
|
787
805
|
</Text>
|
|
788
806
|
}
|
|
@@ -790,21 +808,28 @@ function ChatCard() {
|
|
|
790
808
|
<Item
|
|
791
809
|
label="Estimated arrival"
|
|
792
810
|
description="Tomorrow by 8pm"
|
|
793
|
-
endContent={
|
|
811
|
+
endContent={
|
|
812
|
+
<HStack gap={1} vAlign="center">
|
|
813
|
+
<StatusDot variant="success" label="On time" />
|
|
814
|
+
<Text type="supporting" color="secondary">
|
|
815
|
+
On time
|
|
816
|
+
</Text>
|
|
817
|
+
</HStack>
|
|
818
|
+
}
|
|
794
819
|
/>
|
|
795
820
|
<Item
|
|
796
821
|
label="Tracking"
|
|
797
|
-
description="
|
|
798
|
-
endContent={<Link href="#/templates/theme-showcase">Track
|
|
822
|
+
description="RVN 1Z 999 AA1 0123 4567 84"
|
|
823
|
+
endContent={<Link href="#/templates/theme-showcase">Track</Link>}
|
|
799
824
|
/>
|
|
800
825
|
</VStack>
|
|
801
826
|
</Card>
|
|
802
827
|
</VStack>
|
|
803
828
|
</ChatMessage>
|
|
804
829
|
</ChatMessageList>
|
|
805
|
-
</
|
|
830
|
+
</VStack>
|
|
806
831
|
|
|
807
|
-
<
|
|
832
|
+
<VStack gap={0} style={inlineStyles.chatSuggestions}>
|
|
808
833
|
<HStack gap={1} hAlign="center" wrap="wrap">
|
|
809
834
|
{SUGGESTED_QUESTIONS.map(question => (
|
|
810
835
|
<Button
|
|
@@ -815,14 +840,14 @@ function ChatCard() {
|
|
|
815
840
|
/>
|
|
816
841
|
))}
|
|
817
842
|
</HStack>
|
|
818
|
-
</
|
|
843
|
+
</VStack>
|
|
819
844
|
|
|
820
|
-
<
|
|
845
|
+
<VStack gap={0} style={inlineStyles.chatComposer}>
|
|
821
846
|
<ChatComposer
|
|
822
847
|
value=""
|
|
823
848
|
onChange={() => {}}
|
|
824
849
|
onSubmit={() => {}}
|
|
825
|
-
placeholder="Ask Night Owl
|
|
850
|
+
placeholder="Ask Night Owl..."
|
|
826
851
|
footerActions={
|
|
827
852
|
<Button
|
|
828
853
|
variant="ghost"
|
|
@@ -844,7 +869,7 @@ function ChatCard() {
|
|
|
844
869
|
/>
|
|
845
870
|
}
|
|
846
871
|
/>
|
|
847
|
-
</
|
|
872
|
+
</VStack>
|
|
848
873
|
</Card>
|
|
849
874
|
);
|
|
850
875
|
}
|
|
@@ -908,13 +933,13 @@ function formatAmount(amount: number): string {
|
|
|
908
933
|
|
|
909
934
|
function LatestActivityCard({isMobile}: {isMobile: boolean}) {
|
|
910
935
|
return (
|
|
911
|
-
<Card padding={
|
|
936
|
+
<Card padding={4} style={styles.activityCard}>
|
|
912
937
|
<VStack gap={4} style={styles.activityCardStack}>
|
|
913
938
|
<Heading level={2}>Revenue</Heading>
|
|
914
939
|
|
|
915
940
|
<Grid columns={isMobile ? 1 : 2} gap={3}>
|
|
916
941
|
<VStack gap={0}>
|
|
917
|
-
<Text type="display-3" weight="
|
|
942
|
+
<Text type="display-3" weight="semibold" hasTabularNumbers>
|
|
918
943
|
18K
|
|
919
944
|
</Text>
|
|
920
945
|
<Text type="supporting" color="secondary">
|
|
@@ -922,7 +947,7 @@ function LatestActivityCard({isMobile}: {isMobile: boolean}) {
|
|
|
922
947
|
</Text>
|
|
923
948
|
</VStack>
|
|
924
949
|
<VStack gap={0}>
|
|
925
|
-
<Text type="display-3" weight="
|
|
950
|
+
<Text type="display-3" weight="semibold" hasTabularNumbers>
|
|
926
951
|
+12%
|
|
927
952
|
</Text>
|
|
928
953
|
<Text type="supporting" color="secondary">
|
|
@@ -939,20 +964,38 @@ function LatestActivityCard({isMobile}: {isMobile: boolean}) {
|
|
|
939
964
|
</HStack>
|
|
940
965
|
|
|
941
966
|
<VStack gap={1} style={styles.activityListFade}>
|
|
967
|
+
{/* The sign travels in the string (formatAmount prefixes U+2212), so
|
|
968
|
+
direction is never colour-alone and 1.4.1 holds without a tint.
|
|
969
|
+
A negative text role is not available here, and the reason is
|
|
970
|
+
worth recording rather than working around:
|
|
971
|
+
- TextColorMap is six keys (primary, secondary, disabled,
|
|
972
|
+
placeholder, accent, inherit). No negative.
|
|
973
|
+
- `accent` is Purple in this theme, and principle 2 makes
|
|
974
|
+
purple tappable-only, so a refund would read as a link.
|
|
975
|
+
- --color-negative #FF5555 measures 3.75:1 on this card
|
|
976
|
+
#343746, under the 4.5:1 floor, so a style-prop tint would
|
|
977
|
+
trade a weak signal for a real contrast failure.
|
|
978
|
+
secondary therefore stays and the minus sign is the signal. A
|
|
979
|
+
kit-wide negative text role needs a new token AND a colour that
|
|
980
|
+
clears 4.5:1 on a card — a human call, not a call-site one. */}
|
|
942
981
|
{ACTIVITY.map(item => (
|
|
943
982
|
<Item
|
|
944
983
|
key={item.id}
|
|
945
984
|
startContent={
|
|
946
|
-
<
|
|
985
|
+
<Center
|
|
986
|
+
width={32}
|
|
987
|
+
height={32}
|
|
988
|
+
style={inlineStyles.activityIcon}
|
|
989
|
+
aria-hidden="true">
|
|
947
990
|
{item.icon}
|
|
948
|
-
</
|
|
991
|
+
</Center>
|
|
949
992
|
}
|
|
950
993
|
label={item.label}
|
|
951
994
|
description={item.detail}
|
|
952
995
|
endContent={
|
|
953
996
|
<Text
|
|
954
997
|
type="body"
|
|
955
|
-
weight="
|
|
998
|
+
weight="semibold"
|
|
956
999
|
hasTabularNumbers
|
|
957
1000
|
color={item.amount < 0 ? 'secondary' : 'primary'}>
|
|
958
1001
|
{formatAmount(item.amount)}
|
|
@@ -976,7 +1019,7 @@ export interface InventoryRow extends Record<string, unknown> {
|
|
|
976
1019
|
available: number;
|
|
977
1020
|
location: string;
|
|
978
1021
|
tags: TagSpec[];
|
|
979
|
-
hue:
|
|
1022
|
+
hue: SceneHue;
|
|
980
1023
|
selected: boolean;
|
|
981
1024
|
}
|
|
982
1025
|
|
|
@@ -986,9 +1029,9 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
986
1029
|
name: 'Moonphase Watch',
|
|
987
1030
|
meta: 'Steel case, moonphase dial',
|
|
988
1031
|
available: 42,
|
|
989
|
-
location: '
|
|
990
|
-
tags: [{label: 'New', variant: '
|
|
991
|
-
hue: 'var(--dracula-
|
|
1032
|
+
location: 'Roost 3',
|
|
1033
|
+
tags: [{label: 'New', variant: 'cyan'}],
|
|
1034
|
+
hue: 'var(--dracula-comment)',
|
|
992
1035
|
selected: false,
|
|
993
1036
|
},
|
|
994
1037
|
{
|
|
@@ -996,7 +1039,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
996
1039
|
name: 'Night-Owl Headphones',
|
|
997
1040
|
meta: 'ANC, 30hr battery',
|
|
998
1041
|
available: 128,
|
|
999
|
-
location: '
|
|
1042
|
+
location: 'Roost 1',
|
|
1000
1043
|
tags: [{label: 'Popular', variant: 'green'}],
|
|
1001
1044
|
hue: 'var(--dracula-cyan)',
|
|
1002
1045
|
selected: true,
|
|
@@ -1006,7 +1049,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
1006
1049
|
name: 'Coven Canvas Backpack',
|
|
1007
1050
|
meta: 'Waxed canvas, 25L',
|
|
1008
1051
|
available: 63,
|
|
1009
|
-
location: '
|
|
1052
|
+
location: 'Roost 2',
|
|
1010
1053
|
tags: [{label: 'Limited', variant: 'yellow'}],
|
|
1011
1054
|
hue: 'var(--dracula-yellow)',
|
|
1012
1055
|
selected: false,
|
|
@@ -1016,7 +1059,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
1016
1059
|
name: 'Night Market Wallet',
|
|
1017
1060
|
meta: 'Full-grain, RFID blocking',
|
|
1018
1061
|
available: 15,
|
|
1019
|
-
location: '
|
|
1062
|
+
location: 'Roost 4',
|
|
1020
1063
|
tags: [{label: 'Leather', variant: 'yellow'}],
|
|
1021
1064
|
hue: 'var(--dracula-orange)',
|
|
1022
1065
|
selected: true,
|
|
@@ -1026,8 +1069,8 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
1026
1069
|
name: 'Midnight Tumbler',
|
|
1027
1070
|
meta: 'Vacuum insulated, 16oz',
|
|
1028
1071
|
available: 87,
|
|
1029
|
-
location: '
|
|
1030
|
-
tags: [{label: 'Drinkware', variant: '
|
|
1072
|
+
location: 'Roost 5',
|
|
1073
|
+
tags: [{label: 'Drinkware', variant: 'yellow'}],
|
|
1031
1074
|
hue: 'var(--dracula-pink)',
|
|
1032
1075
|
selected: false,
|
|
1033
1076
|
},
|
|
@@ -1036,8 +1079,8 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
1036
1079
|
name: 'Belfry Throw',
|
|
1037
1080
|
meta: 'Heavyweight, oat',
|
|
1038
1081
|
available: 24,
|
|
1039
|
-
location: '
|
|
1040
|
-
tags: [{label: 'Home', variant: '
|
|
1082
|
+
location: 'Roost 6',
|
|
1083
|
+
tags: [{label: 'Home', variant: 'yellow'}],
|
|
1041
1084
|
hue: 'var(--dracula-green)',
|
|
1042
1085
|
selected: true,
|
|
1043
1086
|
},
|
|
@@ -1059,9 +1102,9 @@ function SelectCell({row}: {row: InventoryRow}) {
|
|
|
1059
1102
|
function ItemCell({row}: {row: InventoryRow}) {
|
|
1060
1103
|
return (
|
|
1061
1104
|
<HStack gap={3} vAlign="center">
|
|
1062
|
-
<
|
|
1063
|
-
<VStack gap={0} style={
|
|
1064
|
-
<Text type="body" weight="
|
|
1105
|
+
<ProductSwatch accent={row.hue} label={row.name} />
|
|
1106
|
+
<VStack gap={0} style={styles.inventoryItemText}>
|
|
1107
|
+
<Text type="body" weight="semibold">
|
|
1065
1108
|
{row.name}
|
|
1066
1109
|
</Text>
|
|
1067
1110
|
<Text type="supporting" color="secondary">
|
|
@@ -1098,7 +1141,79 @@ function ActionsCell() {
|
|
|
1098
1141
|
);
|
|
1099
1142
|
}
|
|
1100
1143
|
|
|
1144
|
+
// The full column set needs 64+80+100+100+80+64 = 488px of grid and the card
|
|
1145
|
+
// insets cost 2 × --spacing-6 on top, so it stops fitting below ~600px. A 375px
|
|
1146
|
+
// phone leaves ~280px inside the card: the phone layout drops the selection
|
|
1147
|
+
// control (no bulk action bar acts on it) and the two metadata columns, which
|
|
1148
|
+
// needs 244px. Table keeps its own scroll wrapper either way; the point is that
|
|
1149
|
+
// a row should not have to be read sideways.
|
|
1150
|
+
const NARROW_TABLE_QUERY = '(max-width: 600px)';
|
|
1151
|
+
const NARROW_COLUMN_KEYS: Record<string, true> = {
|
|
1152
|
+
select: true,
|
|
1153
|
+
location: true,
|
|
1154
|
+
tags: true,
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
const INVENTORY_COLUMNS: TableColumn<InventoryRow>[] = [
|
|
1158
|
+
{
|
|
1159
|
+
key: 'select',
|
|
1160
|
+
header: '',
|
|
1161
|
+
// Wide enough that the control + the theme's cell padding (up to
|
|
1162
|
+
// --spacing-4 = 16px/side on spacious density) fit inside the cell,
|
|
1163
|
+
// so the control's hover background doesn't overflow toward the
|
|
1164
|
+
// card's clipped (rounded) edge on larger-padding themes.
|
|
1165
|
+
width: pixel(64),
|
|
1166
|
+
renderCell: row => <SelectCell row={row} />,
|
|
1167
|
+
},
|
|
1168
|
+
{
|
|
1169
|
+
key: 'item',
|
|
1170
|
+
header: 'Item',
|
|
1171
|
+
// Lower min-width (default 120) so the table fits its container on
|
|
1172
|
+
// larger-spacing themes instead of overflowing the actions column.
|
|
1173
|
+
width: proportional(3, {minWidth: 80}),
|
|
1174
|
+
renderCell: row => <ItemCell row={row} />,
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
key: 'available',
|
|
1178
|
+
header: 'Available',
|
|
1179
|
+
width: pixel(100),
|
|
1180
|
+
renderCell: row => (
|
|
1181
|
+
<Text type="body" hasTabularNumbers>
|
|
1182
|
+
{row.available}
|
|
1183
|
+
</Text>
|
|
1184
|
+
),
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
key: 'location',
|
|
1188
|
+
header: 'Location',
|
|
1189
|
+
width: pixel(100),
|
|
1190
|
+
renderCell: row => <Text type="body">{row.location}</Text>,
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
key: 'tags',
|
|
1194
|
+
header: 'Tags',
|
|
1195
|
+
width: proportional(2, {minWidth: 80}),
|
|
1196
|
+
align: 'end',
|
|
1197
|
+
renderCell: row => <TagsCell row={row} />,
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
key: 'actions',
|
|
1201
|
+
header: '',
|
|
1202
|
+
// Match the select column: fit the sm more-menu button + cell
|
|
1203
|
+
// padding so its hover background stays clear of the card's
|
|
1204
|
+
// clipped rounded edge across themes.
|
|
1205
|
+
width: pixel(64),
|
|
1206
|
+
align: 'end',
|
|
1207
|
+
renderCell: () => <ActionsCell />,
|
|
1208
|
+
},
|
|
1209
|
+
];
|
|
1210
|
+
|
|
1211
|
+
const NARROW_INVENTORY_COLUMNS = INVENTORY_COLUMNS.filter(
|
|
1212
|
+
column => !NARROW_COLUMN_KEYS[column.key],
|
|
1213
|
+
);
|
|
1214
|
+
|
|
1101
1215
|
function InventoryCard({inventory}: {inventory: InventoryRow[]}) {
|
|
1216
|
+
const isNarrow = useMediaQuery(NARROW_TABLE_QUERY);
|
|
1102
1217
|
const lowStockCount = inventory.filter(
|
|
1103
1218
|
row => row.available < LOW_STOCK_THRESHOLD,
|
|
1104
1219
|
).length;
|
|
@@ -1109,7 +1224,6 @@ function InventoryCard({inventory}: {inventory: InventoryRow[]}) {
|
|
|
1109
1224
|
<Button
|
|
1110
1225
|
label="Add item"
|
|
1111
1226
|
variant="primary"
|
|
1112
|
-
size="sm"
|
|
1113
1227
|
icon={<Plus size={16} />}
|
|
1114
1228
|
/>
|
|
1115
1229
|
</HStack>
|
|
@@ -1121,7 +1235,7 @@ function InventoryCard({inventory}: {inventory: InventoryRow[]}) {
|
|
|
1121
1235
|
vAlign="center"
|
|
1122
1236
|
hAlign="between"
|
|
1123
1237
|
style={styles.inventoryFilterRow}>
|
|
1124
|
-
<HStack gap={2} vAlign="center" style={
|
|
1238
|
+
<HStack gap={2} vAlign="center" style={styles.filterRowFill}>
|
|
1125
1239
|
<TextInput
|
|
1126
1240
|
label="Search inventory"
|
|
1127
1241
|
isLabelHidden
|
|
@@ -1160,12 +1274,12 @@ function InventoryCard({inventory}: {inventory: InventoryRow[]}) {
|
|
|
1160
1274
|
value={undefined}
|
|
1161
1275
|
onChange={() => {}}
|
|
1162
1276
|
options={[
|
|
1163
|
-
'
|
|
1164
|
-
'
|
|
1165
|
-
'
|
|
1166
|
-
'
|
|
1167
|
-
'
|
|
1168
|
-
'
|
|
1277
|
+
'Roost 1',
|
|
1278
|
+
'Roost 2',
|
|
1279
|
+
'Roost 3',
|
|
1280
|
+
'Roost 4',
|
|
1281
|
+
'Roost 5',
|
|
1282
|
+
'Roost 6',
|
|
1169
1283
|
]}
|
|
1170
1284
|
/>
|
|
1171
1285
|
<Selector
|
|
@@ -1208,75 +1322,25 @@ function InventoryCard({inventory}: {inventory: InventoryRow[]}) {
|
|
|
1208
1322
|
</HStack>
|
|
1209
1323
|
|
|
1210
1324
|
{lowStockCount > 0 && (
|
|
1211
|
-
<
|
|
1325
|
+
<VStack gap={0} style={inlineStyles.inventoryBannerWrap}>
|
|
1212
1326
|
<Banner
|
|
1213
1327
|
status="warning"
|
|
1214
1328
|
title={lowStockCount + ' items are running low'}
|
|
1215
1329
|
/>
|
|
1216
|
-
</
|
|
1330
|
+
</VStack>
|
|
1217
1331
|
)}
|
|
1218
1332
|
|
|
1219
|
-
<
|
|
1333
|
+
<VStack gap={0} style={styles.inventoryTableWrap}>
|
|
1220
1334
|
<Table<InventoryRow>
|
|
1221
1335
|
data={inventory}
|
|
1222
|
-
columns={
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
header: '',
|
|
1226
|
-
// Wide enough that the control + the theme's cell padding (up to
|
|
1227
|
-
// --spacing-4 = 16px/side on spacious density) fit inside the cell,
|
|
1228
|
-
// so the control's hover background doesn't overflow toward the
|
|
1229
|
-
// card's clipped (rounded) edge on larger-padding themes.
|
|
1230
|
-
width: pixel(64),
|
|
1231
|
-
renderCell: row => <SelectCell row={row} />,
|
|
1232
|
-
},
|
|
1233
|
-
{
|
|
1234
|
-
key: 'item',
|
|
1235
|
-
header: 'Item',
|
|
1236
|
-
// Lower min-width (default 120) so the table fits its container on
|
|
1237
|
-
// larger-spacing themes instead of overflowing the actions column.
|
|
1238
|
-
width: proportional(3, {minWidth: 80}),
|
|
1239
|
-
renderCell: row => <ItemCell row={row} />,
|
|
1240
|
-
},
|
|
1241
|
-
{
|
|
1242
|
-
key: 'available',
|
|
1243
|
-
header: 'Available',
|
|
1244
|
-
width: pixel(100),
|
|
1245
|
-
renderCell: row => (
|
|
1246
|
-
<Text type="body" hasTabularNumbers>
|
|
1247
|
-
{row.available}
|
|
1248
|
-
</Text>
|
|
1249
|
-
),
|
|
1250
|
-
},
|
|
1251
|
-
{
|
|
1252
|
-
key: 'location',
|
|
1253
|
-
header: 'Location',
|
|
1254
|
-
width: pixel(100),
|
|
1255
|
-
renderCell: row => <Text type="body">{row.location}</Text>,
|
|
1256
|
-
},
|
|
1257
|
-
{
|
|
1258
|
-
key: 'tags',
|
|
1259
|
-
header: 'Tags',
|
|
1260
|
-
width: proportional(2, {minWidth: 80}),
|
|
1261
|
-
align: 'end',
|
|
1262
|
-
renderCell: row => <TagsCell row={row} />,
|
|
1263
|
-
},
|
|
1264
|
-
{
|
|
1265
|
-
key: 'actions',
|
|
1266
|
-
header: '',
|
|
1267
|
-
// Match the select column: fit the sm more-menu button + cell
|
|
1268
|
-
// padding so its hover background stays clear of the card's
|
|
1269
|
-
// clipped rounded edge across themes.
|
|
1270
|
-
width: pixel(64),
|
|
1271
|
-
align: 'end',
|
|
1272
|
-
renderCell: () => <ActionsCell />,
|
|
1273
|
-
},
|
|
1274
|
-
]}
|
|
1336
|
+
columns={
|
|
1337
|
+
isNarrow ? NARROW_INVENTORY_COLUMNS : INVENTORY_COLUMNS
|
|
1338
|
+
}
|
|
1275
1339
|
density="spacious"
|
|
1276
1340
|
dividers="rows"
|
|
1277
1341
|
hasHover
|
|
1278
1342
|
/>
|
|
1279
|
-
</
|
|
1343
|
+
</VStack>
|
|
1280
1344
|
</Card>
|
|
1281
1345
|
);
|
|
1282
1346
|
}
|