@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.
Files changed (47) hide show
  1. package/AGENTS.md +2 -2
  2. package/API.md +86 -0
  3. package/README.md +4 -1
  4. package/dist/components/Checkout/CheckoutAddressForm.svelte +227 -0
  5. package/dist/components/Checkout/CheckoutAddressForm.svelte.d.ts +56 -0
  6. package/dist/components/Checkout/CheckoutCartReview.svelte +132 -0
  7. package/dist/components/Checkout/CheckoutCartReview.svelte.d.ts +47 -0
  8. package/dist/components/Checkout/CheckoutCompleteStep.svelte +152 -0
  9. package/dist/components/Checkout/CheckoutCompleteStep.svelte.d.ts +46 -0
  10. package/dist/components/Checkout/CheckoutConfirmStep.svelte +238 -0
  11. package/dist/components/Checkout/CheckoutConfirmStep.svelte.d.ts +46 -0
  12. package/dist/components/Checkout/CheckoutDeliveryOptions.svelte +192 -0
  13. package/dist/components/Checkout/CheckoutDeliveryOptions.svelte.d.ts +53 -0
  14. package/dist/components/Checkout/CheckoutGuestForm.svelte +239 -0
  15. package/dist/components/Checkout/CheckoutGuestForm.svelte.d.ts +51 -0
  16. package/dist/components/Checkout/CheckoutLoginForm.svelte +193 -0
  17. package/dist/components/Checkout/CheckoutLoginForm.svelte.d.ts +46 -0
  18. package/dist/components/Checkout/CheckoutOrderConfirmation.svelte +250 -0
  19. package/dist/components/Checkout/CheckoutOrderConfirmation.svelte.d.ts +47 -0
  20. package/dist/components/Checkout/CheckoutOrderReview.svelte +262 -0
  21. package/dist/components/Checkout/CheckoutOrderReview.svelte.d.ts +65 -0
  22. package/dist/components/Checkout/CheckoutOrderSummary.svelte +170 -0
  23. package/dist/components/Checkout/CheckoutOrderSummary.svelte.d.ts +46 -0
  24. package/dist/components/Checkout/CheckoutProgress.svelte +145 -0
  25. package/dist/components/Checkout/CheckoutProgress.svelte.d.ts +45 -0
  26. package/dist/components/Checkout/CheckoutReviewStep.svelte +248 -0
  27. package/dist/components/Checkout/CheckoutReviewStep.svelte.d.ts +69 -0
  28. package/dist/components/Checkout/CheckoutShippingStep.svelte +309 -0
  29. package/dist/components/Checkout/CheckoutShippingStep.svelte.d.ts +61 -0
  30. package/dist/components/Checkout/_internal/checkout-i18n-defaults.d.ts +5 -0
  31. package/dist/components/Checkout/_internal/checkout-i18n-defaults.js +129 -0
  32. package/dist/components/Checkout/_internal/checkout-types.d.ts +87 -0
  33. package/dist/components/Checkout/_internal/checkout-types.js +4 -0
  34. package/dist/components/Checkout/_internal/checkout-utils.d.ts +14 -0
  35. package/dist/components/Checkout/_internal/checkout-utils.js +87 -0
  36. package/dist/components/Checkout/index.css +1059 -0
  37. package/dist/components/Checkout/index.d.ts +15 -0
  38. package/dist/components/Checkout/index.js +16 -0
  39. package/dist/index.css +1 -0
  40. package/dist/index.d.ts +1 -0
  41. package/dist/index.js +1 -0
  42. package/dist/themes/css/zinc.css +217 -0
  43. package/dist/themes/zinc.d.ts +6 -0
  44. package/dist/themes/zinc.js +115 -0
  45. package/docs/architecture.md +1 -1
  46. package/docs/domains/components.md +6 -1
  47. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -23,7 +23,7 @@
23
23
 
24
24
  ```
25
25
  src/lib/
26
- ├── components/ # 36 UI 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) — 36 components, Props pattern, snippets
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;