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
|
@@ -1,156 +1,136 @@
|
|
|
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
|
+
// V[g=0] > (V[g=0] > (V[g=0] > (TN"Nocturne" > ((TNI"Shop" + TNI"New In" + TNI"Stories" + TNI"Help") + (H[g=2 a=center] > (H[g=0.5] > B.ghost"Search" + B.ghost"Account" + B.ghost"Cart") + B.primary"Sign in"))) + (S.transparent[p=6] > V[g=10 mw=880] > (Ctr > V[g=4 a=center mw=560] > Hd"Little haunts"[level=1 t=display-2] + Tx"We believe"[t=body]) + (G[c={min:200,max:3} g=4] > (C[p=0 h=100%] > (V[g=0 h=100%] > (AR[ratio=1] > Scene{SceneFrame}) + (V[g=2 a=center p=4] > Bd.cyan"New" + Hd"Moonphase Watch"[level=2] + Tx"Description"[t=body] + (H[g=2 a=center] > NI"Quantity" + B.secondary"Add to cart"))))*3)))) + (V[g=0 p=6] > (V[g=8] > ((C[p=0] > (H[g=3 j=between a=center] > Hd"Night Owl AI"[level=2] + (H[g=1 a=center] > B.ghost"Export conversation" + B.ghost"Close chat")) + D + (V[g=0] > ChML > (ChSM"Today" + (ChM > ChB{ChatMessageBubble}"Where's my order?") + (ChM > (V[g=3] > Tx"Body"[t=body] + Tx"Body"[t=body])) + (ChM > ChB{ChatMessageBubble}"Can you show me the full details?") + (ChM > (V[g=3] > Tx"Body"[t=body] + (C[p=3] > (V[g=1] > LI{Item}*4)))))) + (V[g=0] > (H[g=1 a=center wrap] > B.secondary*3)) + (V[g=0] > ChC"Ask Night Owl..."))) + (G[c={min:200} g=4] > (GS[c=3] > (C[p=0] > (H[g=3 j=between a=center] > Hd"Inventory"[level=2] + B.primary"Add item") + D + (H[g=3 j=between a=center] > (H[g=2 a=center] > TI"Search inventory" + (OFL[g=2] > SE{Selector}*3)) + (H[g=1 a=center] > B.ghost"List view" + B.ghost"Grid view")) + (V[g=0] > Bn.warning"2 items are running low") + (V[g=0] > T[density=spacious dv=rows hover] > (TR > THC + THC"Item" + THC"Available" + THC"Location" + THC"Tags" + THC) + (TR > (TC > CB) + (TC > (H[g=3 a=center] > Sw{ProductSwatch} + (V[g=0] > Tx"Moonphase Watch"[t=body] + Tx"Steel case, moonphase dial"[t=supporting]))) + TC"42" + TC"Roost 3" + (TC > Bd) + (TC > MM))*6))) + (GS[c=1] > (C[p=4 h=100%] > (V[g=4 h=100%] > Hd"Revenue"[level=2] + (G[c=2 g=3] > (V[g=0] > Tx"18K"[t=display-3] + Tx"Monthly revenue"[t=supporting]) + (V[g=0] > Tx"+12%"[t=display-3] + Tx"Order growth"[t=supporting])) + D + (H[g=3 j=between a=center] > Hd"Activity"[level=3] + Lk"See all") + (V[g=1] > LI{Item}*5)))))))
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* 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
9
|
*/
|
|
8
10
|
|
|
9
|
-
import
|
|
11
|
+
import { useAppShellMobile } from "@astryxdesign/core/AppShell";
|
|
12
|
+
import { AspectRatio } from "@astryxdesign/core/AspectRatio";
|
|
13
|
+
import { Badge } from "@astryxdesign/core/Badge";
|
|
14
|
+
import { Banner } from "@astryxdesign/core/Banner";
|
|
15
|
+
import { Button } from "@astryxdesign/core/Button";
|
|
16
|
+
import { Card } from "@astryxdesign/core/Card";
|
|
17
|
+
import { Center } from "@astryxdesign/core/Center";
|
|
10
18
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import type {TableColumn} from '@astryxdesign/core/Table';
|
|
39
|
-
import {StatusDot} from '@astryxdesign/core/StatusDot';
|
|
40
|
-
import {ProductSwatch} from 'astryx-dracula/shared/revenue-chart';
|
|
41
|
-
import {useMediaQuery} from '@astryxdesign/core/hooks';
|
|
42
|
-
import {TextInput} from '@astryxdesign/core/TextInput';
|
|
43
|
-
import {Selector} from '@astryxdesign/core/Selector';
|
|
44
|
-
import {MoreMenu} from '@astryxdesign/core/MoreMenu';
|
|
45
|
-
import {Center} from '@astryxdesign/core/Center';
|
|
46
|
-
import {Section} from '@astryxdesign/core/Section';
|
|
47
|
-
import {AspectRatio} from '@astryxdesign/core/AspectRatio';
|
|
48
|
-
import {NumberInput} from '@astryxdesign/core/NumberInput';
|
|
49
|
-
import {OverflowList} from '@astryxdesign/core/OverflowList';
|
|
50
|
-
import {TopNav, TopNavHeading, TopNavItem} from '@astryxdesign/core/TopNav';
|
|
19
|
+
ChatComposer,
|
|
20
|
+
ChatMessage,
|
|
21
|
+
ChatMessageBubble,
|
|
22
|
+
ChatMessageList,
|
|
23
|
+
ChatSystemMessage,
|
|
24
|
+
} from "@astryxdesign/core/Chat";
|
|
25
|
+
import { CheckboxInput } from "@astryxdesign/core/CheckboxInput";
|
|
26
|
+
import { Divider } from "@astryxdesign/core/Divider";
|
|
27
|
+
import { Grid, GridSpan } from "@astryxdesign/core/Grid";
|
|
28
|
+
import { useMediaQuery } from "@astryxdesign/core/hooks";
|
|
29
|
+
import { Item } from "@astryxdesign/core/Item";
|
|
30
|
+
import { HStack, VStack } from "@astryxdesign/core/Layout";
|
|
31
|
+
import { Link } from "@astryxdesign/core/Link";
|
|
32
|
+
import { MoreMenu } from "@astryxdesign/core/MoreMenu";
|
|
33
|
+
import { NumberInput } from "@astryxdesign/core/NumberInput";
|
|
34
|
+
import { OverflowList } from "@astryxdesign/core/OverflowList";
|
|
35
|
+
import { Section } from "@astryxdesign/core/Section";
|
|
36
|
+
import { Selector } from "@astryxdesign/core/Selector";
|
|
37
|
+
import { StatusDot } from "@astryxdesign/core/StatusDot";
|
|
38
|
+
import type { TableColumn } from "@astryxdesign/core/Table";
|
|
39
|
+
import { pixel, proportional, Table } from "@astryxdesign/core/Table";
|
|
40
|
+
import { Heading, Text } from "@astryxdesign/core/Text";
|
|
41
|
+
import { TextInput } from "@astryxdesign/core/TextInput";
|
|
42
|
+
import { TopNav, TopNavHeading, TopNavItem } from "@astryxdesign/core/TopNav";
|
|
43
|
+
import { ProductSwatch } from "astryx-dracula/shared/revenue-chart";
|
|
44
|
+
import { SceneFrame } from "astryx-dracula/shared/scene-frame";
|
|
45
|
+
import type { SceneHue } from "astryx-dracula/shared/scene-hues";
|
|
51
46
|
import {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
47
|
+
Banknote,
|
|
48
|
+
Download,
|
|
49
|
+
Folder,
|
|
50
|
+
LayoutGrid,
|
|
51
|
+
List,
|
|
52
|
+
MapPin,
|
|
53
|
+
Mic,
|
|
54
|
+
Plus,
|
|
55
|
+
Search,
|
|
56
|
+
ShoppingBag,
|
|
57
|
+
Tag,
|
|
58
|
+
User,
|
|
59
|
+
X,
|
|
60
|
+
} from "lucide-react";
|
|
61
|
+
import type { ReactNode } from "react";
|
|
60
62
|
|
|
61
63
|
// One Dracula accent per hero product slot. Purple stays off decorative art:
|
|
62
64
|
// it reads as interactive, and nothing here is tappable.
|
|
63
|
-
const PRODUCT_HUES = [
|
|
64
|
-
'var(--dracula-green)',
|
|
65
|
-
'var(--dracula-cyan)',
|
|
66
|
-
'var(--dracula-yellow)',
|
|
67
|
-
];
|
|
65
|
+
const PRODUCT_HUES = ["var(--dracula-green)", "var(--dracula-cyan)", "var(--dracula-yellow)"];
|
|
68
66
|
|
|
69
67
|
// Grid recipes the store renders at every width above the phone layout. Kept
|
|
70
68
|
// at module scope: an object literal rebuilt per render is a new value prop
|
|
71
69
|
// every time.
|
|
72
|
-
const SHOWCASE_COLUMNS = {minWidth: 200, repeat:
|
|
73
|
-
const PRODUCT_COLUMNS = {minWidth: 200, max: 3} as const;
|
|
70
|
+
const SHOWCASE_COLUMNS = { minWidth: 200, repeat: "fit" } as const;
|
|
71
|
+
const PRODUCT_COLUMNS = { minWidth: 200, max: 3 } as const;
|
|
74
72
|
|
|
75
73
|
/** Categorical badge variants usable for showcase product/inventory tags. */
|
|
76
74
|
export type ShowcaseBadgeVariant =
|
|
77
|
-
|
|
78
|
-
| 'cyan'
|
|
79
|
-
| 'green'
|
|
80
|
-
| 'orange'
|
|
81
|
-
| 'pink'
|
|
82
|
-
| 'purple'
|
|
83
|
-
| 'red'
|
|
84
|
-
| 'teal'
|
|
85
|
-
| 'yellow';
|
|
75
|
+
"blue" | "cyan" | "green" | "orange" | "pink" | "purple" | "red" | "teal" | "yellow";
|
|
86
76
|
|
|
87
77
|
export interface ProductSpec {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
78
|
+
name: string;
|
|
79
|
+
description: string;
|
|
80
|
+
badge: string;
|
|
81
|
+
badgeVariant: ShowcaseBadgeVariant;
|
|
92
82
|
}
|
|
93
83
|
|
|
94
84
|
const DEFAULT_PRODUCTS: ProductSpec[] = [
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
85
|
+
{
|
|
86
|
+
name: "Moonphase Watch",
|
|
87
|
+
description: "Clean lines and lume that carry through the longest night.",
|
|
88
|
+
badge: "New",
|
|
89
|
+
badgeVariant: "cyan",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "Night-Owl Headphones",
|
|
93
|
+
description: "Deep sound and soft cushions for all-night listening.",
|
|
94
|
+
badge: "Popular",
|
|
95
|
+
badgeVariant: "green",
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "Coven Canvas Backpack",
|
|
99
|
+
description: "Waxed canvas with a quiet, moonlit profile.",
|
|
100
|
+
badge: "Limited",
|
|
101
|
+
badgeVariant: "yellow",
|
|
102
|
+
},
|
|
113
103
|
];
|
|
114
104
|
|
|
115
105
|
// Dracula product scene: harvest moon over sleeping hills with a per-product
|
|
116
106
|
// accent glyph. All fills are brand vars, corners rx=4.
|
|
117
|
-
function ProductArt({hue, label}: {hue: string; label: string}) {
|
|
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
|
-
stroke={hue}
|
|
145
|
-
strokeWidth="5"
|
|
146
|
-
strokeLinecap="round"
|
|
147
|
-
strokeLinejoin="round">
|
|
148
|
-
<rect x="-30" y="-30" width="60" height="60" rx={4} />
|
|
149
|
-
<circle cx="13" cy="-13" r="2.5" fill={hue} stroke="none" />
|
|
150
|
-
<path d="M-22 20 L-5 2 L7 12 L14 5 L23 15" />
|
|
151
|
-
</g>
|
|
152
|
-
</SceneFrame>
|
|
153
|
-
);
|
|
107
|
+
function ProductArt({ hue, label }: { hue: string; label: string }) {
|
|
108
|
+
return (
|
|
109
|
+
<SceneFrame
|
|
110
|
+
label={`${label} artwork`}
|
|
111
|
+
viewBox="0 0 400 300"
|
|
112
|
+
fill="var(--dracula-bg-light)"
|
|
113
|
+
hasBackdrop={false}
|
|
114
|
+
>
|
|
115
|
+
<rect width="400" height="300" fill="var(--dracula-bg-light)" />
|
|
116
|
+
<circle cx="200" cy="110" r="52" fill={hue} opacity={0.9} />
|
|
117
|
+
<circle cx="184" cy="100" r={44} fill="var(--dracula-bg-light)" opacity={0.55} />
|
|
118
|
+
<path d="M0 210 Q120 170 240 200 T400 190 V300 H0 Z" fill="var(--dracula-current-line)" />
|
|
119
|
+
<path d="M0 245 Q140 215 300 240 T400 235 V300 H0 Z" fill="var(--dracula-bg)" />
|
|
120
|
+
<g
|
|
121
|
+
transform="translate(200 232)"
|
|
122
|
+
fill="none"
|
|
123
|
+
stroke={hue}
|
|
124
|
+
strokeWidth="5"
|
|
125
|
+
strokeLinecap="round"
|
|
126
|
+
strokeLinejoin="round"
|
|
127
|
+
>
|
|
128
|
+
<rect x="-30" y="-30" width="60" height="60" rx={4} />
|
|
129
|
+
<circle cx="13" cy="-13" r="2.5" fill={hue} stroke="none" />
|
|
130
|
+
<path d="M-22 20 L-5 2 L7 12 L14 5 L23 15" />
|
|
131
|
+
</g>
|
|
132
|
+
</SceneFrame>
|
|
133
|
+
);
|
|
154
134
|
}
|
|
155
135
|
|
|
156
136
|
// Default export is the route page (sandbox renders this as a Next.js page, so
|
|
@@ -159,495 +139,456 @@ function ProductArt({hue, label}: {hue: string; label: string}) {
|
|
|
159
139
|
// or inventory in, so the props/store split bought indirection for zero
|
|
160
140
|
// callers.
|
|
161
141
|
export default function ThemeShowcase() {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
padding={6}
|
|
172
|
-
style={{backgroundColor: 'var(--color-background-surface)'}}>
|
|
173
|
-
<CardShowcase inventory={DEFAULT_INVENTORY} isMobile={isMobile} />
|
|
174
|
-
</VStack>
|
|
175
|
-
</VStack>
|
|
176
|
-
);
|
|
142
|
+
const { isMobile } = useAppShellMobile();
|
|
143
|
+
return (
|
|
144
|
+
<VStack gap={0} minHeight="100%" style={{ backgroundColor: "var(--color-background-body)" }}>
|
|
145
|
+
<StorePreview products={DEFAULT_PRODUCTS} isMobile={isMobile} />
|
|
146
|
+
<VStack gap={0} padding={6} style={{ backgroundColor: "var(--color-background-surface)" }}>
|
|
147
|
+
<CardShowcase inventory={DEFAULT_INVENTORY} isMobile={isMobile} />
|
|
148
|
+
</VStack>
|
|
149
|
+
</VStack>
|
|
150
|
+
);
|
|
177
151
|
}
|
|
178
152
|
|
|
179
|
-
function CardShowcase({
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
<GridSpan columns={1}>
|
|
196
|
-
<LatestActivityCard isMobile={isMobile} />
|
|
197
|
-
</GridSpan>
|
|
198
|
-
</Grid>
|
|
199
|
-
</VStack>
|
|
200
|
-
);
|
|
153
|
+
function CardShowcase({ inventory, isMobile }: { inventory: InventoryRow[]; isMobile: boolean }) {
|
|
154
|
+
const columns = isMobile ? 1 : SHOWCASE_COLUMNS;
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<VStack gap={8}>
|
|
158
|
+
<ChatCard />
|
|
159
|
+
<Grid columns={columns} gap={4}>
|
|
160
|
+
<GridSpan columns={isMobile ? 1 : 3}>
|
|
161
|
+
<InventoryCard inventory={inventory} />
|
|
162
|
+
</GridSpan>
|
|
163
|
+
<GridSpan columns={1}>
|
|
164
|
+
<LatestActivityCard isMobile={isMobile} />
|
|
165
|
+
</GridSpan>
|
|
166
|
+
</Grid>
|
|
167
|
+
</VStack>
|
|
168
|
+
);
|
|
201
169
|
}
|
|
202
170
|
|
|
203
|
-
function StorePreview({
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
variant="secondary"
|
|
341
|
-
size="sm"
|
|
342
|
-
href="#/templates/theme-showcase"
|
|
343
|
-
style={{flex: 1}}
|
|
344
|
-
/>
|
|
345
|
-
</HStack>
|
|
346
|
-
</VStack>
|
|
347
|
-
</VStack>
|
|
348
|
-
</Card>
|
|
349
|
-
))}
|
|
350
|
-
</Grid>
|
|
351
|
-
</VStack>
|
|
352
|
-
</Section>
|
|
353
|
-
</VStack>
|
|
354
|
-
</VStack>
|
|
355
|
-
);
|
|
171
|
+
function StorePreview({ products, isMobile }: { products: ProductSpec[]; isMobile: boolean }) {
|
|
172
|
+
// Shell-less preview: no AppShell above means the host context always
|
|
173
|
+
// reports desktop, so the nav reads the real viewport instead.
|
|
174
|
+
const isNarrowViewport = useMediaQuery("(max-width: 640px)");
|
|
175
|
+
const collapseNav = isMobile || isNarrowViewport;
|
|
176
|
+
return (
|
|
177
|
+
<VStack gap={0} data-theme-preview="true">
|
|
178
|
+
<VStack gap={0}>
|
|
179
|
+
<TopNav
|
|
180
|
+
label="Theme preview navigation"
|
|
181
|
+
heading={<TopNavHeading heading="Nocturne" />}
|
|
182
|
+
centerContent={
|
|
183
|
+
collapseNav ? undefined : (
|
|
184
|
+
<>
|
|
185
|
+
<TopNavItem label="Shop" href="#/templates/theme-showcase" isSelected />
|
|
186
|
+
<TopNavItem label="New In" href="#/templates/theme-showcase" />
|
|
187
|
+
<TopNavItem label="Stories" href="#/templates/theme-showcase" />
|
|
188
|
+
<TopNavItem label="Help" href="#/templates/theme-showcase" />
|
|
189
|
+
</>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
endContent={
|
|
193
|
+
collapseNav ? (
|
|
194
|
+
<Button
|
|
195
|
+
label="Cart"
|
|
196
|
+
tooltip="Cart"
|
|
197
|
+
variant="ghost"
|
|
198
|
+
isIconOnly
|
|
199
|
+
icon={<ShoppingBag size={20} />}
|
|
200
|
+
href="#/templates/theme-showcase"
|
|
201
|
+
/>
|
|
202
|
+
) : (
|
|
203
|
+
<HStack gap={2} vAlign="center">
|
|
204
|
+
<HStack gap={0.5}>
|
|
205
|
+
<Button
|
|
206
|
+
label="Search"
|
|
207
|
+
tooltip="Search"
|
|
208
|
+
variant="ghost"
|
|
209
|
+
isIconOnly
|
|
210
|
+
icon={<Search size={20} />}
|
|
211
|
+
href="#/templates/theme-showcase"
|
|
212
|
+
/>
|
|
213
|
+
<Button
|
|
214
|
+
label="Account"
|
|
215
|
+
tooltip="Account"
|
|
216
|
+
variant="ghost"
|
|
217
|
+
isIconOnly
|
|
218
|
+
icon={<User size={20} />}
|
|
219
|
+
href="#/templates/theme-showcase"
|
|
220
|
+
/>
|
|
221
|
+
<Button
|
|
222
|
+
label="Cart"
|
|
223
|
+
tooltip="Cart"
|
|
224
|
+
variant="ghost"
|
|
225
|
+
isIconOnly
|
|
226
|
+
icon={<ShoppingBag size={20} />}
|
|
227
|
+
href="#/templates/theme-showcase"
|
|
228
|
+
/>
|
|
229
|
+
</HStack>
|
|
230
|
+
<Button label="Sign in" variant="primary" href="#/templates/theme-showcase" />
|
|
231
|
+
</HStack>
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
/>
|
|
235
|
+
|
|
236
|
+
<Section padding={6} variant="transparent">
|
|
237
|
+
<VStack
|
|
238
|
+
gap={10}
|
|
239
|
+
maxWidth={880}
|
|
240
|
+
width="100%"
|
|
241
|
+
style={{ marginInline: "auto", minWidth: 0 }}
|
|
242
|
+
>
|
|
243
|
+
<Center>
|
|
244
|
+
<VStack gap={4} hAlign="center" maxWidth={560}>
|
|
245
|
+
<Heading level={1} type="display-2" justify="center">
|
|
246
|
+
Little haunts,
|
|
247
|
+
<br />
|
|
248
|
+
everywhere you roam
|
|
249
|
+
</Heading>
|
|
250
|
+
<Text type="body" color="secondary" justify="center">
|
|
251
|
+
We believe the smallest shadows are the ones that matter most. Turn an ordinary
|
|
252
|
+
evening into something worth remembering.
|
|
253
|
+
</Text>
|
|
254
|
+
</VStack>
|
|
255
|
+
</Center>
|
|
256
|
+
|
|
257
|
+
<Grid columns={isMobile ? 1 : PRODUCT_COLUMNS} gap={4}>
|
|
258
|
+
{products.map((p, i) => (
|
|
259
|
+
<Card key={p.name} padding={0} height="100%">
|
|
260
|
+
<VStack gap={0} height="100%">
|
|
261
|
+
<AspectRatio ratio={1}>
|
|
262
|
+
<ProductArt hue={PRODUCT_HUES[i % PRODUCT_HUES.length]} label={p.name} />
|
|
263
|
+
</AspectRatio>
|
|
264
|
+
<VStack gap={2} hAlign="center" padding={4} style={{ flex: 1 }}>
|
|
265
|
+
<HStack>
|
|
266
|
+
<Badge label={p.badge} variant={p.badgeVariant} />
|
|
267
|
+
</HStack>
|
|
268
|
+
<Heading level={2} justify="center">
|
|
269
|
+
{p.name}
|
|
270
|
+
</Heading>
|
|
271
|
+
<Text type="body" color="secondary" justify="center" style={{ flex: 1 }}>
|
|
272
|
+
{p.description}
|
|
273
|
+
</Text>
|
|
274
|
+
<HStack gap={2} vAlign="center" hAlign="center">
|
|
275
|
+
<NumberInput
|
|
276
|
+
label="Quantity"
|
|
277
|
+
isLabelHidden
|
|
278
|
+
value={1}
|
|
279
|
+
onChange={() => {}}
|
|
280
|
+
min={1}
|
|
281
|
+
max={99}
|
|
282
|
+
size="sm"
|
|
283
|
+
// minWidth (not a hard width) so the field grows to
|
|
284
|
+
// fit the digit + the theme's input padding. A fixed
|
|
285
|
+
// 40px was too tight on themes with larger padding /
|
|
286
|
+
// bigger type scale (e.g. Matcha, Y2K), clipping the
|
|
287
|
+
// value.
|
|
288
|
+
controlStyle={{ minWidth: 64, flexShrink: 0 }}
|
|
289
|
+
/>
|
|
290
|
+
<Button
|
|
291
|
+
label="Add to cart"
|
|
292
|
+
variant="secondary"
|
|
293
|
+
size="sm"
|
|
294
|
+
href="#/templates/theme-showcase"
|
|
295
|
+
style={{ flex: 1 }}
|
|
296
|
+
/>
|
|
297
|
+
</HStack>
|
|
298
|
+
</VStack>
|
|
299
|
+
</VStack>
|
|
300
|
+
</Card>
|
|
301
|
+
))}
|
|
302
|
+
</Grid>
|
|
303
|
+
</VStack>
|
|
304
|
+
</Section>
|
|
305
|
+
</VStack>
|
|
306
|
+
</VStack>
|
|
307
|
+
);
|
|
356
308
|
}
|
|
357
309
|
|
|
358
|
-
const SUGGESTED_QUESTIONS = [
|
|
359
|
-
'Reschedule delivery',
|
|
360
|
-
'Update shipping address',
|
|
361
|
-
'Start a return',
|
|
362
|
-
];
|
|
310
|
+
const SUGGESTED_QUESTIONS = ["Reschedule delivery", "Update shipping address", "Start a return"];
|
|
363
311
|
|
|
364
312
|
function ChatCard() {
|
|
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
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
size="md"
|
|
530
|
-
isIconOnly
|
|
531
|
-
label="Voice input"
|
|
532
|
-
tooltip="Voice input"
|
|
533
|
-
icon={<Mic size={16} />}
|
|
534
|
-
/>
|
|
535
|
-
}
|
|
536
|
-
/>
|
|
537
|
-
</VStack>
|
|
538
|
-
</Card>
|
|
539
|
-
);
|
|
313
|
+
return (
|
|
314
|
+
<Card
|
|
315
|
+
padding={0}
|
|
316
|
+
style={{
|
|
317
|
+
backgroundColor: "var(--color-background-surface)",
|
|
318
|
+
color: "var(--color-text-primary)",
|
|
319
|
+
minWidth: 0,
|
|
320
|
+
overflow: "hidden",
|
|
321
|
+
display: "flex",
|
|
322
|
+
flexDirection: "column",
|
|
323
|
+
}}
|
|
324
|
+
>
|
|
325
|
+
<HStack hAlign="between" vAlign="center" gap={3} paddingBlock={4} paddingInline={4}>
|
|
326
|
+
<Heading level={2}>Night Owl AI</Heading>
|
|
327
|
+
|
|
328
|
+
<HStack gap={1} vAlign="center">
|
|
329
|
+
<Button
|
|
330
|
+
variant="ghost"
|
|
331
|
+
size="sm"
|
|
332
|
+
isIconOnly
|
|
333
|
+
label="Export conversation"
|
|
334
|
+
tooltip="Export conversation"
|
|
335
|
+
icon={<Download size={16} />}
|
|
336
|
+
/>
|
|
337
|
+
<Button
|
|
338
|
+
variant="ghost"
|
|
339
|
+
size="sm"
|
|
340
|
+
isIconOnly
|
|
341
|
+
label="Close chat"
|
|
342
|
+
tooltip="Close chat"
|
|
343
|
+
icon={<X size={16} />}
|
|
344
|
+
/>
|
|
345
|
+
</HStack>
|
|
346
|
+
</HStack>
|
|
347
|
+
|
|
348
|
+
<Divider variant="subtle" />
|
|
349
|
+
|
|
350
|
+
<VStack
|
|
351
|
+
gap={0}
|
|
352
|
+
isScrollable={false}
|
|
353
|
+
tabIndex={0}
|
|
354
|
+
role="region"
|
|
355
|
+
aria-label="Night Owl chat messages"
|
|
356
|
+
style={{
|
|
357
|
+
flex: 1,
|
|
358
|
+
minHeight: 0,
|
|
359
|
+
// The message region is the scroll owner: the card is stretched to the
|
|
360
|
+
// grid row height, so longer conversations must scroll here rather than
|
|
361
|
+
// clip against the card.
|
|
362
|
+
overflowY: "auto",
|
|
363
|
+
}}
|
|
364
|
+
>
|
|
365
|
+
<ChatMessageList>
|
|
366
|
+
<ChatSystemMessage>Today</ChatSystemMessage>
|
|
367
|
+
|
|
368
|
+
<ChatMessage sender="user">
|
|
369
|
+
<ChatMessageBubble variant="filled">Where's my order?</ChatMessageBubble>
|
|
370
|
+
</ChatMessage>
|
|
371
|
+
|
|
372
|
+
<ChatMessage sender="assistant">
|
|
373
|
+
<VStack gap={3}>
|
|
374
|
+
<Text type="body">
|
|
375
|
+
Your order #1043 (the Moonphase Watch and Belfry Throw) shipped this morning from
|
|
376
|
+
the courier-raven roost and is currently in transit with courier-raven. It's on
|
|
377
|
+
track to arrive at your address by end of day tomorrow.
|
|
378
|
+
</Text>
|
|
379
|
+
<Text type="body">
|
|
380
|
+
Let me know if you'd like to reschedule the delivery, redirect it to a pickup point,
|
|
381
|
+
or start a return once it arrives.
|
|
382
|
+
</Text>
|
|
383
|
+
</VStack>
|
|
384
|
+
</ChatMessage>
|
|
385
|
+
|
|
386
|
+
<ChatMessage sender="user">
|
|
387
|
+
<ChatMessageBubble variant="filled">
|
|
388
|
+
Can you show me the full details?
|
|
389
|
+
</ChatMessageBubble>
|
|
390
|
+
</ChatMessage>
|
|
391
|
+
|
|
392
|
+
<ChatMessage sender="assistant">
|
|
393
|
+
<VStack gap={3}>
|
|
394
|
+
<Text type="body">Here's everything I have on order #1043:</Text>
|
|
395
|
+
<Card padding={3}>
|
|
396
|
+
<VStack gap={1}>
|
|
397
|
+
<Item
|
|
398
|
+
label="Items"
|
|
399
|
+
description="Moonphase Watch · Belfry Throw"
|
|
400
|
+
endContent={
|
|
401
|
+
<Text type="body" weight="semibold" hasTabularNumbers>
|
|
402
|
+
$248
|
|
403
|
+
</Text>
|
|
404
|
+
}
|
|
405
|
+
/>
|
|
406
|
+
<Item
|
|
407
|
+
label="Shipping"
|
|
408
|
+
description="Courier-raven ground"
|
|
409
|
+
endContent={
|
|
410
|
+
<Text type="body" weight="semibold" hasTabularNumbers>
|
|
411
|
+
$12
|
|
412
|
+
</Text>
|
|
413
|
+
}
|
|
414
|
+
/>
|
|
415
|
+
<Item
|
|
416
|
+
label="Estimated arrival"
|
|
417
|
+
description="Tomorrow by 8pm"
|
|
418
|
+
endContent={
|
|
419
|
+
<HStack gap={1} vAlign="center">
|
|
420
|
+
<StatusDot variant="success" label="On time" />
|
|
421
|
+
<Text type="supporting" color="secondary">
|
|
422
|
+
On time
|
|
423
|
+
</Text>
|
|
424
|
+
</HStack>
|
|
425
|
+
}
|
|
426
|
+
/>
|
|
427
|
+
<Item
|
|
428
|
+
label="Tracking"
|
|
429
|
+
description="RVN 1Z 999 AA1 0123 4567 84"
|
|
430
|
+
endContent={<Link href="#/templates/theme-showcase">Track</Link>}
|
|
431
|
+
/>
|
|
432
|
+
</VStack>
|
|
433
|
+
</Card>
|
|
434
|
+
</VStack>
|
|
435
|
+
</ChatMessage>
|
|
436
|
+
</ChatMessageList>
|
|
437
|
+
</VStack>
|
|
438
|
+
|
|
439
|
+
<VStack gap={0} paddingInline={4} paddingBlockEnd={2}>
|
|
440
|
+
<HStack gap={1} hAlign="center" wrap="wrap">
|
|
441
|
+
{SUGGESTED_QUESTIONS.map((question) => (
|
|
442
|
+
<Button key={question} variant="secondary" size="sm" label={question} />
|
|
443
|
+
))}
|
|
444
|
+
</HStack>
|
|
445
|
+
</VStack>
|
|
446
|
+
|
|
447
|
+
<VStack gap={0} paddingInline={4} paddingBlockEnd={4}>
|
|
448
|
+
<ChatComposer
|
|
449
|
+
value=""
|
|
450
|
+
onChange={() => {}}
|
|
451
|
+
onSubmit={() => {}}
|
|
452
|
+
placeholder="Ask Night Owl..."
|
|
453
|
+
footerActions={
|
|
454
|
+
<Button
|
|
455
|
+
variant="ghost"
|
|
456
|
+
size="md"
|
|
457
|
+
isIconOnly
|
|
458
|
+
label="Attach"
|
|
459
|
+
tooltip="Attach"
|
|
460
|
+
icon={<Plus size={16} />}
|
|
461
|
+
/>
|
|
462
|
+
}
|
|
463
|
+
sendActions={
|
|
464
|
+
<Button
|
|
465
|
+
variant="ghost"
|
|
466
|
+
size="md"
|
|
467
|
+
isIconOnly
|
|
468
|
+
label="Voice input"
|
|
469
|
+
tooltip="Voice input"
|
|
470
|
+
icon={<Mic size={16} />}
|
|
471
|
+
/>
|
|
472
|
+
}
|
|
473
|
+
/>
|
|
474
|
+
</VStack>
|
|
475
|
+
</Card>
|
|
476
|
+
);
|
|
540
477
|
}
|
|
541
478
|
|
|
542
479
|
interface ActivityRow {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
480
|
+
id: string;
|
|
481
|
+
icon: ReactNode;
|
|
482
|
+
label: string;
|
|
483
|
+
detail: string;
|
|
484
|
+
time: string;
|
|
485
|
+
amount: number;
|
|
549
486
|
}
|
|
550
487
|
|
|
551
488
|
const ACTIVITY: ActivityRow[] = [
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
489
|
+
{
|
|
490
|
+
id: "1",
|
|
491
|
+
icon: <ShoppingBag size={16} />,
|
|
492
|
+
label: "Order #1043",
|
|
493
|
+
detail: "Placed · 1:59 pm",
|
|
494
|
+
time: "1:59 pm",
|
|
495
|
+
amount: 248,
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
id: "2",
|
|
499
|
+
icon: <Banknote size={16} />,
|
|
500
|
+
label: "Order #1041",
|
|
501
|
+
detail: "Refunded · 12:40 pm",
|
|
502
|
+
time: "12:40 pm",
|
|
503
|
+
amount: -89,
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: "3",
|
|
507
|
+
icon: <ShoppingBag size={16} />,
|
|
508
|
+
label: "Order #1040",
|
|
509
|
+
detail: "Placed · 10:30 am",
|
|
510
|
+
time: "10:30 am",
|
|
511
|
+
amount: 156,
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
id: "4",
|
|
515
|
+
icon: <ShoppingBag size={16} />,
|
|
516
|
+
label: "Order #1038",
|
|
517
|
+
detail: "Placed · 9:11 am",
|
|
518
|
+
time: "9:11 am",
|
|
519
|
+
amount: 412,
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
id: "5",
|
|
523
|
+
icon: <ShoppingBag size={16} />,
|
|
524
|
+
label: "Order #1037",
|
|
525
|
+
detail: "Placed · 8:42 am",
|
|
526
|
+
time: "8:42 am",
|
|
527
|
+
amount: 95,
|
|
528
|
+
},
|
|
592
529
|
];
|
|
593
530
|
|
|
594
531
|
function formatAmount(amount: number): string {
|
|
595
|
-
|
|
596
|
-
|
|
532
|
+
const sign = amount < 0 ? "−" : "+";
|
|
533
|
+
return `${sign}$${Math.abs(amount).toLocaleString()}`;
|
|
597
534
|
}
|
|
598
535
|
|
|
599
|
-
function LatestActivityCard({isMobile}: {isMobile: boolean}) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
536
|
+
function LatestActivityCard({ isMobile }: { isMobile: boolean }) {
|
|
537
|
+
return (
|
|
538
|
+
<Card
|
|
539
|
+
padding={4}
|
|
540
|
+
height="100%"
|
|
541
|
+
style={{
|
|
542
|
+
backgroundColor: "var(--color-background-surface)",
|
|
543
|
+
color: "var(--color-text-primary)",
|
|
544
|
+
minWidth: 0,
|
|
545
|
+
}}
|
|
546
|
+
>
|
|
547
|
+
<VStack gap={4} height="100%">
|
|
548
|
+
<Heading level={2}>Revenue</Heading>
|
|
549
|
+
|
|
550
|
+
<Grid columns={isMobile ? 1 : 2} gap={3}>
|
|
551
|
+
<VStack gap={0}>
|
|
552
|
+
<Text type="display-3" weight="semibold" hasTabularNumbers>
|
|
553
|
+
18K
|
|
554
|
+
</Text>
|
|
555
|
+
<Text type="supporting" color="secondary">
|
|
556
|
+
Monthly revenue
|
|
557
|
+
</Text>
|
|
558
|
+
</VStack>
|
|
559
|
+
<VStack gap={0}>
|
|
560
|
+
<Text type="display-3" weight="semibold" hasTabularNumbers>
|
|
561
|
+
+12%
|
|
562
|
+
</Text>
|
|
563
|
+
<Text type="supporting" color="secondary">
|
|
564
|
+
Order growth
|
|
565
|
+
</Text>
|
|
566
|
+
</VStack>
|
|
567
|
+
</Grid>
|
|
568
|
+
|
|
569
|
+
<Divider variant="subtle" />
|
|
570
|
+
|
|
571
|
+
<HStack hAlign="between" vAlign="center">
|
|
572
|
+
<Heading level={3}>Activity</Heading>
|
|
573
|
+
<Link href="#/templates/theme-showcase">See all</Link>
|
|
574
|
+
</HStack>
|
|
575
|
+
|
|
576
|
+
<VStack
|
|
577
|
+
gap={1}
|
|
578
|
+
style={{
|
|
579
|
+
flex: 1,
|
|
580
|
+
minHeight: 0,
|
|
581
|
+
overflow: "hidden",
|
|
582
|
+
maskImage: "linear-gradient(to bottom, black calc(100% - 48px), transparent)",
|
|
583
|
+
WebkitMaskImage: "linear-gradient(to bottom, black calc(100% - 48px), transparent)",
|
|
584
|
+
// Cancel the Item inline inset with the inset ITSELF: core
|
|
585
|
+
// publishes it as --_item-inset-inline (0.6.7), and it
|
|
586
|
+
// tracks density and theme overrides. A literal spacing
|
|
587
|
+
// step silently stops matching when either moves.
|
|
588
|
+
marginInline: "calc(-1 * var(--_item-inset-inline))",
|
|
589
|
+
}}
|
|
590
|
+
>
|
|
591
|
+
{/* The sign travels in the string (formatAmount prefixes U+2212), so
|
|
651
592
|
direction is never colour-alone and 1.4.1 holds without a tint.
|
|
652
593
|
A negative text role is not available here, and the reason is
|
|
653
594
|
worth recording rather than working around:
|
|
@@ -661,178 +602,180 @@ function LatestActivityCard({isMobile}: {isMobile: boolean}) {
|
|
|
661
602
|
secondary therefore stays and the minus sign is the signal. A
|
|
662
603
|
kit-wide negative text role needs a new token AND a colour that
|
|
663
604
|
clears 4.5:1 on a card — a human call, not a call-site one. */}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
605
|
+
{ACTIVITY.map((item) => (
|
|
606
|
+
<Item
|
|
607
|
+
key={item.id}
|
|
608
|
+
startContent={
|
|
609
|
+
<Center
|
|
610
|
+
width={32}
|
|
611
|
+
height={32}
|
|
612
|
+
aria-hidden="true"
|
|
613
|
+
style={{
|
|
614
|
+
// Center supplies the 32px box and the centering; only the
|
|
615
|
+
// paint stays here. Surface, not muted: secondary
|
|
616
|
+
// #9AA1BC is 3.57:1 on muted #44475A, under the 4.5:1 text
|
|
617
|
+
// floor. The chip is aria-hidden decorative art so it
|
|
618
|
+
// clears the 3:1 non-text bar as-is, but the surface fill
|
|
619
|
+
// carries the same 4.60:1 pairing at no cost.
|
|
620
|
+
borderRadius: "var(--radius-element)",
|
|
621
|
+
backgroundColor: "var(--color-background-surface)",
|
|
622
|
+
color: "var(--color-text-secondary)",
|
|
623
|
+
flexShrink: 0,
|
|
624
|
+
}}
|
|
625
|
+
>
|
|
626
|
+
{item.icon}
|
|
627
|
+
</Center>
|
|
628
|
+
}
|
|
629
|
+
label={item.label}
|
|
630
|
+
description={item.detail}
|
|
631
|
+
endContent={
|
|
632
|
+
<Text
|
|
633
|
+
type="body"
|
|
634
|
+
weight="semibold"
|
|
635
|
+
hasTabularNumbers
|
|
636
|
+
color={item.amount < 0 ? "secondary" : "primary"}
|
|
637
|
+
>
|
|
638
|
+
{formatAmount(item.amount)}
|
|
639
|
+
</Text>
|
|
640
|
+
}
|
|
641
|
+
href="#/templates/theme-showcase"
|
|
642
|
+
/>
|
|
643
|
+
))}
|
|
644
|
+
</VStack>
|
|
645
|
+
</VStack>
|
|
646
|
+
</Card>
|
|
647
|
+
);
|
|
705
648
|
}
|
|
706
649
|
|
|
707
|
-
type TagSpec = {label: string; variant: ShowcaseBadgeVariant};
|
|
650
|
+
type TagSpec = { label: string; variant: ShowcaseBadgeVariant };
|
|
708
651
|
|
|
709
652
|
export interface InventoryRow extends Record<string, unknown> {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
653
|
+
id: string;
|
|
654
|
+
name: string;
|
|
655
|
+
meta: string;
|
|
656
|
+
available: number;
|
|
657
|
+
location: string;
|
|
658
|
+
tags: TagSpec[];
|
|
659
|
+
hue: SceneHue;
|
|
660
|
+
selected: boolean;
|
|
718
661
|
}
|
|
719
662
|
|
|
720
663
|
const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
664
|
+
{
|
|
665
|
+
id: "a",
|
|
666
|
+
name: "Moonphase Watch",
|
|
667
|
+
meta: "Steel case, moonphase dial",
|
|
668
|
+
available: 42,
|
|
669
|
+
location: "Roost 3",
|
|
670
|
+
tags: [{ label: "New", variant: "cyan" }],
|
|
671
|
+
hue: "var(--dracula-comment)",
|
|
672
|
+
selected: false,
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
id: "b",
|
|
676
|
+
name: "Night-Owl Headphones",
|
|
677
|
+
meta: "ANC, 30hr battery",
|
|
678
|
+
available: 128,
|
|
679
|
+
location: "Roost 1",
|
|
680
|
+
tags: [{ label: "Popular", variant: "green" }],
|
|
681
|
+
hue: "var(--dracula-cyan)",
|
|
682
|
+
selected: true,
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
id: "c",
|
|
686
|
+
name: "Coven Canvas Backpack",
|
|
687
|
+
meta: "Waxed canvas, 25L",
|
|
688
|
+
available: 63,
|
|
689
|
+
location: "Roost 2",
|
|
690
|
+
tags: [{ label: "Limited", variant: "yellow" }],
|
|
691
|
+
hue: "var(--dracula-yellow)",
|
|
692
|
+
selected: false,
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
id: "d",
|
|
696
|
+
name: "Night Market Wallet",
|
|
697
|
+
meta: "Full-grain, RFID blocking",
|
|
698
|
+
available: 15,
|
|
699
|
+
location: "Roost 4",
|
|
700
|
+
tags: [{ label: "Leather", variant: "yellow" }],
|
|
701
|
+
hue: "var(--dracula-orange)",
|
|
702
|
+
selected: true,
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
id: "e",
|
|
706
|
+
name: "Midnight Tumbler",
|
|
707
|
+
meta: "Vacuum insulated, 16oz",
|
|
708
|
+
available: 87,
|
|
709
|
+
location: "Roost 5",
|
|
710
|
+
tags: [{ label: "Drinkware", variant: "yellow" }],
|
|
711
|
+
hue: "var(--dracula-pink)",
|
|
712
|
+
selected: false,
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
id: "f",
|
|
716
|
+
name: "Belfry Throw",
|
|
717
|
+
meta: "Heavyweight, oat",
|
|
718
|
+
available: 24,
|
|
719
|
+
location: "Roost 6",
|
|
720
|
+
tags: [{ label: "Home", variant: "yellow" }],
|
|
721
|
+
hue: "var(--dracula-green)",
|
|
722
|
+
selected: true,
|
|
723
|
+
},
|
|
781
724
|
];
|
|
782
725
|
|
|
783
726
|
const LOW_STOCK_THRESHOLD = 25;
|
|
784
727
|
|
|
785
|
-
function SelectCell({row}: {row: InventoryRow}) {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
728
|
+
function SelectCell({ row }: { row: InventoryRow }) {
|
|
729
|
+
return (
|
|
730
|
+
<CheckboxInput
|
|
731
|
+
label={`Select ${row.name}`}
|
|
732
|
+
isLabelHidden
|
|
733
|
+
value={row.selected}
|
|
734
|
+
onChange={() => {}}
|
|
735
|
+
/>
|
|
736
|
+
);
|
|
794
737
|
}
|
|
795
738
|
|
|
796
|
-
function ItemCell({row}: {row: InventoryRow}) {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
739
|
+
function ItemCell({ row }: { row: InventoryRow }) {
|
|
740
|
+
return (
|
|
741
|
+
<HStack gap={3} vAlign="center">
|
|
742
|
+
<ProductSwatch accent={row.hue} label={row.name} />
|
|
743
|
+
<VStack gap={0} style={{ minWidth: 0 }}>
|
|
744
|
+
<Text type="body" weight="semibold">
|
|
745
|
+
{row.name}
|
|
746
|
+
</Text>
|
|
747
|
+
<Text type="supporting" color="secondary">
|
|
748
|
+
{row.meta}
|
|
749
|
+
</Text>
|
|
750
|
+
</VStack>
|
|
751
|
+
</HStack>
|
|
752
|
+
);
|
|
810
753
|
}
|
|
811
754
|
|
|
812
|
-
function TagsCell({row}: {row: InventoryRow}) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
755
|
+
function TagsCell({ row }: { row: InventoryRow }) {
|
|
756
|
+
return (
|
|
757
|
+
<HStack gap={1} wrap="wrap" hAlign="end">
|
|
758
|
+
{row.tags.map((tag) => (
|
|
759
|
+
<Badge key={tag.label} label={tag.label} variant={tag.variant} />
|
|
760
|
+
))}
|
|
761
|
+
</HStack>
|
|
762
|
+
);
|
|
820
763
|
}
|
|
821
764
|
|
|
822
765
|
function ActionsCell() {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
766
|
+
return (
|
|
767
|
+
<MoreMenu
|
|
768
|
+
label="Row actions"
|
|
769
|
+
size="sm"
|
|
770
|
+
items={[
|
|
771
|
+
{ label: "Edit" },
|
|
772
|
+
{ label: "Duplicate" },
|
|
773
|
+
{ label: "Move to…" },
|
|
774
|
+
{ type: "divider" },
|
|
775
|
+
{ label: "Delete" },
|
|
776
|
+
]}
|
|
777
|
+
/>
|
|
778
|
+
);
|
|
836
779
|
}
|
|
837
780
|
|
|
838
781
|
// The full column set needs 64+80+100+100+80+64 = 488px of grid and the card
|
|
@@ -841,215 +784,192 @@ function ActionsCell() {
|
|
|
841
784
|
// control (no bulk action bar acts on it) and the two metadata columns, which
|
|
842
785
|
// needs 244px. Table keeps its own scroll wrapper either way; the point is that
|
|
843
786
|
// a row should not have to be read sideways.
|
|
844
|
-
const NARROW_TABLE_QUERY =
|
|
787
|
+
const NARROW_TABLE_QUERY = "(max-width: 600px)";
|
|
845
788
|
const NARROW_COLUMN_KEYS: Record<string, true> = {
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
789
|
+
select: true,
|
|
790
|
+
location: true,
|
|
791
|
+
tags: true,
|
|
849
792
|
};
|
|
850
793
|
|
|
851
794
|
const INVENTORY_COLUMNS: TableColumn<InventoryRow>[] = [
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
795
|
+
{
|
|
796
|
+
key: "select",
|
|
797
|
+
header: "",
|
|
798
|
+
// Wide enough that the control + the theme's cell padding (up to
|
|
799
|
+
// --spacing-4 = 16px/side on spacious density) fit inside the cell,
|
|
800
|
+
// so the control's hover background doesn't overflow toward the
|
|
801
|
+
// card's clipped (rounded) edge on larger-padding themes.
|
|
802
|
+
width: pixel(64),
|
|
803
|
+
renderCell: (row) => <SelectCell row={row} />,
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
key: "item",
|
|
807
|
+
header: "Item",
|
|
808
|
+
// Lower min-width (default 120) so the table fits its container on
|
|
809
|
+
// larger-spacing themes instead of overflowing the actions column.
|
|
810
|
+
width: proportional(3, { minWidth: 80 }),
|
|
811
|
+
renderCell: (row) => <ItemCell row={row} />,
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
key: "available",
|
|
815
|
+
header: "Available",
|
|
816
|
+
width: pixel(100),
|
|
817
|
+
renderCell: (row) => (
|
|
818
|
+
<Text type="body" hasTabularNumbers>
|
|
819
|
+
{row.available}
|
|
820
|
+
</Text>
|
|
821
|
+
),
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
key: "location",
|
|
825
|
+
header: "Location",
|
|
826
|
+
width: pixel(100),
|
|
827
|
+
renderCell: (row) => <Text type="body">{row.location}</Text>,
|
|
828
|
+
},
|
|
829
|
+
{
|
|
830
|
+
key: "tags",
|
|
831
|
+
header: "Tags",
|
|
832
|
+
width: proportional(2, { minWidth: 80 }),
|
|
833
|
+
align: "end",
|
|
834
|
+
renderCell: (row) => <TagsCell row={row} />,
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
key: "actions",
|
|
838
|
+
header: "",
|
|
839
|
+
// Match the select column: fit the sm more-menu button + cell
|
|
840
|
+
// padding so its hover background stays clear of the card's
|
|
841
|
+
// clipped rounded edge across themes.
|
|
842
|
+
width: pixel(64),
|
|
843
|
+
align: "end",
|
|
844
|
+
renderCell: () => <ActionsCell />,
|
|
845
|
+
},
|
|
903
846
|
];
|
|
904
847
|
|
|
905
848
|
const NARROW_INVENTORY_COLUMNS = INVENTORY_COLUMNS.filter(
|
|
906
|
-
|
|
849
|
+
(column) => !NARROW_COLUMN_KEYS[column.key],
|
|
907
850
|
);
|
|
908
851
|
|
|
909
|
-
function InventoryCard({inventory}: {inventory: InventoryRow[]}) {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
<Button
|
|
1021
|
-
variant="ghost"
|
|
1022
|
-
size="sm"
|
|
1023
|
-
isIconOnly
|
|
1024
|
-
label="Grid view"
|
|
1025
|
-
tooltip="Grid view"
|
|
1026
|
-
icon={<LayoutGrid size={18} />}
|
|
1027
|
-
/>
|
|
1028
|
-
</HStack>
|
|
1029
|
-
</HStack>
|
|
1030
|
-
|
|
1031
|
-
{lowStockCount > 0 && (
|
|
1032
|
-
<VStack gap={0} paddingInline={6} paddingBlockEnd={4}>
|
|
1033
|
-
<Banner
|
|
1034
|
-
status="warning"
|
|
1035
|
-
title={lowStockCount + ' items are running low'}
|
|
1036
|
-
/>
|
|
1037
|
-
</VStack>
|
|
1038
|
-
)}
|
|
1039
|
-
|
|
1040
|
-
{/* Inset the table by --spacing-6 (the card is padding={0}) so its edge
|
|
852
|
+
function InventoryCard({ inventory }: { inventory: InventoryRow[] }) {
|
|
853
|
+
const isNarrow = useMediaQuery(NARROW_TABLE_QUERY);
|
|
854
|
+
const lowStockCount = inventory.filter((row) => row.available < LOW_STOCK_THRESHOLD).length;
|
|
855
|
+
return (
|
|
856
|
+
<Card
|
|
857
|
+
padding={0}
|
|
858
|
+
style={{
|
|
859
|
+
backgroundColor: "var(--color-background-surface)",
|
|
860
|
+
color: "var(--color-text-primary)",
|
|
861
|
+
overflow: "hidden",
|
|
862
|
+
}}
|
|
863
|
+
>
|
|
864
|
+
<HStack hAlign="between" vAlign="center" paddingBlock={6} paddingInline={6}>
|
|
865
|
+
<Heading level={2}>Inventory</Heading>
|
|
866
|
+
<Button label="Add item" variant="primary" icon={<Plus size={16} />} />
|
|
867
|
+
</HStack>
|
|
868
|
+
|
|
869
|
+
<Divider variant="subtle" />
|
|
870
|
+
|
|
871
|
+
<HStack
|
|
872
|
+
gap={3}
|
|
873
|
+
vAlign="center"
|
|
874
|
+
hAlign="between"
|
|
875
|
+
width="100%"
|
|
876
|
+
paddingBlock={4}
|
|
877
|
+
paddingInline={6}
|
|
878
|
+
style={{ overflowX: "auto" }}
|
|
879
|
+
>
|
|
880
|
+
<HStack gap={2} vAlign="center" style={{ flex: 1, minWidth: 0 }}>
|
|
881
|
+
<TextInput
|
|
882
|
+
label="Search inventory"
|
|
883
|
+
isLabelHidden
|
|
884
|
+
placeholder="Type and hit enter…"
|
|
885
|
+
value=""
|
|
886
|
+
onChange={() => {}}
|
|
887
|
+
startIcon={<Search size={16} />}
|
|
888
|
+
controlStyle={{ flex: 1, minWidth: 0, maxWidth: 240 }}
|
|
889
|
+
/>
|
|
890
|
+
<OverflowList
|
|
891
|
+
gap={2}
|
|
892
|
+
overflowRenderer={() => (
|
|
893
|
+
<Button label="Filters" variant="ghost" size="sm" icon={<Tag size={16} />} />
|
|
894
|
+
)}
|
|
895
|
+
>
|
|
896
|
+
<Selector
|
|
897
|
+
label="Categories"
|
|
898
|
+
isLabelHidden
|
|
899
|
+
placeholder="Categories"
|
|
900
|
+
size="sm"
|
|
901
|
+
startIcon={<Folder size={16} />}
|
|
902
|
+
value={undefined}
|
|
903
|
+
onChange={() => {}}
|
|
904
|
+
options={["Wearables", "Audio", "Bags", "Drinkware", "Home"]}
|
|
905
|
+
/>
|
|
906
|
+
<Selector
|
|
907
|
+
label="Locations"
|
|
908
|
+
isLabelHidden
|
|
909
|
+
placeholder="Locations"
|
|
910
|
+
size="sm"
|
|
911
|
+
startIcon={<MapPin size={16} />}
|
|
912
|
+
value={undefined}
|
|
913
|
+
onChange={() => {}}
|
|
914
|
+
options={["Roost 1", "Roost 2", "Roost 3", "Roost 4", "Roost 5", "Roost 6"]}
|
|
915
|
+
/>
|
|
916
|
+
<Selector
|
|
917
|
+
label="Tags"
|
|
918
|
+
isLabelHidden
|
|
919
|
+
placeholder="Tags"
|
|
920
|
+
size="sm"
|
|
921
|
+
startIcon={<Tag size={16} />}
|
|
922
|
+
value={undefined}
|
|
923
|
+
onChange={() => {}}
|
|
924
|
+
options={["New", "Popular", "Limited", "Leather", "Drinkware", "Home"]}
|
|
925
|
+
/>
|
|
926
|
+
</OverflowList>
|
|
927
|
+
</HStack>
|
|
928
|
+
<HStack gap={1} vAlign="center">
|
|
929
|
+
<Button
|
|
930
|
+
variant="ghost"
|
|
931
|
+
size="sm"
|
|
932
|
+
isIconOnly
|
|
933
|
+
label="List view"
|
|
934
|
+
tooltip="List view"
|
|
935
|
+
icon={<List size={18} />}
|
|
936
|
+
/>
|
|
937
|
+
<Button
|
|
938
|
+
variant="ghost"
|
|
939
|
+
size="sm"
|
|
940
|
+
isIconOnly
|
|
941
|
+
label="Grid view"
|
|
942
|
+
tooltip="Grid view"
|
|
943
|
+
icon={<LayoutGrid size={18} />}
|
|
944
|
+
/>
|
|
945
|
+
</HStack>
|
|
946
|
+
</HStack>
|
|
947
|
+
|
|
948
|
+
{lowStockCount > 0 && (
|
|
949
|
+
<VStack gap={0} paddingInline={6} paddingBlockEnd={4}>
|
|
950
|
+
<Banner
|
|
951
|
+
status="warning"
|
|
952
|
+
// Core 0.7.0 stopped deriving the live-region role from
|
|
953
|
+
// `status`, so this banner states its own. It appears when
|
|
954
|
+
// the count crosses the threshold, and a screen reader must
|
|
955
|
+
// hear it.
|
|
956
|
+
role="status"
|
|
957
|
+
title={`${lowStockCount} items are running low`}
|
|
958
|
+
/>
|
|
959
|
+
</VStack>
|
|
960
|
+
)}
|
|
961
|
+
|
|
962
|
+
{/* Inset the table by --spacing-6 (the card is padding={0}) so its edge
|
|
1041
963
|
lines up with the header/filter row in every theme's spacing scale. */}
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
</Card>
|
|
1054
|
-
);
|
|
964
|
+
<VStack gap={0} paddingInline={6} paddingBlockEnd={2}>
|
|
965
|
+
<Table<InventoryRow>
|
|
966
|
+
data={inventory}
|
|
967
|
+
columns={isNarrow ? NARROW_INVENTORY_COLUMNS : INVENTORY_COLUMNS}
|
|
968
|
+
density="spacious"
|
|
969
|
+
dividers="rows"
|
|
970
|
+
hasHover
|
|
971
|
+
/>
|
|
972
|
+
</VStack>
|
|
973
|
+
</Card>
|
|
974
|
+
);
|
|
1055
975
|
}
|