@marianmeres/stuic 3.9.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -2
- package/API.md +86 -0
- package/README.md +4 -1
- package/dist/components/Checkout/CheckoutAddressForm.svelte +227 -0
- package/dist/components/Checkout/CheckoutAddressForm.svelte.d.ts +56 -0
- package/dist/components/Checkout/CheckoutCartReview.svelte +132 -0
- package/dist/components/Checkout/CheckoutCartReview.svelte.d.ts +47 -0
- package/dist/components/Checkout/CheckoutCompleteStep.svelte +152 -0
- package/dist/components/Checkout/CheckoutCompleteStep.svelte.d.ts +46 -0
- package/dist/components/Checkout/CheckoutConfirmStep.svelte +238 -0
- package/dist/components/Checkout/CheckoutConfirmStep.svelte.d.ts +46 -0
- package/dist/components/Checkout/CheckoutDeliveryOptions.svelte +192 -0
- package/dist/components/Checkout/CheckoutDeliveryOptions.svelte.d.ts +53 -0
- package/dist/components/Checkout/CheckoutGuestForm.svelte +239 -0
- package/dist/components/Checkout/CheckoutGuestForm.svelte.d.ts +51 -0
- package/dist/components/Checkout/CheckoutLoginForm.svelte +193 -0
- package/dist/components/Checkout/CheckoutLoginForm.svelte.d.ts +46 -0
- package/dist/components/Checkout/CheckoutOrderConfirmation.svelte +250 -0
- package/dist/components/Checkout/CheckoutOrderConfirmation.svelte.d.ts +47 -0
- package/dist/components/Checkout/CheckoutOrderReview.svelte +262 -0
- package/dist/components/Checkout/CheckoutOrderReview.svelte.d.ts +65 -0
- package/dist/components/Checkout/CheckoutOrderSummary.svelte +170 -0
- package/dist/components/Checkout/CheckoutOrderSummary.svelte.d.ts +46 -0
- package/dist/components/Checkout/CheckoutProgress.svelte +145 -0
- package/dist/components/Checkout/CheckoutProgress.svelte.d.ts +45 -0
- package/dist/components/Checkout/CheckoutReviewStep.svelte +248 -0
- package/dist/components/Checkout/CheckoutReviewStep.svelte.d.ts +69 -0
- package/dist/components/Checkout/CheckoutShippingStep.svelte +309 -0
- package/dist/components/Checkout/CheckoutShippingStep.svelte.d.ts +61 -0
- package/dist/components/Checkout/_internal/checkout-i18n-defaults.d.ts +5 -0
- package/dist/components/Checkout/_internal/checkout-i18n-defaults.js +129 -0
- package/dist/components/Checkout/_internal/checkout-types.d.ts +87 -0
- package/dist/components/Checkout/_internal/checkout-types.js +4 -0
- package/dist/components/Checkout/_internal/checkout-utils.d.ts +14 -0
- package/dist/components/Checkout/_internal/checkout-utils.js +87 -0
- package/dist/components/Checkout/index.css +1059 -0
- package/dist/components/Checkout/index.d.ts +15 -0
- package/dist/components/Checkout/index.js +16 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/themes/css/zinc.css +217 -0
- package/dist/themes/zinc.d.ts +6 -0
- package/dist/themes/zinc.js +115 -0
- package/docs/architecture.md +1 -1
- package/docs/domains/components.md +6 -1
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
src/lib/
|
|
26
|
-
├── components/ #
|
|
26
|
+
├── components/ # 37 UI components
|
|
27
27
|
├── actions/ # 12 Svelte actions
|
|
28
28
|
├── utils/ # 42 utility functions
|
|
29
29
|
├── themes/ # 26 theme definitions (.ts) + generated CSS (css/)
|
|
@@ -72,7 +72,7 @@ src/lib/
|
|
|
72
72
|
- [Tasks](./docs/tasks.md) — Common procedures
|
|
73
73
|
|
|
74
74
|
### Domain Docs
|
|
75
|
-
- [Components](./docs/domains/components.md) —
|
|
75
|
+
- [Components](./docs/domains/components.md) — 37 components, Props pattern, snippets
|
|
76
76
|
- [Theming](./docs/domains/theming.md) — CSS tokens, dark mode, themes
|
|
77
77
|
- [Actions](./docs/domains/actions.md) — 12 Svelte directives
|
|
78
78
|
- [Utils](./docs/domains/utils.md) — 42 utility functions
|
package/API.md
CHANGED
|
@@ -323,6 +323,56 @@ Loading placeholder with animation.
|
|
|
323
323
|
|
|
324
324
|
---
|
|
325
325
|
|
|
326
|
+
### E-commerce
|
|
327
|
+
|
|
328
|
+
#### `Cart`
|
|
329
|
+
|
|
330
|
+
Shopping cart component with quantity controls, pricing, and summary. Supports interactive (full cart), readonly (checkout summary), and compact (popover preview) modes.
|
|
331
|
+
|
|
332
|
+
| Prop | Type | Default | Description |
|
|
333
|
+
|------|------|---------|-------------|
|
|
334
|
+
| `items` | `CartComponentItem[]` | required | Cart items to display |
|
|
335
|
+
| `variant` | `"default" \| "compact"` | `"default"` | Layout variant. Compact is smaller, scrollable, implicitly readonly |
|
|
336
|
+
| `formatPrice` | `(value: number) => string` | `(v) => (v / 100).toFixed(2)` | Format numeric price for display |
|
|
337
|
+
| `onQuantityChange` | `(id: string, qty: number) => void` | — | Called when quantity changes |
|
|
338
|
+
| `onRemove` | `(id: string) => void` | — | Called when remove is clicked |
|
|
339
|
+
| `noThumbnails` | `boolean` | `false` | Hide all thumbnails |
|
|
340
|
+
| `readonly` | `boolean` | `false` | Hide interactive controls |
|
|
341
|
+
| `loading` | `boolean` | `false` | Show loading skeleton |
|
|
342
|
+
| `updatingItems` | `Set<string>` | `new Set()` | Item IDs currently being updated (reduced opacity) |
|
|
343
|
+
| `t` | `TranslateFn` | built-in | Translation function for i18n |
|
|
344
|
+
|
|
345
|
+
**Snippets:**
|
|
346
|
+
|
|
347
|
+
| Snippet | Params | Description |
|
|
348
|
+
|---------|--------|-------------|
|
|
349
|
+
| `thumbnail` | `{ item }` | Override thumbnail rendering |
|
|
350
|
+
| `itemRow` | `{ item, isUpdating, readonly, formatPrice }` | Override entire item row |
|
|
351
|
+
| `summary` | `{ items, total, itemCount, formatPrice }` | Override summary section |
|
|
352
|
+
| `empty` | — | Custom empty state |
|
|
353
|
+
| `footer` | `{ items, total, itemCount }` | Content after summary (CTAs) |
|
|
354
|
+
|
|
355
|
+
```svelte
|
|
356
|
+
<Cart
|
|
357
|
+
{items}
|
|
358
|
+
formatPrice={(v) => `$${(v / 100).toFixed(2)}`}
|
|
359
|
+
onQuantityChange={handleQuantityChange}
|
|
360
|
+
onRemove={handleRemove}
|
|
361
|
+
/>
|
|
362
|
+
|
|
363
|
+
<!-- Readonly mode (checkout summary) -->
|
|
364
|
+
<Cart {items} readonly formatPrice={(v) => `$${(v / 100).toFixed(2)}`} />
|
|
365
|
+
|
|
366
|
+
<!-- Compact variant (for popovers) -->
|
|
367
|
+
<Cart {items} variant="compact" formatPrice={(v) => `$${(v / 100).toFixed(2)}`}>
|
|
368
|
+
{#snippet footer({ total, itemCount })}
|
|
369
|
+
<a href="/cart">View Cart</a>
|
|
370
|
+
{/snippet}
|
|
371
|
+
</Cart>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
326
376
|
### Display
|
|
327
377
|
|
|
328
378
|
#### `Avatar`
|
|
@@ -356,6 +406,39 @@ Image/content slider with navigation.
|
|
|
356
406
|
|
|
357
407
|
Animated loading dots ("...").
|
|
358
408
|
|
|
409
|
+
#### `DataTable`
|
|
410
|
+
|
|
411
|
+
Responsive data table with paging, row selection, batch actions, and mobile card view.
|
|
412
|
+
|
|
413
|
+
| Prop | Type | Default | Description |
|
|
414
|
+
|------|------|---------|-------------|
|
|
415
|
+
| `columns` | `DataTableColumn<T>[]` | required | Column definitions |
|
|
416
|
+
| `data` | `T[]` | required | Row data objects |
|
|
417
|
+
| `getRowId` | `(row: T, index: number) => string \| number` | index | Unique row ID extractor |
|
|
418
|
+
| `paging` | `PagingCalcResult` | — | Paging calculation from `@marianmeres/paging-store` |
|
|
419
|
+
| `onPageChange` | `(offset: number) => void` | — | Page navigation callback |
|
|
420
|
+
| `selectable` | `boolean` | `false` | Enable row selection checkboxes |
|
|
421
|
+
| `selected` | `Set<string \| number>` | — | Bindable set of selected row IDs |
|
|
422
|
+
| `selectOnRowClick` | `boolean` | `false` | Toggle selection on row click |
|
|
423
|
+
| `onRowClick` | `(row: T, index: number) => void` | — | Row click callback |
|
|
424
|
+
| `loading` | `boolean` | `false` | Show loading overlay |
|
|
425
|
+
| `t` | `TranslateFn` | built-in | Translation function for i18n |
|
|
426
|
+
|
|
427
|
+
**Snippets:** `cell`, `batchActions`, `empty`, `mobileRow`
|
|
428
|
+
|
|
429
|
+
```svelte
|
|
430
|
+
<DataTable
|
|
431
|
+
columns={[
|
|
432
|
+
{ key: "name", label: "Name" },
|
|
433
|
+
{ key: "email", label: "Email" },
|
|
434
|
+
{ key: "role", label: "Role", align: "center" },
|
|
435
|
+
]}
|
|
436
|
+
data={users}
|
|
437
|
+
selectable
|
|
438
|
+
bind:selected={selectedIds}
|
|
439
|
+
/>
|
|
440
|
+
```
|
|
441
|
+
|
|
359
442
|
#### `ThemePreview`
|
|
360
443
|
|
|
361
444
|
Theme color swatch preview.
|
|
@@ -886,6 +969,8 @@ import type {
|
|
|
886
969
|
Naming pattern: `{ComponentName}Props`
|
|
887
970
|
|
|
888
971
|
Additional exported types include:
|
|
972
|
+
- `CartComponentItem`, `CartVariant` — Cart component types
|
|
973
|
+
- `DataTableColumn` — DataTable column definition type
|
|
889
974
|
- `FieldAsset`, `FieldAssetUrlObj`, `FieldAssetWithBlobUrl` — Asset field types
|
|
890
975
|
- `FieldOption` — Option type for FieldOptions
|
|
891
976
|
- `KeyValueEntry` — Entry type for FieldKeyValues
|
|
@@ -969,6 +1054,7 @@ Each component defines customization tokens. Override globally in `:root {}` or
|
|
|
969
1054
|
| Tooltip | `--stuic-tooltip-*` | `bg`, `text` |
|
|
970
1055
|
| Popover | `--stuic-popover-*` | `bg`, `text`, `border` |
|
|
971
1056
|
| Skeleton | `--stuic-skeleton-*` | `bg`, `bg-highlight`, `duration` |
|
|
1057
|
+
| Cart | `--stuic-cart-*` | `gap`, `item-padding`, `item-radius`, `item-border-color`, `item-bg`, `thumbnail-size`, `quantity-border-color`, `remove-color`, `summary-border-color`, `compact-max-height`, `transition` |
|
|
972
1058
|
|
|
973
1059
|
### CSS Variable Naming Convention
|
|
974
1060
|
|
package/README.md
CHANGED
|
@@ -141,7 +141,10 @@ Notifications, AlertConfirmPrompt, DismissibleMessage, Progress, Spinner, Skelet
|
|
|
141
141
|
CommandMenu, DropdownMenu, TabbedMenu, TypeaheadInput, KbdShortcut
|
|
142
142
|
|
|
143
143
|
### Display & Utility
|
|
144
|
-
Avatar, Carousel, AnimatedElipsis, ThemePreview, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview
|
|
144
|
+
Avatar, Carousel, AnimatedElipsis, ThemePreview, ColorScheme, Thc, HoverExpandableWidth, AssetsPreview, DataTable
|
|
145
|
+
|
|
146
|
+
### E-commerce
|
|
147
|
+
Cart
|
|
145
148
|
|
|
146
149
|
## Actions
|
|
147
150
|
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import type { TranslateFn } from "../../types.js";
|
|
5
|
+
import type {
|
|
6
|
+
CheckoutAddressData,
|
|
7
|
+
CheckoutValidationError,
|
|
8
|
+
} from "./_internal/checkout-types.js";
|
|
9
|
+
|
|
10
|
+
export interface Props extends Omit<HTMLAttributes<HTMLFieldSetElement>, "children"> {
|
|
11
|
+
/** Bindable address data. Default: createEmptyAddress() */
|
|
12
|
+
address?: CheckoutAddressData;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Label prefix used for:
|
|
16
|
+
* - Accessible field IDs (e.g., "shipping-name", "billing-street")
|
|
17
|
+
* - Error field matching (errors with field "shipping.name" match when label="shipping")
|
|
18
|
+
* Default: "address"
|
|
19
|
+
*/
|
|
20
|
+
label?: string;
|
|
21
|
+
|
|
22
|
+
/** External validation errors */
|
|
23
|
+
errors?: CheckoutValidationError[];
|
|
24
|
+
|
|
25
|
+
/** Which fields to display. All default to true. */
|
|
26
|
+
fields?: {
|
|
27
|
+
name?: boolean;
|
|
28
|
+
street?: boolean;
|
|
29
|
+
city?: boolean;
|
|
30
|
+
postal_code?: boolean;
|
|
31
|
+
country?: boolean;
|
|
32
|
+
phone?: boolean;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Which fields are required (shown with * marker, included in built-in validation).
|
|
37
|
+
* Default: ["name", "street", "city", "postal_code", "country"]
|
|
38
|
+
*/
|
|
39
|
+
requiredFields?: string[];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Override the country field with a custom selector.
|
|
43
|
+
* When provided, replaces the default text input for country.
|
|
44
|
+
*/
|
|
45
|
+
countryField?: Snippet<
|
|
46
|
+
[
|
|
47
|
+
{
|
|
48
|
+
/** Current country value */
|
|
49
|
+
value: string;
|
|
50
|
+
/** Called when country changes */
|
|
51
|
+
onchange: (value: string) => void;
|
|
52
|
+
/** Error message for this field (if any) */
|
|
53
|
+
error?: string;
|
|
54
|
+
/** Field label text */
|
|
55
|
+
label: string;
|
|
56
|
+
/** HTML id attribute for the input */
|
|
57
|
+
id: string;
|
|
58
|
+
},
|
|
59
|
+
]
|
|
60
|
+
>;
|
|
61
|
+
|
|
62
|
+
t?: TranslateFn;
|
|
63
|
+
unstyled?: boolean;
|
|
64
|
+
class?: string;
|
|
65
|
+
el?: HTMLFieldSetElement;
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<script lang="ts">
|
|
70
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
71
|
+
import { t_default } from "./_internal/checkout-i18n-defaults.js";
|
|
72
|
+
import { createEmptyAddress } from "./_internal/checkout-utils.js";
|
|
73
|
+
import FieldInput from "../Input/FieldInput.svelte";
|
|
74
|
+
|
|
75
|
+
const DEFAULT_REQUIRED = ["name", "street", "city", "postal_code", "country"];
|
|
76
|
+
|
|
77
|
+
let {
|
|
78
|
+
address = $bindable(createEmptyAddress()),
|
|
79
|
+
label = "address",
|
|
80
|
+
errors: externalErrors = [],
|
|
81
|
+
fields,
|
|
82
|
+
requiredFields = DEFAULT_REQUIRED,
|
|
83
|
+
countryField,
|
|
84
|
+
t: tProp,
|
|
85
|
+
unstyled = false,
|
|
86
|
+
class: classProp,
|
|
87
|
+
el = $bindable(),
|
|
88
|
+
...rest
|
|
89
|
+
}: Props = $props();
|
|
90
|
+
|
|
91
|
+
let t = $derived(tProp ?? t_default);
|
|
92
|
+
|
|
93
|
+
function fieldError(field: string): string | undefined {
|
|
94
|
+
return externalErrors.find((e) => e.field.endsWith(`.${field}`))?.message;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isRequired(field: string): boolean {
|
|
98
|
+
return requiredFields.includes(field);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let _class = $derived(
|
|
102
|
+
unstyled ? classProp : twMerge("stuic-checkout-address", classProp)
|
|
103
|
+
);
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
<fieldset bind:this={el} class={_class} {...rest}>
|
|
107
|
+
<!-- Name (full width, block label) -->
|
|
108
|
+
{#if fields?.name !== false}
|
|
109
|
+
<FieldInput
|
|
110
|
+
bind:value={address.name}
|
|
111
|
+
label={t("checkout.address.name_label")}
|
|
112
|
+
placeholder={t("checkout.address.name_placeholder")}
|
|
113
|
+
required={isRequired("name")}
|
|
114
|
+
name="{label}-name"
|
|
115
|
+
id="{label}-name"
|
|
116
|
+
labelLeftBreakpoint={0}
|
|
117
|
+
validate={{
|
|
118
|
+
customValidator(val) {
|
|
119
|
+
return fieldError("name") || "";
|
|
120
|
+
},
|
|
121
|
+
}}
|
|
122
|
+
/>
|
|
123
|
+
{/if}
|
|
124
|
+
|
|
125
|
+
<!-- Street (full width, block label) -->
|
|
126
|
+
{#if fields?.street !== false}
|
|
127
|
+
<FieldInput
|
|
128
|
+
bind:value={address.street}
|
|
129
|
+
label={t("checkout.address.street_label")}
|
|
130
|
+
placeholder={t("checkout.address.street_placeholder")}
|
|
131
|
+
required={isRequired("street")}
|
|
132
|
+
name="{label}-street"
|
|
133
|
+
id="{label}-street"
|
|
134
|
+
labelLeftBreakpoint={0}
|
|
135
|
+
validate={{
|
|
136
|
+
customValidator(val) {
|
|
137
|
+
return fieldError("street") || "";
|
|
138
|
+
},
|
|
139
|
+
}}
|
|
140
|
+
/>
|
|
141
|
+
{/if}
|
|
142
|
+
|
|
143
|
+
<!-- City + Postal Code (2-column grid) -->
|
|
144
|
+
{#if fields?.city !== false || fields?.postal_code !== false}
|
|
145
|
+
<div class={unstyled ? undefined : "stuic-checkout-address-row"}>
|
|
146
|
+
{#if fields?.city !== false}
|
|
147
|
+
<FieldInput
|
|
148
|
+
bind:value={address.city}
|
|
149
|
+
label={t("checkout.address.city_label")}
|
|
150
|
+
placeholder={t("checkout.address.city_placeholder")}
|
|
151
|
+
required={isRequired("city")}
|
|
152
|
+
name="{label}-city"
|
|
153
|
+
id="{label}-city"
|
|
154
|
+
validate={{
|
|
155
|
+
customValidator(val) {
|
|
156
|
+
return fieldError("city") || "";
|
|
157
|
+
},
|
|
158
|
+
}}
|
|
159
|
+
/>
|
|
160
|
+
{/if}
|
|
161
|
+
{#if fields?.postal_code !== false}
|
|
162
|
+
<FieldInput
|
|
163
|
+
bind:value={address.postal_code}
|
|
164
|
+
label={t("checkout.address.postal_code_label")}
|
|
165
|
+
placeholder={t("checkout.address.postal_code_placeholder")}
|
|
166
|
+
required={isRequired("postal_code")}
|
|
167
|
+
name="{label}-postal_code"
|
|
168
|
+
id="{label}-postal_code"
|
|
169
|
+
validate={{
|
|
170
|
+
customValidator(val) {
|
|
171
|
+
return fieldError("postal_code") || "";
|
|
172
|
+
},
|
|
173
|
+
}}
|
|
174
|
+
/>
|
|
175
|
+
{/if}
|
|
176
|
+
</div>
|
|
177
|
+
{/if}
|
|
178
|
+
|
|
179
|
+
<!-- Country (full width, block label) -->
|
|
180
|
+
{#if fields?.country !== false}
|
|
181
|
+
{#if countryField}
|
|
182
|
+
{@render countryField({
|
|
183
|
+
value: address.country,
|
|
184
|
+
onchange: (v) => {
|
|
185
|
+
address.country = v;
|
|
186
|
+
},
|
|
187
|
+
error: fieldError("country"),
|
|
188
|
+
label: t("checkout.address.country_label"),
|
|
189
|
+
id: `${label}-country`,
|
|
190
|
+
})}
|
|
191
|
+
{:else}
|
|
192
|
+
<FieldInput
|
|
193
|
+
bind:value={address.country}
|
|
194
|
+
label={t("checkout.address.country_label")}
|
|
195
|
+
placeholder={t("checkout.address.country_placeholder")}
|
|
196
|
+
required={isRequired("country")}
|
|
197
|
+
name="{label}-country"
|
|
198
|
+
id="{label}-country"
|
|
199
|
+
labelLeftBreakpoint={0}
|
|
200
|
+
validate={{
|
|
201
|
+
customValidator(val) {
|
|
202
|
+
return fieldError("country") || "";
|
|
203
|
+
},
|
|
204
|
+
}}
|
|
205
|
+
/>
|
|
206
|
+
{/if}
|
|
207
|
+
{/if}
|
|
208
|
+
|
|
209
|
+
<!-- Phone (full width, block label) -->
|
|
210
|
+
{#if fields?.phone !== false}
|
|
211
|
+
<FieldInput
|
|
212
|
+
bind:value={address.phone}
|
|
213
|
+
label={t("checkout.address.phone_label")}
|
|
214
|
+
type="tel"
|
|
215
|
+
placeholder={t("checkout.address.phone_placeholder")}
|
|
216
|
+
required={isRequired("phone")}
|
|
217
|
+
name="{label}-phone"
|
|
218
|
+
id="{label}-phone"
|
|
219
|
+
labelLeftBreakpoint={0}
|
|
220
|
+
validate={{
|
|
221
|
+
customValidator(val) {
|
|
222
|
+
return fieldError("phone") || "";
|
|
223
|
+
},
|
|
224
|
+
}}
|
|
225
|
+
/>
|
|
226
|
+
{/if}
|
|
227
|
+
</fieldset>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { TranslateFn } from "../../types.js";
|
|
4
|
+
import type { CheckoutAddressData, CheckoutValidationError } from "./_internal/checkout-types.js";
|
|
5
|
+
export interface Props extends Omit<HTMLAttributes<HTMLFieldSetElement>, "children"> {
|
|
6
|
+
/** Bindable address data. Default: createEmptyAddress() */
|
|
7
|
+
address?: CheckoutAddressData;
|
|
8
|
+
/**
|
|
9
|
+
* Label prefix used for:
|
|
10
|
+
* - Accessible field IDs (e.g., "shipping-name", "billing-street")
|
|
11
|
+
* - Error field matching (errors with field "shipping.name" match when label="shipping")
|
|
12
|
+
* Default: "address"
|
|
13
|
+
*/
|
|
14
|
+
label?: string;
|
|
15
|
+
/** External validation errors */
|
|
16
|
+
errors?: CheckoutValidationError[];
|
|
17
|
+
/** Which fields to display. All default to true. */
|
|
18
|
+
fields?: {
|
|
19
|
+
name?: boolean;
|
|
20
|
+
street?: boolean;
|
|
21
|
+
city?: boolean;
|
|
22
|
+
postal_code?: boolean;
|
|
23
|
+
country?: boolean;
|
|
24
|
+
phone?: boolean;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Which fields are required (shown with * marker, included in built-in validation).
|
|
28
|
+
* Default: ["name", "street", "city", "postal_code", "country"]
|
|
29
|
+
*/
|
|
30
|
+
requiredFields?: string[];
|
|
31
|
+
/**
|
|
32
|
+
* Override the country field with a custom selector.
|
|
33
|
+
* When provided, replaces the default text input for country.
|
|
34
|
+
*/
|
|
35
|
+
countryField?: Snippet<[
|
|
36
|
+
{
|
|
37
|
+
/** Current country value */
|
|
38
|
+
value: string;
|
|
39
|
+
/** Called when country changes */
|
|
40
|
+
onchange: (value: string) => void;
|
|
41
|
+
/** Error message for this field (if any) */
|
|
42
|
+
error?: string;
|
|
43
|
+
/** Field label text */
|
|
44
|
+
label: string;
|
|
45
|
+
/** HTML id attribute for the input */
|
|
46
|
+
id: string;
|
|
47
|
+
}
|
|
48
|
+
]>;
|
|
49
|
+
t?: TranslateFn;
|
|
50
|
+
unstyled?: boolean;
|
|
51
|
+
class?: string;
|
|
52
|
+
el?: HTMLFieldSetElement;
|
|
53
|
+
}
|
|
54
|
+
declare const CheckoutAddressForm: import("svelte").Component<Props, {}, "address" | "el">;
|
|
55
|
+
type CheckoutAddressForm = ReturnType<typeof CheckoutAddressForm>;
|
|
56
|
+
export default CheckoutAddressForm;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
4
|
+
import type { TranslateFn } from "../../types.js";
|
|
5
|
+
import type { CartComponentItem } from "../Cart/Cart.svelte";
|
|
6
|
+
|
|
7
|
+
export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> {
|
|
8
|
+
/** Cart items in stuic CartComponentItem format */
|
|
9
|
+
items: CartComponentItem[];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Format a number (in cents) to a display string.
|
|
13
|
+
* Default: defaultFormatPrice (cents / 100, 2 decimal places)
|
|
14
|
+
*/
|
|
15
|
+
formatPrice?: (value: number) => string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Called when "Edit Cart" is clicked.
|
|
19
|
+
* If undefined, the edit action is not rendered.
|
|
20
|
+
*/
|
|
21
|
+
onEditCart?: () => void;
|
|
22
|
+
|
|
23
|
+
/** Override thumbnail rendering (passed through to Cart) */
|
|
24
|
+
thumbnail?: Snippet<[{ item: CartComponentItem }]>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Override the Cart's summary section.
|
|
28
|
+
* Passed through to Cart's `summary` snippet.
|
|
29
|
+
*/
|
|
30
|
+
summary?: Snippet<
|
|
31
|
+
[{
|
|
32
|
+
items: CartComponentItem[];
|
|
33
|
+
total: number;
|
|
34
|
+
itemCount: number;
|
|
35
|
+
formatPrice: (v: number) => string;
|
|
36
|
+
}]
|
|
37
|
+
>;
|
|
38
|
+
|
|
39
|
+
/** Override the title (default: "Order Summary") */
|
|
40
|
+
title?: Snippet | string;
|
|
41
|
+
|
|
42
|
+
/** Override the edit action */
|
|
43
|
+
editAction?: Snippet<[{ onEditCart?: () => void }]>;
|
|
44
|
+
|
|
45
|
+
t?: TranslateFn;
|
|
46
|
+
unstyled?: boolean;
|
|
47
|
+
class?: string;
|
|
48
|
+
el?: HTMLDivElement;
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<script lang="ts">
|
|
53
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
54
|
+
import { t_default } from "./_internal/checkout-i18n-defaults.js";
|
|
55
|
+
import { defaultFormatPrice } from "./_internal/checkout-utils.js";
|
|
56
|
+
import Cart from "../Cart/Cart.svelte";
|
|
57
|
+
import Button from "../Button/Button.svelte";
|
|
58
|
+
|
|
59
|
+
let {
|
|
60
|
+
items,
|
|
61
|
+
formatPrice: formatPriceProp,
|
|
62
|
+
onEditCart,
|
|
63
|
+
thumbnail,
|
|
64
|
+
summary: summaryProp,
|
|
65
|
+
title: titleProp,
|
|
66
|
+
editAction,
|
|
67
|
+
t: tProp,
|
|
68
|
+
unstyled = false,
|
|
69
|
+
class: classProp,
|
|
70
|
+
el = $bindable(),
|
|
71
|
+
...rest
|
|
72
|
+
}: Props = $props();
|
|
73
|
+
|
|
74
|
+
let t = $derived(tProp ?? t_default);
|
|
75
|
+
let fp = $derived(formatPriceProp ?? defaultFormatPrice);
|
|
76
|
+
|
|
77
|
+
let total = $derived(items.reduce((sum, item) => sum + item.lineTotal, 0));
|
|
78
|
+
let itemCount = $derived(items.reduce((sum, item) => sum + item.quantity, 0));
|
|
79
|
+
|
|
80
|
+
let _class = $derived(
|
|
81
|
+
unstyled ? classProp : twMerge("stuic-checkout-cart-review", classProp)
|
|
82
|
+
);
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<div bind:this={el} class={_class} {...rest}>
|
|
86
|
+
<!-- Header bar -->
|
|
87
|
+
<div class={unstyled ? undefined : "stuic-checkout-cart-review-header"}>
|
|
88
|
+
{#if typeof titleProp === "function"}
|
|
89
|
+
{@render titleProp()}
|
|
90
|
+
{:else}
|
|
91
|
+
<h3 class={unstyled ? undefined : "stuic-checkout-cart-review-title"}>
|
|
92
|
+
{typeof titleProp === "string" ? titleProp : t("checkout.cart.title")}
|
|
93
|
+
</h3>
|
|
94
|
+
{/if}
|
|
95
|
+
|
|
96
|
+
{#if editAction}
|
|
97
|
+
{@render editAction({ onEditCart })}
|
|
98
|
+
{:else if onEditCart}
|
|
99
|
+
<Button variant="outline" size="sm" onclick={onEditCart}>
|
|
100
|
+
{t("checkout.cart.edit")}
|
|
101
|
+
</Button>
|
|
102
|
+
{/if}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<!-- Cart (readonly) -->
|
|
106
|
+
<Cart
|
|
107
|
+
{items}
|
|
108
|
+
readonly
|
|
109
|
+
formatPrice={fp}
|
|
110
|
+
{thumbnail}
|
|
111
|
+
t={tProp}
|
|
112
|
+
{unstyled}
|
|
113
|
+
>
|
|
114
|
+
{#snippet summary({ items: _items, total: _total, itemCount: _itemCount, formatPrice: _fp })}
|
|
115
|
+
{#if summaryProp}
|
|
116
|
+
{@render summaryProp({ items: _items, total: _total, itemCount: _itemCount, formatPrice: _fp })}
|
|
117
|
+
{:else}
|
|
118
|
+
<div class={unstyled ? undefined : "stuic-checkout-cart-review-summary"}>
|
|
119
|
+
<span>
|
|
120
|
+
{t("checkout.cart.subtotal")}
|
|
121
|
+
({_itemCount === 1
|
|
122
|
+
? t("checkout.cart.item_count_1")
|
|
123
|
+
: t("checkout.cart.item_count_n", { count: _itemCount })})
|
|
124
|
+
</span>
|
|
125
|
+
<span class={unstyled ? undefined : "stuic-checkout-cart-review-summary-total"}>
|
|
126
|
+
{_fp(_total)}
|
|
127
|
+
</span>
|
|
128
|
+
</div>
|
|
129
|
+
{/if}
|
|
130
|
+
{/snippet}
|
|
131
|
+
</Cart>
|
|
132
|
+
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { TranslateFn } from "../../types.js";
|
|
4
|
+
import type { CartComponentItem } from "../Cart/Cart.svelte";
|
|
5
|
+
export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> {
|
|
6
|
+
/** Cart items in stuic CartComponentItem format */
|
|
7
|
+
items: CartComponentItem[];
|
|
8
|
+
/**
|
|
9
|
+
* Format a number (in cents) to a display string.
|
|
10
|
+
* Default: defaultFormatPrice (cents / 100, 2 decimal places)
|
|
11
|
+
*/
|
|
12
|
+
formatPrice?: (value: number) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Called when "Edit Cart" is clicked.
|
|
15
|
+
* If undefined, the edit action is not rendered.
|
|
16
|
+
*/
|
|
17
|
+
onEditCart?: () => void;
|
|
18
|
+
/** Override thumbnail rendering (passed through to Cart) */
|
|
19
|
+
thumbnail?: Snippet<[{
|
|
20
|
+
item: CartComponentItem;
|
|
21
|
+
}]>;
|
|
22
|
+
/**
|
|
23
|
+
* Override the Cart's summary section.
|
|
24
|
+
* Passed through to Cart's `summary` snippet.
|
|
25
|
+
*/
|
|
26
|
+
summary?: Snippet<[
|
|
27
|
+
{
|
|
28
|
+
items: CartComponentItem[];
|
|
29
|
+
total: number;
|
|
30
|
+
itemCount: number;
|
|
31
|
+
formatPrice: (v: number) => string;
|
|
32
|
+
}
|
|
33
|
+
]>;
|
|
34
|
+
/** Override the title (default: "Order Summary") */
|
|
35
|
+
title?: Snippet | string;
|
|
36
|
+
/** Override the edit action */
|
|
37
|
+
editAction?: Snippet<[{
|
|
38
|
+
onEditCart?: () => void;
|
|
39
|
+
}]>;
|
|
40
|
+
t?: TranslateFn;
|
|
41
|
+
unstyled?: boolean;
|
|
42
|
+
class?: string;
|
|
43
|
+
el?: HTMLDivElement;
|
|
44
|
+
}
|
|
45
|
+
declare const CheckoutCartReview: import("svelte").Component<Props, {}, "el">;
|
|
46
|
+
type CheckoutCartReview = ReturnType<typeof CheckoutCartReview>;
|
|
47
|
+
export default CheckoutCartReview;
|