@astryxdesign/cli 0.7.11 → 0.7.12-canary.ccb4a6d
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/api/build/kit/rank.mjs +21 -4
- package/api/build/kit/words.d.mts +1 -0
- package/api/build/kit/words.mjs +1 -1
- package/api/icon/_adapter.d.mts +3 -2
- package/api/icon/_adapter.mjs +8 -2
- package/api/icon/_authoring.d.mts +1 -0
- package/api/icon/_authoring.mjs +586 -66
- package/api/icon/_default-look.d.mts +155 -0
- package/api/icon/_default-look.mjs +474 -0
- package/api/icon/_records.d.mts +1 -1
- package/api/icon/_records.mjs +1 -0
- package/api/icon/add/add.mjs +224 -38
- package/api/icon/icon.type.d.mts +22 -2
- package/api/icon/icon.type.mjs +6 -2
- package/api/icon/iconAdd.doc.mjs +2 -2
- package/api/icon/iconNewDefinition.doc.mjs +3 -2
- package/api/icon/iconNewSet.doc.mjs +4 -4
- package/api/icon/search/search.mjs +10 -9
- package/api/integration/add-helpers.d.mts +11 -1
- package/api/integration/add-helpers.mjs +5 -2
- package/api/integration/authoring-checks.d.mts +0 -2
- package/api/integration/authoring-checks.mjs +1 -6
- package/api/logger.mjs +2 -1
- package/api/swizzle/_adapter.d.mts +1 -0
- package/api/swizzle/_adapter.mjs +4 -1
- package/api/swizzle/_moduleGraph.d.mts +171 -0
- package/api/swizzle/_moduleGraph.mjs +795 -0
- package/api/swizzle/copy/copy.d.mts +2 -43
- package/api/swizzle/copy/copy.mjs +195 -314
- package/api/swizzle/swizzle.d.mts +0 -2
- package/api/swizzle/swizzle.doc.mjs +9 -4
- package/api/swizzle/swizzle.mjs +3 -7
- package/api/swizzle/swizzle.type.d.mts +2 -1
- package/api/swizzle/swizzle.type.mjs +3 -2
- package/assets/discover/official-packages.json +20 -0
- package/assets/docs/tree/icon-sets.doc.mjs +11 -5
- package/assets/templates/blocks/components/Table/TableRowIndexTable.doc.mjs +1 -1
- package/assets/templates/blocks/components/Table/TableRowIndexTable.tsx +2 -2
- package/assets/templates/pages/course-detail/page.tsx +699 -0
- package/assets/templates/pages/course-detail/template.doc.mjs +37 -0
- package/assets/templates/pages/file-explorer/page.tsx +103 -79
- package/assets/templates/pages/table-grouped/page.tsx +160 -232
- package/assets/templates/pages/table-grouped/template.doc.mjs +0 -2
- package/assets/templates/pages/theme-showcase/page.tsx +724 -792
- package/assets/templates/pages/theme-showcase/template.doc.mjs +4 -5
- package/authoring/doctypes/template/type.ts +1 -0
- package/clients/cli/commands/experimental/icon.mjs +31 -10
- package/clients/cli/commands/experimental-icon-add.doc.mjs +1 -1
- package/clients/cli/commands/experimental-icon-new-definition.doc.mjs +2 -1
- package/clients/cli/commands/experimental-icon-new-set.doc.mjs +2 -2
- package/clients/cli/commands/swizzle.doc.mjs +8 -3
- package/clients/cli/commands/swizzle.mjs +21 -9
- package/foundation/discovery/component-loader.d.mts +1 -1
- package/foundation/discovery/doc-types.mjs +9 -2
- package/foundation/discovery/icon-ranking.d.mts +27 -3
- package/foundation/discovery/icon-ranking.mjs +125 -8
- package/foundation/discovery/icon-set-checks.mjs +44 -35
- package/foundation/discovery/icon-set-discovery.d.mts +13 -0
- package/foundation/discovery/icon-set-discovery.mjs +25 -0
- package/foundation/discovery/icon-set-writer.d.mts +238 -16
- package/foundation/discovery/icon-set-writer.mjs +676 -79
- package/foundation/discovery/module-exports.d.mts +3 -2
- package/foundation/discovery/module-exports.mjs +167 -37
- package/foundation/fs/paths.d.mts +2 -1
- package/foundation/fs/paths.mjs +3 -4
- package/foundation/text/shell-arg.d.mts +14 -0
- package/foundation/text/shell-arg.mjs +17 -0
- package/package.json +6 -6
|
@@ -2,7 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
'use client';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* @file Theme comparison shell and embeddable preview.
|
|
7
|
+
* @input Optional product images, product descriptions, and inventory fixtures
|
|
8
|
+
* @output One responsive showcase built from Astryx components
|
|
9
|
+
* @position Theme preview shared by the sandbox, docsite, and playground
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {type CSSProperties, useRef, useState} from 'react';
|
|
6
13
|
import {
|
|
7
14
|
Plus,
|
|
8
15
|
Search,
|
|
@@ -23,11 +30,20 @@ import {
|
|
|
23
30
|
User,
|
|
24
31
|
} from 'lucide-react';
|
|
25
32
|
import {Text, Heading} from '@astryxdesign/core/Text';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
import {
|
|
34
|
+
VStack,
|
|
35
|
+
HStack,
|
|
36
|
+
Layout,
|
|
37
|
+
LayoutContent,
|
|
38
|
+
LayoutHeader,
|
|
39
|
+
LayoutFooter,
|
|
40
|
+
} from '@astryxdesign/core/Layout';
|
|
41
|
+
import {StackItem} from '@astryxdesign/core/Stack';
|
|
42
|
+
import {AppShell, useAppShellMobile} from '@astryxdesign/core/AppShell';
|
|
43
|
+
import {Grid} from '@astryxdesign/core/Grid';
|
|
29
44
|
import {Card} from '@astryxdesign/core/Card';
|
|
30
45
|
import {Button} from '@astryxdesign/core/Button';
|
|
46
|
+
import {Icon} from '@astryxdesign/core/Icon';
|
|
31
47
|
import {Link} from '@astryxdesign/core/Link';
|
|
32
48
|
import {Badge} from '@astryxdesign/core/Badge';
|
|
33
49
|
import {Banner} from '@astryxdesign/core/Banner';
|
|
@@ -44,7 +60,7 @@ import {Center} from '@astryxdesign/core/Center';
|
|
|
44
60
|
import {Section} from '@astryxdesign/core/Section';
|
|
45
61
|
import {AspectRatio} from '@astryxdesign/core/AspectRatio';
|
|
46
62
|
import {NumberInput} from '@astryxdesign/core/NumberInput';
|
|
47
|
-
import {
|
|
63
|
+
import {ScrollableArea} from '@astryxdesign/core/ScrollableArea';
|
|
48
64
|
import {TopNav, TopNavHeading, TopNavItem} from '@astryxdesign/core/TopNav';
|
|
49
65
|
import {
|
|
50
66
|
ChatComposer,
|
|
@@ -52,204 +68,17 @@ import {
|
|
|
52
68
|
ChatMessageBubble,
|
|
53
69
|
ChatMessageList,
|
|
54
70
|
ChatSystemMessage,
|
|
71
|
+
useChatNewMessages,
|
|
72
|
+
useChatStreamScroll,
|
|
55
73
|
} from '@astryxdesign/core/Chat';
|
|
56
74
|
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
// compiled builds and in the live playground preview alike.
|
|
75
|
+
// StackItem has no wrapping basis; Stack has no body variant.
|
|
76
|
+
// LayoutContent has no height cap; keep the message viewport bounded.
|
|
60
77
|
const styles: Record<string, CSSProperties> = {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
borderColor: 'transparent',
|
|
66
|
-
},
|
|
67
|
-
checkoutStack: {
|
|
68
|
-
minWidth: 0,
|
|
69
|
-
width: '100%',
|
|
70
|
-
},
|
|
71
|
-
paymentCardContent: {
|
|
72
|
-
minWidth: 0,
|
|
73
|
-
width: '100%',
|
|
74
|
-
textAlign: 'center',
|
|
75
|
-
wordBreak: 'break-word',
|
|
76
|
-
},
|
|
77
|
-
inventoryCard: {
|
|
78
|
-
backgroundColor: 'var(--color-background-surface)',
|
|
79
|
-
color: 'var(--color-text-primary)',
|
|
80
|
-
overflow: 'hidden',
|
|
81
|
-
},
|
|
82
|
-
inventoryHeader: {
|
|
83
|
-
paddingBlock: 'var(--spacing-6)',
|
|
84
|
-
paddingInline: 'var(--spacing-6)',
|
|
85
|
-
},
|
|
86
|
-
inventoryFilterRow: {
|
|
87
|
-
paddingBlock: 'var(--spacing-4)',
|
|
88
|
-
paddingInline: 'var(--spacing-6)',
|
|
89
|
-
width: '100%',
|
|
90
|
-
overflowX: 'auto' as const,
|
|
91
|
-
},
|
|
92
|
-
// Inset the table by --spacing-6 (the card is padding={0}) so its edge lines
|
|
93
|
-
// up with the header/filter row in every theme's spacing scale.
|
|
94
|
-
inventoryTableWrap: {
|
|
95
|
-
paddingInline: 'var(--spacing-6)',
|
|
96
|
-
paddingBlockEnd: 'var(--spacing-2)',
|
|
97
|
-
},
|
|
98
|
-
searchInput: {
|
|
99
|
-
flex: 1,
|
|
100
|
-
minWidth: 0,
|
|
101
|
-
maxWidth: 240,
|
|
102
|
-
},
|
|
103
|
-
activityCard: {
|
|
104
|
-
backgroundColor: 'var(--color-background-surface)',
|
|
105
|
-
color: 'var(--color-text-primary)',
|
|
106
|
-
minWidth: 0,
|
|
107
|
-
height: '100%',
|
|
108
|
-
},
|
|
109
|
-
chatCard: {
|
|
110
|
-
backgroundColor: 'var(--color-background-surface)',
|
|
111
|
-
color: 'var(--color-text-primary)',
|
|
112
|
-
minWidth: 0,
|
|
113
|
-
overflow: 'hidden',
|
|
114
|
-
display: 'flex',
|
|
115
|
-
flexDirection: 'column' as const,
|
|
116
|
-
},
|
|
117
|
-
chatHeader: {
|
|
118
|
-
paddingBlock: 'var(--spacing-4)',
|
|
119
|
-
paddingInline: 'var(--spacing-4)',
|
|
120
|
-
},
|
|
121
|
-
activityCardStack: {
|
|
122
|
-
height: '100%',
|
|
123
|
-
},
|
|
124
|
-
activityListFade: {
|
|
125
|
-
flex: 1,
|
|
126
|
-
minHeight: 0,
|
|
127
|
-
overflow: 'hidden',
|
|
128
|
-
maskImage:
|
|
129
|
-
'linear-gradient(to bottom, black calc(100% - 48px), transparent)',
|
|
130
|
-
WebkitMaskImage:
|
|
131
|
-
'linear-gradient(to bottom, black calc(100% - 48px), transparent)',
|
|
132
|
-
marginInline: 'calc(var(--spacing-2) * -1)',
|
|
133
|
-
},
|
|
134
|
-
content: {
|
|
135
|
-
maxWidth: 960,
|
|
136
|
-
marginInline: 'auto',
|
|
137
|
-
minWidth: 0,
|
|
138
|
-
},
|
|
139
|
-
contentFluid: {
|
|
140
|
-
maxWidth: 880,
|
|
141
|
-
},
|
|
142
|
-
heroText: {
|
|
143
|
-
textAlign: 'center' as const,
|
|
144
|
-
maxWidth: 560,
|
|
145
|
-
},
|
|
146
|
-
centerText: {
|
|
147
|
-
textAlign: 'center',
|
|
148
|
-
},
|
|
149
|
-
cardStack: {
|
|
150
|
-
height: '100%',
|
|
151
|
-
},
|
|
152
|
-
cardDescription: {
|
|
153
|
-
flex: 1,
|
|
154
|
-
},
|
|
155
|
-
quantityInput: {
|
|
156
|
-
flexShrink: 0,
|
|
157
|
-
},
|
|
158
|
-
// `auto` basis so the button wraps to its own line rather than ellipsizing.
|
|
159
|
-
cartButton: {
|
|
160
|
-
flex: '1 1 auto',
|
|
161
|
-
minWidth: 0,
|
|
162
|
-
},
|
|
163
|
-
cartRow: {
|
|
164
|
-
width: '100%',
|
|
165
|
-
minWidth: 0,
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
// Styles applied directly to plain DOM elements via the `style` prop.
|
|
170
|
-
// Plain inline styles so they render with no CSS compiler. All are static
|
|
171
|
-
// (no media/pseudo variants), so inline styles reproduce them exactly.
|
|
172
|
-
const inlineStyles: Record<string, CSSProperties> = {
|
|
173
|
-
inventoryBannerWrap: {
|
|
174
|
-
paddingInline: 'var(--spacing-6)',
|
|
175
|
-
paddingBottom: 'var(--spacing-4)',
|
|
176
|
-
},
|
|
177
|
-
thumbnail: {
|
|
178
|
-
width: 40,
|
|
179
|
-
height: 40,
|
|
180
|
-
borderRadius: 'var(--radius-element)',
|
|
181
|
-
objectFit: 'cover',
|
|
182
|
-
display: 'block',
|
|
183
|
-
flexShrink: 0,
|
|
184
|
-
},
|
|
185
|
-
thumbnailFallback: {
|
|
186
|
-
width: 40,
|
|
187
|
-
height: 40,
|
|
188
|
-
borderRadius: 'var(--radius-element)',
|
|
189
|
-
backgroundColor: 'var(--color-background-muted)',
|
|
190
|
-
color: 'var(--color-text-secondary)',
|
|
191
|
-
display: 'flex',
|
|
192
|
-
alignItems: 'center',
|
|
193
|
-
justifyContent: 'center',
|
|
194
|
-
fontSize: 'var(--font-size-sm)',
|
|
195
|
-
fontWeight: 600,
|
|
196
|
-
flexShrink: 0,
|
|
197
|
-
},
|
|
198
|
-
kpiValue: {
|
|
199
|
-
fontSize: 24,
|
|
200
|
-
fontWeight: 700,
|
|
201
|
-
lineHeight: 1.1,
|
|
202
|
-
color: 'var(--color-text-primary)',
|
|
203
|
-
fontFamily: 'var(--font-family-heading)',
|
|
204
|
-
letterSpacing: '-0.01em',
|
|
205
|
-
},
|
|
206
|
-
chatBody: {
|
|
207
|
-
flex: 1,
|
|
208
|
-
minHeight: 0,
|
|
209
|
-
overflow: 'hidden',
|
|
210
|
-
},
|
|
211
|
-
chatSuggestions: {
|
|
212
|
-
paddingInline: 'var(--spacing-4)',
|
|
213
|
-
paddingBottom: 'var(--spacing-2)',
|
|
214
|
-
},
|
|
215
|
-
chatComposer: {
|
|
216
|
-
paddingInline: 'var(--spacing-4)',
|
|
217
|
-
paddingBottom: 'var(--spacing-4)',
|
|
218
|
-
},
|
|
219
|
-
activityIcon: {
|
|
220
|
-
width: 32,
|
|
221
|
-
height: 32,
|
|
222
|
-
borderRadius: 'var(--radius-full)',
|
|
223
|
-
backgroundColor: 'var(--color-background-muted)',
|
|
224
|
-
color: 'var(--color-text-secondary)',
|
|
225
|
-
display: 'flex',
|
|
226
|
-
alignItems: 'center',
|
|
227
|
-
justifyContent: 'center',
|
|
228
|
-
flexShrink: 0,
|
|
229
|
-
},
|
|
230
|
-
productImage: {
|
|
231
|
-
width: '100%',
|
|
232
|
-
height: '100%',
|
|
233
|
-
objectFit: 'cover',
|
|
234
|
-
},
|
|
235
|
-
cardBody: {
|
|
236
|
-
padding: 'var(--spacing-4)',
|
|
237
|
-
flex: 1,
|
|
238
|
-
display: 'flex',
|
|
239
|
-
flexDirection: 'column',
|
|
240
|
-
},
|
|
241
|
-
// Flex bases, not grid tracks: the checkout form needs a width floor, and the
|
|
242
|
-
// chat wraps to its own row rather than squeezing it under one.
|
|
243
|
-
checkoutColumn: {
|
|
244
|
-
flex: '1 1 300px',
|
|
245
|
-
minWidth: 0,
|
|
246
|
-
display: 'grid',
|
|
247
|
-
},
|
|
248
|
-
chatColumn: {
|
|
249
|
-
flex: '2 1 520px',
|
|
250
|
-
minWidth: 0,
|
|
251
|
-
display: 'grid',
|
|
252
|
-
},
|
|
78
|
+
preview: {backgroundColor: 'var(--color-background-body)'},
|
|
79
|
+
checkoutColumn: {flex: '1 1 300px'},
|
|
80
|
+
chatColumn: {flex: '2 1 520px'},
|
|
81
|
+
chatViewport: {maxHeight: 720},
|
|
253
82
|
};
|
|
254
83
|
|
|
255
84
|
const PRODUCT_IMAGE_KEYS = ['watch', 'headphones', 'backpack'];
|
|
@@ -294,8 +123,6 @@ const DEFAULT_PRODUCTS: ProductSpec[] = [
|
|
|
294
123
|
},
|
|
295
124
|
];
|
|
296
125
|
|
|
297
|
-
// Neutral product photos, served from the shared astryx asset CDN so the
|
|
298
|
-
// scaffolded template renders real imagery without needing local public assets.
|
|
299
126
|
const DEFAULT_IMAGES: Record<string, string> = {
|
|
300
127
|
watch: '/template-assets/Neutral-Watch.png',
|
|
301
128
|
headphones: '/template-assets/Neutral-Headphones.png',
|
|
@@ -314,39 +141,106 @@ export interface ThemeShowcaseProps {
|
|
|
314
141
|
inventory?: InventoryRow[];
|
|
315
142
|
}
|
|
316
143
|
|
|
317
|
-
// Default export is the route page (sandbox renders this as a Next.js page, so
|
|
318
|
-
// it must take no props / satisfy PageProps). It renders the store with the
|
|
319
|
-
// neutral defaults. Consumers that need per-theme content import the named
|
|
320
|
-
// `ThemeShowcaseStore` below and pass images/products/inventory.
|
|
321
144
|
export default function ThemeShowcase() {
|
|
322
|
-
return
|
|
145
|
+
return (
|
|
146
|
+
<AppShell
|
|
147
|
+
height="auto"
|
|
148
|
+
variant="surface"
|
|
149
|
+
contentPadding={0}
|
|
150
|
+
data-theme-preview="true"
|
|
151
|
+
topNav={<ShowcaseNavigation />}>
|
|
152
|
+
<ShowcaseContent headingLevel={1} />
|
|
153
|
+
</AppShell>
|
|
154
|
+
);
|
|
323
155
|
}
|
|
324
156
|
|
|
325
|
-
|
|
157
|
+
/** Embeddable comparison surface: the host owns its application shell. */
|
|
158
|
+
export function ThemeShowcaseStore(props: ThemeShowcaseProps = {}) {
|
|
159
|
+
return (
|
|
160
|
+
<VStack gap={0} data-theme-preview="true" style={styles.preview}>
|
|
161
|
+
<ShowcaseNavigation />
|
|
162
|
+
<ShowcaseContent {...props} />
|
|
163
|
+
</VStack>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function ShowcaseContent({
|
|
326
168
|
images = DEFAULT_IMAGES,
|
|
327
169
|
products = DEFAULT_PRODUCTS,
|
|
328
170
|
inventory = DEFAULT_INVENTORY,
|
|
329
|
-
|
|
171
|
+
headingLevel = 2,
|
|
172
|
+
}: ThemeShowcaseProps & {headingLevel?: 1 | 2} = {}) {
|
|
330
173
|
const {isMobile} = useAppShellMobile();
|
|
331
174
|
return (
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
padding
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
</
|
|
349
|
-
</
|
|
175
|
+
<Layout height="auto" padding={0}>
|
|
176
|
+
<LayoutContent padding={0} isScrollable={false}>
|
|
177
|
+
<VStack gap={0}>
|
|
178
|
+
<StorePreview
|
|
179
|
+
images={images}
|
|
180
|
+
products={products}
|
|
181
|
+
headingLevel={headingLevel}
|
|
182
|
+
/>
|
|
183
|
+
<Section padding={6} variant="transparent">
|
|
184
|
+
<CardShowcase
|
|
185
|
+
images={images}
|
|
186
|
+
inventory={inventory}
|
|
187
|
+
isMobile={isMobile}
|
|
188
|
+
/>
|
|
189
|
+
</Section>
|
|
190
|
+
</VStack>
|
|
191
|
+
</LayoutContent>
|
|
192
|
+
</Layout>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function ShowcaseNavigation() {
|
|
197
|
+
const {isMobile} = useAppShellMobile();
|
|
198
|
+
return (
|
|
199
|
+
<TopNav
|
|
200
|
+
label="Theme preview navigation"
|
|
201
|
+
heading={<TopNavHeading heading="Studio" />}
|
|
202
|
+
centerContent={
|
|
203
|
+
isMobile ? undefined : (
|
|
204
|
+
<>
|
|
205
|
+
<TopNavItem label="Shop" href="#" isSelected />
|
|
206
|
+
<TopNavItem label="New In" href="#" />
|
|
207
|
+
<TopNavItem label="Stories" href="#" />
|
|
208
|
+
<TopNavItem label="Help" href="#" />
|
|
209
|
+
</>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
endContent={
|
|
213
|
+
<HStack gap={2} vAlign="center">
|
|
214
|
+
<HStack gap={0.5}>
|
|
215
|
+
<Button
|
|
216
|
+
label="Search"
|
|
217
|
+
tooltip="Search"
|
|
218
|
+
variant="ghost"
|
|
219
|
+
isIconOnly
|
|
220
|
+
icon={<Icon icon={Search} size="md" />}
|
|
221
|
+
href="#"
|
|
222
|
+
/>
|
|
223
|
+
<Button
|
|
224
|
+
label="Account"
|
|
225
|
+
tooltip="Account"
|
|
226
|
+
variant="ghost"
|
|
227
|
+
isIconOnly
|
|
228
|
+
icon={<Icon icon={User} size="md" />}
|
|
229
|
+
href="#"
|
|
230
|
+
/>
|
|
231
|
+
<Button
|
|
232
|
+
label="Cart"
|
|
233
|
+
tooltip="Cart"
|
|
234
|
+
variant="ghost"
|
|
235
|
+
isIconOnly
|
|
236
|
+
icon={<Icon icon={ShoppingBag} size="md" />}
|
|
237
|
+
href="#"
|
|
238
|
+
/>
|
|
239
|
+
</HStack>
|
|
240
|
+
<Button label="Sign in" variant="primary" href="#" />
|
|
241
|
+
</HStack>
|
|
242
|
+
}
|
|
243
|
+
/>
|
|
350
244
|
);
|
|
351
245
|
}
|
|
352
246
|
|
|
@@ -359,26 +253,32 @@ function CardShowcase({
|
|
|
359
253
|
inventory: InventoryRow[];
|
|
360
254
|
isMobile: boolean;
|
|
361
255
|
}) {
|
|
362
|
-
const
|
|
363
|
-
|
|
256
|
+
const activity = <LatestActivityCard />;
|
|
364
257
|
return (
|
|
365
258
|
<VStack gap={8}>
|
|
366
259
|
<HStack gap={4} wrap="wrap">
|
|
367
|
-
<
|
|
368
|
-
<CheckoutCard
|
|
369
|
-
</
|
|
370
|
-
<
|
|
260
|
+
<StackItem size="fill" style={styles.checkoutColumn}>
|
|
261
|
+
<CheckoutCard />
|
|
262
|
+
</StackItem>
|
|
263
|
+
<StackItem size="fill" style={styles.chatColumn}>
|
|
371
264
|
<ChatCard />
|
|
372
|
-
</
|
|
265
|
+
</StackItem>
|
|
266
|
+
</HStack>
|
|
267
|
+
<HStack gap={isMobile ? 4 : 0} wrap={isMobile ? 'wrap' : 'nowrap'}>
|
|
268
|
+
<StackItem size="fill">
|
|
269
|
+
<InventoryCard
|
|
270
|
+
key={inventory.map(row => row.name).join('|')}
|
|
271
|
+
images={images}
|
|
272
|
+
inventory={inventory}
|
|
273
|
+
/>
|
|
274
|
+
</StackItem>
|
|
275
|
+
<VStack
|
|
276
|
+
as={StackItem}
|
|
277
|
+
width={isMobile ? '100%' : 280}
|
|
278
|
+
paddingInlineStart={isMobile ? 0 : 4}>
|
|
279
|
+
{activity}
|
|
280
|
+
</VStack>
|
|
373
281
|
</HStack>
|
|
374
|
-
<Grid columns={columns} gap={4}>
|
|
375
|
-
<GridSpan columns={isMobile ? 1 : 3}>
|
|
376
|
-
<InventoryCard images={images} inventory={inventory} />
|
|
377
|
-
</GridSpan>
|
|
378
|
-
<GridSpan columns={1}>
|
|
379
|
-
<LatestActivityCard isMobile={isMobile} />
|
|
380
|
-
</GridSpan>
|
|
381
|
-
</Grid>
|
|
382
282
|
</VStack>
|
|
383
283
|
);
|
|
384
284
|
}
|
|
@@ -386,163 +286,137 @@ function CardShowcase({
|
|
|
386
286
|
function StorePreview({
|
|
387
287
|
images,
|
|
388
288
|
products,
|
|
389
|
-
|
|
289
|
+
headingLevel,
|
|
390
290
|
}: {
|
|
391
291
|
images: Record<string, string>;
|
|
392
292
|
products: ProductSpec[];
|
|
393
|
-
|
|
293
|
+
headingLevel: 1 | 2;
|
|
394
294
|
}) {
|
|
395
295
|
return (
|
|
396
|
-
<
|
|
397
|
-
<
|
|
398
|
-
<
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
<
|
|
408
|
-
</>
|
|
409
|
-
)
|
|
410
|
-
}
|
|
411
|
-
endContent={
|
|
412
|
-
<HStack gap={2} vAlign="center">
|
|
413
|
-
<HStack gap={0.5}>
|
|
414
|
-
<Button
|
|
415
|
-
label="Search"
|
|
416
|
-
tooltip="Search"
|
|
417
|
-
variant="ghost"
|
|
418
|
-
isIconOnly
|
|
419
|
-
icon={<Search size={20} />}
|
|
420
|
-
href="#"
|
|
421
|
-
/>
|
|
422
|
-
<Button
|
|
423
|
-
label="Account"
|
|
424
|
-
tooltip="Account"
|
|
425
|
-
variant="ghost"
|
|
426
|
-
isIconOnly
|
|
427
|
-
icon={<User size={20} />}
|
|
428
|
-
href="#"
|
|
429
|
-
/>
|
|
430
|
-
<Button
|
|
431
|
-
label="Cart"
|
|
432
|
-
tooltip="Cart"
|
|
433
|
-
variant="ghost"
|
|
434
|
-
isIconOnly
|
|
435
|
-
icon={<ShoppingBag size={20} />}
|
|
436
|
-
href="#"
|
|
437
|
-
/>
|
|
438
|
-
</HStack>
|
|
439
|
-
<Button label="Sign in" variant="primary" href="#" />
|
|
440
|
-
</HStack>
|
|
441
|
-
}
|
|
442
|
-
/>
|
|
443
|
-
|
|
444
|
-
<Section padding={6} variant="transparent">
|
|
445
|
-
<VStack gap={10} style={{...styles.content, ...styles.contentFluid}}>
|
|
446
|
-
<Center>
|
|
447
|
-
<VStack gap={4} hAlign="center" style={styles.heroText}>
|
|
448
|
-
<Text type="display-2" color="accent">
|
|
296
|
+
<Section padding={6} variant="transparent">
|
|
297
|
+
<Center>
|
|
298
|
+
<VStack gap={10} width="100%" maxWidth={880}>
|
|
299
|
+
<Center>
|
|
300
|
+
<VStack gap={4} hAlign="center" maxWidth={560}>
|
|
301
|
+
<Heading
|
|
302
|
+
level={1}
|
|
303
|
+
accessibilityLevel={headingLevel}
|
|
304
|
+
type="display-2"
|
|
305
|
+
color="accent"
|
|
306
|
+
justify="center">
|
|
307
|
+
<Text type="inherit" display="block">
|
|
449
308
|
Little joys,
|
|
450
|
-
<br />
|
|
451
|
-
everywhere you go
|
|
452
309
|
</Text>
|
|
453
|
-
<Text type="
|
|
454
|
-
|
|
455
|
-
Turn an ordinary day into something worth remembering.
|
|
310
|
+
<Text type="inherit" display="block">
|
|
311
|
+
everywhere you go
|
|
456
312
|
</Text>
|
|
457
|
-
</
|
|
458
|
-
|
|
313
|
+
</Heading>
|
|
314
|
+
<Text type="body" color="secondary" justify="center">
|
|
315
|
+
We believe the smallest details are the ones that matter most.
|
|
316
|
+
Turn an ordinary day into something worth remembering.
|
|
317
|
+
</Text>
|
|
318
|
+
</VStack>
|
|
319
|
+
</Center>
|
|
320
|
+
<Grid columns={{minWidth: 200, max: 3}} gap={4}>
|
|
321
|
+
{products.map((product, index) => (
|
|
322
|
+
<ProductCard
|
|
323
|
+
key={product.name}
|
|
324
|
+
product={product}
|
|
325
|
+
image={images[PRODUCT_IMAGE_KEYS[index]]}
|
|
326
|
+
/>
|
|
327
|
+
))}
|
|
328
|
+
</Grid>
|
|
329
|
+
</VStack>
|
|
330
|
+
</Center>
|
|
331
|
+
</Section>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
459
334
|
|
|
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
|
-
style={styles.cartButton}
|
|
511
|
-
/>
|
|
512
|
-
</HStack>
|
|
513
|
-
</VStack>
|
|
514
|
-
</div>
|
|
515
|
-
</VStack>
|
|
516
|
-
</Card>
|
|
517
|
-
))}
|
|
518
|
-
</Grid>
|
|
335
|
+
function ProductCard({product, image}: {product: ProductSpec; image?: string}) {
|
|
336
|
+
const [quantity, setQuantity] = useState<number | null>(1);
|
|
337
|
+
const [isAdded, setIsAdded] = useState(false);
|
|
338
|
+
return (
|
|
339
|
+
<Card padding={0} height="100%">
|
|
340
|
+
<VStack gap={0} height="100%">
|
|
341
|
+
<AspectRatio ratio={1} fit="cover">
|
|
342
|
+
<img src={image} alt={product.name} />
|
|
343
|
+
</AspectRatio>
|
|
344
|
+
<StackItem size="fill">
|
|
345
|
+
<VStack padding={4} gap={2} hAlign="center" height="100%">
|
|
346
|
+
<Badge label={product.badge} variant={product.badgeVariant} />
|
|
347
|
+
<Heading level={2} justify="center">
|
|
348
|
+
{product.name}
|
|
349
|
+
</Heading>
|
|
350
|
+
<StackItem size="fill">
|
|
351
|
+
<Text
|
|
352
|
+
type="supporting"
|
|
353
|
+
color="secondary"
|
|
354
|
+
justify="center"
|
|
355
|
+
display="block">
|
|
356
|
+
{product.description}
|
|
357
|
+
</Text>
|
|
358
|
+
</StackItem>
|
|
359
|
+
<HStack
|
|
360
|
+
gap={2}
|
|
361
|
+
vAlign="center"
|
|
362
|
+
hAlign="center"
|
|
363
|
+
wrap="wrap"
|
|
364
|
+
width="100%">
|
|
365
|
+
<NumberInput
|
|
366
|
+
label="Quantity"
|
|
367
|
+
isLabelHidden
|
|
368
|
+
value={quantity}
|
|
369
|
+
onChange={value => {
|
|
370
|
+
setQuantity(value);
|
|
371
|
+
setIsAdded(false);
|
|
372
|
+
}}
|
|
373
|
+
min={1}
|
|
374
|
+
max={99}
|
|
375
|
+
size="sm"
|
|
376
|
+
width={72}
|
|
377
|
+
/>
|
|
378
|
+
<Button
|
|
379
|
+
label={isAdded ? 'Added to cart' : 'Add to cart'}
|
|
380
|
+
variant="secondary"
|
|
381
|
+
size="sm"
|
|
382
|
+
onClick={() => setIsAdded(true)}
|
|
383
|
+
/>
|
|
384
|
+
</HStack>
|
|
519
385
|
</VStack>
|
|
520
|
-
</
|
|
386
|
+
</StackItem>
|
|
521
387
|
</VStack>
|
|
522
|
-
</
|
|
388
|
+
</Card>
|
|
523
389
|
);
|
|
524
390
|
}
|
|
525
391
|
|
|
526
|
-
function CheckoutCard(
|
|
392
|
+
function CheckoutCard() {
|
|
393
|
+
const {isMobile} = useAppShellMobile();
|
|
394
|
+
const [email, setEmail] = useState('');
|
|
395
|
+
const [shipping, setShipping] = useState('economy');
|
|
396
|
+
const [payment, setPayment] = useState('card');
|
|
397
|
+
const [cardNumber, setCardNumber] = useState('');
|
|
398
|
+
const [expiry, setExpiry] = useState('');
|
|
399
|
+
const [cvc, setCvc] = useState('');
|
|
400
|
+
const [country, setCountry] = useState<string | undefined>('us');
|
|
401
|
+
const [saveInformation, setSaveInformation] = useState(true);
|
|
402
|
+
const [showPaymentPreview, setShowPaymentPreview] = useState(false);
|
|
527
403
|
return (
|
|
528
|
-
<Card padding={isMobile ? 4 : 5}
|
|
529
|
-
<VStack gap={4}
|
|
404
|
+
<Card height="100%" padding={isMobile ? 4 : 5}>
|
|
405
|
+
<VStack gap={4}>
|
|
530
406
|
<Heading level={2}>Checkout</Heading>
|
|
531
|
-
|
|
532
|
-
<VStack gap={3} style={styles.checkoutStack}>
|
|
407
|
+
<VStack gap={3}>
|
|
533
408
|
<TextInput
|
|
534
409
|
label="Email"
|
|
535
410
|
placeholder="you@studio.com"
|
|
536
|
-
value=
|
|
537
|
-
onChange={
|
|
411
|
+
value={email}
|
|
412
|
+
onChange={setEmail}
|
|
538
413
|
size="lg"
|
|
539
414
|
/>
|
|
540
|
-
|
|
541
415
|
<RadioList
|
|
542
416
|
label="Shipping method"
|
|
543
417
|
description="Delivery time may vary based on location and availability."
|
|
544
|
-
value=
|
|
545
|
-
onChange={
|
|
418
|
+
value={shipping}
|
|
419
|
+
onChange={setShipping}>
|
|
546
420
|
<RadioListItem
|
|
547
421
|
value="economy"
|
|
548
422
|
label="Economy Shipping"
|
|
@@ -574,90 +448,85 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
574
448
|
}
|
|
575
449
|
/>
|
|
576
450
|
</RadioList>
|
|
577
|
-
|
|
578
|
-
<VStack gap={2} style={styles.checkoutStack}>
|
|
451
|
+
<VStack gap={2}>
|
|
579
452
|
<Text type="supporting" weight="bold">
|
|
580
453
|
Payment method
|
|
581
454
|
</Text>
|
|
582
|
-
<Grid
|
|
455
|
+
<Grid
|
|
456
|
+
columns={
|
|
457
|
+
isMobile ? {minWidth: 80, max: 1} : {minWidth: 80, max: 3}
|
|
458
|
+
}
|
|
459
|
+
gap={2}>
|
|
583
460
|
<SelectableCard
|
|
584
461
|
label="Pay with card"
|
|
585
|
-
isSelected={
|
|
586
|
-
onChange={() =>
|
|
462
|
+
isSelected={payment === 'card'}
|
|
463
|
+
onChange={() => setPayment('card')}
|
|
587
464
|
padding={3}>
|
|
588
|
-
<VStack
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
style={styles.paymentCardContent}>
|
|
592
|
-
<CreditCard size={20} />
|
|
593
|
-
<Text type="supporting" weight="bold">
|
|
465
|
+
<VStack gap={1} hAlign="center">
|
|
466
|
+
<Icon icon={CreditCard} size="md" />
|
|
467
|
+
<Text type="supporting" weight="bold" justify="center">
|
|
594
468
|
Card
|
|
595
469
|
</Text>
|
|
596
470
|
</VStack>
|
|
597
471
|
</SelectableCard>
|
|
598
472
|
<SelectableCard
|
|
599
473
|
label="Pay with Apple Pay"
|
|
600
|
-
isSelected={
|
|
601
|
-
onChange={() =>
|
|
474
|
+
isSelected={payment === 'apple'}
|
|
475
|
+
onChange={() => setPayment('apple')}
|
|
602
476
|
padding={3}>
|
|
603
|
-
<VStack
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
style={styles.paymentCardContent}>
|
|
607
|
-
<Smartphone size={20} />
|
|
608
|
-
<Text type="supporting" weight="bold">
|
|
477
|
+
<VStack gap={1} hAlign="center">
|
|
478
|
+
<Icon icon={Smartphone} size="md" />
|
|
479
|
+
<Text type="supporting" weight="bold" justify="center">
|
|
609
480
|
Apple Pay
|
|
610
481
|
</Text>
|
|
611
482
|
</VStack>
|
|
612
483
|
</SelectableCard>
|
|
613
484
|
<SelectableCard
|
|
614
485
|
label="Pay with Google Pay"
|
|
615
|
-
isSelected={
|
|
616
|
-
onChange={() =>
|
|
486
|
+
isSelected={payment === 'google'}
|
|
487
|
+
onChange={() => setPayment('google')}
|
|
617
488
|
padding={3}>
|
|
618
|
-
<VStack
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
style={styles.paymentCardContent}>
|
|
622
|
-
<Wallet size={20} />
|
|
623
|
-
<Text type="supporting" weight="bold">
|
|
489
|
+
<VStack gap={1} hAlign="center">
|
|
490
|
+
<Icon icon={Wallet} size="md" />
|
|
491
|
+
<Text type="supporting" weight="bold" justify="center">
|
|
624
492
|
Google Pay
|
|
625
493
|
</Text>
|
|
626
494
|
</VStack>
|
|
627
495
|
</SelectableCard>
|
|
628
496
|
</Grid>
|
|
629
497
|
</VStack>
|
|
630
|
-
|
|
631
498
|
<TextInput
|
|
632
499
|
label="Card number"
|
|
633
500
|
placeholder="1234 1234 1234 1234"
|
|
634
|
-
value=
|
|
635
|
-
onChange={
|
|
636
|
-
startIcon={
|
|
501
|
+
value={cardNumber}
|
|
502
|
+
onChange={setCardNumber}
|
|
503
|
+
startIcon={
|
|
504
|
+
isMobile ? undefined : <Icon icon={CreditCard} size="sm" />
|
|
505
|
+
}
|
|
637
506
|
size="lg"
|
|
638
507
|
/>
|
|
639
|
-
|
|
640
|
-
|
|
508
|
+
<Grid
|
|
509
|
+
columns={isMobile ? {minWidth: 90, max: 1} : {minWidth: 90, max: 2}}
|
|
510
|
+
gap={2}>
|
|
641
511
|
<TextInput
|
|
642
512
|
label="Expiry"
|
|
643
513
|
placeholder="MM / YY"
|
|
644
|
-
value=
|
|
645
|
-
onChange={
|
|
514
|
+
value={expiry}
|
|
515
|
+
onChange={setExpiry}
|
|
646
516
|
size="lg"
|
|
647
517
|
/>
|
|
648
518
|
<TextInput
|
|
649
519
|
label="CVC"
|
|
650
520
|
placeholder="123"
|
|
651
|
-
value=
|
|
652
|
-
onChange={
|
|
521
|
+
value={cvc}
|
|
522
|
+
onChange={setCvc}
|
|
653
523
|
size="lg"
|
|
654
524
|
/>
|
|
655
525
|
</Grid>
|
|
656
|
-
|
|
657
526
|
<Selector
|
|
658
527
|
label="Country"
|
|
659
|
-
value=
|
|
660
|
-
onChange={
|
|
528
|
+
value={country}
|
|
529
|
+
onChange={setCountry}
|
|
661
530
|
size="lg"
|
|
662
531
|
options={[
|
|
663
532
|
{value: 'us', label: 'United States'},
|
|
@@ -669,19 +538,25 @@ function CheckoutCard({isMobile}: {isMobile: boolean}) {
|
|
|
669
538
|
]}
|
|
670
539
|
/>
|
|
671
540
|
</VStack>
|
|
672
|
-
|
|
673
541
|
<CheckboxInput
|
|
674
542
|
label="Securely save my information for 1-click checkout"
|
|
675
543
|
description="Pay faster on Studio and everywhere Link is accepted."
|
|
676
|
-
value={
|
|
677
|
-
onChange={
|
|
544
|
+
value={saveInformation}
|
|
545
|
+
onChange={setSaveInformation}
|
|
678
546
|
/>
|
|
679
|
-
|
|
547
|
+
{showPaymentPreview && (
|
|
548
|
+
<Banner
|
|
549
|
+
status="info"
|
|
550
|
+
title="Payment preview only"
|
|
551
|
+
description="No payment is submitted or saved."
|
|
552
|
+
/>
|
|
553
|
+
)}
|
|
680
554
|
<Button
|
|
681
555
|
variant="primary"
|
|
682
556
|
size="lg"
|
|
683
557
|
label="Pay now"
|
|
684
|
-
icon={<
|
|
558
|
+
icon={<Icon icon={Lock} size="sm" />}
|
|
559
|
+
onClick={() => setShowPaymentPreview(true)}
|
|
685
560
|
/>
|
|
686
561
|
</VStack>
|
|
687
562
|
</Card>
|
|
@@ -695,201 +570,224 @@ const SUGGESTED_QUESTIONS = [
|
|
|
695
570
|
];
|
|
696
571
|
|
|
697
572
|
function ChatCard() {
|
|
573
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
574
|
+
const {lock, isLocked, scrollIfLocked} = useChatStreamScroll({scrollRef});
|
|
575
|
+
const {contentRef} = useChatNewMessages({isLocked, onResize: scrollIfLocked});
|
|
576
|
+
const [draft, setDraft] = useState('');
|
|
577
|
+
const [messages, setMessages] = useState<Array<{id: number; text: string}>>(
|
|
578
|
+
[],
|
|
579
|
+
);
|
|
580
|
+
function submitMessage() {
|
|
581
|
+
const message = draft.trim();
|
|
582
|
+
if (!message) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
lock();
|
|
586
|
+
setMessages(previous => [
|
|
587
|
+
...previous,
|
|
588
|
+
{id: (previous.at(-1)?.id ?? 0) + 1, text: message},
|
|
589
|
+
]);
|
|
590
|
+
setDraft('');
|
|
591
|
+
}
|
|
698
592
|
return (
|
|
699
|
-
<Card padding={0}
|
|
700
|
-
<
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
<ChatMessage sender="assistant">
|
|
740
|
-
<VStack gap={3}>
|
|
741
|
-
<Text type="body">
|
|
742
|
-
Your order #1043 — the Minimalist Watch and Linen Throw —
|
|
743
|
-
shipped this morning from the Aisle 3 warehouse and is currently
|
|
744
|
-
in transit with UPS. It’s on track to arrive at your address by
|
|
745
|
-
end of day tomorrow.
|
|
746
|
-
</Text>
|
|
747
|
-
<Text type="body">
|
|
748
|
-
Let me know if you’d like to reschedule the delivery, redirect
|
|
749
|
-
it to a pickup point, or start a return once it arrives.
|
|
750
|
-
</Text>
|
|
751
|
-
</VStack>
|
|
752
|
-
</ChatMessage>
|
|
753
|
-
|
|
754
|
-
<ChatMessage sender="user">
|
|
755
|
-
<ChatMessageBubble variant="filled">
|
|
756
|
-
Can you show me the full details?
|
|
757
|
-
</ChatMessageBubble>
|
|
758
|
-
</ChatMessage>
|
|
759
|
-
|
|
760
|
-
<ChatMessage sender="assistant">
|
|
761
|
-
<VStack gap={3}>
|
|
762
|
-
<Text type="body">Here’s everything I have on order #1043:</Text>
|
|
763
|
-
<Card padding={3}>
|
|
764
|
-
<VStack gap={1}>
|
|
765
|
-
<Item
|
|
766
|
-
label="Items"
|
|
767
|
-
description="Minimalist Watch · Linen Throw"
|
|
768
|
-
endContent={
|
|
769
|
-
<Text type="body" weight="bold">
|
|
770
|
-
$248
|
|
771
|
-
</Text>
|
|
772
|
-
}
|
|
773
|
-
/>
|
|
774
|
-
<Item
|
|
775
|
-
label="Shipping"
|
|
776
|
-
description="UPS Ground"
|
|
777
|
-
endContent={
|
|
778
|
-
<Text type="body" weight="bold">
|
|
779
|
-
$12
|
|
780
|
-
</Text>
|
|
781
|
-
}
|
|
593
|
+
<Card padding={0} height="100%">
|
|
594
|
+
<Layout
|
|
595
|
+
height="fill"
|
|
596
|
+
padding={4}
|
|
597
|
+
header={
|
|
598
|
+
<LayoutHeader hasDivider>
|
|
599
|
+
<HStack hAlign="between" vAlign="center" gap={3}>
|
|
600
|
+
<Heading level={2}>Studio AI</Heading>
|
|
601
|
+
<HStack gap={1} vAlign="center">
|
|
602
|
+
<Button
|
|
603
|
+
variant="ghost"
|
|
604
|
+
size="sm"
|
|
605
|
+
isIconOnly
|
|
606
|
+
label="Export conversation"
|
|
607
|
+
tooltip="Export conversation"
|
|
608
|
+
icon={<Icon icon={Download} size="sm" />}
|
|
609
|
+
/>
|
|
610
|
+
<Button
|
|
611
|
+
variant="ghost"
|
|
612
|
+
size="sm"
|
|
613
|
+
isIconOnly
|
|
614
|
+
label="Close chat"
|
|
615
|
+
tooltip="Close chat"
|
|
616
|
+
icon={<Icon icon={X} size="sm" />}
|
|
617
|
+
/>
|
|
618
|
+
</HStack>
|
|
619
|
+
</HStack>
|
|
620
|
+
</LayoutHeader>
|
|
621
|
+
}
|
|
622
|
+
footer={
|
|
623
|
+
<LayoutFooter>
|
|
624
|
+
<VStack gap={2}>
|
|
625
|
+
<HStack gap={1} hAlign="center" wrap="wrap">
|
|
626
|
+
{SUGGESTED_QUESTIONS.map(question => (
|
|
627
|
+
<Button
|
|
628
|
+
key={question}
|
|
629
|
+
variant="secondary"
|
|
630
|
+
size="sm"
|
|
631
|
+
label={question}
|
|
632
|
+
onClick={() => setDraft(question)}
|
|
782
633
|
/>
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
634
|
+
))}
|
|
635
|
+
</HStack>
|
|
636
|
+
<ChatComposer
|
|
637
|
+
value={draft}
|
|
638
|
+
onChange={setDraft}
|
|
639
|
+
onSubmit={submitMessage}
|
|
640
|
+
placeholder="Ask Studio AI…"
|
|
641
|
+
footerActions={
|
|
642
|
+
<Button
|
|
643
|
+
variant="ghost"
|
|
644
|
+
size="md"
|
|
645
|
+
isIconOnly
|
|
646
|
+
label="Attach"
|
|
647
|
+
tooltip="Attach"
|
|
648
|
+
icon={<Icon icon={Plus} size="sm" />}
|
|
787
649
|
/>
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
650
|
+
}
|
|
651
|
+
sendActions={
|
|
652
|
+
<Button
|
|
653
|
+
variant="ghost"
|
|
654
|
+
size="md"
|
|
655
|
+
isIconOnly
|
|
656
|
+
label="Voice input"
|
|
657
|
+
tooltip="Voice input"
|
|
658
|
+
icon={<Icon icon={Mic} size="sm" />}
|
|
792
659
|
/>
|
|
793
|
-
|
|
794
|
-
|
|
660
|
+
}
|
|
661
|
+
/>
|
|
795
662
|
</VStack>
|
|
796
|
-
</
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
663
|
+
</LayoutFooter>
|
|
664
|
+
}>
|
|
665
|
+
<LayoutContent
|
|
666
|
+
ref={scrollRef}
|
|
667
|
+
style={styles.chatViewport}
|
|
668
|
+
padding={0}
|
|
669
|
+
label="Studio AI conversation">
|
|
670
|
+
<ChatMessageList ref={contentRef}>
|
|
671
|
+
<ChatSystemMessage>Today</ChatSystemMessage>
|
|
672
|
+
<ChatMessage sender="user">
|
|
673
|
+
<ChatMessageBubble variant="filled">
|
|
674
|
+
Where’s my order?
|
|
675
|
+
</ChatMessageBubble>
|
|
676
|
+
</ChatMessage>
|
|
677
|
+
<ChatMessage sender="assistant">
|
|
678
|
+
<VStack gap={3}>
|
|
679
|
+
<Text type="body">
|
|
680
|
+
Your order #1043 — the Minimalist Watch and Linen Throw —
|
|
681
|
+
shipped this morning from the Aisle 3 warehouse and is
|
|
682
|
+
currently in transit with UPS. It’s on track to arrive at your
|
|
683
|
+
address by end of day tomorrow.
|
|
684
|
+
</Text>
|
|
685
|
+
<Text type="body">
|
|
686
|
+
Let me know if you’d like to reschedule the delivery, redirect
|
|
687
|
+
it to a pickup point, or start a return once it arrives.
|
|
688
|
+
</Text>
|
|
689
|
+
</VStack>
|
|
690
|
+
</ChatMessage>
|
|
691
|
+
<ChatMessage sender="user">
|
|
692
|
+
<ChatMessageBubble variant="filled">
|
|
693
|
+
Can you show me the full details?
|
|
694
|
+
</ChatMessageBubble>
|
|
695
|
+
</ChatMessage>
|
|
696
|
+
<ChatMessage sender="assistant">
|
|
697
|
+
<VStack gap={3}>
|
|
698
|
+
<Text type="body">
|
|
699
|
+
Here’s everything I have on order #1043:
|
|
700
|
+
</Text>
|
|
701
|
+
<Card padding={3}>
|
|
702
|
+
<VStack gap={1}>
|
|
703
|
+
<Item
|
|
704
|
+
label="Items"
|
|
705
|
+
description="Minimalist Watch · Linen Throw"
|
|
706
|
+
endContent={
|
|
707
|
+
<Text type="body" weight="bold">
|
|
708
|
+
$248
|
|
709
|
+
</Text>
|
|
710
|
+
}
|
|
711
|
+
/>
|
|
712
|
+
<Item
|
|
713
|
+
label="Shipping"
|
|
714
|
+
description="UPS Ground"
|
|
715
|
+
endContent={
|
|
716
|
+
<Text type="body" weight="bold">
|
|
717
|
+
$12
|
|
718
|
+
</Text>
|
|
719
|
+
}
|
|
720
|
+
/>
|
|
721
|
+
<Item
|
|
722
|
+
label="Estimated arrival"
|
|
723
|
+
description="Tomorrow by 8pm"
|
|
724
|
+
endContent={<Badge variant="green" label="On time" />}
|
|
725
|
+
/>
|
|
726
|
+
<Item
|
|
727
|
+
label="Tracking"
|
|
728
|
+
description="UPS 1Z 999 AA1 0123 4567 84"
|
|
729
|
+
endContent={<Link href="#">Track</Link>}
|
|
730
|
+
/>
|
|
731
|
+
</VStack>
|
|
732
|
+
</Card>
|
|
733
|
+
</VStack>
|
|
734
|
+
</ChatMessage>
|
|
735
|
+
{messages.map(message => (
|
|
736
|
+
<ChatMessage key={message.id} sender="user">
|
|
737
|
+
<ChatMessageBubble variant="filled">
|
|
738
|
+
{message.text}
|
|
739
|
+
</ChatMessageBubble>
|
|
740
|
+
</ChatMessage>
|
|
741
|
+
))}
|
|
742
|
+
</ChatMessageList>
|
|
743
|
+
</LayoutContent>
|
|
744
|
+
</Layout>
|
|
841
745
|
</Card>
|
|
842
746
|
);
|
|
843
747
|
}
|
|
844
748
|
|
|
845
749
|
interface ActivityRow {
|
|
846
750
|
id: string;
|
|
847
|
-
|
|
751
|
+
kind: 'order' | 'refund';
|
|
848
752
|
label: string;
|
|
849
753
|
detail: string;
|
|
850
|
-
time: string;
|
|
851
754
|
amount: number;
|
|
852
755
|
}
|
|
853
756
|
|
|
854
757
|
const ACTIVITY: ActivityRow[] = [
|
|
855
758
|
{
|
|
856
759
|
id: '1',
|
|
857
|
-
|
|
760
|
+
kind: 'order',
|
|
858
761
|
label: 'Order #1043',
|
|
859
762
|
detail: 'Placed · 1:59 pm',
|
|
860
|
-
time: '1:59 pm',
|
|
861
763
|
amount: 248,
|
|
862
764
|
},
|
|
863
765
|
{
|
|
864
766
|
id: '2',
|
|
865
|
-
|
|
767
|
+
kind: 'refund',
|
|
866
768
|
label: 'Order #1041',
|
|
867
769
|
detail: 'Refunded · 12:40 pm',
|
|
868
|
-
time: '12:40 pm',
|
|
869
770
|
amount: -89,
|
|
870
771
|
},
|
|
871
772
|
{
|
|
872
773
|
id: '3',
|
|
873
|
-
|
|
774
|
+
kind: 'order',
|
|
874
775
|
label: 'Order #1040',
|
|
875
776
|
detail: 'Placed · 10:30 am',
|
|
876
|
-
time: '10:30 am',
|
|
877
777
|
amount: 156,
|
|
878
778
|
},
|
|
879
779
|
{
|
|
880
780
|
id: '4',
|
|
881
|
-
|
|
781
|
+
kind: 'order',
|
|
882
782
|
label: 'Order #1038',
|
|
883
783
|
detail: 'Placed · 9:11 am',
|
|
884
|
-
time: '9:11 am',
|
|
885
784
|
amount: 412,
|
|
886
785
|
},
|
|
887
786
|
{
|
|
888
787
|
id: '5',
|
|
889
|
-
|
|
788
|
+
kind: 'order',
|
|
890
789
|
label: 'Order #1037',
|
|
891
790
|
detail: 'Placed · 8:42 am',
|
|
892
|
-
time: '8:42 am',
|
|
893
791
|
amount: 95,
|
|
894
792
|
},
|
|
895
793
|
];
|
|
@@ -899,57 +797,68 @@ function formatAmount(amount: number): string {
|
|
|
899
797
|
return sign + '$' + Math.abs(amount).toLocaleString();
|
|
900
798
|
}
|
|
901
799
|
|
|
902
|
-
function LatestActivityCard(
|
|
800
|
+
function LatestActivityCard() {
|
|
801
|
+
const {isMobile} = useAppShellMobile();
|
|
903
802
|
return (
|
|
904
|
-
<Card padding={5}
|
|
905
|
-
<VStack gap={4}
|
|
803
|
+
<Card height="100%" padding={5}>
|
|
804
|
+
<VStack gap={4} height="100%">
|
|
906
805
|
<Heading level={2}>Revenue</Heading>
|
|
907
|
-
|
|
908
|
-
|
|
806
|
+
<Grid
|
|
807
|
+
columns={isMobile ? {minWidth: 80, max: 1} : {minWidth: 80, max: 2}}
|
|
808
|
+
gap={3}>
|
|
909
809
|
<VStack gap={0}>
|
|
910
|
-
<
|
|
810
|
+
<Text type="large" weight="bold" hasTabularNumbers>
|
|
811
|
+
18K
|
|
812
|
+
</Text>
|
|
911
813
|
<Text type="supporting" color="secondary">
|
|
912
814
|
Monthly revenue
|
|
913
815
|
</Text>
|
|
914
816
|
</VStack>
|
|
915
817
|
<VStack gap={0}>
|
|
916
|
-
<
|
|
818
|
+
<Text type="large" weight="bold" hasTabularNumbers>
|
|
819
|
+
+12%
|
|
820
|
+
</Text>
|
|
917
821
|
<Text type="supporting" color="secondary">
|
|
918
822
|
Order growth
|
|
919
823
|
</Text>
|
|
920
824
|
</VStack>
|
|
921
825
|
</Grid>
|
|
922
|
-
|
|
923
826
|
<Divider variant="subtle" />
|
|
924
|
-
|
|
925
827
|
<HStack hAlign="between" vAlign="center">
|
|
926
828
|
<Heading level={3}>Activity</Heading>
|
|
927
829
|
<Link href="#">See all</Link>
|
|
928
830
|
</HStack>
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
{
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
831
|
+
<StackItem size="fill">
|
|
832
|
+
<ScrollableArea label="Recent order activity" height="100%">
|
|
833
|
+
<VStack gap={1}>
|
|
834
|
+
{ACTIVITY.map(item => (
|
|
835
|
+
<Item
|
|
836
|
+
key={item.id}
|
|
837
|
+
startContent={
|
|
838
|
+
<Center width={32} height={32}>
|
|
839
|
+
<Icon
|
|
840
|
+
icon={item.kind === 'refund' ? Banknote : ShoppingBag}
|
|
841
|
+
size="sm"
|
|
842
|
+
color="secondary"
|
|
843
|
+
/>
|
|
844
|
+
</Center>
|
|
845
|
+
}
|
|
846
|
+
label={item.label}
|
|
847
|
+
description={item.detail}
|
|
848
|
+
endContent={
|
|
849
|
+
<Text
|
|
850
|
+
type="body"
|
|
851
|
+
weight="bold"
|
|
852
|
+
color={item.amount < 0 ? 'secondary' : 'primary'}>
|
|
853
|
+
{formatAmount(item.amount)}
|
|
854
|
+
</Text>
|
|
855
|
+
}
|
|
856
|
+
href="#"
|
|
857
|
+
/>
|
|
858
|
+
))}
|
|
859
|
+
</VStack>
|
|
860
|
+
</ScrollableArea>
|
|
861
|
+
</StackItem>
|
|
953
862
|
</VStack>
|
|
954
863
|
</Card>
|
|
955
864
|
);
|
|
@@ -963,6 +872,8 @@ export interface InventoryRow extends Record<string, unknown> {
|
|
|
963
872
|
meta: string;
|
|
964
873
|
available: number;
|
|
965
874
|
location: string;
|
|
875
|
+
/** Optional category supplied by a preview's inventory fixtures. */
|
|
876
|
+
category?: string;
|
|
966
877
|
tags: TagSpec[];
|
|
967
878
|
imageKey?: string;
|
|
968
879
|
thumbnailFallback: string;
|
|
@@ -976,6 +887,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
976
887
|
meta: 'Stainless steel, sapphire crystal',
|
|
977
888
|
available: 42,
|
|
978
889
|
location: 'Aisle 3',
|
|
890
|
+
category: 'Wearables',
|
|
979
891
|
tags: [{label: 'New', variant: 'blue'}],
|
|
980
892
|
imageKey: 'watch',
|
|
981
893
|
thumbnailFallback: 'M',
|
|
@@ -987,6 +899,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
987
899
|
meta: 'ANC, 30hr battery',
|
|
988
900
|
available: 128,
|
|
989
901
|
location: 'Aisle 1',
|
|
902
|
+
category: 'Audio',
|
|
990
903
|
tags: [{label: 'Popular', variant: 'green'}],
|
|
991
904
|
imageKey: 'headphones',
|
|
992
905
|
thumbnailFallback: 'W',
|
|
@@ -998,6 +911,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
998
911
|
meta: 'Water-resistant, 25L',
|
|
999
912
|
available: 63,
|
|
1000
913
|
location: 'Aisle 2',
|
|
914
|
+
category: 'Bags',
|
|
1001
915
|
tags: [{label: 'Limited', variant: 'yellow'}],
|
|
1002
916
|
imageKey: 'backpack',
|
|
1003
917
|
thumbnailFallback: 'C',
|
|
@@ -1009,6 +923,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
1009
923
|
meta: 'Full-grain, RFID blocking',
|
|
1010
924
|
available: 15,
|
|
1011
925
|
location: 'Aisle 4',
|
|
926
|
+
category: 'Wearables',
|
|
1012
927
|
tags: [{label: 'Leather', variant: 'yellow'}],
|
|
1013
928
|
imageKey: 'wallet',
|
|
1014
929
|
thumbnailFallback: 'L',
|
|
@@ -1020,6 +935,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
1020
935
|
meta: 'Vacuum insulated, 16oz',
|
|
1021
936
|
available: 87,
|
|
1022
937
|
location: 'Aisle 5',
|
|
938
|
+
category: 'Drinkware',
|
|
1023
939
|
tags: [{label: 'Drinkware', variant: 'green'}],
|
|
1024
940
|
imageKey: 'tumbler',
|
|
1025
941
|
thumbnailFallback: 'T',
|
|
@@ -1031,6 +947,7 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
1031
947
|
meta: 'Heavyweight, oat',
|
|
1032
948
|
available: 24,
|
|
1033
949
|
location: 'Aisle 6',
|
|
950
|
+
category: 'Home',
|
|
1034
951
|
tags: [{label: 'Home', variant: 'orange'}],
|
|
1035
952
|
imageKey: 'throw_',
|
|
1036
953
|
thumbnailFallback: 'L',
|
|
@@ -1040,17 +957,6 @@ const DEFAULT_INVENTORY: InventoryRow[] = [
|
|
|
1040
957
|
|
|
1041
958
|
const LOW_STOCK_THRESHOLD = 25;
|
|
1042
959
|
|
|
1043
|
-
function SelectCell({row}: {row: InventoryRow}) {
|
|
1044
|
-
return (
|
|
1045
|
-
<CheckboxInput
|
|
1046
|
-
label={'Select ' + row.name}
|
|
1047
|
-
isLabelHidden
|
|
1048
|
-
value={row.selected}
|
|
1049
|
-
onChange={() => {}}
|
|
1050
|
-
/>
|
|
1051
|
-
);
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
960
|
function ItemCell({
|
|
1055
961
|
row,
|
|
1056
962
|
images,
|
|
@@ -1061,21 +967,31 @@ function ItemCell({
|
|
|
1061
967
|
const thumbnailSrc = row.imageKey ? images[row.imageKey] : undefined;
|
|
1062
968
|
return (
|
|
1063
969
|
<HStack gap={3} vAlign="center">
|
|
1064
|
-
|
|
1065
|
-
<
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
</
|
|
1078
|
-
</
|
|
970
|
+
<StackItem>
|
|
971
|
+
<VStack width={40}>
|
|
972
|
+
{thumbnailSrc ? (
|
|
973
|
+
<AspectRatio ratio={1} fit="cover">
|
|
974
|
+
<img src={thumbnailSrc} alt="" />
|
|
975
|
+
</AspectRatio>
|
|
976
|
+
) : (
|
|
977
|
+
<Center width={40} height={40} aria-hidden="true">
|
|
978
|
+
<Text type="supporting" weight="bold">
|
|
979
|
+
{row.thumbnailFallback}
|
|
980
|
+
</Text>
|
|
981
|
+
</Center>
|
|
982
|
+
)}
|
|
983
|
+
</VStack>
|
|
984
|
+
</StackItem>
|
|
985
|
+
<StackItem size="fill">
|
|
986
|
+
<VStack gap={0}>
|
|
987
|
+
<Text type="body" weight="bold">
|
|
988
|
+
{row.name}
|
|
989
|
+
</Text>
|
|
990
|
+
<Text type="supporting" color="secondary">
|
|
991
|
+
{row.meta}
|
|
992
|
+
</Text>
|
|
993
|
+
</VStack>
|
|
994
|
+
</StackItem>
|
|
1079
995
|
</HStack>
|
|
1080
996
|
);
|
|
1081
997
|
}
|
|
@@ -1113,180 +1029,196 @@ function InventoryCard({
|
|
|
1113
1029
|
images: Record<string, string>;
|
|
1114
1030
|
inventory: InventoryRow[];
|
|
1115
1031
|
}) {
|
|
1032
|
+
const [query, setQuery] = useState('');
|
|
1033
|
+
const [category, setCategory] = useState<string | null>(null);
|
|
1034
|
+
const [location, setLocation] = useState<string | null>(null);
|
|
1035
|
+
const [tag, setTag] = useState<string | null>(null);
|
|
1036
|
+
const [selectedIds, setSelectedIds] = useState(
|
|
1037
|
+
() => new Set(inventory.filter(row => row.selected).map(row => row.id)),
|
|
1038
|
+
);
|
|
1116
1039
|
const lowStockCount = inventory.filter(
|
|
1117
1040
|
row => row.available < LOW_STOCK_THRESHOLD,
|
|
1118
1041
|
).length;
|
|
1042
|
+
const categories = [
|
|
1043
|
+
...new Set(inventory.flatMap(row => (row.category ? [row.category] : []))),
|
|
1044
|
+
];
|
|
1045
|
+
const rows = inventory.filter(
|
|
1046
|
+
row =>
|
|
1047
|
+
(!query ||
|
|
1048
|
+
`${row.name} ${row.meta}`
|
|
1049
|
+
.toLowerCase()
|
|
1050
|
+
.includes(query.toLowerCase())) &&
|
|
1051
|
+
(!category || row.category === category) &&
|
|
1052
|
+
(!location || row.location === location) &&
|
|
1053
|
+
(!tag || row.tags.some(value => value.label === tag)),
|
|
1054
|
+
);
|
|
1055
|
+
function selectRow(id: string, selected: boolean) {
|
|
1056
|
+
setSelectedIds(previous => {
|
|
1057
|
+
const next = new Set(previous);
|
|
1058
|
+
if (selected) {
|
|
1059
|
+
next.add(id);
|
|
1060
|
+
} else {
|
|
1061
|
+
next.delete(id);
|
|
1062
|
+
}
|
|
1063
|
+
return next;
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1119
1066
|
return (
|
|
1120
|
-
<Card padding={0}
|
|
1121
|
-
<
|
|
1122
|
-
<
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
</HStack>
|
|
1130
|
-
|
|
1131
|
-
<Divider variant="subtle" />
|
|
1132
|
-
|
|
1133
|
-
<HStack
|
|
1134
|
-
gap={3}
|
|
1135
|
-
vAlign="center"
|
|
1136
|
-
hAlign="between"
|
|
1137
|
-
style={styles.inventoryFilterRow}>
|
|
1138
|
-
<HStack gap={2} vAlign="center" style={{flex: 1, minWidth: 0}}>
|
|
1139
|
-
<TextInput
|
|
1140
|
-
label="Search inventory"
|
|
1141
|
-
isLabelHidden
|
|
1142
|
-
placeholder="Type and hit enter…"
|
|
1143
|
-
value=""
|
|
1144
|
-
onChange={() => {}}
|
|
1145
|
-
startIcon={<Search size={16} />}
|
|
1146
|
-
controlStyle={styles.searchInput}
|
|
1067
|
+
<Card padding={0}>
|
|
1068
|
+
<VStack gap={0}>
|
|
1069
|
+
<HStack hAlign="between" vAlign="center" padding={6}>
|
|
1070
|
+
<Heading level={2}>Inventory</Heading>
|
|
1071
|
+
<Button
|
|
1072
|
+
label="Add item"
|
|
1073
|
+
variant="primary"
|
|
1074
|
+
size="sm"
|
|
1075
|
+
icon={<Icon icon={Plus} size="sm" />}
|
|
1147
1076
|
/>
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1077
|
+
</HStack>
|
|
1078
|
+
<Divider variant="subtle" />
|
|
1079
|
+
<ScrollableArea
|
|
1080
|
+
label="Inventory filters"
|
|
1081
|
+
axis="inline"
|
|
1082
|
+
paddingBlock={4}
|
|
1083
|
+
paddingInline={6}>
|
|
1084
|
+
<HStack gap={3} vAlign="center" hAlign="between">
|
|
1085
|
+
<HStack gap={2} vAlign="center">
|
|
1086
|
+
<TextInput
|
|
1087
|
+
label="Search inventory"
|
|
1088
|
+
isLabelHidden
|
|
1089
|
+
placeholder="Type and hit enter…"
|
|
1090
|
+
value={query}
|
|
1091
|
+
onChange={setQuery}
|
|
1092
|
+
startIcon={<Icon icon={Search} size="sm" />}
|
|
1093
|
+
width={240}
|
|
1094
|
+
/>
|
|
1095
|
+
<HStack gap={2} wrap="wrap" vAlign="center">
|
|
1096
|
+
<Selector
|
|
1097
|
+
label="Categories"
|
|
1098
|
+
isLabelHidden
|
|
1099
|
+
placeholder="Categories"
|
|
1100
|
+
size="sm"
|
|
1101
|
+
startIcon={<Icon icon={Folder} size="sm" />}
|
|
1102
|
+
value={category}
|
|
1103
|
+
hasClear
|
|
1104
|
+
onChange={setCategory}
|
|
1105
|
+
options={categories}
|
|
1106
|
+
isDisabled={categories.length === 0}
|
|
1107
|
+
/>
|
|
1108
|
+
<Selector
|
|
1109
|
+
label="Locations"
|
|
1110
|
+
isLabelHidden
|
|
1111
|
+
placeholder="Locations"
|
|
1112
|
+
size="sm"
|
|
1113
|
+
startIcon={<Icon icon={MapPin} size="sm" />}
|
|
1114
|
+
value={location}
|
|
1115
|
+
hasClear
|
|
1116
|
+
onChange={setLocation}
|
|
1117
|
+
options={[...new Set(inventory.map(row => row.location))]}
|
|
1118
|
+
/>
|
|
1119
|
+
<Selector
|
|
1120
|
+
label="Tags"
|
|
1121
|
+
isLabelHidden
|
|
1122
|
+
placeholder="Tags"
|
|
1123
|
+
size="sm"
|
|
1124
|
+
startIcon={<Icon icon={Tag} size="sm" />}
|
|
1125
|
+
value={tag}
|
|
1126
|
+
hasClear
|
|
1127
|
+
onChange={setTag}
|
|
1128
|
+
options={[
|
|
1129
|
+
...new Set(
|
|
1130
|
+
inventory.flatMap(row =>
|
|
1131
|
+
row.tags.map(value => value.label),
|
|
1132
|
+
),
|
|
1133
|
+
),
|
|
1134
|
+
]}
|
|
1135
|
+
/>
|
|
1136
|
+
</HStack>
|
|
1137
|
+
</HStack>
|
|
1138
|
+
<HStack gap={1} vAlign="center">
|
|
1151
1139
|
<Button
|
|
1152
|
-
label="Filters"
|
|
1153
1140
|
variant="ghost"
|
|
1154
1141
|
size="sm"
|
|
1155
|
-
|
|
1142
|
+
isIconOnly
|
|
1143
|
+
label="List view"
|
|
1144
|
+
tooltip="List view"
|
|
1145
|
+
icon={<List size={18} />}
|
|
1156
1146
|
/>
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
startIcon={<MapPin size={16} />}
|
|
1174
|
-
value={undefined}
|
|
1175
|
-
onChange={() => {}}
|
|
1176
|
-
options={[
|
|
1177
|
-
'Aisle 1',
|
|
1178
|
-
'Aisle 2',
|
|
1179
|
-
'Aisle 3',
|
|
1180
|
-
'Aisle 4',
|
|
1181
|
-
'Aisle 5',
|
|
1182
|
-
'Aisle 6',
|
|
1183
|
-
]}
|
|
1184
|
-
/>
|
|
1185
|
-
<Selector
|
|
1186
|
-
label="Tags"
|
|
1187
|
-
isLabelHidden
|
|
1188
|
-
placeholder="Tags"
|
|
1189
|
-
size="sm"
|
|
1190
|
-
startIcon={<Tag size={16} />}
|
|
1191
|
-
value={undefined}
|
|
1192
|
-
onChange={() => {}}
|
|
1193
|
-
options={[
|
|
1194
|
-
'New',
|
|
1195
|
-
'Popular',
|
|
1196
|
-
'Limited',
|
|
1197
|
-
'Leather',
|
|
1198
|
-
'Drinkware',
|
|
1199
|
-
'Home',
|
|
1200
|
-
]}
|
|
1147
|
+
<Button
|
|
1148
|
+
variant="ghost"
|
|
1149
|
+
size="sm"
|
|
1150
|
+
isIconOnly
|
|
1151
|
+
label="Grid view"
|
|
1152
|
+
tooltip="Grid view"
|
|
1153
|
+
icon={<LayoutGrid size={18} />}
|
|
1154
|
+
/>
|
|
1155
|
+
</HStack>
|
|
1156
|
+
</HStack>
|
|
1157
|
+
</ScrollableArea>
|
|
1158
|
+
{lowStockCount > 0 && (
|
|
1159
|
+
<VStack paddingInline={6} paddingBlockEnd={4}>
|
|
1160
|
+
<Banner
|
|
1161
|
+
status="warning"
|
|
1162
|
+
title={lowStockCount + ' items are running low'}
|
|
1201
1163
|
/>
|
|
1202
|
-
</
|
|
1203
|
-
|
|
1204
|
-
<
|
|
1205
|
-
<
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1164
|
+
</VStack>
|
|
1165
|
+
)}
|
|
1166
|
+
<VStack paddingInline={6} paddingBlockEnd={2}>
|
|
1167
|
+
<Table<InventoryRow>
|
|
1168
|
+
data={rows}
|
|
1169
|
+
columns={[
|
|
1170
|
+
{
|
|
1171
|
+
key: 'select',
|
|
1172
|
+
header: '',
|
|
1173
|
+
width: pixel(64),
|
|
1174
|
+
renderCell: row => (
|
|
1175
|
+
<CheckboxInput
|
|
1176
|
+
label={'Select ' + row.name}
|
|
1177
|
+
isLabelHidden
|
|
1178
|
+
value={selectedIds.has(row.id)}
|
|
1179
|
+
onChange={value => selectRow(row.id, value)}
|
|
1180
|
+
/>
|
|
1181
|
+
),
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
key: 'item',
|
|
1185
|
+
header: 'Item',
|
|
1186
|
+
width: proportional(3, {minWidth: 80}),
|
|
1187
|
+
renderCell: row => <ItemCell row={row} images={images} />,
|
|
1188
|
+
},
|
|
1189
|
+
{
|
|
1190
|
+
key: 'available',
|
|
1191
|
+
header: 'Available',
|
|
1192
|
+
width: pixel(100),
|
|
1193
|
+
renderCell: row => <Text type="body">{row.available}</Text>,
|
|
1194
|
+
},
|
|
1195
|
+
{
|
|
1196
|
+
key: 'location',
|
|
1197
|
+
header: 'Location',
|
|
1198
|
+
width: pixel(100),
|
|
1199
|
+
renderCell: row => <Text type="body">{row.location}</Text>,
|
|
1200
|
+
},
|
|
1201
|
+
{
|
|
1202
|
+
key: 'tags',
|
|
1203
|
+
header: 'Tags',
|
|
1204
|
+
width: proportional(2, {minWidth: 80}),
|
|
1205
|
+
align: 'end',
|
|
1206
|
+
renderCell: row => <TagsCell row={row} />,
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
key: 'actions',
|
|
1210
|
+
header: '',
|
|
1211
|
+
width: pixel(64),
|
|
1212
|
+
align: 'end',
|
|
1213
|
+
renderCell: () => <ActionsCell />,
|
|
1214
|
+
},
|
|
1215
|
+
]}
|
|
1216
|
+
density="spacious"
|
|
1217
|
+
dividers="rows"
|
|
1218
|
+
hasHover
|
|
1229
1219
|
/>
|
|
1230
|
-
</
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
<div style={styles.inventoryTableWrap}>
|
|
1234
|
-
<Table<InventoryRow>
|
|
1235
|
-
data={inventory}
|
|
1236
|
-
columns={[
|
|
1237
|
-
{
|
|
1238
|
-
key: 'select',
|
|
1239
|
-
header: '',
|
|
1240
|
-
// Wide enough that the control + the theme's cell padding (up to
|
|
1241
|
-
// --spacing-4 = 16px/side on spacious density) fit inside the cell,
|
|
1242
|
-
// so the control's hover background doesn't overflow toward the
|
|
1243
|
-
// card's clipped (rounded) edge on larger-padding themes.
|
|
1244
|
-
width: pixel(64),
|
|
1245
|
-
renderCell: row => <SelectCell row={row} />,
|
|
1246
|
-
},
|
|
1247
|
-
{
|
|
1248
|
-
key: 'item',
|
|
1249
|
-
header: 'Item',
|
|
1250
|
-
// Lower min-width (default 120) so the table fits its container on
|
|
1251
|
-
// larger-spacing themes instead of overflowing the actions column.
|
|
1252
|
-
width: proportional(3, {minWidth: 80}),
|
|
1253
|
-
renderCell: row => <ItemCell row={row} images={images} />,
|
|
1254
|
-
},
|
|
1255
|
-
{
|
|
1256
|
-
key: 'available',
|
|
1257
|
-
header: 'Available',
|
|
1258
|
-
width: pixel(100),
|
|
1259
|
-
renderCell: row => <Text type="body">{row.available}</Text>,
|
|
1260
|
-
},
|
|
1261
|
-
{
|
|
1262
|
-
key: 'location',
|
|
1263
|
-
header: 'Location',
|
|
1264
|
-
width: pixel(100),
|
|
1265
|
-
renderCell: row => <Text type="body">{row.location}</Text>,
|
|
1266
|
-
},
|
|
1267
|
-
{
|
|
1268
|
-
key: 'tags',
|
|
1269
|
-
header: 'Tags',
|
|
1270
|
-
width: proportional(2, {minWidth: 80}),
|
|
1271
|
-
align: 'end',
|
|
1272
|
-
renderCell: row => <TagsCell row={row} />,
|
|
1273
|
-
},
|
|
1274
|
-
{
|
|
1275
|
-
key: 'actions',
|
|
1276
|
-
header: '',
|
|
1277
|
-
// Match the select column: fit the sm more-menu button + cell
|
|
1278
|
-
// padding so its hover background stays clear of the card's
|
|
1279
|
-
// clipped rounded edge across themes.
|
|
1280
|
-
width: pixel(64),
|
|
1281
|
-
align: 'end',
|
|
1282
|
-
renderCell: () => <ActionsCell />,
|
|
1283
|
-
},
|
|
1284
|
-
]}
|
|
1285
|
-
density="spacious"
|
|
1286
|
-
dividers="rows"
|
|
1287
|
-
hasHover
|
|
1288
|
-
/>
|
|
1289
|
-
</div>
|
|
1220
|
+
</VStack>
|
|
1221
|
+
</VStack>
|
|
1290
1222
|
</Card>
|
|
1291
1223
|
);
|
|
1292
1224
|
}
|