@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.
- package/dist/components/ChapterNav.svelte +1 -1
- package/dist/components/Checkbox.svelte +13 -15
- package/dist/components/Checkbox.svelte.d.ts +1 -0
- package/dist/components/ContentLogo.svelte +18 -13
- package/dist/components/ContentLogo.svelte.d.ts +6 -5
- package/dist/components/Grid.svelte +9 -5
- package/dist/components/Label.svelte +12 -1
- package/dist/components/Label.svelte.d.ts +8 -0
- package/dist/components/PageHero.svelte +36 -2
- package/dist/components/PageHero.svelte.d.ts +9 -0
- package/dist/components/Pager.svelte +5 -2
- package/dist/components/PhotoTile.svelte +4 -2
- package/dist/components/ProductCard.svelte +66 -44
- package/dist/components/ProductCard.svelte.d.ts +17 -4
- package/dist/components/Radio.svelte +12 -8
- package/dist/components/Radio.svelte.d.ts +1 -0
- package/dist/components/Select.svelte +70 -12
- package/dist/components/Select.svelte.d.ts +11 -0
- package/dist/components/SiteFooter.svelte +19 -0
- package/dist/components/SiteFooter.svelte.d.ts +8 -0
- package/dist/components/SiteHeader.svelte +11 -9
- package/dist/components/Slider.svelte +6 -11
- package/dist/components/Switch.svelte +76 -0
- package/dist/components/Switch.svelte.d.ts +12 -0
- package/dist/components/TextField.svelte +61 -10
- package/dist/components/TextField.svelte.d.ts +13 -0
- package/dist/components/Textarea.svelte +92 -0
- package/dist/components/Textarea.svelte.d.ts +23 -0
- package/dist/components/Tile.svelte +12 -11
- package/dist/components/Tile.svelte.d.ts +8 -2
- package/dist/components/blocks/CmsButton.svelte +36 -0
- package/dist/components/blocks/CmsButton.svelte.d.ts +12 -0
- package/dist/components/blocks/CmsColumns.svelte +20 -0
- package/dist/components/blocks/CmsColumns.svelte.d.ts +9 -0
- package/dist/components/blocks/CmsCover.svelte +71 -0
- package/dist/components/blocks/CmsCover.svelte.d.ts +17 -0
- package/dist/components/blocks/CmsHeading.svelte +50 -0
- package/dist/components/blocks/CmsHeading.svelte.d.ts +14 -0
- package/dist/components/blocks/CmsImage.svelte +43 -0
- package/dist/components/blocks/CmsImage.svelte.d.ts +13 -0
- package/dist/components/blocks/CmsMediaText.svelte +64 -0
- package/dist/components/blocks/CmsMediaText.svelte.d.ts +16 -0
- package/dist/components/blocks/CmsText.svelte +53 -0
- package/dist/components/blocks/CmsText.svelte.d.ts +10 -0
- package/dist/components/commerce/Breadcrumbs.svelte +41 -0
- package/dist/components/commerce/Breadcrumbs.svelte.d.ts +11 -0
- package/dist/components/commerce/CartDrawer.svelte +131 -0
- package/dist/components/commerce/CartDrawer.svelte.d.ts +37 -0
- package/dist/components/commerce/CartLineItem.svelte +67 -0
- package/dist/components/commerce/CartLineItem.svelte.d.ts +14 -0
- package/dist/components/commerce/CheckoutProgress.svelte +49 -0
- package/dist/components/commerce/CheckoutProgress.svelte.d.ts +11 -0
- package/dist/components/commerce/ConfigSection.svelte +24 -0
- package/dist/components/commerce/ConfigSection.svelte.d.ts +11 -0
- package/dist/components/commerce/DimensionSlider.svelte +86 -0
- package/dist/components/commerce/DimensionSlider.svelte.d.ts +18 -0
- package/dist/components/commerce/Drawer.svelte +81 -0
- package/dist/components/commerce/Drawer.svelte.d.ts +12 -0
- package/dist/components/commerce/EmptyState.svelte +37 -0
- package/dist/components/commerce/EmptyState.svelte.d.ts +13 -0
- package/dist/components/commerce/FilterChip.svelte +35 -0
- package/dist/components/commerce/FilterChip.svelte.d.ts +10 -0
- package/dist/components/commerce/FreeShippingBar.svelte +63 -0
- package/dist/components/commerce/FreeShippingBar.svelte.d.ts +12 -0
- package/dist/components/commerce/Gallery.svelte +194 -0
- package/dist/components/commerce/Gallery.svelte.d.ts +27 -0
- package/dist/components/commerce/ListingToolbar.svelte +62 -0
- package/dist/components/commerce/ListingToolbar.svelte.d.ts +19 -0
- package/dist/components/commerce/Modal.svelte +73 -0
- package/dist/components/commerce/Modal.svelte.d.ts +11 -0
- package/dist/components/commerce/OrderSummary.svelte +85 -0
- package/dist/components/commerce/OrderSummary.svelte.d.ts +25 -0
- package/dist/components/commerce/Pagination.svelte +115 -0
- package/dist/components/commerce/Pagination.svelte.d.ts +14 -0
- package/dist/components/commerce/PriceDisplay.svelte +41 -0
- package/dist/components/commerce/PriceDisplay.svelte.d.ts +14 -0
- package/dist/components/commerce/ProductHeader.svelte +62 -0
- package/dist/components/commerce/ProductHeader.svelte.d.ts +21 -0
- package/dist/components/commerce/ProductStickyBar.svelte +42 -0
- package/dist/components/commerce/ProductStickyBar.svelte.d.ts +14 -0
- package/dist/components/commerce/ProductTabs.svelte +87 -0
- package/dist/components/commerce/ProductTabs.svelte.d.ts +18 -0
- package/dist/components/commerce/QuantityStepper.svelte +44 -0
- package/dist/components/commerce/QuantityStepper.svelte.d.ts +13 -0
- package/dist/components/commerce/RatingStars.svelte +39 -0
- package/dist/components/commerce/RatingStars.svelte.d.ts +9 -0
- package/dist/components/commerce/SearchField.svelte +92 -0
- package/dist/components/commerce/SearchField.svelte.d.ts +15 -0
- package/dist/components/commerce/SideToggle.svelte +79 -0
- package/dist/components/commerce/SideToggle.svelte.d.ts +13 -0
- package/dist/components/commerce/Skeleton.svelte +13 -0
- package/dist/components/commerce/Skeleton.svelte.d.ts +7 -0
- package/dist/components/commerce/StickyBar.svelte +27 -0
- package/dist/components/commerce/StickyBar.svelte.d.ts +8 -0
- package/dist/components/commerce/StockBadge.svelte +45 -0
- package/dist/components/commerce/StockBadge.svelte.d.ts +9 -0
- package/dist/components/commerce/Toaster.svelte +38 -0
- package/dist/components/commerce/Toaster.svelte.d.ts +7 -0
- package/dist/components/commerce/TrustBadges.svelte +26 -0
- package/dist/components/commerce/TrustBadges.svelte.d.ts +16 -0
- package/dist/components/commerce/cart-types.d.ts +12 -0
- package/dist/components/commerce/cart-types.js +1 -0
- package/dist/components/commerce/money.d.ts +14 -0
- package/dist/components/commerce/money.js +26 -0
- package/dist/components/commerce/overlay.d.ts +26 -0
- package/dist/components/commerce/overlay.js +69 -0
- package/dist/components/commerce/toast.d.ts +26 -0
- package/dist/components/commerce/toast.js +44 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +41 -0
- package/dist/theme/base.css +56 -7
- package/dist/theme/extras.css +63 -0
- package/dist/theme/fonts.css +6 -42
- package/dist/theme/tokens.css +16 -9
- package/package.json +6 -2
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// The whole cart drawer, composed from kit parts (Drawer + CartLineItem +
|
|
3
|
+
// FreeShippingBar). Purely presentational: the app owns the cart store and
|
|
4
|
+
// maps it onto these props; navigation targets come in as hrefs.
|
|
5
|
+
import Button from '../Button.svelte';
|
|
6
|
+
import CartLineItem from './CartLineItem.svelte';
|
|
7
|
+
import Drawer from './Drawer.svelte';
|
|
8
|
+
import FreeShippingBar from './FreeShippingBar.svelte';
|
|
9
|
+
import { formatMoney } from './money.js';
|
|
10
|
+
import type { CartLine } from './cart-types.js';
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
open: boolean;
|
|
14
|
+
onclose: () => void;
|
|
15
|
+
items: CartLine[];
|
|
16
|
+
/** Item count shown next to the title. */
|
|
17
|
+
count: number;
|
|
18
|
+
currency?: string;
|
|
19
|
+
/** Items total minus discounts — the number above the CTA. */
|
|
20
|
+
subtotal: number;
|
|
21
|
+
checkoutHref: string;
|
|
22
|
+
viewCartHref?: string;
|
|
23
|
+
/** Disables steppers/remove while a cart mutation is in flight. */
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
onQuantity: (id: CartLine['id'], quantity: number) => void;
|
|
26
|
+
onRemove: (id: CartLine['id']) => void;
|
|
27
|
+
/** Free-shipping progress; omit to hide the bar. */
|
|
28
|
+
shipping?: { threshold: number; remaining: number; qualified: boolean };
|
|
29
|
+
labels?: {
|
|
30
|
+
title?: string;
|
|
31
|
+
close?: string;
|
|
32
|
+
empty?: string;
|
|
33
|
+
subtotal?: string;
|
|
34
|
+
checkout?: string;
|
|
35
|
+
viewCart?: string;
|
|
36
|
+
remove?: string;
|
|
37
|
+
decrease?: string;
|
|
38
|
+
increase?: string;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let {
|
|
43
|
+
open,
|
|
44
|
+
onclose,
|
|
45
|
+
items,
|
|
46
|
+
count,
|
|
47
|
+
currency,
|
|
48
|
+
subtotal,
|
|
49
|
+
checkoutHref,
|
|
50
|
+
viewCartHref,
|
|
51
|
+
disabled = false,
|
|
52
|
+
onQuantity,
|
|
53
|
+
onRemove,
|
|
54
|
+
shipping,
|
|
55
|
+
labels
|
|
56
|
+
}: Props = $props();
|
|
57
|
+
|
|
58
|
+
const title = $derived(labels?.title ?? 'Košík');
|
|
59
|
+
const closeLabel = $derived(labels?.close ?? 'Zavřít');
|
|
60
|
+
const emptyLabel = $derived(labels?.empty ?? 'Košík je prázdný');
|
|
61
|
+
const subtotalLabel = $derived(labels?.subtotal ?? 'Mezisoučet');
|
|
62
|
+
const checkoutLabel = $derived(labels?.checkout ?? 'K pokladně');
|
|
63
|
+
const viewCartLabel = $derived(labels?.viewCart ?? 'Zobrazit košík');
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<Drawer {open} {onclose} side="right" labelledby="cart-drawer-title">
|
|
67
|
+
<!-- Bez vodorovných linek (majitel 14. 8. 2026) — hlavičku a patičku
|
|
68
|
+
odděluje jen vzduch. -->
|
|
69
|
+
<header class="flex items-center justify-between px-5 py-4">
|
|
70
|
+
<h2 id="cart-drawer-title" class="font-primary text-(length:--step1) font-bold text-fg">
|
|
71
|
+
{title} ({count})
|
|
72
|
+
</h2>
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
class="rounded-button hover:bg-surface-gray flex items-center justify-center p-2 text-fg transition-colors"
|
|
76
|
+
aria-label={closeLabel}
|
|
77
|
+
onclick={onclose}
|
|
78
|
+
>
|
|
79
|
+
<svg viewBox="0 0 24 24" class="size-5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg>
|
|
80
|
+
</button>
|
|
81
|
+
</header>
|
|
82
|
+
|
|
83
|
+
<div class="flex-1 overflow-y-auto px-5">
|
|
84
|
+
{#if items.length === 0}
|
|
85
|
+
<p class="text-fg-muted1 py-10 text-center">{emptyLabel}</p>
|
|
86
|
+
{:else}
|
|
87
|
+
{#if shipping}
|
|
88
|
+
<div class="py-4">
|
|
89
|
+
<FreeShippingBar
|
|
90
|
+
threshold={shipping.threshold}
|
|
91
|
+
remaining={shipping.remaining}
|
|
92
|
+
qualified={shipping.qualified}
|
|
93
|
+
{currency}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
{/if}
|
|
97
|
+
<!-- Řádky odděluje jen vzduch — žádné vodorovné linky (majitel 14. 8. 2026). -->
|
|
98
|
+
<div class="flex flex-col gap-1">
|
|
99
|
+
{#each items as item (item.id)}
|
|
100
|
+
<CartLineItem
|
|
101
|
+
{item}
|
|
102
|
+
{currency}
|
|
103
|
+
{disabled}
|
|
104
|
+
{onQuantity}
|
|
105
|
+
{onRemove}
|
|
106
|
+
removeLabel={labels?.remove}
|
|
107
|
+
decreaseLabel={labels?.decrease}
|
|
108
|
+
increaseLabel={labels?.increase}
|
|
109
|
+
/>
|
|
110
|
+
{/each}
|
|
111
|
+
</div>
|
|
112
|
+
{/if}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{#if items.length > 0}
|
|
116
|
+
<footer class="px-5 py-4">
|
|
117
|
+
<div class="mb-3 flex items-center justify-between text-(length:--step0) font-bold text-fg">
|
|
118
|
+
<span>{subtotalLabel}</span>
|
|
119
|
+
<span>{formatMoney(subtotal, currency)}</span>
|
|
120
|
+
</div>
|
|
121
|
+
<Button color="brown" fullWidth href={checkoutHref} onclick={onclose} class="mb-2">
|
|
122
|
+
{checkoutLabel}
|
|
123
|
+
</Button>
|
|
124
|
+
{#if viewCartHref}
|
|
125
|
+
<Button variant="ghost" color="brown" fullWidth href={viewCartHref} onclick={onclose}>
|
|
126
|
+
{viewCartLabel}
|
|
127
|
+
</Button>
|
|
128
|
+
{/if}
|
|
129
|
+
</footer>
|
|
130
|
+
{/if}
|
|
131
|
+
</Drawer>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CartLine } from './cart-types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onclose: () => void;
|
|
5
|
+
items: CartLine[];
|
|
6
|
+
/** Item count shown next to the title. */
|
|
7
|
+
count: number;
|
|
8
|
+
currency?: string;
|
|
9
|
+
/** Items total minus discounts — the number above the CTA. */
|
|
10
|
+
subtotal: number;
|
|
11
|
+
checkoutHref: string;
|
|
12
|
+
viewCartHref?: string;
|
|
13
|
+
/** Disables steppers/remove while a cart mutation is in flight. */
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
onQuantity: (id: CartLine['id'], quantity: number) => void;
|
|
16
|
+
onRemove: (id: CartLine['id']) => void;
|
|
17
|
+
/** Free-shipping progress; omit to hide the bar. */
|
|
18
|
+
shipping?: {
|
|
19
|
+
threshold: number;
|
|
20
|
+
remaining: number;
|
|
21
|
+
qualified: boolean;
|
|
22
|
+
};
|
|
23
|
+
labels?: {
|
|
24
|
+
title?: string;
|
|
25
|
+
close?: string;
|
|
26
|
+
empty?: string;
|
|
27
|
+
subtotal?: string;
|
|
28
|
+
checkout?: string;
|
|
29
|
+
viewCart?: string;
|
|
30
|
+
remove?: string;
|
|
31
|
+
decrease?: string;
|
|
32
|
+
increase?: string;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
declare const CartDrawer: import("svelte").Component<Props, {}, "">;
|
|
36
|
+
type CartDrawer = ReturnType<typeof CartDrawer>;
|
|
37
|
+
export default CartDrawer;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import QuantityStepper from './QuantityStepper.svelte';
|
|
3
|
+
import { formatMoney } from './money.js';
|
|
4
|
+
import type { CartLine } from './cart-types.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
item: CartLine;
|
|
8
|
+
currency?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
onQuantity: (id: CartLine['id'], quantity: number) => void;
|
|
11
|
+
onRemove: (id: CartLine['id']) => void;
|
|
12
|
+
removeLabel?: string;
|
|
13
|
+
decreaseLabel?: string;
|
|
14
|
+
increaseLabel?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
item,
|
|
19
|
+
currency,
|
|
20
|
+
disabled = false,
|
|
21
|
+
onQuantity,
|
|
22
|
+
onRemove,
|
|
23
|
+
removeLabel = 'Odebrat z košíku',
|
|
24
|
+
decreaseLabel,
|
|
25
|
+
increaseLabel
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div class="flex gap-3 py-4 font-secondary font-light">
|
|
30
|
+
<div class="rounded-image bg-surface-gray h-20 w-20 shrink-0 overflow-hidden">
|
|
31
|
+
{#if item.imageUrl}
|
|
32
|
+
<img src={item.imageUrl} alt={item.productName} class="h-full w-full object-cover" />
|
|
33
|
+
{/if}
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="flex min-w-0 flex-1 flex-col">
|
|
37
|
+
<div class="flex items-start justify-between gap-2">
|
|
38
|
+
<p class="font-medium text-fg">{item.productName}</p>
|
|
39
|
+
<p class="shrink-0 font-bold text-fg">
|
|
40
|
+
{formatMoney(item.lineTotal, currency)}
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
{#if item.configSummary}
|
|
44
|
+
<p class="text-fg-muted1 mt-0.5 text-(length:--step-down2)">{item.configSummary}</p>
|
|
45
|
+
{/if}
|
|
46
|
+
|
|
47
|
+
<div class="mt-auto flex items-center justify-between pt-2">
|
|
48
|
+
<QuantityStepper
|
|
49
|
+
value={item.quantity}
|
|
50
|
+
min={1}
|
|
51
|
+
{disabled}
|
|
52
|
+
onchange={(quantity) => onQuantity(item.id, quantity)}
|
|
53
|
+
{decreaseLabel}
|
|
54
|
+
{increaseLabel}
|
|
55
|
+
/>
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
class="rounded-button text-fg-muted1 hover:text-(--sem-error-strong) flex items-center justify-center p-1.5 transition-colors disabled:opacity-40"
|
|
59
|
+
aria-label={removeLabel}
|
|
60
|
+
{disabled}
|
|
61
|
+
onclick={() => onRemove(item.id)}
|
|
62
|
+
>
|
|
63
|
+
<svg viewBox="0 0 24 24" class="size-[17px]" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2m3 0v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6h14ZM10 11v6M14 11v6" /></svg>
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CartLine } from './cart-types.js';
|
|
2
|
+
interface Props {
|
|
3
|
+
item: CartLine;
|
|
4
|
+
currency?: string;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
onQuantity: (id: CartLine['id'], quantity: number) => void;
|
|
7
|
+
onRemove: (id: CartLine['id']) => void;
|
|
8
|
+
removeLabel?: string;
|
|
9
|
+
decreaseLabel?: string;
|
|
10
|
+
increaseLabel?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const CartLineItem: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type CartLineItem = ReturnType<typeof CartLineItem>;
|
|
14
|
+
export default CartLineItem;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
/** 1-based current step. */
|
|
4
|
+
step: number;
|
|
5
|
+
/** Step captions in order; length defines the step count. */
|
|
6
|
+
steps?: string[];
|
|
7
|
+
/** Called with the step number when a completed step is clicked. */
|
|
8
|
+
onStep?: (step: number) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { step, steps = ['Rekapitulace', 'Doprava', 'Platba'], onStep }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<ol class="flex items-center justify-center gap-2 font-secondary font-light sm:gap-4">
|
|
15
|
+
{#each steps as label, i (i)}
|
|
16
|
+
{@const n = i + 1}
|
|
17
|
+
<li class="flex items-center gap-2">
|
|
18
|
+
<button
|
|
19
|
+
type="button"
|
|
20
|
+
disabled={n >= step}
|
|
21
|
+
class="flex items-center gap-2 {n > step ? 'opacity-50' : ''}"
|
|
22
|
+
onclick={() => n < step && onStep?.(n)}
|
|
23
|
+
>
|
|
24
|
+
<span
|
|
25
|
+
class="flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-(length:--step-down1) font-bold {n <
|
|
26
|
+
step
|
|
27
|
+
? 'bg-(--JELINEK-BROWN) text-fg-inverse'
|
|
28
|
+
: n === step
|
|
29
|
+
? 'border-2 border-(--JELINEK-BROWN) text-(--color-accent-text)'
|
|
30
|
+
: 'bg-surface-gray text-fg-muted2'}"
|
|
31
|
+
>
|
|
32
|
+
{#if n < step}
|
|
33
|
+
<svg viewBox="0 0 24 24" class="size-3.5" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 13 4 4L19 7" /></svg>
|
|
34
|
+
{:else}
|
|
35
|
+
{n}
|
|
36
|
+
{/if}
|
|
37
|
+
</span>
|
|
38
|
+
<span
|
|
39
|
+
class="text-(length:--step-down1) font-medium max-sm:hidden {n === step
|
|
40
|
+
? 'text-fg'
|
|
41
|
+
: 'text-fg-muted1'}">{label}</span
|
|
42
|
+
>
|
|
43
|
+
</button>
|
|
44
|
+
{#if n < steps.length}
|
|
45
|
+
<span class="bg-border-subtle h-px w-6 sm:w-10"></span>
|
|
46
|
+
{/if}
|
|
47
|
+
</li>
|
|
48
|
+
{/each}
|
|
49
|
+
</ol>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** 1-based current step. */
|
|
3
|
+
step: number;
|
|
4
|
+
/** Step captions in order; length defines the step count. */
|
|
5
|
+
steps?: string[];
|
|
6
|
+
/** Called with the step number when a completed step is clicked. */
|
|
7
|
+
onStep?: (step: number) => void;
|
|
8
|
+
}
|
|
9
|
+
declare const CheckoutProgress: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type CheckoutProgress = ReturnType<typeof CheckoutProgress>;
|
|
11
|
+
export default CheckoutProgress;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Numbered configurator section („1. Šířka“, „2. Tuhost“…) — the numbering
|
|
3
|
+
// pattern matrace-fe's configurator established for guiding the shopper
|
|
4
|
+
// through a long decision list.
|
|
5
|
+
import type { Snippet } from 'svelte';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
number: number;
|
|
9
|
+
title: string;
|
|
10
|
+
/** One-line helper under the heading. */
|
|
11
|
+
hint?: string;
|
|
12
|
+
children: Snippet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let { number, title, hint, children }: Props = $props();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<section class="py-6 font-secondary font-light">
|
|
19
|
+
<h3 class="flex items-baseline gap-3 text-(length:--step4) font-bold">
|
|
20
|
+
<span class="text-(--color-accent-text)">{number}.</span>{title}
|
|
21
|
+
</h3>
|
|
22
|
+
{#if hint}<p class="text-fg-muted1 mt-1 text-(length:--step-down1)">{hint}</p>{/if}
|
|
23
|
+
<div class="mt-4">{@render children()}</div>
|
|
24
|
+
</section>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
number: number;
|
|
4
|
+
title: string;
|
|
5
|
+
/** One-line helper under the heading. */
|
|
6
|
+
hint?: string;
|
|
7
|
+
children: Snippet;
|
|
8
|
+
}
|
|
9
|
+
declare const ConfigSection: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type ConfigSection = ReturnType<typeof ConfigSection>;
|
|
11
|
+
export default ConfigSection;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
// Custom-dimension picker (matrace-fe's DimensionSlider, restyled to kit
|
|
3
|
+
// tokens): a range slider synced with a stepper number input, plus the
|
|
4
|
+
// price delta the chosen dimension costs.
|
|
5
|
+
import { formatPrice } from './money.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
value: number;
|
|
9
|
+
min: number;
|
|
10
|
+
max: number;
|
|
11
|
+
step?: number;
|
|
12
|
+
/** Dimension name shown next to the input („Šířka“). */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Price delta of the current value in CZK; 0 hides the tag. */
|
|
15
|
+
surcharge?: number;
|
|
16
|
+
/** One-line note above the slider („Atypický rozměr vyrábíme na míru“). */
|
|
17
|
+
note?: string | null;
|
|
18
|
+
/** Unit shown after the number input. */
|
|
19
|
+
unit?: string;
|
|
20
|
+
currency?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
value = $bindable(),
|
|
25
|
+
min,
|
|
26
|
+
max,
|
|
27
|
+
step = 1,
|
|
28
|
+
label,
|
|
29
|
+
surcharge = 0,
|
|
30
|
+
note = null,
|
|
31
|
+
unit = 'cm',
|
|
32
|
+
currency
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
function update(val: number) {
|
|
36
|
+
value = Math.max(min, Math.min(max, val));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const stepButton =
|
|
40
|
+
'text-fg hover:bg-surface-gray flex cursor-pointer items-center justify-center rounded border-none bg-transparent px-1.5 text-xl leading-none font-medium transition-colors duration-200 select-none';
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<div
|
|
44
|
+
class="bg-surface border-border-subtle rounded-card mt-2 flex flex-col gap-2 border font-secondary font-light pt-5 pr-4 pb-4 pl-4"
|
|
45
|
+
>
|
|
46
|
+
{#if note}
|
|
47
|
+
<div class="text-fg-muted1 mb-1 text-(length:--step-down2) font-medium">{note}</div>
|
|
48
|
+
{/if}
|
|
49
|
+
<input
|
|
50
|
+
type="range"
|
|
51
|
+
{min}
|
|
52
|
+
{max}
|
|
53
|
+
{step}
|
|
54
|
+
bind:value
|
|
55
|
+
oninput={() => update(value)}
|
|
56
|
+
aria-label={label}
|
|
57
|
+
class="w-full cursor-pointer border-none bg-transparent outline-none accent-(--JELINEK-BROWN)"
|
|
58
|
+
/>
|
|
59
|
+
<div
|
|
60
|
+
class="flex items-center justify-between text-[0.95rem] max-sm:flex-col max-sm:items-start max-sm:gap-1"
|
|
61
|
+
>
|
|
62
|
+
<div class="flex items-center gap-2">
|
|
63
|
+
<span>{label}:</span>
|
|
64
|
+
<div
|
|
65
|
+
class="bg-surface border-border-subtle rounded-button flex items-center gap-1 border px-2 py-1 transition-colors focus-within:border-(--JELINEK-BROWN)"
|
|
66
|
+
>
|
|
67
|
+
<button type="button" class={stepButton} onclick={() => update(value - step)}>−</button>
|
|
68
|
+
<input
|
|
69
|
+
type="number"
|
|
70
|
+
bind:value
|
|
71
|
+
{min}
|
|
72
|
+
{max}
|
|
73
|
+
oninput={() => update(value)}
|
|
74
|
+
aria-label={label}
|
|
75
|
+
class="w-[50px] border-none p-0 font-bold text-fg outline-none [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
|
76
|
+
/>
|
|
77
|
+
<span class="text-fg-muted1 text-(length:--step-down1) font-medium">{unit}</span>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
{#if surcharge !== 0}
|
|
81
|
+
<span class="text-(--color-accent-text) text-(length:--step-down1) font-bold">
|
|
82
|
+
{surcharge > 0 ? '+' : '−'}{formatPrice(Math.abs(surcharge), currency)}
|
|
83
|
+
</span>
|
|
84
|
+
{/if}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
value: number;
|
|
3
|
+
min: number;
|
|
4
|
+
max: number;
|
|
5
|
+
step?: number;
|
|
6
|
+
/** Dimension name shown next to the input („Šířka“). */
|
|
7
|
+
label: string;
|
|
8
|
+
/** Price delta of the current value in CZK; 0 hides the tag. */
|
|
9
|
+
surcharge?: number;
|
|
10
|
+
/** One-line note above the slider („Atypický rozměr vyrábíme na míru“). */
|
|
11
|
+
note?: string | null;
|
|
12
|
+
/** Unit shown after the number input. */
|
|
13
|
+
unit?: string;
|
|
14
|
+
currency?: string;
|
|
15
|
+
}
|
|
16
|
+
declare const DimensionSlider: import("svelte").Component<Props, {}, "value">;
|
|
17
|
+
type DimensionSlider = ReturnType<typeof DimensionSlider>;
|
|
18
|
+
export default DimensionSlider;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fade, fly } from 'svelte/transition';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import { portal, lockScroll, trapFocus, rememberFocus } from './overlay.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
open: boolean;
|
|
8
|
+
onclose: () => void;
|
|
9
|
+
side?: 'left' | 'right';
|
|
10
|
+
labelledby?: string;
|
|
11
|
+
class?: string;
|
|
12
|
+
children: Snippet;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// closeLabel zanikl s přestavbou pozadí na nefokusovatelnou vrstvu
|
|
16
|
+
// (přístupnost EAA) — zavírá Escape, klik vedle, nebo viditelné tlačítko
|
|
17
|
+
// v obsahu (CartDrawer ho nese). Poslaný prop se neškodně ignoruje.
|
|
18
|
+
let { open, onclose, side = 'right', labelledby, class: className = '', children }: Props =
|
|
19
|
+
$props();
|
|
20
|
+
|
|
21
|
+
let panel: HTMLElement | undefined = $state();
|
|
22
|
+
|
|
23
|
+
$effect(() => {
|
|
24
|
+
if (!open) return;
|
|
25
|
+
// Přístupnost překryvu (EAA): fokus se po zavření vrací na prvek,
|
|
26
|
+
// který drawer otevřel, a Tab cyklí uvnitř panelu (trapFocus).
|
|
27
|
+
const restore = rememberFocus();
|
|
28
|
+
const unlock = lockScroll();
|
|
29
|
+
panel?.focus();
|
|
30
|
+
const untrap = panel ? trapFocus(panel) : () => {};
|
|
31
|
+
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && onclose();
|
|
32
|
+
window.addEventListener('keydown', onKey);
|
|
33
|
+
return () => {
|
|
34
|
+
unlock();
|
|
35
|
+
untrap();
|
|
36
|
+
window.removeEventListener('keydown', onKey);
|
|
37
|
+
restore();
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
{#if open}
|
|
43
|
+
<!-- Škála vrstev (jelinek.css :root): hlavička --z-header (50); překryvy
|
|
44
|
+
(tento, Modal) nad ní na --z-overlay (1200); toasty --z-toast (1500)
|
|
45
|
+
úplně nahoře. -->
|
|
46
|
+
<!-- CHOVÁNÍ SJEDNOCENÉ S MOBILNÍM MENU (majitel, 14. 8. 2026): vyjetí
|
|
47
|
+
zprava na tokenu --dur-slow (0,2 s), zavření Esc i klepnutím vedle,
|
|
48
|
+
zamčený scroll, šířka min(84vw, 448px) nechává vidět proužek stránky
|
|
49
|
+
i na telefonu. MATERIÁL je ale kvůli čitelnosti obsahu (ceny, řádky
|
|
50
|
+
košíku) neprůhledný panel — majitel 14. 8. 2026 sklo vrátil: bílá
|
|
51
|
+
plocha (--bg-white, v tmavém režimu tmavá), jemná hrana
|
|
52
|
+
border-subtle a silnější ztmavení stránky o 50 %. -->
|
|
53
|
+
<div use:portal class="fixed inset-0 z-(--z-overlay)">
|
|
54
|
+
<!-- Pozadí je čistě myší zkratka pro zavření — NENÍ v tab pořadí
|
|
55
|
+
(dřív to byl <button> a neviditelné celoobrazovkové tlačítko bylo
|
|
56
|
+
první zastávkou tabulátoru). Klávesnice zavírá Escapem, čtečky
|
|
57
|
+
mají viditelné zavírací tlačítko v obsahu (CartDrawer ho nese). -->
|
|
58
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
|
|
59
|
+
<div
|
|
60
|
+
data-overlay-backdrop
|
|
61
|
+
aria-hidden="true"
|
|
62
|
+
class="absolute inset-0 bg-[var(--overlay-scrim)] [-webkit-backdrop-filter:var(--overlay-filter)] [backdrop-filter:var(--overlay-filter)]"
|
|
63
|
+
transition:fade={{ duration: 200 }}
|
|
64
|
+
onclick={onclose}
|
|
65
|
+
></div>
|
|
66
|
+
<div
|
|
67
|
+
bind:this={panel}
|
|
68
|
+
tabindex="-1"
|
|
69
|
+
role="dialog"
|
|
70
|
+
aria-modal="true"
|
|
71
|
+
aria-labelledby={labelledby}
|
|
72
|
+
class="absolute inset-y-0 {side === 'right'
|
|
73
|
+
? 'right-0 border-l'
|
|
74
|
+
: 'left-0 border-r'} border-border-subtle bg-surface
|
|
75
|
+
shadow-resting flex w-[min(84vw,448px)] flex-col font-secondary font-light outline-none {className}"
|
|
76
|
+
transition:fly={{ x: side === 'right' ? 400 : -400, duration: 200 }}
|
|
77
|
+
>
|
|
78
|
+
{@render children()}
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
{/if}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onclose: () => void;
|
|
5
|
+
side?: 'left' | 'right';
|
|
6
|
+
labelledby?: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
children: Snippet;
|
|
9
|
+
}
|
|
10
|
+
declare const Drawer: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type Drawer = ReturnType<typeof Drawer>;
|
|
12
|
+
export default Drawer;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { cn } from '../../utils/cn.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/** Titulek prázdného stavu. */
|
|
7
|
+
heading?: string;
|
|
8
|
+
/** Vysvětlení a rada, co zkusit jinak. */
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
/** Akce pod textem — typicky tlačítko „Zrušit filtry“. */
|
|
11
|
+
action?: Snippet;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
heading = 'Nic jsme nenašli',
|
|
17
|
+
children,
|
|
18
|
+
action,
|
|
19
|
+
class: className
|
|
20
|
+
}: Props = $props();
|
|
21
|
+
|
|
22
|
+
// A quiet gray panel, no illustration (the kit ships no icon set) and no
|
|
23
|
+
// colour — an empty result is a normal state, not an error. Centered so it
|
|
24
|
+
// visibly replaces the product grid it stands in for.
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div class={cn('rounded-card bg-surface-gray px-6 py-14 text-center', className)}>
|
|
28
|
+
<h3 class="font-primary text-step1 font-bold text-fg">{heading}</h3>
|
|
29
|
+
{#if children}
|
|
30
|
+
<p class="mx-auto mt-2 max-w-[480px] font-secondary font-light text-step-down1 text-fg-muted1">
|
|
31
|
+
{@render children()}
|
|
32
|
+
</p>
|
|
33
|
+
{/if}
|
|
34
|
+
{#if action}
|
|
35
|
+
<div class="mt-5 flex justify-center gap-3">{@render action()}</div>
|
|
36
|
+
{/if}
|
|
37
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Titulek prázdného stavu. */
|
|
4
|
+
heading?: string;
|
|
5
|
+
/** Vysvětlení a rada, co zkusit jinak. */
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
/** Akce pod textem — typicky tlačítko „Zrušit filtry“. */
|
|
8
|
+
action?: Snippet;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const EmptyState: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type EmptyState = ReturnType<typeof EmptyState>;
|
|
13
|
+
export default EmptyState;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../../utils/cn.js';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/** Text nasazeného filtru, např. „Výška: 22 cm“. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Zavolá se po kliknutí — odebrání filtru řeší aplikace. */
|
|
8
|
+
onremove?: () => void;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { label, onremove, class: className }: Props = $props();
|
|
13
|
+
|
|
14
|
+
// The whole chip is one button whose action is REMOVAL — the × is just the
|
|
15
|
+
// visual hint, the accessible name says what the click does. Neutral
|
|
16
|
+
// emphasis (forms rule): resting border-fg-muted2 like an input, black on
|
|
17
|
+
// hover like a tile; colour stays reserved for errors.
|
|
18
|
+
const CHIP =
|
|
19
|
+
'inline-flex cursor-pointer items-center gap-1.5 rounded-pill border border-fg-muted2 ' +
|
|
20
|
+
'bg-surface py-[5px] pr-2.5 pl-3 font-secondary text-step-down1 text-fg ' +
|
|
21
|
+
'transition-colors duration-[var(--dur-base)] ease-[var(--ease)] hover:border-jelinek-black';
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<button type="button" aria-label="Zrušit filtr: {label}" class={cn(CHIP, className)} onclick={onremove}>
|
|
25
|
+
{label}
|
|
26
|
+
<svg
|
|
27
|
+
viewBox="0 0 24 24"
|
|
28
|
+
class="size-3.5 text-fg-muted1"
|
|
29
|
+
fill="none"
|
|
30
|
+
stroke="currentColor"
|
|
31
|
+
stroke-width="2"
|
|
32
|
+
stroke-linecap="round"
|
|
33
|
+
aria-hidden="true"><path d="M18 6 6 18M6 6l12 12" /></svg
|
|
34
|
+
>
|
|
35
|
+
</button>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Text nasazeného filtru, např. „Výška: 22 cm“. */
|
|
3
|
+
label: string;
|
|
4
|
+
/** Zavolá se po kliknutí — odebrání filtru řeší aplikace. */
|
|
5
|
+
onremove?: () => void;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
declare const FilterChip: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type FilterChip = ReturnType<typeof FilterChip>;
|
|
10
|
+
export default FilterChip;
|