@jelinek/ui 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +492 -0
- package/dist/components/Alert.svelte +59 -0
- package/dist/components/Alert.svelte.d.ts +13 -0
- package/dist/components/Button.svelte +178 -0
- package/dist/components/Button.svelte.d.ts +19 -0
- package/dist/components/Card.svelte +68 -0
- package/dist/components/Card.svelte.d.ts +11 -0
- package/dist/components/ChapterNav.svelte +290 -0
- package/dist/components/ChapterNav.svelte.d.ts +18 -0
- package/dist/components/Checkbox.svelte +52 -0
- package/dist/components/Checkbox.svelte.d.ts +11 -0
- package/dist/components/CodeCopy.svelte +294 -0
- package/dist/components/CodeCopy.svelte.d.ts +51 -0
- package/dist/components/Container.svelte +34 -0
- package/dist/components/Container.svelte.d.ts +10 -0
- package/dist/components/ContentLogo.svelte +151 -0
- package/dist/components/ContentLogo.svelte.d.ts +37 -0
- package/dist/components/Demo.svelte +111 -0
- package/dist/components/Demo.svelte.d.ts +12 -0
- package/dist/components/Eyebrow.svelte +70 -0
- package/dist/components/Eyebrow.svelte.d.ts +12 -0
- package/dist/components/Glass.svelte +75 -0
- package/dist/components/Glass.svelte.d.ts +10 -0
- package/dist/components/Grid.svelte +87 -0
- package/dist/components/Grid.svelte.d.ts +11 -0
- package/dist/components/Label.svelte +37 -0
- package/dist/components/Label.svelte.d.ts +11 -0
- package/dist/components/PageHero.svelte +312 -0
- package/dist/components/PageHero.svelte.d.ts +55 -0
- package/dist/components/Pager.svelte +98 -0
- package/dist/components/Pager.svelte.d.ts +13 -0
- package/dist/components/Panel.svelte +89 -0
- package/dist/components/Panel.svelte.d.ts +12 -0
- package/dist/components/ProductCard.svelte +81 -0
- package/dist/components/ProductCard.svelte.d.ts +18 -0
- package/dist/components/Radio.svelte +54 -0
- package/dist/components/Radio.svelte.d.ts +11 -0
- package/dist/components/RadioGroup.svelte +42 -0
- package/dist/components/RadioGroup.svelte.d.ts +12 -0
- package/dist/components/Select.svelte +65 -0
- package/dist/components/Select.svelte.d.ts +20 -0
- package/dist/components/SiteFooter.svelte +121 -0
- package/dist/components/SiteFooter.svelte.d.ts +13 -0
- package/dist/components/SiteHeader.svelte +613 -0
- package/dist/components/SiteHeader.svelte.d.ts +80 -0
- package/dist/components/Slider.svelte +55 -0
- package/dist/components/Slider.svelte.d.ts +14 -0
- package/dist/components/Swatch.svelte +174 -0
- package/dist/components/Swatch.svelte.d.ts +58 -0
- package/dist/components/Tab.svelte +88 -0
- package/dist/components/Tab.svelte.d.ts +11 -0
- package/dist/components/Tabs.svelte +306 -0
- package/dist/components/Tabs.svelte.d.ts +20 -0
- package/dist/components/Tag.svelte +71 -0
- package/dist/components/Tag.svelte.d.ts +16 -0
- package/dist/components/TextField.svelte +100 -0
- package/dist/components/TextField.svelte.d.ts +18 -0
- package/dist/components/ThemeToggle.svelte +122 -0
- package/dist/components/ThemeToggle.svelte.d.ts +11 -0
- package/dist/components/Tile.svelte +115 -0
- package/dist/components/Tile.svelte.d.ts +14 -0
- package/dist/components/TileScroller.svelte +199 -0
- package/dist/components/TileScroller.svelte.d.ts +25 -0
- package/dist/components/container-context.d.ts +1 -0
- package/dist/components/container-context.js +6 -0
- package/dist/components/deer-mark-path.d.ts +16 -0
- package/dist/components/deer-mark-path.js +16 -0
- package/dist/components/nav.d.ts +25 -0
- package/dist/components/nav.js +17 -0
- package/dist/components/radio-context.d.ts +6 -0
- package/dist/components/radio-context.js +1 -0
- package/dist/components/tabs-context.d.ts +5 -0
- package/dist/components/tabs-context.js +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +37 -0
- package/dist/theme/base.css +439 -0
- package/dist/theme/extras.css +2 -0
- package/dist/theme/fonts.css +107 -0
- package/dist/theme/tokens.css +208 -0
- package/dist/utils/chapter-nav-dock.svelte.d.ts +23 -0
- package/dist/utils/chapter-nav-dock.svelte.js +65 -0
- package/dist/utils/cn.d.ts +2 -0
- package/dist/utils/cn.js +27 -0
- package/package.json +79 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
4
|
+
import { cn } from '../utils/cn.js';
|
|
5
|
+
|
|
6
|
+
type Variant = 'primary' | 'secondary' | 'ghost';
|
|
7
|
+
type Color = 'brown' | 'magenta' | 'black';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
variant?: Variant;
|
|
11
|
+
color?: Color;
|
|
12
|
+
href?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
active?: boolean;
|
|
15
|
+
fullWidth?: boolean;
|
|
16
|
+
class?: string;
|
|
17
|
+
onclick?: (event: MouseEvent) => void;
|
|
18
|
+
children: Snippet;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let {
|
|
22
|
+
variant = 'primary',
|
|
23
|
+
color = 'brown',
|
|
24
|
+
href,
|
|
25
|
+
disabled = false,
|
|
26
|
+
active = false,
|
|
27
|
+
fullWidth = false,
|
|
28
|
+
class: className,
|
|
29
|
+
onclick,
|
|
30
|
+
children,
|
|
31
|
+
...rest
|
|
32
|
+
}: Props &
|
|
33
|
+
Omit<
|
|
34
|
+
HTMLButtonAttributes & HTMLAnchorAttributes,
|
|
35
|
+
'href' | 'disabled' | 'onclick'
|
|
36
|
+
> = $props();
|
|
37
|
+
|
|
38
|
+
// jelinek.css:734-741 fixes the button's type size, line height and padding
|
|
39
|
+
// to exact pixel/rem values, not the modular type scale or 8pt spacing grid.
|
|
40
|
+
// Fidelity to the guide beats token purity for this component — do not
|
|
41
|
+
// "tidy" text-[0.98rem]/leading-[1.2]/px-[26px]/py-[11px] back to scale
|
|
42
|
+
// tokens.
|
|
43
|
+
//
|
|
44
|
+
// Task 31 — the transition is now the guide's OWN property list and its own
|
|
45
|
+
// duration/easing tokens (jelinek.css:739: `transition: background
|
|
46
|
+
// var(--dur-base) var(--ease), color …, border-color …, box-shadow …`),
|
|
47
|
+
// replacing a bare `transition-all`. Two separate corrections in one:
|
|
48
|
+
// - PROPERTY LIST. `transition-all` animates every animatable property,
|
|
49
|
+
// including ones the guide deliberately leaves alone. The guide names
|
|
50
|
+
// four; so does this now.
|
|
51
|
+
// - EASING, and this one was a real (if invisible) mismatch even before
|
|
52
|
+
// the merge. Tailwind's default timing function is
|
|
53
|
+
// `cubic-bezier(0.4, 0, 0.2, 1)` (verified in
|
|
54
|
+
// node_modules/tailwindcss/theme.css's --default-transition-timing-
|
|
55
|
+
// function), while the guide's --ease is the CSS keyword `ease` =
|
|
56
|
+
// `cubic-bezier(0.25, 0.1, 0.25, 1)`. A bare `transition-*` utility
|
|
57
|
+
// therefore never matched the guide's curve. Naming --ease explicitly
|
|
58
|
+
// fixes that and makes the kit track the token.
|
|
59
|
+
// Duration happened to agree (--dur-base 0.15s = Tailwind's 150ms
|
|
60
|
+
// default), but it is spelled out for the same tracking reason: the
|
|
61
|
+
// agreement is a property of today's value, not of the system.
|
|
62
|
+
// NOT done as a global `--default-transition-*` override in extras.css,
|
|
63
|
+
// which would have covered every component at once: extras.css is OPTIONAL
|
|
64
|
+
// (package.json exports it separately), so a consumer who skips it would get
|
|
65
|
+
// silently un-branded transitions everywhere. Components must not depend on
|
|
66
|
+
// optional CSS for correctness — the same call base.css's own header makes.
|
|
67
|
+
const BASE =
|
|
68
|
+
'inline-flex items-center justify-center gap-2 border-2 border-transparent rounded-button ' +
|
|
69
|
+
'px-[26px] py-[11px] font-primary text-[0.98rem] leading-[1.2] font-bold no-underline ' +
|
|
70
|
+
'cursor-pointer shadow-resting ' +
|
|
71
|
+
'transition-[background,color,border-color,box-shadow] ' +
|
|
72
|
+
'duration-[var(--dur-base)] ease-[var(--ease)]';
|
|
73
|
+
|
|
74
|
+
const PRIMARY: Record<Color, string> = {
|
|
75
|
+
brown:
|
|
76
|
+
'bg-jelinek-brown text-fg-inverse hover:bg-jelinek-brown-highlight hover:shadow-interactive',
|
|
77
|
+
magenta:
|
|
78
|
+
'bg-jelinek-magenta text-fg-inverse hover:bg-jelinek-magenta-highlight hover:shadow-interactive',
|
|
79
|
+
black:
|
|
80
|
+
'bg-jelinek-black text-on-jelinek-black hover:bg-jelinek-black-highlight hover:shadow-interactive'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const SECONDARY: Record<Color, string> = {
|
|
84
|
+
brown:
|
|
85
|
+
'bg-transparent text-jelinek-brown border-jelinek-brown ' +
|
|
86
|
+
'hover:bg-jelinek-brown-highlight hover:text-fg-inverse hover:border-transparent hover:shadow-interactive',
|
|
87
|
+
magenta:
|
|
88
|
+
'bg-transparent text-jelinek-magenta border-jelinek-magenta ' +
|
|
89
|
+
'hover:bg-jelinek-magenta-highlight hover:text-fg-inverse hover:border-transparent hover:shadow-interactive',
|
|
90
|
+
black:
|
|
91
|
+
'bg-transparent text-jelinek-black border-jelinek-black ' +
|
|
92
|
+
'hover:bg-jelinek-black-highlight hover:text-on-jelinek-black hover:border-transparent hover:shadow-interactive'
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// jelinek.css:765 (and its :hover at :769) keeps ghost buttons flat — no
|
|
96
|
+
// resting or hover shadow — unlike primary/secondary which get shadow-resting
|
|
97
|
+
// from BASE and shadow-interactive on hover/active.
|
|
98
|
+
const GHOST: Record<Color, string> = {
|
|
99
|
+
brown:
|
|
100
|
+
'bg-transparent border-transparent shadow-none text-jelinek-brown hover:text-jelinek-brown-highlight',
|
|
101
|
+
magenta:
|
|
102
|
+
'bg-transparent border-transparent shadow-none text-jelinek-magenta hover:text-jelinek-magenta-highlight',
|
|
103
|
+
black:
|
|
104
|
+
'bg-transparent border-transparent shadow-none text-jelinek-black hover:text-jelinek-black-highlight'
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Active always means a full fill in the accent colour — the highlight shade
|
|
108
|
+
// is reserved for hover. (ui/komponenty.html, "Aktivní / vybraný stav")
|
|
109
|
+
// jelinek.css:776-779 also raises active buttons to shadow-interactive.
|
|
110
|
+
const ACTIVE: Record<Color, string> = {
|
|
111
|
+
brown: 'bg-jelinek-brown text-fg-inverse border-transparent shadow-interactive',
|
|
112
|
+
magenta: 'bg-jelinek-magenta text-fg-inverse border-transparent shadow-interactive',
|
|
113
|
+
black: 'bg-jelinek-black text-on-jelinek-black border-transparent shadow-interactive'
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const DISABLED =
|
|
117
|
+
'bg-surface-gray text-fg-muted2 border-transparent shadow-none cursor-not-allowed ' +
|
|
118
|
+
'pointer-events-none hover:bg-surface-gray hover:text-fg-muted2 hover:shadow-none';
|
|
119
|
+
|
|
120
|
+
const byVariant = $derived(
|
|
121
|
+
variant === 'primary' ? PRIMARY[color] : variant === 'secondary' ? SECONDARY[color] : GHOST[color]
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
const classes = $derived(
|
|
125
|
+
cn(
|
|
126
|
+
BASE,
|
|
127
|
+
byVariant,
|
|
128
|
+
active && ACTIVE[color],
|
|
129
|
+
disabled && DISABLED,
|
|
130
|
+
fullWidth && 'w-full',
|
|
131
|
+
className
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// A disabled link keeps its genuine href and native `link` role — a
|
|
136
|
+
// screen-reader user must still land on something announced as a link,
|
|
137
|
+
// not on an inert generic element. Activation is blocked at the handler
|
|
138
|
+
// level instead: mouse/AT clicks are intercepted here, and keyboard
|
|
139
|
+
// activation (Enter on a focused <a href>) is intercepted separately,
|
|
140
|
+
// rather than relying on `pointer-events-none` alone (it only stops
|
|
141
|
+
// pointer input, not keyboard or assistive-technology activation).
|
|
142
|
+
function handleClick(event: MouseEvent) {
|
|
143
|
+
if (disabled) {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
onclick?.(event);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Only ever wired to the <a> branch below, so the event is typed to match
|
|
151
|
+
// exactly the overload `rest.onkeydown` resolves to there — a plain
|
|
152
|
+
// `KeyboardEvent` doesn't satisfy either half of HTMLButtonAttributes &
|
|
153
|
+
// HTMLAnchorAttributes's overloaded onkeydown at once.
|
|
154
|
+
function handleKeydown(event: KeyboardEvent & { currentTarget: EventTarget & HTMLAnchorElement }) {
|
|
155
|
+
if (disabled && event.key === 'Enter') {
|
|
156
|
+
event.preventDefault();
|
|
157
|
+
}
|
|
158
|
+
rest.onkeydown?.(event);
|
|
159
|
+
}
|
|
160
|
+
</script>
|
|
161
|
+
|
|
162
|
+
{#if href}
|
|
163
|
+
<a
|
|
164
|
+
{...rest}
|
|
165
|
+
{href}
|
|
166
|
+
aria-disabled={disabled ? 'true' : rest['aria-disabled']}
|
|
167
|
+
tabindex={disabled ? -1 : rest.tabindex}
|
|
168
|
+
onclick={handleClick}
|
|
169
|
+
onkeydown={handleKeydown}
|
|
170
|
+
class={classes}
|
|
171
|
+
>
|
|
172
|
+
{@render children()}
|
|
173
|
+
</a>
|
|
174
|
+
{:else}
|
|
175
|
+
<button {...rest} type={rest.type ?? 'button'} {disabled} onclick={handleClick} class={classes}>
|
|
176
|
+
{@render children()}
|
|
177
|
+
</button>
|
|
178
|
+
{/if}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
type Variant = 'primary' | 'secondary' | 'ghost';
|
|
4
|
+
type Color = 'brown' | 'magenta' | 'black';
|
|
5
|
+
interface Props {
|
|
6
|
+
variant?: Variant;
|
|
7
|
+
color?: Color;
|
|
8
|
+
href?: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
active?: boolean;
|
|
11
|
+
fullWidth?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
onclick?: (event: MouseEvent) => void;
|
|
14
|
+
children: Snippet;
|
|
15
|
+
}
|
|
16
|
+
type $$ComponentProps = Props & Omit<HTMLButtonAttributes & HTMLAnchorAttributes, 'href' | 'disabled' | 'onclick'>;
|
|
17
|
+
declare const Button: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
18
|
+
type Button = ReturnType<typeof Button>;
|
|
19
|
+
export default Button;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { cn } from '../utils/cn.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
padded?: boolean;
|
|
8
|
+
class?: string;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
padded = true,
|
|
14
|
+
class: className,
|
|
15
|
+
children,
|
|
16
|
+
...rest
|
|
17
|
+
}: Props & HTMLAttributes<HTMLDivElement> = $props();
|
|
18
|
+
|
|
19
|
+
// jelinek.css:474-481 draws the card border as a two-layer gradient
|
|
20
|
+
// (padding-box fill + border-box gradient from --box-accent to
|
|
21
|
+
// --border-subtle) so a divisional --box-accent can tint just the edge.
|
|
22
|
+
// This component has no colour-variant prop, and --box-accent defaults to
|
|
23
|
+
// --border-subtle, so both gradient stops are identical and the technique
|
|
24
|
+
// collapses to a flat 1px border-border-subtle — reproduced directly
|
|
25
|
+
// rather than replicating the (here invisible) gradient machinery.
|
|
26
|
+
// jelinek.css:322-325 puts .card on the secondary (Avenir) family at
|
|
27
|
+
// weight 300 (Light) as its body text, same as .alert/.panel/etc.
|
|
28
|
+
// jelinek.css:483's `padding: 24px` matches p-6 exactly — p-6 resolves to
|
|
29
|
+
// a fixed 24px at every viewport width because theme.css pins Tailwind's
|
|
30
|
+
// `--spacing` to a literal 4px (gen-tokens.js), not its rem-relative
|
|
31
|
+
// default; see that file's comment for why a rem-based p-6 would have
|
|
32
|
+
// drifted below the guide's 24px once base.css's html got a responsive
|
|
33
|
+
// font-size (Task 20).
|
|
34
|
+
// Deliberately NOT reproduced: the same line's `margin-bottom: 1.5rem`.
|
|
35
|
+
// Same reasoning as Alert's dropped margin: a reusable Card shouldn't
|
|
36
|
+
// impose spacing on whatever a consumer stacks it in, and the guide's own
|
|
37
|
+
// CSS zeroes this exact margin inside `.grid` (line 485) — treating it as
|
|
38
|
+
// a page-layout convenience, not a property of "card". Flagging for
|
|
39
|
+
// adjudication per the task brief.
|
|
40
|
+
// jelinek.css:652 (`.card h3, .tile h3`): headings inside components are
|
|
41
|
+
// deliberately NOT the content hierarchy — they hold a smaller,
|
|
42
|
+
// componentised --step1 size at line-height 1.3, not the page's large h3
|
|
43
|
+
// modular-scale step. (Tile's own h3 rule also adds a 6px margin-bottom
|
|
44
|
+
// via a Tile-only selector, jelinek.css:648 — that one is not shared with
|
|
45
|
+
// .card, so it is not reproduced here.) jelinek.css:327-331 (the same
|
|
46
|
+
// shared strong/b block that gives the card itself font-secondary/
|
|
47
|
+
// font-light) raises nested strong/b back to font-weight 500. Both
|
|
48
|
+
// reproduced as compound arbitrary variants on the root element, matching
|
|
49
|
+
// the pattern Tile.svelte uses for its own equivalent rules, since Card's
|
|
50
|
+
// children are an opaque Snippet this component doesn't otherwise touch.
|
|
51
|
+
// TASK 34 — the dark-mode inset top highlight, jelinek.css:251-257:
|
|
52
|
+
// `:root[data-theme="dark"] .panel, .card, .demo, .swatch, .logo-tool {
|
|
53
|
+
// box-shadow: inset 0 2px 1px -1px var(--box-primary) }`. `.card` is in that
|
|
54
|
+
// selector list and this was missing, so the guide's card computed
|
|
55
|
+
// `inset 0 2px 1px -1px rgb(128,128,128)` in dark where the kit's computed
|
|
56
|
+
// `none`. See Panel.svelte for why it was dropped and why that was wrong.
|
|
57
|
+
// This component has no colour-variant prop, so jelinek.css:705-706's
|
|
58
|
+
// `.card--brown`/`.card--magenta` --box-primary overrides have no
|
|
59
|
+
// counterpart to attach to; the highlight is always the :root 50% grey here.
|
|
60
|
+
const BASE =
|
|
61
|
+
'rounded-card border border-border-subtle bg-surface font-secondary font-light ' +
|
|
62
|
+
'[&_h3]:text-step1 [&_h3]:leading-[1.3] [&_strong]:font-medium [&_b]:font-medium ' +
|
|
63
|
+
'dark:shadow-[inset_0_2px_1px_-1px_var(--box-primary)]';
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<div {...rest} class={cn(BASE, padded && 'p-6', className)}>
|
|
67
|
+
{@render children()}
|
|
68
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props {
|
|
4
|
+
padded?: boolean;
|
|
5
|
+
class?: string;
|
|
6
|
+
children: Snippet;
|
|
7
|
+
}
|
|
8
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
|
|
9
|
+
declare const Card: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type Card = ReturnType<typeof Card>;
|
|
11
|
+
export default Card;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils/cn.js';
|
|
4
|
+
import { isActive, type NavItem } from './nav.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
items: NavItem[];
|
|
8
|
+
active?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Accessible name for the `<nav>` landmark. The bar is a horizontal
|
|
11
|
+
* scroll region with `tabindex="0"` (see INNER below), so it needs a
|
|
12
|
+
* name for a screen-reader user to know what they have landed in.
|
|
13
|
+
* Czech by default, like every other user-facing string this kit ships.
|
|
14
|
+
*/
|
|
15
|
+
label?: string;
|
|
16
|
+
class?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
items,
|
|
21
|
+
active,
|
|
22
|
+
label = 'Kapitoly',
|
|
23
|
+
class: className,
|
|
24
|
+
...rest
|
|
25
|
+
}: Props & HTMLAttributes<HTMLElement> = $props();
|
|
26
|
+
|
|
27
|
+
// jelinek.css:559-638 (.chapter-nav / .chapter-nav__inner) — the guide's
|
|
28
|
+
// floating pill bar, tucked under the (also floating) SiteHeader. Per
|
|
29
|
+
// direction, this is reproduced as the component's own default rather
|
|
30
|
+
// than left to a consuming app.
|
|
31
|
+
//
|
|
32
|
+
// Task 26 — mobile-first, and the `min-[620.02px]:` epsilon is GONE.
|
|
33
|
+
// The guide used to express the phone case as
|
|
34
|
+
// `@media (max-width: 620px) { .site-header, .chapter-nav { position: static } }`
|
|
35
|
+
// and the epsilon existed purely to convert that INCLUSIVE max-width
|
|
36
|
+
// boundary into a mobile-first `min-width` without a gap (Tailwind's
|
|
37
|
+
// `max-[620px]:` compiles to the exclusive `width < 620px`). That block is
|
|
38
|
+
// gone: the guide declares the phone layout unconditionally and enhances it
|
|
39
|
+
// in `@media (min-width: 640px)`, so `min-[640px]:` is an EXACT translation.
|
|
40
|
+
// Task 30 moved the boundary from 621 to 640: the guide converged on the
|
|
41
|
+
// designer's 640/1024/1440 ladder, and 621 was an artifact of the
|
|
42
|
+
// `--ratio` tiers that no longer exist. Grid no longer needs an epsilon
|
|
43
|
+
// either — its `.grid--*` rules are mobile-first `min-width` now too.
|
|
44
|
+
//
|
|
45
|
+
// TASK 33 — ON A PHONE THIS BAR IS NOT RENDERED AT ALL. jelinek.css:521-525
|
|
46
|
+
// now reads `.chapter-nav { display: none; position: static; padding: 0
|
|
47
|
+
// var(--space-3) }`, and `@media (min-width: 640px)` turns it back on with
|
|
48
|
+
// `display: block; position: absolute`. That is the designer's decision
|
|
49
|
+
// (6750ee8): below 640px the chapters live inside the header's burger panel
|
|
50
|
+
// instead, which is why SiteHeader now takes a `chapterItems` prop — a phone
|
|
51
|
+
// reader has to be able to reach them somewhere, and a second glass strip
|
|
52
|
+
// under a full-width glass header was the arrangement he replaced.
|
|
53
|
+
//
|
|
54
|
+
// Everything the previous phone base carried is gone with it: the `-mt-1.5`
|
|
55
|
+
// that cancelled the old `py-1.5` header gutter (the header has no gutter on
|
|
56
|
+
// a phone any more, `padding: 0`), the `pb-2`, and the narrower `px-3`. The
|
|
57
|
+
// padding is now a flat 24px (`px-6` = --space-3) at every width, so it does
|
|
58
|
+
// not need a variant either.
|
|
59
|
+
//
|
|
60
|
+
// `position: static` in the base is the guide's own wording and is harmless
|
|
61
|
+
// while `display: none` holds; from 640px up the bar is `absolute`, which is
|
|
62
|
+
// what makes it the containing block for the scroll-affordance fades below.
|
|
63
|
+
const OUTER =
|
|
64
|
+
'hidden z-[49] px-6 ' +
|
|
65
|
+
'min-[640px]:block min-[640px]:absolute min-[640px]:inset-x-0 ' +
|
|
66
|
+
'min-[640px]:top-[calc(var(--header-h)-11px)]';
|
|
67
|
+
|
|
68
|
+
// The inner bar itself (jelinek.css:593-604): flex/gap-1.5 (6px), 16px/8px
|
|
69
|
+
// padding (py-2 px-4), max-w-page + mx-auto centering, the glass, and
|
|
70
|
+
// shadow-resting. The dark variant's box-shadow is the SAME `shadow-resting`
|
|
71
|
+
// as light — no extra inset highlight here (that's SiteHeader-only, see
|
|
72
|
+
// there).
|
|
73
|
+
//
|
|
74
|
+
// Task 31, part 1 — THE GLASS IS THE SHARED TOKEN SET NOW. jelinek.css:597-600
|
|
75
|
+
// reads `background: var(--glass-fill); backdrop-filter: var(--glass-filter);
|
|
76
|
+
// border: 1px solid var(--glass-border)`, identical to .site-header__inner
|
|
77
|
+
// and .glassmorphism. Fill was `rgba(255,255,255,.6)` light /
|
|
78
|
+
// `rgba(13,13,13,.6)` dark (both hand-written here) and is now one token that
|
|
79
|
+
// flips itself; filter was `blur(20px) brightness(150%)`/`brightness(40%)`
|
|
80
|
+
// and is now `blur(25px) saturate(140%)` in both themes; border was
|
|
81
|
+
// --border-subtle and is now --glass-border. Both `dark:` utilities are gone
|
|
82
|
+
// with the guide's own dark rules — jelinek.css:262-264 now only sets the
|
|
83
|
+
// FILL token in dark, and the filter override was deleted outright. See
|
|
84
|
+
// Glass.svelte for why the filter is an arbitrary property pair.
|
|
85
|
+
//
|
|
86
|
+
// Task 31, part 2 — the guide gave this bar the SAME scroller treatment as
|
|
87
|
+
// `.tabs`, in a rule that names both (jelinek.css:834-846):
|
|
88
|
+
// `flex-wrap: nowrap; overflow-x: auto; scroll-behavior: smooth;
|
|
89
|
+
// scrollbar-width: none; -ms-overflow-style: none` plus
|
|
90
|
+
// `::-webkit-scrollbar { display: none }`. Two of those are real changes
|
|
91
|
+
// here: `scrollbar-width` was `thin` and is now `none`, and
|
|
92
|
+
// `scroll-behavior: smooth` is new (it makes the mount-time "scroll the
|
|
93
|
+
// active chapter into view" below animate instead of jumping). `flex-wrap:
|
|
94
|
+
// nowrap` is now explicit in the guide where it was previously only the
|
|
95
|
+
// flex default — spelled out here for the same reason.
|
|
96
|
+
//
|
|
97
|
+
// HIDING THE SCROLLBAR COSTS THIS COMPONENT NOTHING, which is why it is
|
|
98
|
+
// reproduced as-is: unlike Tabs (see Tabs.svelte's own note on the same
|
|
99
|
+
// change), this bar already carries the guide's replacement affordance —
|
|
100
|
+
// the gradient edge fades below, driven by real scroll position — plus
|
|
101
|
+
// `tabindex="0"` for keyboard scrolling. The guide reached the same
|
|
102
|
+
// conclusion for the same element: assets/js/jelinek.js:467-474 deliberately
|
|
103
|
+
// EXCLUDES `.chapter-nav__inner` from the arrow-button wrapper because it
|
|
104
|
+
// already has the fades, and says so ("Dvě různá vodítka na jednom prvku …
|
|
105
|
+
// nenavrhl nikdo" — nobody designed two different affordances on one
|
|
106
|
+
// element). The `.chapter-nav .tabs-scroll*` rules at jelinek.css:853-855
|
|
107
|
+
// are dead code kept only so that decision is one step to reverse; there is
|
|
108
|
+
// nothing there for this component to reproduce.
|
|
109
|
+
//
|
|
110
|
+
// Fix round 1: NO `flex-wrap: wrap` — wrapping would be self-contradictory
|
|
111
|
+
// (a wrapped row never overflows, so the horizontal scroll this bar is built
|
|
112
|
+
// around never engages) and would break `--subnav-h: 58px`, a fixed
|
|
113
|
+
// single-row-height token other layout maths depends on.
|
|
114
|
+
//
|
|
115
|
+
// Task 26 (audit M4) — this stayed a scroller rather than becoming a
|
|
116
|
+
// wrapping row, and that was measured rather than assumed: the guide's nine
|
|
117
|
+
// brand chapters have 860px of intrinsic pill width, which at a 320px
|
|
118
|
+
// viewport wraps to four rows, ~164px — it would hand back most of what the
|
|
119
|
+
// header redesign just saved. So the scroller keeps its single row and
|
|
120
|
+
// gains the two things it was missing:
|
|
121
|
+
// - `tabindex="0"` + a focus ring, so the region is keyboard-operable
|
|
122
|
+
// (WCAG 2.1.1). Note the links inside were already reachable — focusing
|
|
123
|
+
// an off-screen link scrolls it into view — so this closes the
|
|
124
|
+
// "scroll the region without moving focus link-by-link" case, not a
|
|
125
|
+
// total lockout.
|
|
126
|
+
// - an edge fade on whichever side still has content, driven by the real
|
|
127
|
+
// scroll position (see `atStart`/`atEnd` below), so it never appears
|
|
128
|
+
// where there is nothing to scroll.
|
|
129
|
+
// `rounded-b-card` + `border-t-0`, NOT `rounded-card`/`rounded-pill`: the
|
|
130
|
+
// guide rounds only the bottom two corners and zeroes the top border,
|
|
131
|
+
// UNCONDITIONALLY — this is the static half of the joined-header seam,
|
|
132
|
+
// reproduced as-is regardless of scroll position or of whether a SiteHeader
|
|
133
|
+
// is actually rendered above this component. The OTHER, scroll-linked half
|
|
134
|
+
// (the header's own top-only radius, dropped bottom border and shadow)
|
|
135
|
+
// lives in SiteHeader's `hasChapterNav` prop (Task 15;
|
|
136
|
+
// chapter-nav-dock.svelte.ts). That module is unchanged by Task 26 — its
|
|
137
|
+
// 50px threshold and its meaning are untouched — but the state it exposes
|
|
138
|
+
// now matters on phones too, where the header did not previously stay on
|
|
139
|
+
// screen at all.
|
|
140
|
+
// Task 32 — THE FOCUS RING IS ON THE FOCUS TOKENS, not --JELINEK-BROWN.
|
|
141
|
+
// jelinek.css:637-640 now reads `outline: var(--focus-width) solid
|
|
142
|
+
// var(--focus-color); outline-offset: calc(-1 * var(--focus-offset))`. The
|
|
143
|
+
// colour is the point: --focus-color is --JELINEK-BROWN in light mode but
|
|
144
|
+
// --JELINEK-BROWN-HIGHLIGHT in dark (tokens.css:47/100), so the previous
|
|
145
|
+
// hard-coded `outline-jelinek-brown` here rendered rgb(147,101,57) against
|
|
146
|
+
// the rgb(201,157,115) every other focusable element in the kit shows in
|
|
147
|
+
// dark mode — measurably darker, on the one control whose ring sits on
|
|
148
|
+
// glass. See TextField.svelte for the same reasoning and the same
|
|
149
|
+
// `[length:…]` hint (Tailwind needs it to read a var() as a width rather
|
|
150
|
+
// than a colour).
|
|
151
|
+
//
|
|
152
|
+
// The OFFSET stays negative — the ring is drawn inside the bar, because an
|
|
153
|
+
// outward ring would collide with the SiteHeader this bar is docked to —
|
|
154
|
+
// but it is now derived from --focus-offset rather than a literal -2px, so
|
|
155
|
+
// the three focus tokens move together.
|
|
156
|
+
const INNER =
|
|
157
|
+
'mx-auto flex max-w-page flex-nowrap items-center gap-1.5 rounded-b-card ' +
|
|
158
|
+
'border border-t-0 border-[var(--glass-border)] bg-[var(--glass-fill)] px-4 py-2 shadow-resting ' +
|
|
159
|
+
'[-webkit-backdrop-filter:var(--glass-filter)] [backdrop-filter:var(--glass-filter)] ' +
|
|
160
|
+
'overflow-x-auto [scroll-behavior:smooth] [scrollbar-width:none] ' +
|
|
161
|
+
'[-ms-overflow-style:none] [&::-webkit-scrollbar]:hidden ' +
|
|
162
|
+
'focus-visible:outline-[length:var(--focus-width)] ' +
|
|
163
|
+
'focus-visible:outline-offset-[calc(-1*var(--focus-offset))] ' +
|
|
164
|
+
'focus-visible:outline-[var(--focus-color)]';
|
|
165
|
+
|
|
166
|
+
// jelinek.css:622-629. Resting colour is text-muted1 with hover adding
|
|
167
|
+
// BOTH a text colour change and a background. The active pill is
|
|
168
|
+
// `color: var(--text-black); border-color: var(--JELINEK-BROWN);
|
|
169
|
+
// background: var(--bg-brown)` — a light brown-bordered pill with full-
|
|
170
|
+
// strength text, NOT a solid black fill. `border: 1px solid transparent`
|
|
171
|
+
// is kept on rest/hover so the active item's border doesn't shift layout
|
|
172
|
+
// when it appears — tailwind-merge drops it in favour of the active
|
|
173
|
+
// variant's `border-jelinek-brown` since both target the same
|
|
174
|
+
// border-color group.
|
|
175
|
+
const REST = 'text-fg-muted1 hover:bg-surface-gray hover:text-fg';
|
|
176
|
+
const ACTIVE = 'border-jelinek-brown bg-surface-brown text-fg';
|
|
177
|
+
|
|
178
|
+
// Scroll affordance. The fades are real elements rather than `before:`/
|
|
179
|
+
// `after:` utilities because a component owns its markup — the same call
|
|
180
|
+
// PageHero makes about the guide's `<hr class="rule">` marker. They are
|
|
181
|
+
// positioned against OUTER (`relative` on a phone, `absolute` from 640px
|
|
182
|
+
// up — both are positioned, so both establish the containing block), and
|
|
183
|
+
// inset 1px so they never blur the bar's own border.
|
|
184
|
+
//
|
|
185
|
+
// State is read from the scroller, not guessed from a breakpoint: a nav
|
|
186
|
+
// with three chapters does not overflow at any width and correctly shows
|
|
187
|
+
// no fade at all. A ResizeObserver covers the viewport changing under a
|
|
188
|
+
// fixed set of items; the scroll listener covers the reader moving.
|
|
189
|
+
let scroller = $state<HTMLElement | null>(null);
|
|
190
|
+
let scrollLeft = $state(0);
|
|
191
|
+
let scrollMax = $state(0);
|
|
192
|
+
const showStart = $derived(scrollMax > 1 && scrollLeft > 1);
|
|
193
|
+
const showEnd = $derived(scrollMax > 1 && scrollLeft < scrollMax - 1);
|
|
194
|
+
|
|
195
|
+
$effect(() => {
|
|
196
|
+
const el = scroller;
|
|
197
|
+
if (!el) return;
|
|
198
|
+
function measure() {
|
|
199
|
+
if (!el) return;
|
|
200
|
+
scrollLeft = el.scrollLeft;
|
|
201
|
+
scrollMax = el.scrollWidth - el.clientWidth;
|
|
202
|
+
}
|
|
203
|
+
// Bring the active chapter into view on mount, mirroring
|
|
204
|
+
// assets/js/jelinek.js's initChapterNavScroll(): a nav whose own current
|
|
205
|
+
// entry is off-screen is worse than no scroller. Only when it really is
|
|
206
|
+
// clipped, though — centring unconditionally would also move the bar on
|
|
207
|
+
// the (common, wider) renders where the active pill is already visible,
|
|
208
|
+
// which is a change nobody asked for.
|
|
209
|
+
const current = el.querySelector<HTMLElement>('[aria-current="page"]');
|
|
210
|
+
if (current) {
|
|
211
|
+
const pill = current.getBoundingClientRect();
|
|
212
|
+
const port = el.getBoundingClientRect();
|
|
213
|
+
if (pill.left < port.left - 1 || pill.right > port.right + 1) {
|
|
214
|
+
const offset = pill.left - port.left + el.scrollLeft;
|
|
215
|
+
el.scrollLeft = Math.max(0, offset - (el.clientWidth - current.offsetWidth) / 2);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
measure();
|
|
219
|
+
el.addEventListener('scroll', measure, { passive: true });
|
|
220
|
+
const ro = new ResizeObserver(measure);
|
|
221
|
+
ro.observe(el);
|
|
222
|
+
return () => {
|
|
223
|
+
el.removeEventListener('scroll', measure);
|
|
224
|
+
ro.disconnect();
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// jelinek.css:573-592 (.chapter-nav::before/::after) + :670 (the ≥640px
|
|
229
|
+
// `bottom: 1px`). The fade's `transition` used to be the ONE in the guide
|
|
230
|
+
// still spelled as a literal `0.15s ease` while everything else had moved
|
|
231
|
+
// onto --dur-*/--ease; this component referenced the tokens anyway, and
|
|
232
|
+
// Task 31 reported the literal as a likely oversight rather than copying it
|
|
233
|
+
// as a divergence. Task 32 closed it in the guide itself (:581 now reads
|
|
234
|
+
// `transition: opacity var(--dur-base) var(--ease)`), so the two spellings
|
|
235
|
+
// agree and no divergence is left to record. The explicit easing still
|
|
236
|
+
// matters, because Tailwind's default curve is not `ease` (see
|
|
237
|
+
// Button.svelte).
|
|
238
|
+
// Task 33 — `bottom: 1px` is unconditional in the guide now (the phone-only
|
|
239
|
+
// `bottom: 9px`, which cleared the old `pb-2`, went with the phone layout),
|
|
240
|
+
// and so are the 25px insets below: the bar's own padding is a flat
|
|
241
|
+
// --space-3 (24px) plus its 1px border at every width.
|
|
242
|
+
const FADE =
|
|
243
|
+
'pointer-events-none absolute top-px bottom-px w-9 ' +
|
|
244
|
+
'transition-opacity duration-[var(--dur-base)] ease-[var(--ease)]';
|
|
245
|
+
</script>
|
|
246
|
+
|
|
247
|
+
<nav {...rest} class={cn(OUTER, className)} aria-label={label}>
|
|
248
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
249
|
+
<!-- The tabindex is deliberate and is the fix for audit M4, not an
|
|
250
|
+
oversight the linter caught: WCAG 2.1.1 requires a scrollable region to
|
|
251
|
+
be operable from the keyboard, and a scroll container is only
|
|
252
|
+
scrollable by arrow key once it can hold focus. The rule fires because
|
|
253
|
+
a plain <div> is non-interactive, which is true and is exactly the
|
|
254
|
+
situation the success criterion covers. No `role` is added: the
|
|
255
|
+
enclosing <nav> landmark already carries the accessible name, and
|
|
256
|
+
`role="group"` here would make a screen reader announce "Kapitoly"
|
|
257
|
+
twice. -->
|
|
258
|
+
<div bind:this={scroller} class={INNER} tabindex="0">
|
|
259
|
+
{#each items as item (item.href)}
|
|
260
|
+
<a
|
|
261
|
+
href={item.href}
|
|
262
|
+
aria-current={isActive(item, active) ? 'page' : undefined}
|
|
263
|
+
class={cn(
|
|
264
|
+
'whitespace-nowrap rounded-pill border border-transparent px-[13px] py-1.5 font-secondary text-[0.88rem] font-medium no-underline transition-colors',
|
|
265
|
+
isActive(item, active) ? ACTIVE : REST
|
|
266
|
+
)}
|
|
267
|
+
>
|
|
268
|
+
{item.label}
|
|
269
|
+
</a>
|
|
270
|
+
{/each}
|
|
271
|
+
</div>
|
|
272
|
+
<span
|
|
273
|
+
aria-hidden="true"
|
|
274
|
+
class={cn(
|
|
275
|
+
FADE,
|
|
276
|
+
'left-[25px] rounded-bl-[calc(var(--radius-card)-1px)]',
|
|
277
|
+
'bg-[linear-gradient(to_right,var(--color-surface)_20%,transparent)]',
|
|
278
|
+
showStart ? 'opacity-100' : 'opacity-0'
|
|
279
|
+
)}
|
|
280
|
+
></span>
|
|
281
|
+
<span
|
|
282
|
+
aria-hidden="true"
|
|
283
|
+
class={cn(
|
|
284
|
+
FADE,
|
|
285
|
+
'right-[25px] rounded-br-[calc(var(--radius-card)-1px)]',
|
|
286
|
+
'bg-[linear-gradient(to_left,var(--color-surface)_20%,transparent)]',
|
|
287
|
+
showEnd ? 'opacity-100' : 'opacity-0'
|
|
288
|
+
)}
|
|
289
|
+
></span>
|
|
290
|
+
</nav>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type NavItem } from './nav.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
items: NavItem[];
|
|
5
|
+
active?: string;
|
|
6
|
+
/**
|
|
7
|
+
* Accessible name for the `<nav>` landmark. The bar is a horizontal
|
|
8
|
+
* scroll region with `tabindex="0"` (see INNER below), so it needs a
|
|
9
|
+
* name for a screen-reader user to know what they have landed in.
|
|
10
|
+
* Czech by default, like every other user-facing string this kit ships.
|
|
11
|
+
*/
|
|
12
|
+
label?: string;
|
|
13
|
+
class?: string;
|
|
14
|
+
}
|
|
15
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLElement>;
|
|
16
|
+
declare const ChapterNav: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
17
|
+
type ChapterNav = ReturnType<typeof ChapterNav>;
|
|
18
|
+
export default ChapterNav;
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
checked = $bindable(false),
|
|
14
|
+
label,
|
|
15
|
+
id,
|
|
16
|
+
class: className,
|
|
17
|
+
...rest
|
|
18
|
+
}: Props & Omit<HTMLInputAttributes, 'checked' | 'type'> = $props();
|
|
19
|
+
|
|
20
|
+
const uid = $props.id();
|
|
21
|
+
const inputId = $derived(id ?? uid);
|
|
22
|
+
|
|
23
|
+
// jelinek.css has NO rule at all for checkbox inputs or their wrapper —
|
|
24
|
+
// ui/komponenty.html renders them as bare
|
|
25
|
+
// `<label style="display:flex;gap:10px;...">` with no class. The label
|
|
26
|
+
// TEXT still ends up on Avenir in the real guide page, though: that demo
|
|
27
|
+
// markup sits inside `<div class="demo">`, and `.card, .panel, .alert,
|
|
28
|
+
// .tile, .demo, .swatch { font-family: var(--font-secondary); ... }`
|
|
29
|
+
// (jelinek.css:322-324) applies font-secondary to it ambiently. Setting
|
|
30
|
+
// font-secondary/text-fg explicitly here reproduces that real rendering
|
|
31
|
+
// without depending on an ambient .demo wrapper the consumer may not
|
|
32
|
+
// have. gap-2.5 matches the 10px inline gap.
|
|
33
|
+
//
|
|
34
|
+
// ACCENT COLOUR — no CSS source of truth exists, so this is a judgement
|
|
35
|
+
// call, flagged for adjudication (see task-7-report.md). The checkbox
|
|
36
|
+
// square itself uses accent-jelinek-brown, NOT accent-jelinek-magenta:
|
|
37
|
+
// magenta is reserved for the Zdravý spánek (mattress) division and the
|
|
38
|
+
// guide is explicit brown/magenta never mix in one interface, so baking
|
|
39
|
+
// magenta into every checkbox would leak the mattress accent into
|
|
40
|
+
// furniture-division UIs. jelinek-brown is also what TextField/Select's
|
|
41
|
+
// own focus ring already uses (jelinek.css:661), and the token layer
|
|
42
|
+
// already re-themes --JELINEK-BROWN to magenta under
|
|
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';
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<label for={inputId} class={cn(LABEL, className)}>
|
|
50
|
+
<input {...rest} id={inputId} type="checkbox" bind:checked class="accent-jelinek-brown" />
|
|
51
|
+
{label}
|
|
52
|
+
</label>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props {
|
|
3
|
+
checked?: boolean;
|
|
4
|
+
label: string;
|
|
5
|
+
id?: string;
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
type $$ComponentProps = Props & Omit<HTMLInputAttributes, 'checked' | 'type'>;
|
|
9
|
+
declare const Checkbox: import("svelte").Component<$$ComponentProps, {}, "checked">;
|
|
10
|
+
type Checkbox = ReturnType<typeof Checkbox>;
|
|
11
|
+
export default Checkbox;
|