@jelinek/ui 0.9.0 → 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 +6 -7
- 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 +22 -5
- package/dist/components/commerce/CartDrawer.svelte +7 -4
- package/dist/components/commerce/CartLineItem.svelte +1 -1
- package/dist/components/commerce/CheckoutProgress.svelte +1 -1
- package/dist/components/commerce/ConfigSection.svelte +2 -2
- package/dist/components/commerce/DimensionSlider.svelte +1 -1
- package/dist/components/commerce/Drawer.svelte +38 -24
- package/dist/components/commerce/Drawer.svelte.d.ts +0 -2
- 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 +1 -1
- package/dist/components/commerce/Gallery.svelte +4 -2
- package/dist/components/commerce/ListingToolbar.svelte +62 -0
- package/dist/components/commerce/ListingToolbar.svelte.d.ts +19 -0
- package/dist/components/commerce/Modal.svelte +31 -21
- package/dist/components/commerce/Modal.svelte.d.ts +0 -2
- package/dist/components/commerce/OrderSummary.svelte +1 -1
- package/dist/components/commerce/Pagination.svelte +115 -0
- package/dist/components/commerce/Pagination.svelte.d.ts +14 -0
- package/dist/components/commerce/PriceDisplay.svelte +3 -3
- package/dist/components/commerce/ProductHeader.svelte +2 -2
- package/dist/components/commerce/QuantityStepper.svelte +1 -1
- package/dist/components/commerce/RatingStars.svelte +1 -1
- package/dist/components/commerce/SearchField.svelte +92 -0
- package/dist/components/commerce/SearchField.svelte.d.ts +15 -0
- package/dist/components/commerce/SideToggle.svelte +1 -1
- package/dist/components/commerce/Skeleton.svelte +4 -1
- package/dist/components/commerce/StickyBar.svelte +4 -4
- package/dist/components/commerce/StockBadge.svelte +2 -2
- package/dist/components/commerce/Toaster.svelte +9 -5
- package/dist/components/commerce/TrustBadges.svelte +1 -1
- package/dist/components/commerce/overlay.d.ts +14 -0
- package/dist/components/commerce/overlay.js +49 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +14 -0
- package/dist/theme/base.css +22 -0
- package/dist/theme/extras.css +44 -0
- package/dist/theme/fonts.css +6 -42
- package/dist/theme/tokens.css +16 -9
- package/package.json +1 -1
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
// while `display: none` holds; from 640px up the bar is `absolute`, which is
|
|
62
62
|
// what makes it the containing block for the scroll-affordance fades below.
|
|
63
63
|
const OUTER =
|
|
64
|
-
'hidden z-
|
|
64
|
+
'hidden z-(--z-chapter-nav) px-6 ' +
|
|
65
65
|
'min-[640px]:block min-[640px]:absolute min-[640px]:inset-x-0 ' +
|
|
66
66
|
'min-[640px]:top-[calc(var(--header-h)-11px)]';
|
|
67
67
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
checked?: boolean;
|
|
7
7
|
label: string;
|
|
8
8
|
id?: string;
|
|
9
|
+
disabled?: boolean;
|
|
9
10
|
class?: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
checked = $bindable(false),
|
|
14
15
|
label,
|
|
15
16
|
id,
|
|
17
|
+
disabled = false,
|
|
16
18
|
class: className,
|
|
17
19
|
...rest
|
|
18
20
|
}: Props & Omit<HTMLInputAttributes, 'checked' | 'type'> = $props();
|
|
@@ -31,22 +33,18 @@
|
|
|
31
33
|
// without depending on an ambient .demo wrapper the consumer may not
|
|
32
34
|
// have. gap-2.5 matches the 10px inline gap.
|
|
33
35
|
//
|
|
34
|
-
// ACCENT COLOUR —
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
// [data-division="mattress"] (tokens.css:161-166) — the same mechanism
|
|
44
|
-
// Button relies on for its brand colours — so this accent still turns
|
|
45
|
-
// magenta automatically on mattress-themed pages, without hardcoding it.
|
|
46
|
-
const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary text-step0 text-fg';
|
|
36
|
+
// ACCENT COLOUR — adjudicated by the owner (14. 8. 2026): forms are
|
|
37
|
+
// colour-neutral. Every emphasis in a form is BLACK (--JELINEK-BLACK,
|
|
38
|
+
// which flips to near-white in dark mode so it stays visible); the only
|
|
39
|
+
// colour a form carries is the error state. No division re-theme here —
|
|
40
|
+
// forms look the same in Nábytek and Zdravý spánek.
|
|
41
|
+
const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary font-light text-step0 text-fg';
|
|
42
|
+
// Zakázaný stav mluví jako zakázané tlačítko (Button.svelte:117):
|
|
43
|
+
// utlumený text a cursor-not-allowed; políčko samotné tlumí prohlížeč.
|
|
44
|
+
const DISABLED = 'cursor-not-allowed text-fg-muted2';
|
|
47
45
|
</script>
|
|
48
46
|
|
|
49
|
-
<label for={inputId} class={cn(LABEL, className)}>
|
|
50
|
-
<input {...rest} id={inputId} type="checkbox" bind:checked class="accent-jelinek-
|
|
47
|
+
<label for={inputId} class={cn(LABEL, disabled && DISABLED, className)}>
|
|
48
|
+
<input {...rest} id={inputId} type="checkbox" {disabled} bind:checked class="accent-jelinek-black" />
|
|
51
49
|
{label}
|
|
52
50
|
</label>
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
import { cn } from '../utils/cn.js';
|
|
4
4
|
import { DEER_MARK_PATH } from './deer-mark-path.js';
|
|
5
5
|
|
|
6
|
-
type Variant = 'brown'
|
|
6
|
+
type Variant = 'brown';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
/** Jelen je jen HNĚDÝ, nebo BÍLÝ (brand → Znak, Užití s produktovými
|
|
10
|
+
* barvami; majitel, 21. 8. 2026): `brown` pro divizi Nábytek, bez
|
|
11
|
+
* varianty BÍLÝ — divize Zdravý spánek a neutrální užití (interní
|
|
12
|
+
* aplikace). Purpurová varianta NEEXISTUJE — dřívější `magenta` byla
|
|
13
|
+
* chyba proti manuálu a je odstraněná. */
|
|
13
14
|
variant?: Variant;
|
|
14
15
|
/**
|
|
15
16
|
* Sets `--logo-mark` (default matches jelinek.css:743's own inline
|
|
@@ -76,14 +77,13 @@
|
|
|
76
77
|
// actually invisible once the colour scoping below is right.
|
|
77
78
|
const BASE = 'inline-flex items-center gap-[calc(var(--logo-mark)*107/400)] text-fg-inverse';
|
|
78
79
|
|
|
79
|
-
// jelinek.css
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
80
|
+
// jelinek.css .content-logo--brown: the brown deer. The division re-theme
|
|
81
|
+
// remaps --JELINEK-BROWN to magenta — for the deer that is a FORBIDDEN
|
|
82
|
+
// state, so under [data-division="mattress"] every lockup flips to WHITE
|
|
83
|
+
// instead; the rule lives in extras.css and hooks on the data-content-logo
|
|
84
|
+
// attribute below, mirroring jelinek.css's own division override.
|
|
84
85
|
const VARIANT: Record<Variant, string> = {
|
|
85
|
-
brown: 'text-jelinek-brown'
|
|
86
|
-
magenta: 'text-jelinek-magenta'
|
|
86
|
+
brown: 'text-jelinek-brown'
|
|
87
87
|
};
|
|
88
88
|
|
|
89
89
|
// jelinek.css:746 (.content-logo__svg): width/height both var(--logo-mark),
|
|
@@ -142,7 +142,12 @@
|
|
|
142
142
|
);
|
|
143
143
|
</script>
|
|
144
144
|
|
|
145
|
-
<div
|
|
145
|
+
<div
|
|
146
|
+
{...rest}
|
|
147
|
+
style={mergedStyle}
|
|
148
|
+
data-content-logo=""
|
|
149
|
+
class={cn(BASE, variant && VARIANT[variant], className)}
|
|
150
|
+
>
|
|
146
151
|
<svg class={SVG} viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
147
152
|
<rect width="400" height="400" rx="25" ry="25" fill="#000" />
|
|
148
153
|
<path d={DEER_MARK_PATH} fill="currentColor" />
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
-
type Variant = 'brown'
|
|
2
|
+
type Variant = 'brown';
|
|
3
3
|
interface Props {
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
/** Jelen je jen HNĚDÝ, nebo BÍLÝ (brand → Znak, Užití s produktovými
|
|
5
|
+
* barvami; majitel, 21. 8. 2026): `brown` pro divizi Nábytek, bez
|
|
6
|
+
* varianty BÍLÝ — divize Zdravý spánek a neutrální užití (interní
|
|
7
|
+
* aplikace). Purpurová varianta NEEXISTUJE — dřívější `magenta` byla
|
|
8
|
+
* chyba proti manuálu a je odstraněná. */
|
|
8
9
|
variant?: Variant;
|
|
9
10
|
/**
|
|
10
11
|
* Sets `--logo-mark` (default matches jelinek.css:743's own inline
|
|
@@ -49,10 +49,11 @@
|
|
|
49
49
|
// Behaviour by width (the guide's, verbatim):
|
|
50
50
|
// w < 640 -> 1 column, every cols value
|
|
51
51
|
// 640 <= w < 1024 -> 2 columns, every cols value
|
|
52
|
-
// w >= 1024 -> the cols value itself
|
|
53
|
-
// (
|
|
54
|
-
//
|
|
55
|
-
//
|
|
52
|
+
// w >= 1024 -> the cols value itself for 2/3; cols=4 steps
|
|
53
|
+
// 3 (>=1024) -> 4 (>=1280) -> 5 (>=1600), so the
|
|
54
|
+
// four-column grid climbs 1/2/3/4/5 with width
|
|
55
|
+
// (owner's request) instead of jumping 2 -> 4.
|
|
56
|
+
// (cols=2 has no 1024px rule at all, it is already 2-up from 640px.)
|
|
56
57
|
//
|
|
57
58
|
// Why Tailwind's native `min-*` variant family and NOT arbitrary *media*
|
|
58
59
|
// variants (`[@media(min-width:1024px)]:...`), even though those can express
|
|
@@ -75,10 +76,13 @@
|
|
|
75
76
|
// `min-[1024px]:grid-cols-${n}` template, so Tailwind's static scanner can
|
|
76
77
|
// see each one literally — a template string is invisible to it and the
|
|
77
78
|
// class would never be generated.
|
|
79
|
+
// cols={4} přidává od 1600 px pátý sloupec (jelinek.css .grid--4, majitel:
|
|
80
|
+
// širší web = víc obsahu do řady; trojka a dvojka zůstávají — jsou volené
|
|
81
|
+
// kvůli obsahu, ne kvůli místu).
|
|
78
82
|
const COLS: Record<2 | 3 | 4, string> = {
|
|
79
83
|
2: 'grid-cols-1 min-[640px]:grid-cols-2',
|
|
80
84
|
3: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-3',
|
|
81
|
-
4: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-4'
|
|
85
|
+
4: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-3 min-[1280px]:grid-cols-4 min-[1600px]:grid-cols-5'
|
|
82
86
|
};
|
|
83
87
|
</script>
|
|
84
88
|
|
|
@@ -5,12 +5,21 @@
|
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
for?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Hvězdička povinného pole za popiskem. Jen vizuální značka
|
|
10
|
+
* (aria-hidden) — povinnost samotnou hlásí nativní `required` na poli,
|
|
11
|
+
* které ji zapíná. Černá (--JELINEK-BLACK, v tmavém režimu se sama
|
|
12
|
+
* překlopí na bílou): formuláře jsou podle rozhodnutí majitele
|
|
13
|
+
* (14. 8. 2026) barevně neutrální — barvu nese jen chyba.
|
|
14
|
+
*/
|
|
15
|
+
required?: boolean;
|
|
8
16
|
class?: string;
|
|
9
17
|
children: Snippet;
|
|
10
18
|
}
|
|
11
19
|
|
|
12
20
|
let {
|
|
13
21
|
for: forId,
|
|
22
|
+
required = false,
|
|
14
23
|
class: className,
|
|
15
24
|
children,
|
|
16
25
|
...rest
|
|
@@ -33,5 +42,7 @@
|
|
|
33
42
|
for={forId}
|
|
34
43
|
class={cn('mb-1.5 block font-secondary text-[0.88rem] font-medium', className)}
|
|
35
44
|
>
|
|
36
|
-
{@render children()}
|
|
45
|
+
{@render children()}{#if required}<span aria-hidden="true" class="text-jelinek-black">
|
|
46
|
+
*</span
|
|
47
|
+
>{/if}
|
|
37
48
|
</label>
|
|
@@ -2,6 +2,14 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLLabelAttributes } from 'svelte/elements';
|
|
3
3
|
interface Props {
|
|
4
4
|
for?: string;
|
|
5
|
+
/**
|
|
6
|
+
* Hvězdička povinného pole za popiskem. Jen vizuální značka
|
|
7
|
+
* (aria-hidden) — povinnost samotnou hlásí nativní `required` na poli,
|
|
8
|
+
* které ji zapíná. Černá (--JELINEK-BLACK, v tmavém režimu se sama
|
|
9
|
+
* překlopí na bílou): formuláře jsou podle rozhodnutí majitele
|
|
10
|
+
* (14. 8. 2026) barevně neutrální — barvu nese jen chyba.
|
|
11
|
+
*/
|
|
12
|
+
required?: boolean;
|
|
5
13
|
class?: string;
|
|
6
14
|
children: Snippet;
|
|
7
15
|
}
|
|
@@ -82,15 +82,14 @@
|
|
|
82
82
|
...rest
|
|
83
83
|
}: Props & HTMLAttributes<HTMLElement> = $props();
|
|
84
84
|
|
|
85
|
-
// jelinek.css .breadcrumbs — a small
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
85
|
+
// jelinek.css .breadcrumbs — a small muted UI row: step-down2, links darken
|
|
86
|
+
// on hover, current chapter reads in full text colour.
|
|
87
|
+
// GT Walsheim Medium — Avenir do drobečků nepatří (majitel, 21. 8. 2026):
|
|
88
|
+
// pravidla znají Avenir jen u podnázvu loga.
|
|
89
89
|
const CRUMBS =
|
|
90
|
-
'mb-4 flex flex-wrap items-center gap-2 text-step-down2 font-
|
|
90
|
+
'mb-4 flex flex-wrap items-center gap-2 font-secondary text-step-down2 font-medium tracking-[0.02em] text-fg-muted1';
|
|
91
91
|
const CRUMB_LINK =
|
|
92
92
|
'text-fg-muted1 no-underline transition-[color] duration-[var(--dur-base)] ease-[var(--ease)] hover:text-fg hover:no-underline';
|
|
93
|
-
const CRUMBS_STYLE = "font-family: 'Avenir Next LT Pro', 'Avenir Next', Arial, sans-serif;";
|
|
94
93
|
|
|
95
94
|
// `hasContext` must run synchronously during this component's own
|
|
96
95
|
// initialization (Svelte's lifecycle rule for context APIs) — captured
|
|
@@ -329,7 +328,7 @@
|
|
|
329
328
|
two copies drifting apart. -->
|
|
330
329
|
{#snippet content()}
|
|
331
330
|
{#if crumbs.length > 0}
|
|
332
|
-
<nav class={CRUMBS}
|
|
331
|
+
<nav class={CRUMBS} aria-label="Drobečková navigace">
|
|
333
332
|
{#each crumbs as crumb, i (crumb.label)}
|
|
334
333
|
{#if i > 0}<span class="text-fg-muted2" aria-hidden="true">/</span>{/if}
|
|
335
334
|
{#if crumb.href}
|
|
@@ -62,15 +62,18 @@
|
|
|
62
62
|
// a 0 basis can always be squeezed into the line). jelinek.css:1116 is
|
|
63
63
|
// `flex: 1 1 200px`; the 200px basis is what makes ROOT's `flex-wrap`
|
|
64
64
|
// actually fire. Changing one without the other is a no-op.
|
|
65
|
+
// font-light, not font-normal: jelinek.css's `.tile, .pager a` moved to
|
|
66
|
+
// weight 300 (typografická revize 21. 8. 2026 — Walsheim má jen
|
|
67
|
+
// Bold/Medium/Light); the bold lives on the inner <strong>.
|
|
65
68
|
const LINK =
|
|
66
69
|
'max-w-[340px] flex-[1_1_200px] rounded-card border border-border-subtle px-5 py-4 font-secondary ' +
|
|
67
|
-
'font-
|
|
70
|
+
'font-light text-fg no-underline shadow-resting hover:shadow-interactive ' +
|
|
68
71
|
'transition-shadow duration-[var(--dur-base)] ease-[var(--ease)] ' +
|
|
69
72
|
'dark:border-t-[var(--border-highlight-white)]';
|
|
70
73
|
|
|
71
74
|
// jelinek.css:1123 (.pager a span): block, 0.78rem (not on the --step
|
|
72
75
|
// scale), text-muted1.
|
|
73
|
-
const SUBLABEL = 'block text-[0.78rem] text-fg-muted1';
|
|
76
|
+
const SUBLABEL = 'block text-[0.78rem] font-light text-fg-muted1';
|
|
74
77
|
</script>
|
|
75
78
|
|
|
76
79
|
<nav {...rest} class={cn(ROOT, className)}>
|
|
@@ -66,8 +66,10 @@
|
|
|
66
66
|
const BASE =
|
|
67
67
|
'group relative flex flex-col justify-end overflow-hidden aspect-[4/3] ' +
|
|
68
68
|
'rounded-section shadow-resting no-underline ' +
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// Bez vyskočení na hover (majitel 14. 8. 2026, stejně jako Tile) —
|
|
70
|
+
// zvýrazňuje jen stín.
|
|
71
|
+
'transition-[box-shadow] duration-[var(--dur-base)] ease-[var(--ease)] ' +
|
|
72
|
+
'hover:shadow-interactive ' +
|
|
71
73
|
'dark:shadow-[var(--shadow-resting),inset_0_2px_1px_-1px_var(--box-primary)] ' +
|
|
72
74
|
'dark:hover:shadow-[var(--shadow-interactive),inset_0_2px_1px_-1px_var(--box-primary)]';
|
|
73
75
|
|
|
@@ -2,18 +2,31 @@
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
4
|
import { cn } from '../utils/cn.js';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
5
|
+
import PriceDisplay from './commerce/PriceDisplay.svelte';
|
|
6
|
+
import StockBadge from './commerce/StockBadge.svelte';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
title: string;
|
|
10
|
+
/** Krátký popis pod názvem — kolekce, materiál, shrnutí konfigurace. */
|
|
10
11
|
description?: string;
|
|
11
12
|
image?: string;
|
|
12
13
|
imageAlt?: string;
|
|
14
|
+
/** Odkaz na detail — celá karta je jeden odkaz, žádné tlačítko uvnitř. */
|
|
13
15
|
href?: string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
/**
|
|
16
|
+
/** Cena v celých korunách (formátuje `formatPrice`). */
|
|
17
|
+
price?: number;
|
|
18
|
+
/** Přeškrtnutý původ — vykreslí se jen, když je vyšší než `price`. */
|
|
19
|
+
originalPrice?: number;
|
|
20
|
+
/** Kvalifikátor „od“ před cenou (konfigurovatelné produkty). */
|
|
21
|
+
priceFrom?: boolean;
|
|
22
|
+
/** Dostupnost pod cenou (kompaktní StockBadge). */
|
|
23
|
+
stock?: 'in-stock' | 'on-order' | 'sold-out' | 'lead';
|
|
24
|
+
/** Přepis textu dostupnosti — u stavu `lead` konkrétní lhůta. */
|
|
25
|
+
stockLabel?: string;
|
|
26
|
+
/** Produktová fotka na bílém pozadí (packshot): čtverec bez ořezu.
|
|
27
|
+
Výchozí je ambientní fotka v ořezu 4:3. */
|
|
28
|
+
packshot?: boolean;
|
|
29
|
+
/** Štítek nad názvem, např. <Tag variant="instock">. */
|
|
17
30
|
tag?: Snippet;
|
|
18
31
|
class?: string;
|
|
19
32
|
}
|
|
@@ -24,58 +37,67 @@
|
|
|
24
37
|
image,
|
|
25
38
|
imageAlt = '',
|
|
26
39
|
href,
|
|
27
|
-
|
|
28
|
-
|
|
40
|
+
price,
|
|
41
|
+
originalPrice,
|
|
42
|
+
priceFrom = false,
|
|
43
|
+
stock,
|
|
44
|
+
stockLabel,
|
|
45
|
+
packshot = false,
|
|
29
46
|
tag,
|
|
30
47
|
class: className,
|
|
31
48
|
...rest
|
|
32
|
-
}: Props & HTMLAttributes<
|
|
49
|
+
}: Props & HTMLAttributes<HTMLElement> = $props();
|
|
33
50
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
51
|
+
// DEFINITIVNÍ PODOBA (audit P2, 21. 8. 2026) — do té doby bylo tohle
|
|
52
|
+
// provizorium složené z Card + Button. Předloha je karta výpisu z dema
|
|
53
|
+
// kategorie (demo/ortopedicke-lamelove-matrace), kterou schválil majitel:
|
|
54
|
+
//
|
|
55
|
+
// - CELÁ KARTA JE JEDEN ODKAZ. Ve výpisu se kliká na produkt, ne na
|
|
56
|
+
// tlačítko v něm — tlačítko uvnitř odkazu je past pro čtečky i palce.
|
|
57
|
+
// Bez `href` se vykreslí jako <div> (čistě prezentační užití).
|
|
58
|
+
// - Neutrální zvýraznění (pravidlo formulářů): klidová hrana
|
|
59
|
+
// border-subtle, na najetí tenká černá + zvednutý stín, ŽÁDNÝ posun.
|
|
60
|
+
// Barva divize na kartě není — nese ji obsah stránky, ne rámeček.
|
|
61
|
+
// - Cena a dostupnost jsou existující atomy PriceDisplay a StockBadge,
|
|
62
|
+
// karta si je nekreslí po svém.
|
|
63
|
+
// - Fotky: ambientní záběry ořez 4:3 (jako PhotoTile), packshoty na bílém
|
|
64
|
+
// pozadí čtverec bez ořezu (`packshot`) — matrace nesmí přijít o rohy.
|
|
65
|
+
const CARD =
|
|
66
|
+
'block overflow-hidden rounded-card border border-border-subtle bg-surface ' +
|
|
67
|
+
'no-underline shadow-resting transition-[border-color,box-shadow] ' +
|
|
68
|
+
'duration-[var(--dur-base)] ease-[var(--ease)]';
|
|
69
|
+
const LINKED = 'hover:border-jelinek-black hover:shadow-interactive';
|
|
41
70
|
</script>
|
|
42
71
|
|
|
43
|
-
<
|
|
72
|
+
<svelte:element this={href ? 'a' : 'div'} {...rest} {href} class={cn(CARD, href && LINKED, className)}>
|
|
44
73
|
{#if image}
|
|
45
|
-
<!--
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
<img src={image} alt={imageAlt} class="aspect-[4/3] w-full rounded-none object-cover" />
|
|
74
|
+
<!-- rounded-none: fotka lícuje s kulatými rohy karty, ořez dělá
|
|
75
|
+
overflow-hidden nahoře — viz stejná poznámka v PhotoTile. -->
|
|
76
|
+
{#if packshot}
|
|
77
|
+
<img src={image} alt={imageAlt} class="aspect-square w-full rounded-none bg-surface object-contain" />
|
|
78
|
+
{:else}
|
|
79
|
+
<img src={image} alt={imageAlt} class="aspect-[4/3] w-full rounded-none object-cover" />
|
|
80
|
+
{/if}
|
|
53
81
|
{/if}
|
|
54
82
|
<div class="p-[18px]">
|
|
55
83
|
{#if tag}
|
|
56
84
|
<div class="mb-2.5">{@render tag()}</div>
|
|
57
85
|
{/if}
|
|
58
|
-
|
|
59
|
-
Card's own [&_h3]:text-step1 [&_h3]:leading-[1.3] descendant rule
|
|
60
|
-
(Card.svelte) — repeating text-step1/leading here would duplicate
|
|
61
|
-
Card, not correct it. text-fg is still needed: Card sets no text
|
|
62
|
-
color of its own (the static site's .card h3 inherits its color
|
|
63
|
-
from body), so without it this heading would fall back to
|
|
64
|
-
whichever default the consuming app happens to have. mt-2/mb-1
|
|
65
|
-
reproduce the demo's inline `margin:8px 0 4px` exactly — no CSS
|
|
66
|
-
rule sets this margin (it's specific to that one demo instance),
|
|
67
|
-
but both values already sit on Tailwind's default spacing scale. -->
|
|
68
|
-
<h3 class="mt-2 mb-1 text-fg">{title}</h3>
|
|
86
|
+
<h3 class="mt-0 mb-1 font-primary text-step1 leading-[1.3] font-bold text-fg">{title}</h3>
|
|
69
87
|
{#if description}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
88
|
+
<p class="mb-0 font-secondary font-light text-step-down1 leading-[1.3] text-fg-muted1">
|
|
89
|
+
{description}
|
|
90
|
+
</p>
|
|
91
|
+
{/if}
|
|
92
|
+
{#if price !== undefined}
|
|
93
|
+
<div class="mt-2.5 text-fg">
|
|
94
|
+
<PriceDisplay {price} {originalPrice} from={priceFrom} size="sm" />
|
|
95
|
+
</div>
|
|
76
96
|
{/if}
|
|
77
|
-
{#if
|
|
78
|
-
<
|
|
97
|
+
{#if stock}
|
|
98
|
+
<div class="mt-1.5">
|
|
99
|
+
<StockBadge state={stock} label={stockLabel} compact />
|
|
100
|
+
</div>
|
|
79
101
|
{/if}
|
|
80
102
|
</div>
|
|
81
|
-
</
|
|
103
|
+
</svelte:element>
|
|
@@ -2,17 +2,30 @@ import type { Snippet } from 'svelte';
|
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
3
|
interface Props {
|
|
4
4
|
title: string;
|
|
5
|
+
/** Krátký popis pod názvem — kolekce, materiál, shrnutí konfigurace. */
|
|
5
6
|
description?: string;
|
|
6
7
|
image?: string;
|
|
7
8
|
imageAlt?: string;
|
|
9
|
+
/** Odkaz na detail — celá karta je jeden odkaz, žádné tlačítko uvnitř. */
|
|
8
10
|
href?: string;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
11
|
+
/** Cena v celých korunách (formátuje `formatPrice`). */
|
|
12
|
+
price?: number;
|
|
13
|
+
/** Přeškrtnutý původ — vykreslí se jen, když je vyšší než `price`. */
|
|
14
|
+
originalPrice?: number;
|
|
15
|
+
/** Kvalifikátor „od“ před cenou (konfigurovatelné produkty). */
|
|
16
|
+
priceFrom?: boolean;
|
|
17
|
+
/** Dostupnost pod cenou (kompaktní StockBadge). */
|
|
18
|
+
stock?: 'in-stock' | 'on-order' | 'sold-out' | 'lead';
|
|
19
|
+
/** Přepis textu dostupnosti — u stavu `lead` konkrétní lhůta. */
|
|
20
|
+
stockLabel?: string;
|
|
21
|
+
/** Produktová fotka na bílém pozadí (packshot): čtverec bez ořezu.
|
|
22
|
+
Výchozí je ambientní fotka v ořezu 4:3. */
|
|
23
|
+
packshot?: boolean;
|
|
24
|
+
/** Štítek nad názvem, např. <Tag variant="instock">. */
|
|
12
25
|
tag?: Snippet;
|
|
13
26
|
class?: string;
|
|
14
27
|
}
|
|
15
|
-
type $$ComponentProps = Props & HTMLAttributes<
|
|
28
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLElement>;
|
|
16
29
|
declare const ProductCard: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
17
30
|
type ProductCard = ReturnType<typeof ProductCard>;
|
|
18
31
|
export default ProductCard;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
value: string;
|
|
9
9
|
label: string;
|
|
10
10
|
id?: string;
|
|
11
|
+
disabled?: boolean;
|
|
11
12
|
class?: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
value,
|
|
16
17
|
label,
|
|
17
18
|
id,
|
|
19
|
+
disabled = false,
|
|
18
20
|
class: className,
|
|
19
21
|
...rest
|
|
20
22
|
}: Props & Omit<HTMLInputAttributes, 'value' | 'type' | 'name'> = $props();
|
|
@@ -28,15 +30,16 @@
|
|
|
28
30
|
|
|
29
31
|
// Same reasoning as Checkbox.svelte: no CSS rule exists for radio inputs
|
|
30
32
|
// or their label; font-secondary/text-fg reproduces what the guide's own
|
|
31
|
-
// `.demo` wrapper applies ambiently (jelinek.css:322-324)
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
// `.demo` wrapper applies ambiently (jelinek.css:322-324). Accent is
|
|
34
|
+
// BLACK per the owner's 14. 8. 2026 call — forms are colour-neutral,
|
|
35
|
+
// only the error state carries colour; --JELINEK-BLACK flips to
|
|
36
|
+
// near-white in dark mode.
|
|
37
|
+
const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary font-light text-step0 text-fg';
|
|
38
|
+
// Stejná řeč jako Checkbox: utlumený text + cursor-not-allowed.
|
|
39
|
+
const DISABLED = 'cursor-not-allowed text-fg-muted2';
|
|
37
40
|
</script>
|
|
38
41
|
|
|
39
|
-
<label for={inputId} class={cn(LABEL, className)}>
|
|
42
|
+
<label for={inputId} class={cn(LABEL, disabled && DISABLED, className)}>
|
|
40
43
|
<input
|
|
41
44
|
{...rest}
|
|
42
45
|
id={inputId}
|
|
@@ -44,11 +47,12 @@
|
|
|
44
47
|
name={group.name}
|
|
45
48
|
{value}
|
|
46
49
|
{checked}
|
|
50
|
+
{disabled}
|
|
47
51
|
onchange={(event) => {
|
|
48
52
|
group.select(value);
|
|
49
53
|
rest.onchange?.(event);
|
|
50
54
|
}}
|
|
51
|
-
class="accent-jelinek-
|
|
55
|
+
class="accent-jelinek-black"
|
|
52
56
|
/>
|
|
53
57
|
{label}
|
|
54
58
|
</label>
|