astryx-dracula 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/BRAND.md +1 -1
  2. package/README.md +2 -0
  3. package/USAGE.md +13 -0
  4. package/astryx-dracula.js +4 -4
  5. package/astryx.integration.mjs +3 -0
  6. package/package.json +4 -2
  7. package/templates/ai-chat-landing.template.mjs +6 -0
  8. package/templates/ai-chat-landing.tsx +476 -0
  9. package/templates/ai-chat.template.mjs +6 -0
  10. package/templates/ai-chat.tsx +762 -0
  11. package/templates/blank.template.mjs +6 -0
  12. package/templates/blank.tsx +16 -0
  13. package/templates/centered-hero.template.mjs +6 -0
  14. package/templates/centered-hero.tsx +188 -0
  15. package/templates/classic-gallery.template.mjs +6 -0
  16. package/templates/classic-gallery.tsx +181 -0
  17. package/templates/contact-form.template.mjs +6 -0
  18. package/templates/contact-form.tsx +292 -0
  19. package/templates/dashboard-portfolio.template.mjs +6 -0
  20. package/templates/dashboard-portfolio.tsx +800 -0
  21. package/templates/dashboard.template.mjs +6 -0
  22. package/templates/dashboard.tsx +774 -0
  23. package/templates/detail-page.template.mjs +6 -0
  24. package/templates/detail-page.tsx +695 -0
  25. package/templates/documentation-design.template.mjs +6 -0
  26. package/templates/documentation-design.tsx +751 -0
  27. package/templates/documentation-technical.template.mjs +6 -0
  28. package/templates/documentation-technical.tsx +292 -0
  29. package/templates/documentation.template.mjs +6 -0
  30. package/templates/documentation.tsx +347 -0
  31. package/templates/editor.template.mjs +6 -0
  32. package/templates/editor.tsx +959 -0
  33. package/templates/file-explorer.template.mjs +6 -0
  34. package/templates/file-explorer.tsx +577 -0
  35. package/templates/form-two-column.template.mjs +6 -0
  36. package/templates/form-two-column.tsx +314 -0
  37. package/templates/gallery-hero.template.mjs +6 -0
  38. package/templates/gallery-hero.tsx +287 -0
  39. package/templates/ide.template.mjs +6 -0
  40. package/templates/ide.tsx +533 -0
  41. package/templates/incident-console.template.mjs +6 -0
  42. package/templates/incident-console.tsx +584 -0
  43. package/templates/kanban-board.template.mjs +6 -0
  44. package/templates/kanban-board.tsx +746 -0
  45. package/templates/library.template.mjs +6 -0
  46. package/templates/library.tsx +489 -0
  47. package/templates/login-card.template.mjs +6 -0
  48. package/templates/login-card.tsx +200 -0
  49. package/templates/login-split.template.mjs +6 -0
  50. package/templates/login-split.tsx +318 -0
  51. package/templates/login-sso.template.mjs +6 -0
  52. package/templates/login-sso.tsx +277 -0
  53. package/templates/login.template.mjs +6 -0
  54. package/templates/login.tsx +98 -0
  55. package/templates/messaging-shell.template.mjs +6 -0
  56. package/templates/messaging-shell.tsx +675 -0
  57. package/templates/mixed-gallery.template.mjs +6 -0
  58. package/templates/mixed-gallery.tsx +190 -0
  59. package/templates/payment-form.template.mjs +6 -0
  60. package/templates/payment-form.tsx +990 -0
  61. package/templates/product-detail.template.mjs +6 -0
  62. package/templates/product-detail.tsx +366 -0
  63. package/templates/product-gallery.template.mjs +6 -0
  64. package/templates/product-gallery.tsx +173 -0
  65. package/templates/settings-dialog.template.mjs +6 -0
  66. package/templates/settings-dialog.tsx +920 -0
  67. package/templates/settings-sidebar.template.mjs +6 -0
  68. package/templates/settings-sidebar.tsx +952 -0
  69. package/templates/settings.template.mjs +6 -0
  70. package/templates/settings.tsx +235 -0
  71. package/templates/shell-nav.template.mjs +6 -0
  72. package/templates/shell-nav.tsx +306 -0
  73. package/templates/shell-side-nav.template.mjs +6 -0
  74. package/templates/shell-side-nav.tsx +238 -0
  75. package/templates/shell-top-nav.template.mjs +6 -0
  76. package/templates/shell-top-nav.tsx +243 -0
  77. package/templates/side-gallery.template.mjs +6 -0
  78. package/templates/side-gallery.tsx +179 -0
  79. package/templates/table-grouped.template.mjs +6 -0
  80. package/templates/table-grouped.tsx +1174 -0
  81. package/templates/table-page-chart.template.mjs +6 -0
  82. package/templates/table-page-chart.tsx +693 -0
  83. package/templates/table-page-heatmap-status.template.mjs +6 -0
  84. package/templates/table-page-heatmap-status.tsx +547 -0
  85. package/templates/table-page-shoe-store-heatmap.template.mjs +6 -0
  86. package/templates/table-page-shoe-store-heatmap.tsx +1038 -0
  87. package/templates/table-page.template.mjs +6 -0
  88. package/templates/table-page.tsx +445 -0
  89. package/templates/table.template.mjs +6 -0
  90. package/templates/table.tsx +84 -0
  91. package/templates/theme-showcase.template.mjs +6 -0
  92. package/templates/theme-showcase.tsx +1282 -0
  93. package/theme.css +4 -4
  94. package/tokens.css +4 -4
@@ -0,0 +1,990 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {useState, type CSSProperties} from 'react';
4
+ import {
5
+ VStack,
6
+ HStack,
7
+ Stack,
8
+ StackItem,
9
+ Layout,
10
+ LayoutContent,
11
+ } from '@astryxdesign/core/Layout';
12
+ import {Grid} from '@astryxdesign/core/Grid';
13
+ import {Button} from '@astryxdesign/core/Button';
14
+ import {Text} from '@astryxdesign/core/Text';
15
+ import {TextInput} from '@astryxdesign/core/TextInput';
16
+ import {Selector} from '@astryxdesign/core/Selector';
17
+ import {CheckboxInput} from '@astryxdesign/core/CheckboxInput';
18
+ import {RadioList, RadioListItem} from '@astryxdesign/core/RadioList';
19
+ import {Link} from '@astryxdesign/core/Link';
20
+ import {TextArea} from '@astryxdesign/core/TextArea';
21
+ import {Divider} from '@astryxdesign/core/Divider';
22
+ import {Banner} from '@astryxdesign/core/Banner';
23
+ import {Card} from '@astryxdesign/core/Card';
24
+ import {Collapsible} from '@astryxdesign/core/Collapsible';
25
+ import {Badge} from '@astryxdesign/core/Badge';
26
+ import {NumberInput} from '@astryxdesign/core/NumberInput';
27
+ import {useMediaQuery} from '@astryxdesign/core/hooks';
28
+ import {Section} from '@astryxdesign/core/Section';
29
+ import {Center} from '@astryxdesign/core/Center';
30
+ import {Thumbnail} from '@astryxdesign/core/Thumbnail';
31
+ import {Icon} from '@astryxdesign/core/Icon';
32
+ import {ShieldCheck, Lock, CircleCheck, Truck} from 'lucide-react';
33
+
34
+ // ── Constants ─────────────────────────────────────────────────────────────────
35
+
36
+ const MONTHS = [
37
+ '01',
38
+ '02',
39
+ '03',
40
+ '04',
41
+ '05',
42
+ '06',
43
+ '07',
44
+ '08',
45
+ '09',
46
+ '10',
47
+ '11',
48
+ '12',
49
+ ];
50
+ const YEARS = Array.from({length: 12}, (_, i) => String(2025 + i));
51
+
52
+ const US_STATES = [
53
+ 'Alabama',
54
+ 'Alaska',
55
+ 'Arizona',
56
+ 'Arkansas',
57
+ 'California',
58
+ 'Colorado',
59
+ 'Connecticut',
60
+ 'Delaware',
61
+ 'Florida',
62
+ 'Georgia',
63
+ 'Hawaii',
64
+ 'Idaho',
65
+ 'Illinois',
66
+ 'Indiana',
67
+ 'Iowa',
68
+ 'Kansas',
69
+ 'Kentucky',
70
+ 'Louisiana',
71
+ 'Maine',
72
+ 'Maryland',
73
+ 'Massachusetts',
74
+ 'Michigan',
75
+ 'Minnesota',
76
+ 'Mississippi',
77
+ 'Missouri',
78
+ 'Montana',
79
+ 'Nebraska',
80
+ 'Nevada',
81
+ 'New Hampshire',
82
+ 'New Jersey',
83
+ 'New Mexico',
84
+ 'New York',
85
+ 'North Carolina',
86
+ 'North Dakota',
87
+ 'Ohio',
88
+ 'Oklahoma',
89
+ 'Oregon',
90
+ 'Pennsylvania',
91
+ 'Rhode Island',
92
+ 'South Carolina',
93
+ 'South Dakota',
94
+ 'Tennessee',
95
+ 'Texas',
96
+ 'Utah',
97
+ 'Vermont',
98
+ 'Virginia',
99
+ 'Washington',
100
+ 'West Virginia',
101
+ 'Wisconsin',
102
+ 'Wyoming',
103
+ ];
104
+
105
+ // Product photos from the local template-assets set (committed to the
106
+ // docsite; the CLI swaps these for an inline placeholder on scaffold).
107
+ const ITEM_IMAGES: Record<string, {src: string}> = {
108
+ '1': {
109
+ src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
110
+ },
111
+ '2': {
112
+ src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
113
+ },
114
+ '3': {
115
+ src: 'data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%20400%20300%22%20preserveAspectRatio%3D%22xMidYMid%20slice%22%3E%3Crect%20width%3D%22400%22%20height%3D%22300%22%20fill%3D%22%2321222C%22%2F%3E%3Cg%20transform%3D%22translate%28200%20150%29%22%20fill%3D%22none%22%20stroke%3D%22%236272A4%22%20stroke-width%3D%225%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%3E%3Crect%20x%3D%22-44%22%20y%3D%22-44%22%20width%3D%2288%22%20height%3D%2288%22%20rx%3D%2216%22%2F%3E%3Ccircle%20cx%3D%2218%22%20cy%3D%22-18%22%20r%3D%222.5%22%20fill%3D%22%236272A4%22%20stroke%3D%22none%22%2F%3E%3Cpath%20d%3D%22M-34%2030%20L-8%200%20L10%2018%20L20%208%20L34%2024%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E',
116
+ },
117
+ };
118
+
119
+ const ORDER_ITEMS = [
120
+ {
121
+ id: '1',
122
+ name: 'Obsidian Ritual Chalice',
123
+ variant: 'Hand-carved · 12 oz',
124
+ price: 78,
125
+ qty: 1,
126
+ limited: false,
127
+ },
128
+ {
129
+ id: '2',
130
+ name: 'Blood-Moon Offering Plate',
131
+ variant: 'Moon-glazed · 10 in',
132
+ price: 72,
133
+ qty: 1,
134
+ limited: false,
135
+ },
136
+ {
137
+ id: '3',
138
+ name: 'Crypt Cereal Bowl',
139
+ variant: 'Speckled obsidian · 6 in',
140
+ price: 80,
141
+ qty: 1,
142
+ limited: true,
143
+ },
144
+ ];
145
+
146
+ const TAX_RATE = 0.08;
147
+ const FREE_SHIPPING_THRESHOLD = 300;
148
+ const fmt = (n: number) => `$${n.toFixed(2)}`;
149
+
150
+ // ── Styles ────────────────────────────────────────────────────────────────────
151
+ // Plain inline styles using Astryx design-token CSS variables (declared at
152
+ // :root by `@astryxdesign/core/astryx.css`). No StyleX compiler required.
153
+
154
+ const fullWidth: CSSProperties = {width: '100%'};
155
+ // Form column flex-basis so the two checkout columns share width evenly.
156
+ const formColBasis: CSSProperties = {flexBasis: 0};
157
+ // Space the Order Summary content below its collapsible trigger title.
158
+ const summaryContent: CSSProperties = {paddingBlockStart: 'var(--spacing-2)'};
159
+ // Order-summary column: sticky beside the form on desktop.
160
+ const summarySticky: CSSProperties = {
161
+ flexBasis: 0,
162
+ position: 'sticky',
163
+ top: 'var(--spacing-4)',
164
+ alignSelf: 'flex-start',
165
+ };
166
+ // On mobile the summary moves above the form.
167
+ const summaryMobileOrder: CSSProperties = {order: -1};
168
+ // Express-checkout buttons (Dracula tokens stand in for brand colors).
169
+ const paypalButton: CSSProperties = {
170
+ backgroundColor: 'var(--color-warning)',
171
+ borderColor: 'var(--color-warning)',
172
+ };
173
+ // Google Pay button: widget-surface token stands in for the official dark
174
+ // button background (no raw hex on brand surfaces).
175
+ const gpayButton: CSSProperties = {
176
+ backgroundColor: 'var(--color-widget-background)',
177
+ borderColor: 'var(--color-widget-background)',
178
+ };
179
+ // Brand logos inside the express-checkout buttons.
180
+ const brandLogo: CSSProperties = {height: 'var(--spacing-5)', width: 'auto'};
181
+ // Accepted card-network marks (Visa/Mastercard/Amex), shared style.
182
+ const cardLogo: CSSProperties = {
183
+ height: 'var(--spacing-7)',
184
+ width: 'auto',
185
+ borderRadius: 'var(--radius-element)',
186
+ borderWidth: 'var(--border-width)',
187
+ borderStyle: 'solid',
188
+ borderColor: 'var(--color-border)',
189
+ backgroundColor: 'var(--color-background-surface)',
190
+ };
191
+
192
+ export default function PaymentForm() {
193
+ const isMobile = useMediaQuery('(max-width: 767px)');
194
+ const isStacked = useMediaQuery('(max-width: 1024px)');
195
+ const [firstName, setFirstName] = useState('');
196
+ const [lastName, setLastName] = useState('');
197
+ const [address, setAddress] = useState('');
198
+ const [city, setCity] = useState('');
199
+ const [zip, setZip] = useState('');
200
+ const [state, setState] = useState('');
201
+ const [phone, setPhone] = useState('');
202
+ const [saveInfo, setSaveInfo] = useState(false);
203
+ const [email, setEmail] = useState('');
204
+ const [emailOffers, setEmailOffers] = useState(false);
205
+ const [deliveryMethod, setDeliveryMethod] = useState('standard');
206
+ const [paymentMethod, _setPaymentMethod] = useState('card');
207
+ const [cardNumber, setCardNumber] = useState('');
208
+ const [expiry, setExpiry] = useState('');
209
+ const [expYear, setExpYear] = useState('');
210
+ const [billingMatchesShipping, setBillingMatchesShipping] = useState(true);
211
+ const [billingAddress, setBillingAddress] = useState('');
212
+ const [billingCity, setBillingCity] = useState('');
213
+ const [billingZip, setBillingZip] = useState('');
214
+ const [billingState, setBillingState] = useState('');
215
+ const [addGiftMessage, setAddGiftMessage] = useState(false);
216
+ const [giftTo, setGiftTo] = useState('');
217
+ const [giftFrom, setGiftFrom] = useState('');
218
+ const [giftMessage, setGiftMessage] = useState('');
219
+ const [cvc, setCvc] = useState('');
220
+ const [cardName, setCardName] = useState('');
221
+ const [promo, setPromo] = useState('');
222
+ const [quantities, setQuantities] = useState<Record<string, number>>({
223
+ '1': 1,
224
+ '2': 1,
225
+ '3': 1,
226
+ });
227
+ const [submitted, setSubmitted] = useState(false);
228
+
229
+ // Totals follow the editable quantities (and the free-shipping banner:
230
+ // orders at or over the threshold ship free).
231
+ const subtotal = ORDER_ITEMS.reduce(
232
+ (sum, item) => sum + item.price * (quantities[item.id] ?? item.qty),
233
+ 0,
234
+ );
235
+ const shipping =
236
+ subtotal >= FREE_SHIPPING_THRESHOLD
237
+ ? 0
238
+ : deliveryMethod === 'expedited'
239
+ ? 9.95
240
+ : 4.95;
241
+ const tax = Math.round(subtotal * TAX_RATE * 100) / 100;
242
+ const total = subtotal + shipping + tax;
243
+
244
+ const errors = submitted
245
+ ? {
246
+ firstName: !firstName.trim() ? 'Required' : undefined,
247
+ lastName: !lastName.trim() ? 'Required' : undefined,
248
+ address: !address.trim() ? 'Required' : undefined,
249
+ city: !city.trim() ? 'Required' : undefined,
250
+ zip: !zip.trim() ? 'Required' : undefined,
251
+ state: !state ? 'Required' : undefined,
252
+ email: !email.trim() ? 'Required' : undefined,
253
+ phone: !phone.trim() ? 'Required' : undefined,
254
+ expiry: !expiry ? 'Required' : undefined,
255
+ expYear: !expYear ? 'Required' : undefined,
256
+ cvc: !cvc.trim() ? 'Required' : undefined,
257
+ cardNumber:
258
+ paymentMethod === 'card' && !cardNumber.trim()
259
+ ? 'Required'
260
+ : undefined,
261
+ cardName:
262
+ paymentMethod === 'card' && !cardName.trim() ? 'Required' : undefined,
263
+ billingAddress:
264
+ !billingMatchesShipping && !billingAddress.trim()
265
+ ? 'Required'
266
+ : undefined,
267
+ billingCity:
268
+ !billingMatchesShipping && !billingCity.trim()
269
+ ? 'Required'
270
+ : undefined,
271
+ billingZip:
272
+ !billingMatchesShipping && !billingZip.trim()
273
+ ? 'Required'
274
+ : undefined,
275
+ billingState:
276
+ !billingMatchesShipping && !billingState ? 'Required' : undefined,
277
+ }
278
+ : {};
279
+
280
+ return (
281
+ <Layout
282
+ height="fill"
283
+ content={
284
+ <LayoutContent padding={0}>
285
+ <Center axis="horizontal">
286
+ <Section
287
+ variant="transparent"
288
+ maxWidth={1100}
289
+ width="100%"
290
+ padding={6}>
291
+ <VStack gap={5}>
292
+ {/* Page header */}
293
+ <VStack gap={6}>
294
+ <VStack gap={2}>
295
+ <Text type="display-1" as="h1">
296
+ Payment Request
297
+ </Text>
298
+ <Text type="body" color="secondary">
299
+ Review your order and complete your purchase. All
300
+ transactions are sealed with 256-bit SSL encryption.
301
+ </Text>
302
+ </VStack>
303
+ <Divider />
304
+ </VStack>
305
+
306
+ <Stack
307
+ direction={isStacked ? 'vertical' : 'horizontal'}
308
+ gap={8}
309
+ vAlign="start">
310
+ <StackItem
311
+ size="fill"
312
+ style={isStacked ? undefined : formColBasis}>
313
+ <VStack gap={8}>
314
+ {/* Sign in */}
315
+ <VStack gap={1}>
316
+ <HStack gap={2} hAlign="between" vAlign="center">
317
+ <Text type="large" weight="bold">
318
+ Sign in to check out
319
+ </Text>
320
+ <Button
321
+ label="Sign In"
322
+ variant="secondary"
323
+ size="sm"
324
+ onClick={() => {}}
325
+ />
326
+ </HStack>
327
+ <Text type="body" color="secondary">
328
+ Sign in to track your order and save your information
329
+ for faster checkout.
330
+ </Text>
331
+ </VStack>
332
+
333
+ {/* Contact Information */}
334
+ <VStack gap={3}>
335
+ <Text type="large" weight="bold">
336
+ Contact Information
337
+ </Text>
338
+ <TextInput
339
+ size="lg"
340
+ label="Email"
341
+ placeholder="you@example.com"
342
+ value={email}
343
+ onChange={setEmail}
344
+ status={
345
+ errors.email
346
+ ? {type: 'error', message: errors.email}
347
+ : undefined
348
+ }
349
+ />
350
+ <CheckboxInput
351
+ label="Email me with news and offers"
352
+ value={emailOffers}
353
+ onChange={setEmailOffers}
354
+ />
355
+ </VStack>
356
+
357
+ {/* Shipping Information */}
358
+ <VStack gap={3}>
359
+ <Text type="large" weight="bold">
360
+ Shipping Information
361
+ </Text>
362
+ <Grid columns={isMobile ? 1 : 2} gap={3}>
363
+ <TextInput
364
+ size="lg"
365
+ label="First Name"
366
+ placeholder="John"
367
+ value={firstName}
368
+ onChange={setFirstName}
369
+ status={
370
+ errors.firstName
371
+ ? {type: 'error', message: errors.firstName}
372
+ : undefined
373
+ }
374
+ />
375
+ <TextInput
376
+ size="lg"
377
+ label="Last Name"
378
+ placeholder="Doe"
379
+ value={lastName}
380
+ onChange={setLastName}
381
+ status={
382
+ errors.lastName
383
+ ? {type: 'error', message: errors.lastName}
384
+ : undefined
385
+ }
386
+ />
387
+ </Grid>
388
+ <TextInput
389
+ size="lg"
390
+ label="Address"
391
+ placeholder="123 Main Street"
392
+ value={address}
393
+ onChange={setAddress}
394
+ status={
395
+ errors.address
396
+ ? {type: 'error', message: errors.address}
397
+ : undefined
398
+ }
399
+ />
400
+ <Grid columns={isMobile ? 1 : 2} gap={3}>
401
+ <TextInput
402
+ size="lg"
403
+ label="City"
404
+ placeholder="New York"
405
+ value={city}
406
+ onChange={setCity}
407
+ status={
408
+ errors.city
409
+ ? {type: 'error', message: errors.city}
410
+ : undefined
411
+ }
412
+ />
413
+ <TextInput
414
+ size="lg"
415
+ label="ZIP Code"
416
+ placeholder="10001"
417
+ value={zip}
418
+ onChange={setZip}
419
+ status={
420
+ errors.zip
421
+ ? {type: 'error', message: errors.zip}
422
+ : undefined
423
+ }
424
+ />
425
+ </Grid>
426
+ <Selector
427
+ size="lg"
428
+ label="State"
429
+ placeholder="Select state"
430
+ options={US_STATES}
431
+ value={state}
432
+ onChange={setState}
433
+ status={
434
+ errors.state
435
+ ? {type: 'error', message: errors.state}
436
+ : undefined
437
+ }
438
+ />
439
+ <TextInput
440
+ size="lg"
441
+ label="Phone Number"
442
+ placeholder="+1 (555) 123-4567"
443
+ value={phone}
444
+ onChange={setPhone}
445
+ labelTooltip="We use your phone number to provide shipping updates and contact you about your delivery if needed."
446
+ status={
447
+ errors.phone
448
+ ? {type: 'error', message: errors.phone}
449
+ : undefined
450
+ }
451
+ />
452
+ <CheckboxInput
453
+ label="Save my information for a faster checkout"
454
+ value={saveInfo}
455
+ onChange={setSaveInfo}
456
+ />
457
+ </VStack>
458
+
459
+ {/* Delivery */}
460
+ <VStack gap={3}>
461
+ <VStack gap={1}>
462
+ <Text type="large" weight="bold">
463
+ Delivery
464
+ </Text>
465
+ <Text type="body" color="secondary">
466
+ Please allow 1–3 business days processing time
467
+ before your order ships.
468
+ </Text>
469
+ </VStack>
470
+ <RadioList
471
+ label="Delivery method"
472
+ value={deliveryMethod}
473
+ onChange={setDeliveryMethod}>
474
+ <RadioListItem
475
+ value="standard"
476
+ label="Standard (3–7 business days)"
477
+ endContent={
478
+ <Text
479
+ type="body"
480
+ weight="medium"
481
+ hasTabularNumbers>
482
+ $4.95
483
+ </Text>
484
+ }
485
+ />
486
+ <RadioListItem
487
+ value="expedited"
488
+ label="Expedited (1–2 business days)"
489
+ endContent={
490
+ <Text
491
+ type="body"
492
+ weight="medium"
493
+ hasTabularNumbers>
494
+ $9.95
495
+ </Text>
496
+ }
497
+ />
498
+ </RadioList>
499
+ </VStack>
500
+
501
+ {/* Payment Method */}
502
+ <VStack gap={3}>
503
+ <VStack gap={1}>
504
+ <Text type="large" weight="bold">
505
+ Payment Method
506
+ </Text>
507
+ <Text type="body" color="secondary">
508
+ All transactions are secure and encrypted.
509
+ </Text>
510
+ </VStack>
511
+
512
+ {/* Express checkout */}
513
+ <VStack gap={3}>
514
+ <Grid columns={isMobile ? 1 : 2} gap={3}>
515
+ {/* PayPal */}
516
+ <Button
517
+ label="PayPal"
518
+ variant="primary"
519
+ size="lg"
520
+ onClick={() => {}}
521
+ style={paypalButton}>
522
+ <img
523
+ src="https://www.paypalobjects.com/webstatic/mktg/Logo/pp-logo-100px.png"
524
+ alt="PayPal"
525
+ style={brandLogo}
526
+ />
527
+ </Button>
528
+ {/* Google Pay */}
529
+ <Button
530
+ label="Google Pay"
531
+ variant="primary"
532
+ size="lg"
533
+ onClick={() => {}}
534
+ style={gpayButton}>
535
+ <img
536
+ src="https://pay.google.com/about/static_kcs/images/logos/google-pay-logo.svg"
537
+ alt="Google Pay"
538
+ style={brandLogo}
539
+ />
540
+ </Button>
541
+ </Grid>
542
+ </VStack>
543
+
544
+ {/* OR divider */}
545
+ <HStack gap={3} vAlign="center">
546
+ <StackItem size="fill">
547
+ <Divider />
548
+ </StackItem>
549
+ <Text type="supporting" color="secondary">
550
+ OR
551
+ </Text>
552
+ <StackItem size="fill">
553
+ <Divider />
554
+ </StackItem>
555
+ </HStack>
556
+
557
+ {/* Credit card fields */}
558
+ <VStack gap={3}>
559
+ {/* Card type icons */}
560
+ <HStack gap={1.5} vAlign="center">
561
+ <img
562
+ src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/visa.svg"
563
+ alt="Visa"
564
+ style={cardLogo}
565
+ />
566
+ <img
567
+ src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/mastercard.svg"
568
+ alt="Mastercard"
569
+ style={cardLogo}
570
+ />
571
+ <img
572
+ src="https://raw.githubusercontent.com/aaronfagan/svg-credit-card-payment-icons/main/flat/amex.svg"
573
+ alt="Amex"
574
+ style={cardLogo}
575
+ />
576
+ </HStack>
577
+ <TextInput
578
+ size="lg"
579
+ label="Card Number"
580
+ placeholder="1234 5678 9012 3456"
581
+ value={cardNumber}
582
+ onChange={setCardNumber}
583
+ status={
584
+ errors.cardNumber
585
+ ? {type: 'error', message: errors.cardNumber}
586
+ : undefined
587
+ }
588
+ />
589
+ <Grid columns={isMobile ? 1 : 3} gap={3}>
590
+ <Selector
591
+ size="lg"
592
+ label="Expiry Month"
593
+ placeholder="MM"
594
+ options={MONTHS}
595
+ value={expiry}
596
+ onChange={setExpiry}
597
+ status={
598
+ errors.expiry
599
+ ? {type: 'error', message: errors.expiry}
600
+ : undefined
601
+ }
602
+ />
603
+ <Selector
604
+ size="lg"
605
+ label="Expiry Year"
606
+ placeholder="YY"
607
+ options={YEARS}
608
+ value={expYear}
609
+ onChange={setExpYear}
610
+ status={
611
+ errors.expYear
612
+ ? {type: 'error', message: errors.expYear}
613
+ : undefined
614
+ }
615
+ />
616
+ <TextInput
617
+ size="lg"
618
+ label="CVC"
619
+ placeholder="123"
620
+ value={cvc}
621
+ onChange={setCvc}
622
+ labelTooltip="3-digit security code usually found on the back of your card. American Express cards have a 4-digit code located on the front."
623
+ status={
624
+ errors.cvc
625
+ ? {type: 'error', message: errors.cvc}
626
+ : undefined
627
+ }
628
+ />
629
+ </Grid>
630
+ <TextInput
631
+ size="lg"
632
+ label="Name on Card"
633
+ placeholder="John Doe"
634
+ value={cardName}
635
+ onChange={setCardName}
636
+ status={
637
+ errors.cardName
638
+ ? {type: 'error', message: errors.cardName}
639
+ : undefined
640
+ }
641
+ />
642
+ <CheckboxInput
643
+ label="Use shipping address as billing address"
644
+ value={billingMatchesShipping}
645
+ onChange={setBillingMatchesShipping}
646
+ />
647
+ {!billingMatchesShipping && (
648
+ <VStack gap={3}>
649
+ <TextInput
650
+ size="lg"
651
+ label="Address"
652
+ placeholder="123 Main Street"
653
+ value={billingAddress}
654
+ onChange={setBillingAddress}
655
+ status={
656
+ errors.billingAddress
657
+ ? {
658
+ type: 'error',
659
+ message: errors.billingAddress,
660
+ }
661
+ : undefined
662
+ }
663
+ />
664
+ <Grid columns={isMobile ? 1 : 2} gap={3}>
665
+ <TextInput
666
+ size="lg"
667
+ label="City"
668
+ placeholder="New York"
669
+ value={billingCity}
670
+ onChange={setBillingCity}
671
+ status={
672
+ errors.billingCity
673
+ ? {
674
+ type: 'error',
675
+ message: errors.billingCity,
676
+ }
677
+ : undefined
678
+ }
679
+ />
680
+ <TextInput
681
+ size="lg"
682
+ label="ZIP Code"
683
+ placeholder="10001"
684
+ value={billingZip}
685
+ onChange={setBillingZip}
686
+ status={
687
+ errors.billingZip
688
+ ? {
689
+ type: 'error',
690
+ message: errors.billingZip,
691
+ }
692
+ : undefined
693
+ }
694
+ />
695
+ </Grid>
696
+ <Selector
697
+ size="lg"
698
+ label="State"
699
+ placeholder="Select state"
700
+ options={US_STATES}
701
+ value={billingState}
702
+ onChange={setBillingState}
703
+ status={
704
+ errors.billingState
705
+ ? {
706
+ type: 'error',
707
+ message: errors.billingState,
708
+ }
709
+ : undefined
710
+ }
711
+ />
712
+ </VStack>
713
+ )}
714
+ </VStack>
715
+ </VStack>
716
+
717
+ {/* Promo Code */}
718
+ <VStack gap={3}>
719
+ <Text type="large" weight="bold">
720
+ Promo Code
721
+ </Text>
722
+ <HStack gap={2} vAlign="end">
723
+ <StackItem size="fill">
724
+ <TextInput
725
+ size="lg"
726
+ label="Promo code"
727
+ placeholder="Enter promo code"
728
+ value={promo}
729
+ onChange={setPromo}
730
+ style={fullWidth}
731
+ />
732
+ </StackItem>
733
+ <Button
734
+ label="Apply"
735
+ variant="secondary"
736
+ size="lg"
737
+ onClick={() => {}}
738
+ />
739
+ </HStack>
740
+ </VStack>
741
+
742
+ {/* Gift Options */}
743
+ <VStack gap={3}>
744
+ <Text type="large" weight="bold">
745
+ Gift Options
746
+ </Text>
747
+ <CheckboxInput
748
+ label="Add a gift message"
749
+ value={addGiftMessage}
750
+ onChange={setAddGiftMessage}
751
+ />
752
+ {addGiftMessage && (
753
+ <VStack gap={3}>
754
+ <Grid columns={isMobile ? 1 : 2} gap={3}>
755
+ <TextInput
756
+ size="lg"
757
+ label="To"
758
+ isLabelHidden
759
+ placeholder="To"
760
+ value={giftTo}
761
+ onChange={setGiftTo}
762
+ />
763
+ <TextInput
764
+ size="lg"
765
+ label="From"
766
+ isLabelHidden
767
+ placeholder="From"
768
+ value={giftFrom}
769
+ onChange={setGiftFrom}
770
+ />
771
+ </Grid>
772
+ <TextArea
773
+ label="Gift message"
774
+ isLabelHidden
775
+ placeholder="Write something here"
776
+ value={giftMessage}
777
+ onChange={setGiftMessage}
778
+ />
779
+ </VStack>
780
+ )}
781
+ </VStack>
782
+
783
+ {/* Trust bar + CTAs + policy links */}
784
+ <VStack gap={4}>
785
+ <HStack gap={5} hAlign="center" wrap="wrap">
786
+ <HStack gap={1} vAlign="center">
787
+ <Icon
788
+ icon={ShieldCheck}
789
+ size="sm"
790
+ color="secondary"
791
+ />
792
+ <Text type="supporting" color="secondary">
793
+ Secure Payment
794
+ </Text>
795
+ </HStack>
796
+ <HStack gap={1} vAlign="center">
797
+ <Icon
798
+ icon={Lock}
799
+ size="sm"
800
+ color="secondary"
801
+ />
802
+ <Text type="supporting" color="secondary">
803
+ SSL Encrypted
804
+ </Text>
805
+ </HStack>
806
+ <HStack gap={1} vAlign="center">
807
+ <Icon
808
+ icon={CircleCheck}
809
+ size="sm"
810
+ color="secondary"
811
+ />
812
+ <Text type="supporting" color="secondary">
813
+ Free Returns
814
+ </Text>
815
+ </HStack>
816
+ </HStack>
817
+ <VStack gap={2}>
818
+ <Button
819
+ label="Place Order"
820
+ variant="primary"
821
+ size="lg"
822
+ style={fullWidth}
823
+ onClick={() => setSubmitted(true)}
824
+ />
825
+ <Button
826
+ label="Continue Shopping"
827
+ variant="secondary"
828
+ size="lg"
829
+ style={fullWidth}
830
+ onClick={() => {}}
831
+ />
832
+ </VStack>
833
+ <Divider />
834
+ <HStack gap={4} vAlign="center" wrap="wrap">
835
+ <Link href="#/templates/payment-form" type="supporting">
836
+ Refund policy
837
+ </Link>
838
+ <Link href="#/templates/payment-form" type="supporting">
839
+ Privacy policy
840
+ </Link>
841
+ <Link href="#/templates/payment-form" type="supporting">
842
+ Terms of service
843
+ </Link>
844
+ <Link href="#/templates/payment-form" type="supporting">
845
+ Cancellations
846
+ </Link>
847
+ </HStack>
848
+ </VStack>
849
+ </VStack>
850
+ </StackItem>
851
+
852
+ <StackItem
853
+ size="fill"
854
+ style={isStacked ? summaryMobileOrder : summarySticky}>
855
+ <Card padding={5}>
856
+ <VStack gap={4}>
857
+ {/* Accordion header — clickable on mobile only */}
858
+ <Collapsible
859
+ trigger="Order Summary"
860
+ defaultIsOpen={true}>
861
+ <VStack gap={4} style={summaryContent}>
862
+ {/* Line items */}
863
+ {ORDER_ITEMS.map(item => (
864
+ <VStack key={item.id} gap={3}>
865
+ <HStack gap={3} vAlign="start">
866
+ <Thumbnail
867
+ src={ITEM_IMAGES[item.id].src}
868
+ alt={item.name}
869
+ />
870
+ <StackItem size="fill">
871
+ <VStack gap={1}>
872
+ <HStack
873
+ gap={2}
874
+ hAlign="between"
875
+ vAlign="start">
876
+ <HStack gap={2} vAlign="center" wrap="wrap">
877
+ <Text type="body" weight="medium">
878
+ {item.name}
879
+ </Text>
880
+ {item.limited && (
881
+ <Badge
882
+ variant="yellow"
883
+ label="LIMITED EDITION"
884
+ />
885
+ )}
886
+ </HStack>
887
+ <Text
888
+ type="body"
889
+ weight="bold"
890
+ hasTabularNumbers>
891
+ {fmt(item.price)}
892
+ </Text>
893
+ </HStack>
894
+ <Text type="supporting" color="secondary">
895
+ {item.variant}
896
+ </Text>
897
+ <HStack gap={2} vAlign="end" wrap="wrap">
898
+ <NumberInput
899
+ label="Qty"
900
+ value={
901
+ quantities[item.id] ?? item.qty
902
+ }
903
+ onChange={v =>
904
+ setQuantities(q => ({
905
+ ...q,
906
+ [item.id]: v,
907
+ }))
908
+ }
909
+ min={1}
910
+ max={10}
911
+ isIntegerOnly
912
+ />
913
+ <Link href="#/templates/payment-form" type="supporting">
914
+ Remove
915
+ </Link>
916
+ <Link href="#/templates/payment-form" type="supporting">
917
+ Save
918
+ </Link>
919
+ </HStack>
920
+ </VStack>
921
+ </StackItem>
922
+ </HStack>
923
+ <Divider />
924
+ </VStack>
925
+ ))}
926
+
927
+ {/* Order total subsection */}
928
+ <VStack gap={3}>
929
+ <Text type="large" weight="bold">
930
+ Order Total
931
+ </Text>
932
+ <VStack gap={2}>
933
+ <HStack hAlign="between" vAlign="center">
934
+ <Text type="body" color="secondary">
935
+ Subtotal
936
+ </Text>
937
+ <Text type="body" hasTabularNumbers>
938
+ {fmt(subtotal)}
939
+ </Text>
940
+ </HStack>
941
+ <HStack hAlign="between" vAlign="center">
942
+ <Text type="body" color="secondary">
943
+ Shipping
944
+ </Text>
945
+ <Text type="body" hasTabularNumbers>
946
+ {fmt(shipping)}
947
+ </Text>
948
+ </HStack>
949
+ <HStack hAlign="between" vAlign="center">
950
+ <Text type="body" color="secondary">
951
+ Tax
952
+ </Text>
953
+ <Text type="body" hasTabularNumbers>
954
+ {fmt(tax)}
955
+ </Text>
956
+ </HStack>
957
+ </VStack>
958
+ <Divider />
959
+ <HStack hAlign="between" vAlign="center">
960
+ <Text type="large" weight="bold">
961
+ Total
962
+ </Text>
963
+ <Text
964
+ type="large"
965
+ weight="bold"
966
+ hasTabularNumbers>
967
+ {fmt(total)}
968
+ </Text>
969
+ </HStack>
970
+ <Banner
971
+ status="info"
972
+ icon={<Icon icon={Truck} size="sm" />}
973
+ title="Free shipping on orders over $300"
974
+ />
975
+ </VStack>
976
+ </VStack>
977
+ </Collapsible>
978
+ </VStack>
979
+ {/* end outer card VStack gap={4} */}
980
+ </Card>
981
+ </StackItem>
982
+ </Stack>
983
+ </VStack>
984
+ </Section>
985
+ </Center>
986
+ </LayoutContent>
987
+ }
988
+ />
989
+ );
990
+ }