@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
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
* the guide styles both in one selector list.
|
|
16
16
|
*/
|
|
17
17
|
error?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Chybová hláška pod polem — stejné chování jako u TextFieldu: zapíná
|
|
20
|
+
* chybový stav, váže se přes `aria-describedby` a `aria-live`,
|
|
21
|
+
* má přednost před `hint`.
|
|
22
|
+
*/
|
|
23
|
+
errorText?: string;
|
|
24
|
+
/** Nápověda pod polem, navázaná přes `aria-describedby`. */
|
|
25
|
+
hint?: string;
|
|
26
|
+
/** Povinné pole: hvězdička u popisku + nativní `required`. */
|
|
27
|
+
required?: boolean;
|
|
28
|
+
disabled?: boolean;
|
|
18
29
|
class?: string;
|
|
19
30
|
children: Snippet;
|
|
20
31
|
}
|
|
@@ -24,6 +35,10 @@
|
|
|
24
35
|
label,
|
|
25
36
|
id,
|
|
26
37
|
error = false,
|
|
38
|
+
errorText,
|
|
39
|
+
hint,
|
|
40
|
+
required = false,
|
|
41
|
+
disabled = false,
|
|
27
42
|
class: className,
|
|
28
43
|
children,
|
|
29
44
|
...rest
|
|
@@ -31,6 +46,14 @@
|
|
|
31
46
|
|
|
32
47
|
const uid = $props.id();
|
|
33
48
|
const selectId = $derived(id ?? uid);
|
|
49
|
+
const hintId = $derived(`${selectId}-hint`);
|
|
50
|
+
const errorId = $derived(`${selectId}-error`);
|
|
51
|
+
const isError = $derived(error || !!errorText);
|
|
52
|
+
const describedBy = $derived(
|
|
53
|
+
[errorText ? errorId : null, !errorText && hint ? hintId : null, rest['aria-describedby']]
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join(' ') || undefined
|
|
56
|
+
);
|
|
34
57
|
|
|
35
58
|
// jelinek.css:966-975 (`.input, .select`) — same rule, and same
|
|
36
59
|
// corrections against the draft brief, as TextField: border-fg-muted2
|
|
@@ -41,25 +64,60 @@
|
|
|
41
64
|
// (jelinek.css:987-991) is new. See TextField.svelte for the full note on
|
|
42
65
|
// both; the guide styles `.input` and `.select` in one selector list, so the
|
|
43
66
|
// two components stay deliberately identical here.
|
|
67
|
+
// Vlastní šipka místo nativní: Chrome na macOS kreslí u stylovaného
|
|
68
|
+
// selectu svůj chevron nalepený na pravý kraj, bez ohledu na padding.
|
|
69
|
+
// `appearance-none` ho vypne a chevron kreslí SVG ve wrapperu níž,
|
|
70
|
+
// posazené na stejných 14 px od kraje jako text. `pr-9` dělá šipce místo.
|
|
71
|
+
// `leading-normal` drží výšku ve shodě s TextFieldem — viz komentář tam.
|
|
44
72
|
const BASE =
|
|
45
|
-
'w-full rounded-button border border-fg-muted2 bg-surface px-[14px] py-[11px] ' +
|
|
46
|
-
'font-secondary text-[0.98rem] text-fg ' +
|
|
73
|
+
'w-full appearance-none rounded-button border border-fg-muted2 bg-surface px-[14px] py-[11px] pr-9 ' +
|
|
74
|
+
'font-secondary font-light text-[0.98rem] leading-normal text-fg [&_option]:font-light ' +
|
|
47
75
|
'focus:outline-[length:var(--focus-width)] focus:outline-offset-1 ' +
|
|
48
76
|
'focus:outline-[var(--focus-color)] focus:border-[var(--focus-color)]';
|
|
49
77
|
|
|
50
78
|
const ERROR =
|
|
51
79
|
'border-sem-error-strong focus:border-sem-error-strong focus:outline-sem-error-strong';
|
|
80
|
+
|
|
81
|
+
// Stejná řeč jako TextField.svelte — zakázaný select podle zakázaného
|
|
82
|
+
// tlačítka (Button.svelte:117), hlášky pod polem beze změny vzhledu.
|
|
83
|
+
const DISABLED = 'cursor-not-allowed border-border-subtle bg-surface-gray text-fg-muted2';
|
|
84
|
+
const MSG_ERROR = 'mt-1.5 font-secondary text-step-down1 font-medium text-sem-error-strong';
|
|
85
|
+
const MSG_HINT = 'mt-1.5 font-secondary text-step-down1 font-light text-fg-muted1';
|
|
52
86
|
</script>
|
|
53
87
|
|
|
54
88
|
{#if label}
|
|
55
|
-
<Label for={selectId}>{label}</Label>
|
|
89
|
+
<Label for={selectId} {required}>{label}</Label>
|
|
90
|
+
{/if}
|
|
91
|
+
<span class="relative block">
|
|
92
|
+
<select
|
|
93
|
+
{...rest}
|
|
94
|
+
id={selectId}
|
|
95
|
+
{required}
|
|
96
|
+
{disabled}
|
|
97
|
+
aria-invalid={isError ? 'true' : rest['aria-invalid']}
|
|
98
|
+
aria-describedby={describedBy}
|
|
99
|
+
bind:value
|
|
100
|
+
class={cn(BASE, isError && ERROR, disabled && DISABLED, className)}
|
|
101
|
+
>
|
|
102
|
+
{@render children()}
|
|
103
|
+
</select>
|
|
104
|
+
<svg
|
|
105
|
+
class="pointer-events-none absolute top-1/2 right-[14px] size-3 -translate-y-1/2 {disabled
|
|
106
|
+
? 'text-fg-muted2'
|
|
107
|
+
: 'text-fg'}"
|
|
108
|
+
viewBox="0 0 12 12"
|
|
109
|
+
fill="none"
|
|
110
|
+
stroke="currentColor"
|
|
111
|
+
stroke-width="1.5"
|
|
112
|
+
stroke-linecap="round"
|
|
113
|
+
stroke-linejoin="round"
|
|
114
|
+
aria-hidden="true"
|
|
115
|
+
>
|
|
116
|
+
<path d="m2.5 4.25 3.5 3.5 3.5-3.5" />
|
|
117
|
+
</svg>
|
|
118
|
+
</span>
|
|
119
|
+
{#if errorText}
|
|
120
|
+
<p id={errorId} aria-live="polite" class={MSG_ERROR}>{errorText}</p>
|
|
121
|
+
{:else if hint}
|
|
122
|
+
<p id={hintId} class={MSG_HINT}>{hint}</p>
|
|
56
123
|
{/if}
|
|
57
|
-
<select
|
|
58
|
-
{...rest}
|
|
59
|
-
id={selectId}
|
|
60
|
-
aria-invalid={error ? 'true' : rest['aria-invalid']}
|
|
61
|
-
bind:value
|
|
62
|
-
class={cn(BASE, error && ERROR, className)}
|
|
63
|
-
>
|
|
64
|
-
{@render children()}
|
|
65
|
-
</select>
|
|
@@ -11,6 +11,17 @@ interface Props {
|
|
|
11
11
|
* the guide styles both in one selector list.
|
|
12
12
|
*/
|
|
13
13
|
error?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Chybová hláška pod polem — stejné chování jako u TextFieldu: zapíná
|
|
16
|
+
* chybový stav, váže se přes `aria-describedby` a `aria-live`,
|
|
17
|
+
* má přednost před `hint`.
|
|
18
|
+
*/
|
|
19
|
+
errorText?: string;
|
|
20
|
+
/** Nápověda pod polem, navázaná přes `aria-describedby`. */
|
|
21
|
+
hint?: string;
|
|
22
|
+
/** Povinné pole: hvězdička u popisku + nativní `required`. */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
14
25
|
class?: string;
|
|
15
26
|
children: Snippet;
|
|
16
27
|
}
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
* Without it (or without `stamp`) the stamp stays a plain line.
|
|
28
28
|
*/
|
|
29
29
|
changelog?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Task 49 — controls in the footer's brand row, after the stamp (the
|
|
32
|
+
* theme toggle moved here from the header, the owner's call —
|
|
33
|
+
* partials/footer.html carries the same markup). The wrapper remaps the
|
|
34
|
+
* fg/surface tokens to their inverse: the footer is ALWAYS black, so a
|
|
35
|
+
* control drawn in text colour would vanish in light mode.
|
|
36
|
+
*/
|
|
37
|
+
actions?: Snippet;
|
|
30
38
|
class?: string;
|
|
31
39
|
}
|
|
32
40
|
|
|
@@ -38,10 +46,18 @@
|
|
|
38
46
|
stamp,
|
|
39
47
|
changelog,
|
|
40
48
|
children,
|
|
49
|
+
actions,
|
|
41
50
|
class: className,
|
|
42
51
|
...rest
|
|
43
52
|
}: Props & HTMLAttributes<HTMLElement> = $props();
|
|
44
53
|
|
|
54
|
+
// Mirrors jelinek.css's `.site-footer .theme-toggle` overrides: light
|
|
55
|
+
// track/knob in both themes, dark icon on the light knob.
|
|
56
|
+
const ACTIONS =
|
|
57
|
+
'flex flex-none items-center ' +
|
|
58
|
+
'[--color-fg:var(--color-fg-inverse)] [--color-surface:var(--color-fg-inverse)] ' +
|
|
59
|
+
'[&_svg]:text-surface-black';
|
|
60
|
+
|
|
45
61
|
let dialogEl = $state<HTMLDialogElement | null>(null);
|
|
46
62
|
|
|
47
63
|
// Mirrors the guide's .changelog dialog (jelinek.css) — a card over the
|
|
@@ -179,6 +195,9 @@
|
|
|
179
195
|
the guide. -->
|
|
180
196
|
<div class="ml-auto font-serif text-[0.85rem] text-fg-muted2">{stamp}</div>
|
|
181
197
|
{/if}
|
|
198
|
+
{#if actions}
|
|
199
|
+
<div class={cn(ACTIONS, !stamp && 'ml-auto')}>{@render actions()}</div>
|
|
200
|
+
{/if}
|
|
182
201
|
</div>
|
|
183
202
|
{#if stamp && changelog}
|
|
184
203
|
<dialog
|
|
@@ -24,6 +24,14 @@ interface Props {
|
|
|
24
24
|
* Without it (or without `stamp`) the stamp stays a plain line.
|
|
25
25
|
*/
|
|
26
26
|
changelog?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Task 49 — controls in the footer's brand row, after the stamp (the
|
|
29
|
+
* theme toggle moved here from the header, the owner's call —
|
|
30
|
+
* partials/footer.html carries the same markup). The wrapper remaps the
|
|
31
|
+
* fg/surface tokens to their inverse: the footer is ALWAYS black, so a
|
|
32
|
+
* control drawn in text colour would vanish in light mode.
|
|
33
|
+
*/
|
|
34
|
+
actions?: Snippet;
|
|
27
35
|
class?: string;
|
|
28
36
|
}
|
|
29
37
|
type $$ComponentProps = Props & HTMLAttributes<HTMLElement>;
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
// Three of his commits land here (36354b9, 6750ee8, 74ef043, a430f9c) and
|
|
277
277
|
// they supersede this component's previous phone treatment wholesale:
|
|
278
278
|
//
|
|
279
|
-
// POSITION. `absolute inset-x-0 top-0 z-50
|
|
279
|
+
// POSITION. `absolute inset-x-0 top-0` at --z-header (50), not `sticky`. His 74ef043 made
|
|
280
280
|
// this change deliberately and says why: with the bar in flow (or sticky at
|
|
281
281
|
// the top) the hero's grey band started BELOW it, leaving a visible seam in
|
|
282
282
|
// the page background right above the bar. Absolute takes the bar out of
|
|
@@ -340,7 +340,7 @@
|
|
|
340
340
|
// consumer's own layout can do that). PageHero and the showcase's
|
|
341
341
|
// `+layout.svelte` model exactly that.
|
|
342
342
|
const OUTER =
|
|
343
|
-
'absolute inset-x-0 top-0 z-
|
|
343
|
+
'absolute inset-x-0 top-0 z-(--z-header) p-0 [--logo-mark:46px] ' +
|
|
344
344
|
'min-[640px]:fixed min-[640px]:px-6 min-[640px]:py-3';
|
|
345
345
|
// Task 43 — the glass card no longer IS the flex row: the row (logo,
|
|
346
346
|
// lockup, burger, nav) moved into its own .site-header__bar-equivalent
|
|
@@ -537,7 +537,7 @@
|
|
|
537
537
|
// Drawer head: small uppercase "Menu" + a 44px close cross. The drawer
|
|
538
538
|
// covers the bar (and the burger with it), so it closes itself.
|
|
539
539
|
const DRAWER_HEAD = 'flex items-center justify-between pb-2 pl-4 min-[640px]:hidden';
|
|
540
|
-
const DRAWER_LABEL = 'text-[0.8rem] font-
|
|
540
|
+
const DRAWER_LABEL = 'font-secondary text-[0.8rem] font-bold tracking-[0.08em] uppercase text-fg-muted1';
|
|
541
541
|
const DRAWER_CLOSE =
|
|
542
542
|
'inline-flex h-11 w-11 flex-none cursor-pointer items-center justify-center ' +
|
|
543
543
|
'rounded-button border-0 bg-transparent p-0 text-fg';
|
|
@@ -585,10 +585,10 @@
|
|
|
585
585
|
'transition-[padding-top] duration-[var(--dur-base)] ease-[var(--ease)]';
|
|
586
586
|
const MEGA_COLS_OPEN = 'pt-4';
|
|
587
587
|
const MEGA_COL = 'flex flex-col gap-0.5';
|
|
588
|
-
// Column heading (shared by the mega-menu and the accordion):
|
|
589
|
-
// small uppercase Avenir
|
|
588
|
+
// Column heading (shared by the mega-menu and the accordion): GT Walsheim
|
|
589
|
+
// Bold, small uppercase — Avenir patří jen podnázvu loga (majitel, 21. 8. 2026).
|
|
590
590
|
const HEADING =
|
|
591
|
-
'px-4 pt-2.5 pb-0.5 text-[0.72rem] font-
|
|
591
|
+
'px-4 pt-2.5 pb-0.5 font-secondary text-[0.72rem] font-bold tracking-[0.08em] uppercase text-fg-muted1';
|
|
592
592
|
|
|
593
593
|
const NAV_ROW = 'hidden ml-auto items-center gap-2 min-[640px]:flex';
|
|
594
594
|
|
|
@@ -684,7 +684,7 @@
|
|
|
684
684
|
<!-- One definition of a submenu column's links, rendered into both the
|
|
685
685
|
mega-menu columns (≥640px) and the drawer accordion (phone). -->
|
|
686
686
|
{#snippet subLinks(group: NavGroup)}
|
|
687
|
-
{#if group.title}<div class={HEADING}
|
|
687
|
+
{#if group.title}<div class={HEADING}>{group.title}</div>{/if}
|
|
688
688
|
{#each group.items as chapter (chapter.href)}
|
|
689
689
|
<Button
|
|
690
690
|
href={chapter.href}
|
|
@@ -726,8 +726,10 @@
|
|
|
726
726
|
xmlns="http://www.w3.org/2000/svg"
|
|
727
727
|
aria-hidden="true"
|
|
728
728
|
>
|
|
729
|
+
<!-- Majitel 21. 8. 2026: jelen v hlavičce je HNĚDÝ (barevná
|
|
730
|
+
varianta znaku), ne bílý — zrcadlí partials/header.html. -->
|
|
729
731
|
<rect width="400" height="400" class="fill-jelinek-black" />
|
|
730
|
-
<path d={DEER_MARK_PATH} class="fill-jelinek-
|
|
732
|
+
<path d={DEER_MARK_PATH} class="fill-jelinek-brown" />
|
|
731
733
|
</svg>
|
|
732
734
|
{/if}
|
|
733
735
|
</a>
|
|
@@ -842,7 +844,7 @@
|
|
|
842
844
|
640px up, so only one of the two navs is ever in the a11y tree. -->
|
|
843
845
|
<nav id={panelId} bind:this={linksEl} class={drawer} aria-label={navLabel}>
|
|
844
846
|
<div class={DRAWER_HEAD}>
|
|
845
|
-
<span class={DRAWER_LABEL}
|
|
847
|
+
<span class={DRAWER_LABEL}>Menu</span>
|
|
846
848
|
<button type="button" class={DRAWER_CLOSE} aria-label={menuLabelClose} onclick={closeMenu}>
|
|
847
849
|
<svg class="h-[18px] w-[18px]" viewBox="0 0 16 16" aria-hidden="true"><path d="M3 3l10 10M13 3L3 13" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" /></svg>
|
|
848
850
|
</button>
|
|
@@ -28,16 +28,11 @@
|
|
|
28
28
|
const inputId = $derived(id ?? uid);
|
|
29
29
|
|
|
30
30
|
// Same reasoning as Checkbox.svelte/Radio.svelte: jelinek.css has NO rule
|
|
31
|
-
// at all for range inputs
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// pages, mixing brown and magenta in one interface exactly as the guide
|
|
37
|
-
// prohibits. Using accent-jelinek-brown instead still reproduces the
|
|
38
|
-
// guide's magenta slider exactly on mattress-division pages, because
|
|
39
|
-
// tokens.css:161-166 re-themes --JELINEK-BROWN to --JELINEK-MAGENTA
|
|
40
|
-
// there — the same division re-theme Checkbox and Radio rely on.
|
|
31
|
+
// at all for range inputs (the archive's magenta sliders were
|
|
32
|
+
// mattress-CONTEXT demos, not a stylesheet rule). Accent adjudicated by
|
|
33
|
+
// the owner (14. 8. 2026): forms are colour-neutral — every emphasis is
|
|
34
|
+
// BLACK (--JELINEK-BLACK, flips to near-white in dark mode), no division
|
|
35
|
+
// re-theme; the only colour a form carries is the error state.
|
|
41
36
|
</script>
|
|
42
37
|
|
|
43
38
|
{#if label}
|
|
@@ -51,5 +46,5 @@
|
|
|
51
46
|
{max}
|
|
52
47
|
{step}
|
|
53
48
|
bind:value
|
|
54
|
-
class={cn('w-full accent-jelinek-
|
|
49
|
+
class={cn('w-full accent-jelinek-black', className)}
|
|
55
50
|
/>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils/cn.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
checked?: boolean;
|
|
7
|
+
label: string;
|
|
8
|
+
id?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
checked = $bindable(false),
|
|
15
|
+
label,
|
|
16
|
+
id,
|
|
17
|
+
disabled = false,
|
|
18
|
+
class: className,
|
|
19
|
+
...rest
|
|
20
|
+
}: Props & Omit<HTMLInputAttributes, 'checked' | 'type'> = $props();
|
|
21
|
+
|
|
22
|
+
const uid = $props.id();
|
|
23
|
+
const inputId = $derived(id ?? uid);
|
|
24
|
+
|
|
25
|
+
// Přepínač zap/vyp — zásobník práce, priorita 1. Uvnitř je nativní
|
|
26
|
+
// checkbox s role="switch" (sr-only), takže klávesnice i čtečky fungují
|
|
27
|
+
// zadarmo; dráha a palec jsou jen kresba nad ním.
|
|
28
|
+
//
|
|
29
|
+
// Geometrie záměrně opakuje přepínač tmavého režimu (ThemeToggle /
|
|
30
|
+
// jelinek.css `.theme-toggle`): dráha 44×24, palec 20 s 2px odsazením —
|
|
31
|
+
// jeden tvar přepínače v celém webu. Barvy: zapnuto = černá
|
|
32
|
+
// (--JELINEK-BLACK, v tmavém režimu se sama překlopí na bílou), vypnuto
|
|
33
|
+
// = šedá --text-muted2. Formuláře jsou podle rozhodnutí majitele
|
|
34
|
+
// (14. 8. 2026) barevně neutrální — žádné přebarvování podle divize,
|
|
35
|
+
// barvu nese jen chyba. Pohyb: tokeny --dur-base/--ease.
|
|
36
|
+
const LABEL = 'flex items-center gap-2.5 font-secondary font-light text-step0 text-fg';
|
|
37
|
+
const DISABLED_LABEL = 'cursor-not-allowed text-fg-muted2';
|
|
38
|
+
|
|
39
|
+
const TRACK =
|
|
40
|
+
'relative inline-flex h-6 w-11 shrink-0 rounded-full ' +
|
|
41
|
+
'transition-colors duration-[var(--dur-base)] ease-[var(--ease)] ' +
|
|
42
|
+
// Fokus z klávesnice kreslí prstenec z fokus tokenů na dráze —
|
|
43
|
+
// vlastní input je sr-only, prstenec na něm by nebyl vidět.
|
|
44
|
+
'peer-focus-visible:outline peer-focus-visible:outline-[length:var(--focus-width)] ' +
|
|
45
|
+
'peer-focus-visible:outline-offset-1 peer-focus-visible:outline-[var(--focus-color)]';
|
|
46
|
+
|
|
47
|
+
const KNOB =
|
|
48
|
+
'absolute top-0.5 left-0.5 size-5 rounded-full border border-border-subtle bg-surface ' +
|
|
49
|
+
'transition-transform duration-[var(--dur-base)] ease-[var(--ease)]';
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<label
|
|
53
|
+
for={inputId}
|
|
54
|
+
class={cn(LABEL, disabled ? DISABLED_LABEL : 'cursor-pointer', className)}
|
|
55
|
+
>
|
|
56
|
+
<input
|
|
57
|
+
{...rest}
|
|
58
|
+
id={inputId}
|
|
59
|
+
type="checkbox"
|
|
60
|
+
role="switch"
|
|
61
|
+
{disabled}
|
|
62
|
+
bind:checked
|
|
63
|
+
class="peer sr-only"
|
|
64
|
+
/>
|
|
65
|
+
<span
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
class={cn(
|
|
68
|
+
TRACK,
|
|
69
|
+
checked ? 'bg-jelinek-black' : 'bg-fg-muted2',
|
|
70
|
+
disabled && 'bg-border-subtle'
|
|
71
|
+
)}
|
|
72
|
+
>
|
|
73
|
+
<span class={cn(KNOB, checked && 'translate-x-5')}></span>
|
|
74
|
+
</span>
|
|
75
|
+
{label}
|
|
76
|
+
</label>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props {
|
|
3
|
+
checked?: boolean;
|
|
4
|
+
label: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
class?: string;
|
|
8
|
+
}
|
|
9
|
+
type $$ComponentProps = Props & Omit<HTMLInputAttributes, 'checked' | 'type'>;
|
|
10
|
+
declare const Switch: import("svelte").Component<$$ComponentProps, {}, "checked">;
|
|
11
|
+
type Switch = ReturnType<typeof Switch>;
|
|
12
|
+
export default Switch;
|
|
@@ -14,6 +14,19 @@
|
|
|
14
14
|
* coloured. See ERROR below.
|
|
15
15
|
*/
|
|
16
16
|
error?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Chybová hláška pod polem. Zapíná chybový stav sama (není třeba
|
|
19
|
+
* posílat i `error`) a naváže se na pole přes `aria-describedby`,
|
|
20
|
+
* takže ji čtečky obrazovky přečtou k poli; `aria-live` ji ohlásí,
|
|
21
|
+
* i když se objeví až po odeslání. Má přednost před `hint`.
|
|
22
|
+
*/
|
|
23
|
+
errorText?: string;
|
|
24
|
+
/** Nápověda pod polem — např. formát hodnoty. Navázaná přes `aria-describedby`. */
|
|
25
|
+
hint?: string;
|
|
26
|
+
/** Povinné pole: hvězdička u popisku + nativní `required` na poli. */
|
|
27
|
+
required?: boolean;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
readonly?: boolean;
|
|
17
30
|
class?: string;
|
|
18
31
|
}
|
|
19
32
|
|
|
@@ -22,6 +35,11 @@
|
|
|
22
35
|
label,
|
|
23
36
|
id,
|
|
24
37
|
error = false,
|
|
38
|
+
errorText,
|
|
39
|
+
hint,
|
|
40
|
+
required = false,
|
|
41
|
+
disabled = false,
|
|
42
|
+
readonly = false,
|
|
25
43
|
class: className,
|
|
26
44
|
...rest
|
|
27
45
|
}: Props & Omit<HTMLInputAttributes, 'value'> = $props();
|
|
@@ -30,6 +48,17 @@
|
|
|
30
48
|
// without the consumer having to invent one.
|
|
31
49
|
const uid = $props.id();
|
|
32
50
|
const inputId = $derived(id ?? uid);
|
|
51
|
+
const hintId = $derived(`${inputId}-hint`);
|
|
52
|
+
const errorId = $derived(`${inputId}-error`);
|
|
53
|
+
const isError = $derived(error || !!errorText);
|
|
54
|
+
// The message under the field is not just visually adjacent — it is wired
|
|
55
|
+
// to the input, so assistive tech reads it as part of the field. A
|
|
56
|
+
// consumer-supplied aria-describedby is kept, appended after ours.
|
|
57
|
+
const describedBy = $derived(
|
|
58
|
+
[errorText ? errorId : null, !errorText && hint ? hintId : null, rest['aria-describedby']]
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.join(' ') || undefined
|
|
61
|
+
);
|
|
33
62
|
|
|
34
63
|
// jelinek.css:966-975 (`.input, .select`), corrected against the draft brief:
|
|
35
64
|
// - border is 1px solid var(--text-muted2) (border-fg-muted2), NOT
|
|
@@ -62,9 +91,13 @@
|
|
|
62
91
|
// mouse click shows the ring too — kept as `focus:`. That is deliberate on
|
|
63
92
|
// the guide's part: base.css's new global ring is `:focus-visible`, and
|
|
64
93
|
// `.input:focus` (specificity 0,2,0) outranks it either way.
|
|
94
|
+
// `leading-normal`: input jinak dědí řádkování 1.6 z body, zatímco select
|
|
95
|
+
// má od prohlížeče `normal` — pole pak byla o ~5 px vyšší než selecty
|
|
96
|
+
// vedle nich (nejnápadnější u type="date"). Explicitní `normal` na obou
|
|
97
|
+
// drží formulářový řádek v jedné výšce.
|
|
65
98
|
const BASE =
|
|
66
99
|
'w-full rounded-button border border-fg-muted2 bg-surface px-[14px] py-[11px] ' +
|
|
67
|
-
'font-secondary text-[0.98rem] text-fg placeholder:text-fg-muted2 ' +
|
|
100
|
+
'font-secondary font-light text-[0.98rem] leading-normal text-fg placeholder:text-fg-muted2 ' +
|
|
68
101
|
'focus:outline-[length:var(--focus-width)] focus:outline-offset-1 ' +
|
|
69
102
|
'focus:outline-[var(--focus-color)] focus:border-[var(--focus-color)]';
|
|
70
103
|
|
|
@@ -77,24 +110,42 @@
|
|
|
77
110
|
// Merged AFTER BASE so it wins on source order within the same
|
|
78
111
|
// tailwind-merge groups, and BEFORE `className` so a consumer override still
|
|
79
112
|
// beats both — the kit's standing cn() contract.
|
|
80
|
-
//
|
|
81
|
-
// NOT reproduced: the guide's `.field-error` message element
|
|
82
|
-
// (jelinek.css:992-995). That is markup plus `aria-describedby` wiring, i.e.
|
|
83
|
-
// a prop-surface addition rather than a rule to copy, and `.field-error` is
|
|
84
|
-
// not a guarded class so the lock makes no claim about it. Noted in the Task
|
|
85
|
-
// 31 report as a candidate follow-up.
|
|
86
113
|
const ERROR =
|
|
87
114
|
'border-sem-error-strong focus:border-sem-error-strong focus:outline-sem-error-strong';
|
|
115
|
+
|
|
116
|
+
// Zakázané pole mluví stejnou řečí jako zakázané tlačítko
|
|
117
|
+
// (Button.svelte:117): šedá výplň, utlumený text, cursor-not-allowed.
|
|
118
|
+
// Jen pro čtení si nechává plný text (obsah platí, jen se nemění) a
|
|
119
|
+
// odlišuje se šedou výplní.
|
|
120
|
+
const DISABLED =
|
|
121
|
+
'cursor-not-allowed border-border-subtle bg-surface-gray text-fg-muted2 ' +
|
|
122
|
+
'placeholder:text-fg-muted2';
|
|
123
|
+
const READONLY = 'bg-surface-gray';
|
|
124
|
+
|
|
125
|
+
// Stejný vzhled, jaký sekce Formulářové prvky používala pro ručně psanou
|
|
126
|
+
// hlášku — teď ho nese komponenta, aby hláška byla vždy i programově
|
|
127
|
+
// navázaná na pole.
|
|
128
|
+
const MSG_ERROR = 'mt-1.5 font-secondary text-step-down1 font-medium text-sem-error-strong';
|
|
129
|
+
const MSG_HINT = 'mt-1.5 font-secondary text-step-down1 font-light text-fg-muted1';
|
|
88
130
|
</script>
|
|
89
131
|
|
|
90
132
|
{#if label}
|
|
91
|
-
<Label for={inputId}>{label}</Label>
|
|
133
|
+
<Label for={inputId} {required}>{label}</Label>
|
|
92
134
|
{/if}
|
|
93
135
|
<input
|
|
94
136
|
{...rest}
|
|
95
137
|
id={inputId}
|
|
96
138
|
type={rest.type ?? 'text'}
|
|
97
|
-
|
|
139
|
+
{required}
|
|
140
|
+
{disabled}
|
|
141
|
+
{readonly}
|
|
142
|
+
aria-invalid={isError ? 'true' : rest['aria-invalid']}
|
|
143
|
+
aria-describedby={describedBy}
|
|
98
144
|
bind:value
|
|
99
|
-
class={cn(BASE,
|
|
145
|
+
class={cn(BASE, isError && ERROR, readonly && !disabled && READONLY, disabled && DISABLED, className)}
|
|
100
146
|
/>
|
|
147
|
+
{#if errorText}
|
|
148
|
+
<p id={errorId} aria-live="polite" class={MSG_ERROR}>{errorText}</p>
|
|
149
|
+
{:else if hint}
|
|
150
|
+
<p id={hintId} class={MSG_HINT}>{hint}</p>
|
|
151
|
+
{/if}
|
|
@@ -10,6 +10,19 @@ interface Props {
|
|
|
10
10
|
* coloured. See ERROR below.
|
|
11
11
|
*/
|
|
12
12
|
error?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Chybová hláška pod polem. Zapíná chybový stav sama (není třeba
|
|
15
|
+
* posílat i `error`) a naváže se na pole přes `aria-describedby`,
|
|
16
|
+
* takže ji čtečky obrazovky přečtou k poli; `aria-live` ji ohlásí,
|
|
17
|
+
* i když se objeví až po odeslání. Má přednost před `hint`.
|
|
18
|
+
*/
|
|
19
|
+
errorText?: string;
|
|
20
|
+
/** Nápověda pod polem — např. formát hodnoty. Navázaná přes `aria-describedby`. */
|
|
21
|
+
hint?: string;
|
|
22
|
+
/** Povinné pole: hvězdička u popisku + nativní `required` na poli. */
|
|
23
|
+
required?: boolean;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
readonly?: boolean;
|
|
13
26
|
class?: string;
|
|
14
27
|
}
|
|
15
28
|
type $$ComponentProps = Props & Omit<HTMLInputAttributes, 'value'>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils/cn.js';
|
|
4
|
+
import Label from './Label.svelte';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
value?: string;
|
|
8
|
+
label?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
/** Počet řádků naprázdno; výšku jde myší táhnout jen svisle (resize-y). */
|
|
11
|
+
rows?: number;
|
|
12
|
+
/** Chybový stav — stejné pravidlo `.input.is-error` jako TextField. */
|
|
13
|
+
error?: boolean;
|
|
14
|
+
/** Chybová hláška pod polem — stejné chování jako u TextFieldu. */
|
|
15
|
+
errorText?: string;
|
|
16
|
+
/** Nápověda pod polem, navázaná přes `aria-describedby`. */
|
|
17
|
+
hint?: string;
|
|
18
|
+
/** Povinné pole: hvězdička u popisku + nativní `required`. */
|
|
19
|
+
required?: boolean;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
readonly?: boolean;
|
|
22
|
+
class?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let {
|
|
26
|
+
value = $bindable(''),
|
|
27
|
+
label,
|
|
28
|
+
id,
|
|
29
|
+
rows = 4,
|
|
30
|
+
error = false,
|
|
31
|
+
errorText,
|
|
32
|
+
hint,
|
|
33
|
+
required = false,
|
|
34
|
+
disabled = false,
|
|
35
|
+
readonly = false,
|
|
36
|
+
class: className,
|
|
37
|
+
...rest
|
|
38
|
+
}: Props & Omit<HTMLTextareaAttributes, 'value'> = $props();
|
|
39
|
+
|
|
40
|
+
const uid = $props.id();
|
|
41
|
+
const inputId = $derived(id ?? uid);
|
|
42
|
+
const hintId = $derived(`${inputId}-hint`);
|
|
43
|
+
const errorId = $derived(`${inputId}-error`);
|
|
44
|
+
const isError = $derived(error || !!errorText);
|
|
45
|
+
const describedBy = $derived(
|
|
46
|
+
[errorText ? errorId : null, !errorText && hint ? hintId : null, rest['aria-describedby']]
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
.join(' ') || undefined
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Víceřádkové pole je TextField s <textarea> uvnitř — záměrně stejné
|
|
52
|
+
// třídy (jelinek.css:966-975 styluje `.input` bez ohledu na element),
|
|
53
|
+
// stejný chybový stav i stejné hlášky. Jediné navíc: resize-y, aby šla
|
|
54
|
+
// výška táhnout, ale pole nikdy nerozbilo layout do šířky.
|
|
55
|
+
const BASE =
|
|
56
|
+
'w-full resize-y rounded-button border border-fg-muted2 bg-surface px-[14px] py-[11px] ' +
|
|
57
|
+
'font-secondary font-light text-[0.98rem] text-fg placeholder:text-fg-muted2 ' +
|
|
58
|
+
'focus:outline-[length:var(--focus-width)] focus:outline-offset-1 ' +
|
|
59
|
+
'focus:outline-[var(--focus-color)] focus:border-[var(--focus-color)]';
|
|
60
|
+
|
|
61
|
+
const ERROR =
|
|
62
|
+
'border-sem-error-strong focus:border-sem-error-strong focus:outline-sem-error-strong';
|
|
63
|
+
|
|
64
|
+
const DISABLED =
|
|
65
|
+
'cursor-not-allowed resize-none border-border-subtle bg-surface-gray text-fg-muted2 ' +
|
|
66
|
+
'placeholder:text-fg-muted2';
|
|
67
|
+
const READONLY = 'bg-surface-gray';
|
|
68
|
+
|
|
69
|
+
const MSG_ERROR = 'mt-1.5 font-secondary text-step-down1 font-medium text-sem-error-strong';
|
|
70
|
+
const MSG_HINT = 'mt-1.5 font-secondary text-step-down1 font-light text-fg-muted1';
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
{#if label}
|
|
74
|
+
<Label for={inputId} {required}>{label}</Label>
|
|
75
|
+
{/if}
|
|
76
|
+
<textarea
|
|
77
|
+
{...rest}
|
|
78
|
+
id={inputId}
|
|
79
|
+
{rows}
|
|
80
|
+
{required}
|
|
81
|
+
{disabled}
|
|
82
|
+
{readonly}
|
|
83
|
+
aria-invalid={isError ? 'true' : rest['aria-invalid']}
|
|
84
|
+
aria-describedby={describedBy}
|
|
85
|
+
bind:value
|
|
86
|
+
class={cn(BASE, isError && ERROR, readonly && !disabled && READONLY, disabled && DISABLED, className)}
|
|
87
|
+
></textarea>
|
|
88
|
+
{#if errorText}
|
|
89
|
+
<p id={errorId} aria-live="polite" class={MSG_ERROR}>{errorText}</p>
|
|
90
|
+
{:else if hint}
|
|
91
|
+
<p id={hintId} class={MSG_HINT}>{hint}</p>
|
|
92
|
+
{/if}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { HTMLTextareaAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props {
|
|
3
|
+
value?: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Počet řádků naprázdno; výšku jde myší táhnout jen svisle (resize-y). */
|
|
7
|
+
rows?: number;
|
|
8
|
+
/** Chybový stav — stejné pravidlo `.input.is-error` jako TextField. */
|
|
9
|
+
error?: boolean;
|
|
10
|
+
/** Chybová hláška pod polem — stejné chování jako u TextFieldu. */
|
|
11
|
+
errorText?: string;
|
|
12
|
+
/** Nápověda pod polem, navázaná přes `aria-describedby`. */
|
|
13
|
+
hint?: string;
|
|
14
|
+
/** Povinné pole: hvězdička u popisku + nativní `required`. */
|
|
15
|
+
required?: boolean;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
readonly?: boolean;
|
|
18
|
+
class?: string;
|
|
19
|
+
}
|
|
20
|
+
type $$ComponentProps = Props & Omit<HTMLTextareaAttributes, 'value'>;
|
|
21
|
+
declare const Textarea: import("svelte").Component<$$ComponentProps, {}, "value">;
|
|
22
|
+
type Textarea = ReturnType<typeof Textarea>;
|
|
23
|
+
export default Textarea;
|