@jelinek/ui 0.5.3 → 0.9.1

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 (115) hide show
  1. package/dist/components/ChapterNav.svelte +1 -1
  2. package/dist/components/Checkbox.svelte +13 -15
  3. package/dist/components/Checkbox.svelte.d.ts +1 -0
  4. package/dist/components/ContentLogo.svelte +18 -13
  5. package/dist/components/ContentLogo.svelte.d.ts +6 -5
  6. package/dist/components/Grid.svelte +9 -5
  7. package/dist/components/Label.svelte +12 -1
  8. package/dist/components/Label.svelte.d.ts +8 -0
  9. package/dist/components/PageHero.svelte +36 -2
  10. package/dist/components/PageHero.svelte.d.ts +9 -0
  11. package/dist/components/Pager.svelte +5 -2
  12. package/dist/components/PhotoTile.svelte +4 -2
  13. package/dist/components/ProductCard.svelte +66 -44
  14. package/dist/components/ProductCard.svelte.d.ts +17 -4
  15. package/dist/components/Radio.svelte +12 -8
  16. package/dist/components/Radio.svelte.d.ts +1 -0
  17. package/dist/components/Select.svelte +70 -12
  18. package/dist/components/Select.svelte.d.ts +11 -0
  19. package/dist/components/SiteFooter.svelte +19 -0
  20. package/dist/components/SiteFooter.svelte.d.ts +8 -0
  21. package/dist/components/SiteHeader.svelte +11 -9
  22. package/dist/components/Slider.svelte +6 -11
  23. package/dist/components/Switch.svelte +76 -0
  24. package/dist/components/Switch.svelte.d.ts +12 -0
  25. package/dist/components/TextField.svelte +61 -10
  26. package/dist/components/TextField.svelte.d.ts +13 -0
  27. package/dist/components/Textarea.svelte +92 -0
  28. package/dist/components/Textarea.svelte.d.ts +23 -0
  29. package/dist/components/Tile.svelte +12 -11
  30. package/dist/components/Tile.svelte.d.ts +8 -2
  31. package/dist/components/blocks/CmsButton.svelte +36 -0
  32. package/dist/components/blocks/CmsButton.svelte.d.ts +12 -0
  33. package/dist/components/blocks/CmsColumns.svelte +20 -0
  34. package/dist/components/blocks/CmsColumns.svelte.d.ts +9 -0
  35. package/dist/components/blocks/CmsCover.svelte +71 -0
  36. package/dist/components/blocks/CmsCover.svelte.d.ts +17 -0
  37. package/dist/components/blocks/CmsHeading.svelte +50 -0
  38. package/dist/components/blocks/CmsHeading.svelte.d.ts +14 -0
  39. package/dist/components/blocks/CmsImage.svelte +43 -0
  40. package/dist/components/blocks/CmsImage.svelte.d.ts +13 -0
  41. package/dist/components/blocks/CmsMediaText.svelte +64 -0
  42. package/dist/components/blocks/CmsMediaText.svelte.d.ts +16 -0
  43. package/dist/components/blocks/CmsText.svelte +53 -0
  44. package/dist/components/blocks/CmsText.svelte.d.ts +10 -0
  45. package/dist/components/commerce/Breadcrumbs.svelte +41 -0
  46. package/dist/components/commerce/Breadcrumbs.svelte.d.ts +11 -0
  47. package/dist/components/commerce/CartDrawer.svelte +131 -0
  48. package/dist/components/commerce/CartDrawer.svelte.d.ts +37 -0
  49. package/dist/components/commerce/CartLineItem.svelte +67 -0
  50. package/dist/components/commerce/CartLineItem.svelte.d.ts +14 -0
  51. package/dist/components/commerce/CheckoutProgress.svelte +49 -0
  52. package/dist/components/commerce/CheckoutProgress.svelte.d.ts +11 -0
  53. package/dist/components/commerce/ConfigSection.svelte +24 -0
  54. package/dist/components/commerce/ConfigSection.svelte.d.ts +11 -0
  55. package/dist/components/commerce/DimensionSlider.svelte +86 -0
  56. package/dist/components/commerce/DimensionSlider.svelte.d.ts +18 -0
  57. package/dist/components/commerce/Drawer.svelte +81 -0
  58. package/dist/components/commerce/Drawer.svelte.d.ts +12 -0
  59. package/dist/components/commerce/EmptyState.svelte +37 -0
  60. package/dist/components/commerce/EmptyState.svelte.d.ts +13 -0
  61. package/dist/components/commerce/FilterChip.svelte +35 -0
  62. package/dist/components/commerce/FilterChip.svelte.d.ts +10 -0
  63. package/dist/components/commerce/FreeShippingBar.svelte +63 -0
  64. package/dist/components/commerce/FreeShippingBar.svelte.d.ts +12 -0
  65. package/dist/components/commerce/Gallery.svelte +194 -0
  66. package/dist/components/commerce/Gallery.svelte.d.ts +27 -0
  67. package/dist/components/commerce/ListingToolbar.svelte +62 -0
  68. package/dist/components/commerce/ListingToolbar.svelte.d.ts +19 -0
  69. package/dist/components/commerce/Modal.svelte +73 -0
  70. package/dist/components/commerce/Modal.svelte.d.ts +11 -0
  71. package/dist/components/commerce/OrderSummary.svelte +85 -0
  72. package/dist/components/commerce/OrderSummary.svelte.d.ts +25 -0
  73. package/dist/components/commerce/Pagination.svelte +115 -0
  74. package/dist/components/commerce/Pagination.svelte.d.ts +14 -0
  75. package/dist/components/commerce/PriceDisplay.svelte +41 -0
  76. package/dist/components/commerce/PriceDisplay.svelte.d.ts +14 -0
  77. package/dist/components/commerce/ProductHeader.svelte +62 -0
  78. package/dist/components/commerce/ProductHeader.svelte.d.ts +21 -0
  79. package/dist/components/commerce/ProductStickyBar.svelte +42 -0
  80. package/dist/components/commerce/ProductStickyBar.svelte.d.ts +14 -0
  81. package/dist/components/commerce/ProductTabs.svelte +87 -0
  82. package/dist/components/commerce/ProductTabs.svelte.d.ts +18 -0
  83. package/dist/components/commerce/QuantityStepper.svelte +44 -0
  84. package/dist/components/commerce/QuantityStepper.svelte.d.ts +13 -0
  85. package/dist/components/commerce/RatingStars.svelte +39 -0
  86. package/dist/components/commerce/RatingStars.svelte.d.ts +9 -0
  87. package/dist/components/commerce/SearchField.svelte +92 -0
  88. package/dist/components/commerce/SearchField.svelte.d.ts +15 -0
  89. package/dist/components/commerce/SideToggle.svelte +79 -0
  90. package/dist/components/commerce/SideToggle.svelte.d.ts +13 -0
  91. package/dist/components/commerce/Skeleton.svelte +13 -0
  92. package/dist/components/commerce/Skeleton.svelte.d.ts +7 -0
  93. package/dist/components/commerce/StickyBar.svelte +27 -0
  94. package/dist/components/commerce/StickyBar.svelte.d.ts +8 -0
  95. package/dist/components/commerce/StockBadge.svelte +45 -0
  96. package/dist/components/commerce/StockBadge.svelte.d.ts +9 -0
  97. package/dist/components/commerce/Toaster.svelte +38 -0
  98. package/dist/components/commerce/Toaster.svelte.d.ts +7 -0
  99. package/dist/components/commerce/TrustBadges.svelte +26 -0
  100. package/dist/components/commerce/TrustBadges.svelte.d.ts +16 -0
  101. package/dist/components/commerce/cart-types.d.ts +12 -0
  102. package/dist/components/commerce/cart-types.js +1 -0
  103. package/dist/components/commerce/money.d.ts +14 -0
  104. package/dist/components/commerce/money.js +26 -0
  105. package/dist/components/commerce/overlay.d.ts +26 -0
  106. package/dist/components/commerce/overlay.js +69 -0
  107. package/dist/components/commerce/toast.d.ts +26 -0
  108. package/dist/components/commerce/toast.js +44 -0
  109. package/dist/index.d.ts +41 -0
  110. package/dist/index.js +41 -0
  111. package/dist/theme/base.css +56 -7
  112. package/dist/theme/extras.css +63 -0
  113. package/dist/theme/fonts.css +6 -42
  114. package/dist/theme/tokens.css +16 -9
  115. package/package.json +6 -2
@@ -0,0 +1,79 @@
1
+ <script lang="ts">
2
+ // Bed-side picker for dual-configuration products (matrace-fe's SideButton,
3
+ // genericized): the caller supplies the selection summary as children, the
4
+ // component owns the active/inactive chrome and the bed illustration.
5
+ import type { Snippet } from 'svelte';
6
+
7
+ interface Props {
8
+ side: 'left' | 'right';
9
+ /** Caption above the summary („Levá strana“). */
10
+ label: string;
11
+ active: boolean;
12
+ onclick: () => void;
13
+ /** Summary of that side's configuration (firmness, foam…). */
14
+ children?: Snippet;
15
+ }
16
+
17
+ let { side, label, active, onclick, children }: Props = $props();
18
+
19
+ // Illustration palette, not UI surface colors — the soft magenta highlight
20
+ // and wood headboard come from matrace-fe's artwork and deliberately stay
21
+ // literal (a division re-theme must not repaint a drawing of a bed).
22
+ const leftFill = $derived(side === 'left' && active ? '#fae6f1' : '#f2f4f3');
23
+ const leftStroke = $derived(side === 'left' && active ? '#f0b1d5' : '#cfd7d2');
24
+ const rightFill = $derived(side === 'right' && active ? '#fae6f1' : '#f2f4f3');
25
+ const rightStroke = $derived(side === 'right' && active ? '#f0b1d5' : '#cfd7d2');
26
+ </script>
27
+
28
+ <button
29
+ type="button"
30
+ class="rounded-card cursor-pointer border px-3 py-2.5 text-left font-secondary font-light transition-all duration-200 max-sm:px-2 max-sm:py-2 {active
31
+ ? 'border-(--JELINEK-BROWN) bg-surface-gray shadow-resting'
32
+ : 'border-border-subtle bg-surface hover:border-(--text-muted2)'}"
33
+ aria-pressed={active}
34
+ {onclick}
35
+ >
36
+ <div class="flex items-start justify-between gap-1 sm:gap-3">
37
+ <div class="flex min-w-0 flex-col">
38
+ <p
39
+ class="text-(--color-accent-text) truncate text-[0.65rem] font-bold tracking-[0.05em] uppercase sm:text-[0.7rem]"
40
+ >
41
+ {label}
42
+ </p>
43
+ {#if children}
44
+ <div class="mt-2 flex flex-col gap-2">
45
+ {@render children()}
46
+ </div>
47
+ {/if}
48
+ </div>
49
+ <svg
50
+ class="mt-0.5 h-20 w-15 shrink-0 max-sm:h-14 max-sm:w-10"
51
+ viewBox="0 0 100 135"
52
+ fill="none"
53
+ xmlns="http://www.w3.org/2000/svg"
54
+ aria-hidden="true"
55
+ >
56
+ <!-- Headboard -->
57
+ <rect x="0" y="10" width="100" height="10" rx="3" fill="#D8B589" />
58
+ <!-- Left mattress -->
59
+ <path d="M 49 22 H 9 A 4 4 0 0 0 5 26 V 116 A 4 4 0 0 0 9 120 H 49 Z" fill={leftFill} />
60
+ <path
61
+ d="M 49 22 H 9 A 4 4 0 0 0 5 26 V 116 A 4 4 0 0 0 9 120 H 49"
62
+ stroke={leftStroke}
63
+ stroke-width="2"
64
+ fill="none"
65
+ />
66
+ <!-- Right mattress -->
67
+ <path d="M 51 22 H 91 A 4 4 0 0 1 95 26 V 116 A 4 4 0 0 1 91 120 H 51 Z" fill={rightFill} />
68
+ <path
69
+ d="M 51 120 H 91 A 4 4 0 0 0 95 116 V 26 A 4 4 0 0 0 91 22 H 51"
70
+ stroke={rightStroke}
71
+ stroke-width="2"
72
+ fill="none"
73
+ />
74
+ <!-- Pillows -->
75
+ <rect x="12" y="29" width="30" height="16" rx="4" fill="white" stroke="#c8d3cd" stroke-width="1.5" />
76
+ <rect x="57" y="29" width="30" height="16" rx="4" fill="white" stroke="#c8d3cd" stroke-width="1.5" />
77
+ </svg>
78
+ </div>
79
+ </button>
@@ -0,0 +1,13 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ side: 'left' | 'right';
4
+ /** Caption above the summary („Levá strana“). */
5
+ label: string;
6
+ active: boolean;
7
+ onclick: () => void;
8
+ /** Summary of that side's configuration (firmness, foam…). */
9
+ children?: Snippet;
10
+ }
11
+ declare const SideToggle: import("svelte").Component<Props, {}, "">;
12
+ type SideToggle = ReturnType<typeof SideToggle>;
13
+ export default SideToggle;
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ /** Rozměry placeholderu — sám je jen plocha. */
4
+ class?: string;
5
+ }
6
+
7
+ let { class: className = '' }: Props = $props();
8
+ </script>
9
+
10
+ <!-- Shimmer místo pulzu (majitel, 14. 8. 2026) — projíždějící gradient,
11
+ definovaný v theme/extras.css (.skeleton-shimmer); tam je i klidová
12
+ varianta pro prefers-reduced-motion. -->
13
+ <div class="skeleton-shimmer rounded-card {className}" aria-hidden="true"></div>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ /** Rozměry placeholderu — sám je jen plocha. */
3
+ class?: string;
4
+ }
5
+ declare const Skeleton: import("svelte").Component<Props, {}, "">;
6
+ type Skeleton = ReturnType<typeof Skeleton>;
7
+ export default Skeleton;
@@ -0,0 +1,27 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ visible: boolean;
6
+ children: Snippet;
7
+ }
8
+
9
+ let { visible, children }: Props = $props();
10
+ </script>
11
+
12
+ <!-- Glass strip under the kit's floating header card. --z-bar (40) keeps it
13
+ BELOW the header (--z-header, 50) — any higher and the strip renders over the
14
+ card and its backdrop blur smears the whole top of the page. Top offset:
15
+ var(--header-h) from 640px up so the strip hangs under the fixed card;
16
+ top-0 on a phone, where the kit bar is absolute and has already scrolled
17
+ away by the time this strip appears (it only shows once the product header
18
+ left the viewport). -->
19
+ <div
20
+ aria-hidden={!visible}
21
+ class="fixed inset-x-0 top-0 z-(--z-bar) border-b border-(--glass-border) bg-(--glass-fill) backdrop-blur-[25px] backdrop-saturate-140 transition-transform duration-(--dur-base) min-[640px]:top-(--header-h)
22
+ {visible ? 'translate-y-0' : 'pointer-events-none -translate-y-[calc(100%+var(--header-h))]'}"
23
+ >
24
+ <div class="max-w-page mx-auto flex w-full items-center gap-4 px-6 py-3 font-secondary font-light">
25
+ {@render children()}
26
+ </div>
27
+ </div>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ visible: boolean;
4
+ children: Snippet;
5
+ }
6
+ declare const StickyBar: import("svelte").Component<Props, {}, "">;
7
+ type StickyBar = ReturnType<typeof StickyBar>;
8
+ export default StickyBar;
@@ -0,0 +1,45 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ state: 'in-stock' | 'on-order' | 'sold-out' | 'lead';
4
+ /** Text override — a localized label or a concrete lead time („Do 3 týdnů“). */
5
+ label?: string;
6
+ compact?: boolean;
7
+ }
8
+
9
+ let { state, label, compact = false }: Props = $props();
10
+
11
+ const DEFAULTS: Record<Props['state'], string> = {
12
+ 'in-stock': 'Skladem',
13
+ 'on-order': 'Na objednávku',
14
+ 'sold-out': 'Vyprodáno',
15
+ // `lead` has no meaningful default — the whole point of the state is a
16
+ // concrete lead time the caller computed. Empty keeps the dot alone.
17
+ lead: ''
18
+ };
19
+
20
+ const config = $derived(
21
+ {
22
+ 'in-stock': {
23
+ cls: 'text-(--sem-success-strong)',
24
+ dot: 'bg-(--sem-success-strong)'
25
+ },
26
+ 'on-order': { cls: 'text-(--color-stock-order)', dot: 'bg-(--color-stock-order)' },
27
+ 'sold-out': { cls: 'text-fg-muted1', dot: 'bg-(--text-muted2)' },
28
+ lead: { cls: 'text-(--color-stock-order)', dot: 'bg-(--color-stock-order)' }
29
+ }[state]
30
+ );
31
+
32
+ const text = $derived(label ?? DEFAULTS[state]);
33
+ </script>
34
+
35
+ {#if compact}
36
+ <span class="inline-flex items-center gap-1.5 font-secondary text-(length:--step-down1) font-medium {config.cls}">
37
+ <span class="size-2 rounded-pill {config.dot}"></span>{text}
38
+ </span>
39
+ {:else}
40
+ <span
41
+ class="rounded-pill bg-surface-gray inline-flex items-center gap-2 px-3 py-1 font-secondary text-(length:--step-down1) font-medium {config.cls}"
42
+ >
43
+ <span class="size-2 rounded-pill {config.dot}"></span>{text}
44
+ </span>
45
+ {/if}
@@ -0,0 +1,9 @@
1
+ interface Props {
2
+ state: 'in-stock' | 'on-order' | 'sold-out' | 'lead';
3
+ /** Text override — a localized label or a concrete lead time („Do 3 týdnů“). */
4
+ label?: string;
5
+ compact?: boolean;
6
+ }
7
+ declare const StockBadge: import("svelte").Component<Props, {}, "">;
8
+ type StockBadge = ReturnType<typeof StockBadge>;
9
+ export default StockBadge;
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { fly } from 'svelte/transition';
3
+ import { onMount } from 'svelte';
4
+ import { toasts, type ToastItem } from './toast.js';
5
+
6
+ interface Props {
7
+ /** Extra classes on the fixed wrapper (e.g. a different bottom offset). */
8
+ class?: string;
9
+ }
10
+
11
+ let { class: className = '' }: Props = $props();
12
+
13
+ // toast.ts is a plain (non-runes) class — see the comment at its top for why — so this
14
+ // component mirrors it into its own `$state` via subscribe() rather than reading it directly.
15
+ let items: readonly ToastItem[] = $state([]);
16
+
17
+ onMount(() => toasts.subscribe((next) => (items = next)));
18
+ </script>
19
+
20
+ <!-- Škála vrstev (jelinek.css :root): hlavička --z-header (50), Drawer/Modal
21
+ --z-overlay (1200), toasty nade vším — --z-toast (1500; potvrzení
22
+ o přidání do košíku nesmí zmizet pod drawerem). role="status"
23
+ + aria-live="polite": kontejner tu je trvale, takže čtečky ohlásí každý
24
+ toast, který do něj přibude — dřív byly hlášky pro čtečky němé (EAA). -->
25
+ <div
26
+ role="status"
27
+ aria-live="polite"
28
+ class="pointer-events-none fixed inset-x-0 bottom-6 z-(--z-toast) flex flex-col items-center gap-2 {className}"
29
+ >
30
+ {#each items as t (t.id)}
31
+ <div
32
+ transition:fly={{ y: 16, duration: 200 }}
33
+ class="rounded-pill text-fg-inverse shadow-interactive pointer-events-auto bg-(--bg-black) px-5 py-2.5 font-secondary text-(length:--step-down1) font-medium"
34
+ >
35
+ {t.message}
36
+ </div>
37
+ {/each}
38
+ </div>
@@ -0,0 +1,7 @@
1
+ interface Props {
2
+ /** Extra classes on the fixed wrapper (e.g. a different bottom offset). */
3
+ class?: string;
4
+ }
5
+ declare const Toaster: import("svelte").Component<Props, {}, "">;
6
+ type Toaster = ReturnType<typeof Toaster>;
7
+ export default Toaster;
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import type { Component } from 'svelte';
3
+
4
+ interface Props {
5
+ /**
6
+ * Required on purpose — the kit ships no icon library, so the badges
7
+ * (icon component + label) come from the app. Icons render at 14 px.
8
+ */
9
+ items: { icon: Component; label: string }[];
10
+ /** Absolute glass strip over a photo's bottom edge instead of an inline row. */
11
+ overlay?: boolean;
12
+ }
13
+
14
+ let { items, overlay = false }: Props = $props();
15
+ </script>
16
+
17
+ <div class={overlay ? 'absolute inset-x-3 bottom-3 flex flex-wrap gap-2' : 'flex flex-wrap gap-2'}>
18
+ {#each items as item (item.label)}
19
+ <span
20
+ class="rounded-pill flex items-center gap-1.5 bg-(--glass-fill) px-3 py-1.5 font-secondary text-(length:--step-down2) font-medium backdrop-blur-[25px]"
21
+ >
22
+ <item.icon class="size-3.5" />
23
+ {item.label}
24
+ </span>
25
+ {/each}
26
+ </div>
@@ -0,0 +1,16 @@
1
+ import type { Component } from 'svelte';
2
+ interface Props {
3
+ /**
4
+ * Required on purpose — the kit ships no icon library, so the badges
5
+ * (icon component + label) come from the app. Icons render at 14 px.
6
+ */
7
+ items: {
8
+ icon: Component;
9
+ label: string;
10
+ }[];
11
+ /** Absolute glass strip over a photo's bottom edge instead of an inline row. */
12
+ overlay?: boolean;
13
+ }
14
+ declare const TrustBadges: Component<Props, {}, "">;
15
+ type TrustBadges = ReturnType<typeof TrustBadges>;
16
+ export default TrustBadges;
@@ -0,0 +1,12 @@
1
+ /** One cart row as the kit's cart components see it — plain data, no PIM types. */
2
+ export type CartLine = {
3
+ id: number | string;
4
+ productName: string;
5
+ /** One-line configuration recap („180×200, dub, tvrdá“). Null and undefined
6
+ * both mean „none“ — API rows often carry explicit nulls. */
7
+ configSummary?: string | null;
8
+ imageUrl?: string | null;
9
+ quantity: number;
10
+ /** Row total (quantity already included). */
11
+ lineTotal: number;
12
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The kit's two money formatters, ported verbatim from the store so every
3
+ * consumer prints prices the same way.
4
+ *
5
+ * `formatPrice` — display surfaces (product cards, price tags, CTAs): whole
6
+ * CZK with non-breaking thousand separators, legibility first.
7
+ * `formatMoney` — cart/checkout amounts: EUR keeps 2-decimal precision, CZK
8
+ * rounds to whole crowns.
9
+ *
10
+ * @example formatPrice(18350) → "18 350 Kč"
11
+ * @example formatMoney(1234.5, "EUR") → "1 234,50 €"
12
+ */
13
+ export declare function formatPrice(amount: number, currency?: string): string;
14
+ export declare function formatMoney(amount: number, currency?: string): string;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The kit's two money formatters, ported verbatim from the store so every
3
+ * consumer prints prices the same way.
4
+ *
5
+ * `formatPrice` — display surfaces (product cards, price tags, CTAs): whole
6
+ * CZK with non-breaking thousand separators, legibility first.
7
+ * `formatMoney` — cart/checkout amounts: EUR keeps 2-decimal precision, CZK
8
+ * rounds to whole crowns.
9
+ *
10
+ * @example formatPrice(18350) → "18 350 Kč"
11
+ * @example formatMoney(1234.5, "EUR") → "1 234,50 €"
12
+ */
13
+ export function formatPrice(amount, currency = 'CZK') {
14
+ return new Intl.NumberFormat('cs-CZ', {
15
+ style: 'currency',
16
+ currency,
17
+ maximumFractionDigits: 0
18
+ }).format(amount);
19
+ }
20
+ export function formatMoney(amount, currency = 'CZK') {
21
+ const rounded = currency === 'EUR' ? Math.round(amount * 100) / 100 : Math.round(amount);
22
+ if (currency === 'EUR') {
23
+ return `${rounded.toLocaleString('sk-SK', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} €`;
24
+ }
25
+ return `${rounded.toLocaleString('cs-CZ')} Kč`;
26
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Svelte action: reparent the node to <body>. `fixed` + z-index is not enough
3
+ * when the overlay is mounted inside an ancestor that creates a stacking
4
+ * context (e.g. a PDP gallery's `lg:sticky` column) — the whole overlay then
5
+ * paints below later siblings regardless of z-index. Portaling out of the
6
+ * ancestor is the only robust fix.
7
+ */
8
+ export declare function portal(node: HTMLElement): {
9
+ destroy: () => void;
10
+ };
11
+ /** Lock body scroll; returns the unlock function (for use inside $effect). */
12
+ export declare function lockScroll(): () => void;
13
+ /**
14
+ * Past na fokus (zákonná přístupnost překryvů — EAA): Tab a Shift+Tab
15
+ * cyklí UVNITŘ containeru, do stránky pod overlayem se tabulátorem nedá
16
+ * vyjet. Myš ani Escape to nijak neomezuje. Vrací úklidovou funkci
17
+ * (pro $effect), stejná smlouva jako lockScroll().
18
+ */
19
+ export declare function trapFocus(container: HTMLElement): () => void;
20
+ /**
21
+ * Zapamatuje si prvek zaostřený PŘED otevřením overlaye a vrátí funkci,
22
+ * která na něj fokus vrátí (pokud mezitím nezmizel ze stránky). Čtenář
23
+ * s klávesnicí tak po zavření pokračuje přesně tam, kde byl — typicky
24
+ * na tlačítku, kterým overlay otevřel.
25
+ */
26
+ export declare function rememberFocus(): () => void;
@@ -0,0 +1,69 @@
1
+ // Shared plumbing for overlay primitives (Modal, Drawer).
2
+ /**
3
+ * Svelte action: reparent the node to <body>. `fixed` + z-index is not enough
4
+ * when the overlay is mounted inside an ancestor that creates a stacking
5
+ * context (e.g. a PDP gallery's `lg:sticky` column) — the whole overlay then
6
+ * paints below later siblings regardless of z-index. Portaling out of the
7
+ * ancestor is the only robust fix.
8
+ */
9
+ export function portal(node) {
10
+ document.body.appendChild(node);
11
+ return { destroy: () => node.remove() };
12
+ }
13
+ /** Lock body scroll; returns the unlock function (for use inside $effect). */
14
+ export function lockScroll() {
15
+ const prev = document.body.style.overflow;
16
+ document.body.style.overflow = 'hidden';
17
+ return () => {
18
+ document.body.style.overflow = prev;
19
+ };
20
+ }
21
+ // Co se v překryvu dá zaostřit tabulátorem. Bez filtru viditelnosti —
22
+ // otevřený overlay svůj obsah zobrazuje celý a jsdom (unit testy)
23
+ // offsetParent nezná.
24
+ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), ' +
25
+ 'select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
26
+ /**
27
+ * Past na fokus (zákonná přístupnost překryvů — EAA): Tab a Shift+Tab
28
+ * cyklí UVNITŘ containeru, do stránky pod overlayem se tabulátorem nedá
29
+ * vyjet. Myš ani Escape to nijak neomezuje. Vrací úklidovou funkci
30
+ * (pro $effect), stejná smlouva jako lockScroll().
31
+ */
32
+ export function trapFocus(container) {
33
+ const onKey = (e) => {
34
+ if (e.key !== 'Tab')
35
+ return;
36
+ const items = Array.from(container.querySelectorAll(FOCUSABLE));
37
+ if (items.length === 0) {
38
+ e.preventDefault();
39
+ container.focus();
40
+ return;
41
+ }
42
+ const first = items[0];
43
+ const last = items[items.length - 1];
44
+ const active = document.activeElement;
45
+ if (e.shiftKey && (active === first || active === container)) {
46
+ e.preventDefault();
47
+ last.focus();
48
+ }
49
+ else if (!e.shiftKey && active === last) {
50
+ e.preventDefault();
51
+ first.focus();
52
+ }
53
+ };
54
+ container.addEventListener('keydown', onKey);
55
+ return () => container.removeEventListener('keydown', onKey);
56
+ }
57
+ /**
58
+ * Zapamatuje si prvek zaostřený PŘED otevřením overlaye a vrátí funkci,
59
+ * která na něj fokus vrátí (pokud mezitím nezmizel ze stránky). Čtenář
60
+ * s klávesnicí tak po zavření pokračuje přesně tam, kde byl — typicky
61
+ * na tlačítku, kterým overlay otevřel.
62
+ */
63
+ export function rememberFocus() {
64
+ const opener = document.activeElement instanceof HTMLElement ? document.activeElement : null;
65
+ return () => {
66
+ if (opener && document.contains(opener))
67
+ opener.focus();
68
+ };
69
+ }
@@ -0,0 +1,26 @@
1
+ export type ToastItem = {
2
+ id: string;
3
+ message: string;
4
+ };
5
+ type Listener = (items: readonly ToastItem[]) => void;
6
+ declare class ToastStore {
7
+ #private;
8
+ /** Plain snapshot, not reactive — re-reading this getter after a mutation sees the update, but
9
+ * nothing re-renders automatically outside a `subscribe()` listener (see below). */
10
+ get items(): readonly ToastItem[];
11
+ /** Show a toast. Passing an `id` that's already showing replaces its message and resets its timer. */
12
+ show(message: string, opts?: {
13
+ id?: string;
14
+ duration?: number;
15
+ }): void;
16
+ dismiss(id: string): void;
17
+ /**
18
+ * For Toaster.svelte to mirror `items` into its own reactive state. Returns an unsubscribe fn.
19
+ * Note: `items` is a plain getter, not a Svelte rune — it is NOT reactive outside a subscriber.
20
+ * Reading `toasts.items` directly from a component template will not update on show/dismiss;
21
+ * either call `subscribe()` yourself or route the UI through `<Toaster />`.
22
+ */
23
+ subscribe(listener: Listener): () => void;
24
+ }
25
+ export declare const toasts: ToastStore;
26
+ export {};
@@ -0,0 +1,44 @@
1
+ class ToastStore {
2
+ #items = [];
3
+ #timers = new Map();
4
+ #listeners = new Set();
5
+ /** Plain snapshot, not reactive — re-reading this getter after a mutation sees the update, but
6
+ * nothing re-renders automatically outside a `subscribe()` listener (see below). */
7
+ get items() {
8
+ return this.#items;
9
+ }
10
+ /** Show a toast. Passing an `id` that's already showing replaces its message and resets its timer. */
11
+ show(message, opts = {}) {
12
+ const id = opts.id ?? crypto.randomUUID();
13
+ const existing = this.#timers.get(id);
14
+ if (existing)
15
+ clearTimeout(existing);
16
+ this.#items = [...this.#items.filter((t) => t.id !== id), { id, message }];
17
+ this.#notify();
18
+ this.#timers.set(id, setTimeout(() => this.dismiss(id), opts.duration ?? 3000));
19
+ }
20
+ dismiss(id) {
21
+ const existing = this.#timers.get(id);
22
+ if (existing)
23
+ clearTimeout(existing);
24
+ this.#items = this.#items.filter((t) => t.id !== id);
25
+ this.#timers.delete(id);
26
+ this.#notify();
27
+ }
28
+ /**
29
+ * For Toaster.svelte to mirror `items` into its own reactive state. Returns an unsubscribe fn.
30
+ * Note: `items` is a plain getter, not a Svelte rune — it is NOT reactive outside a subscriber.
31
+ * Reading `toasts.items` directly from a component template will not update on show/dismiss;
32
+ * either call `subscribe()` yourself or route the UI through `<Toaster />`.
33
+ */
34
+ subscribe(listener) {
35
+ this.#listeners.add(listener);
36
+ listener(this.#items);
37
+ return () => this.#listeners.delete(listener);
38
+ }
39
+ #notify() {
40
+ for (const listener of this.#listeners)
41
+ listener(this.#items);
42
+ }
43
+ }
44
+ export const toasts = new ToastStore();
package/dist/index.d.ts CHANGED
@@ -1,39 +1,80 @@
1
1
  export { cn } from './utils/cn.js';
2
2
  export { createChapterNavDock } from './utils/chapter-nav-dock.svelte.js';
3
3
  export { default as Alert } from './components/Alert.svelte';
4
+ export { default as Breadcrumbs } from './components/commerce/Breadcrumbs.svelte';
4
5
  export { default as Button } from './components/Button.svelte';
5
6
  export { default as Card } from './components/Card.svelte';
7
+ export { default as CartDrawer } from './components/commerce/CartDrawer.svelte';
8
+ export { default as CartLineItem } from './components/commerce/CartLineItem.svelte';
9
+ export { type CartLine } from './components/commerce/cart-types.js';
6
10
  export { default as ChapterNav } from './components/ChapterNav.svelte';
7
11
  export { default as Checkbox } from './components/Checkbox.svelte';
12
+ export { default as CheckoutProgress } from './components/commerce/CheckoutProgress.svelte';
13
+ export { default as CmsButton } from './components/blocks/CmsButton.svelte';
14
+ export { default as CmsColumns } from './components/blocks/CmsColumns.svelte';
15
+ export { default as CmsCover } from './components/blocks/CmsCover.svelte';
16
+ export { default as CmsHeading } from './components/blocks/CmsHeading.svelte';
17
+ export { default as CmsImage } from './components/blocks/CmsImage.svelte';
18
+ export { default as CmsMediaText } from './components/blocks/CmsMediaText.svelte';
19
+ export { default as CmsText } from './components/blocks/CmsText.svelte';
8
20
  export { default as CodeCopy } from './components/CodeCopy.svelte';
21
+ export { default as ConfigSection } from './components/commerce/ConfigSection.svelte';
9
22
  export { CONTAINER_CONTEXT } from './components/container-context.js';
10
23
  export { default as Container } from './components/Container.svelte';
11
24
  export { default as ContentLogo } from './components/ContentLogo.svelte';
12
25
  export { default as Demo } from './components/Demo.svelte';
26
+ export { default as DimensionSlider } from './components/commerce/DimensionSlider.svelte';
27
+ export { default as Drawer } from './components/commerce/Drawer.svelte';
28
+ export { default as EmptyState } from './components/commerce/EmptyState.svelte';
13
29
  export { default as Eyebrow } from './components/Eyebrow.svelte';
30
+ export { default as FilterChip } from './components/commerce/FilterChip.svelte';
31
+ export { formatMoney, formatPrice } from './components/commerce/money.js';
32
+ export { default as FreeShippingBar } from './components/commerce/FreeShippingBar.svelte';
33
+ export { default as Gallery } from './components/commerce/Gallery.svelte';
14
34
  export { default as Glass } from './components/Glass.svelte';
15
35
  export { default as Grid } from './components/Grid.svelte';
16
36
  export { default as Label } from './components/Label.svelte';
37
+ export { default as ListingToolbar } from './components/commerce/ListingToolbar.svelte';
38
+ export { default as Modal } from './components/commerce/Modal.svelte';
17
39
  export { isActive, type NavItem, type NavGroup } from './components/nav.js';
18
40
  export { default as OptionTile } from './components/OptionTile.svelte';
41
+ export { default as OrderSummary } from './components/commerce/OrderSummary.svelte';
19
42
  export { default as PageHero } from './components/PageHero.svelte';
20
43
  export { default as Pager } from './components/Pager.svelte';
44
+ export { default as Pagination } from './components/commerce/Pagination.svelte';
21
45
  export { default as Panel } from './components/Panel.svelte';
22
46
  export { default as PhotoTile } from './components/PhotoTile.svelte';
47
+ export { default as PriceDisplay } from './components/commerce/PriceDisplay.svelte';
23
48
  export { default as ProductCard } from './components/ProductCard.svelte';
49
+ export { default as ProductHeader } from './components/commerce/ProductHeader.svelte';
50
+ export { default as ProductStickyBar } from './components/commerce/ProductStickyBar.svelte';
51
+ export { default as ProductTabs } from './components/commerce/ProductTabs.svelte';
52
+ export { default as QuantityStepper } from './components/commerce/QuantityStepper.svelte';
24
53
  export { default as Radio } from './components/Radio.svelte';
54
+ export { default as RatingStars } from './components/commerce/RatingStars.svelte';
25
55
  export { RADIO_CONTEXT, type RadioContext } from './components/radio-context.js';
26
56
  export { default as RadioGroup } from './components/RadioGroup.svelte';
57
+ export { default as SearchField } from './components/commerce/SearchField.svelte';
27
58
  export { default as Select } from './components/Select.svelte';
59
+ export { portal } from './components/commerce/overlay.js';
60
+ export { default as SideToggle } from './components/commerce/SideToggle.svelte';
28
61
  export { default as SiteFooter } from './components/SiteFooter.svelte';
29
62
  export { default as SiteHeader } from './components/SiteHeader.svelte';
63
+ export { default as Skeleton } from './components/commerce/Skeleton.svelte';
30
64
  export { default as Slider } from './components/Slider.svelte';
65
+ export { default as StickyBar } from './components/commerce/StickyBar.svelte';
66
+ export { default as StockBadge } from './components/commerce/StockBadge.svelte';
31
67
  export { default as Swatch } from './components/Swatch.svelte';
68
+ export { default as Switch } from './components/Switch.svelte';
32
69
  export { default as Tab } from './components/Tab.svelte';
33
70
  export { default as Tabs } from './components/Tabs.svelte';
34
71
  export { TABS_CONTEXT, type TabsContext } from './components/tabs-context.js';
35
72
  export { default as Tag } from './components/Tag.svelte';
73
+ export { default as Textarea } from './components/Textarea.svelte';
36
74
  export { default as TextField } from './components/TextField.svelte';
37
75
  export { default as ThemeToggle } from './components/ThemeToggle.svelte';
38
76
  export { default as Tile } from './components/Tile.svelte';
77
+ export { toasts, type ToastItem } from './components/commerce/toast.js';
78
+ export { default as Toaster } from './components/commerce/Toaster.svelte';
79
+ export { default as TrustBadges } from './components/commerce/TrustBadges.svelte';
39
80
  export { default as TileScroller } from './components/TileScroller.svelte';