@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.
Files changed (84) hide show
  1. package/README.md +492 -0
  2. package/dist/components/Alert.svelte +59 -0
  3. package/dist/components/Alert.svelte.d.ts +13 -0
  4. package/dist/components/Button.svelte +178 -0
  5. package/dist/components/Button.svelte.d.ts +19 -0
  6. package/dist/components/Card.svelte +68 -0
  7. package/dist/components/Card.svelte.d.ts +11 -0
  8. package/dist/components/ChapterNav.svelte +290 -0
  9. package/dist/components/ChapterNav.svelte.d.ts +18 -0
  10. package/dist/components/Checkbox.svelte +52 -0
  11. package/dist/components/Checkbox.svelte.d.ts +11 -0
  12. package/dist/components/CodeCopy.svelte +294 -0
  13. package/dist/components/CodeCopy.svelte.d.ts +51 -0
  14. package/dist/components/Container.svelte +34 -0
  15. package/dist/components/Container.svelte.d.ts +10 -0
  16. package/dist/components/ContentLogo.svelte +151 -0
  17. package/dist/components/ContentLogo.svelte.d.ts +37 -0
  18. package/dist/components/Demo.svelte +111 -0
  19. package/dist/components/Demo.svelte.d.ts +12 -0
  20. package/dist/components/Eyebrow.svelte +70 -0
  21. package/dist/components/Eyebrow.svelte.d.ts +12 -0
  22. package/dist/components/Glass.svelte +75 -0
  23. package/dist/components/Glass.svelte.d.ts +10 -0
  24. package/dist/components/Grid.svelte +87 -0
  25. package/dist/components/Grid.svelte.d.ts +11 -0
  26. package/dist/components/Label.svelte +37 -0
  27. package/dist/components/Label.svelte.d.ts +11 -0
  28. package/dist/components/PageHero.svelte +312 -0
  29. package/dist/components/PageHero.svelte.d.ts +55 -0
  30. package/dist/components/Pager.svelte +98 -0
  31. package/dist/components/Pager.svelte.d.ts +13 -0
  32. package/dist/components/Panel.svelte +89 -0
  33. package/dist/components/Panel.svelte.d.ts +12 -0
  34. package/dist/components/ProductCard.svelte +81 -0
  35. package/dist/components/ProductCard.svelte.d.ts +18 -0
  36. package/dist/components/Radio.svelte +54 -0
  37. package/dist/components/Radio.svelte.d.ts +11 -0
  38. package/dist/components/RadioGroup.svelte +42 -0
  39. package/dist/components/RadioGroup.svelte.d.ts +12 -0
  40. package/dist/components/Select.svelte +65 -0
  41. package/dist/components/Select.svelte.d.ts +20 -0
  42. package/dist/components/SiteFooter.svelte +121 -0
  43. package/dist/components/SiteFooter.svelte.d.ts +13 -0
  44. package/dist/components/SiteHeader.svelte +613 -0
  45. package/dist/components/SiteHeader.svelte.d.ts +80 -0
  46. package/dist/components/Slider.svelte +55 -0
  47. package/dist/components/Slider.svelte.d.ts +14 -0
  48. package/dist/components/Swatch.svelte +174 -0
  49. package/dist/components/Swatch.svelte.d.ts +58 -0
  50. package/dist/components/Tab.svelte +88 -0
  51. package/dist/components/Tab.svelte.d.ts +11 -0
  52. package/dist/components/Tabs.svelte +306 -0
  53. package/dist/components/Tabs.svelte.d.ts +20 -0
  54. package/dist/components/Tag.svelte +71 -0
  55. package/dist/components/Tag.svelte.d.ts +16 -0
  56. package/dist/components/TextField.svelte +100 -0
  57. package/dist/components/TextField.svelte.d.ts +18 -0
  58. package/dist/components/ThemeToggle.svelte +122 -0
  59. package/dist/components/ThemeToggle.svelte.d.ts +11 -0
  60. package/dist/components/Tile.svelte +115 -0
  61. package/dist/components/Tile.svelte.d.ts +14 -0
  62. package/dist/components/TileScroller.svelte +199 -0
  63. package/dist/components/TileScroller.svelte.d.ts +25 -0
  64. package/dist/components/container-context.d.ts +1 -0
  65. package/dist/components/container-context.js +6 -0
  66. package/dist/components/deer-mark-path.d.ts +16 -0
  67. package/dist/components/deer-mark-path.js +16 -0
  68. package/dist/components/nav.d.ts +25 -0
  69. package/dist/components/nav.js +17 -0
  70. package/dist/components/radio-context.d.ts +6 -0
  71. package/dist/components/radio-context.js +1 -0
  72. package/dist/components/tabs-context.d.ts +5 -0
  73. package/dist/components/tabs-context.js +1 -0
  74. package/dist/index.d.ts +37 -0
  75. package/dist/index.js +37 -0
  76. package/dist/theme/base.css +439 -0
  77. package/dist/theme/extras.css +2 -0
  78. package/dist/theme/fonts.css +107 -0
  79. package/dist/theme/tokens.css +208 -0
  80. package/dist/utils/chapter-nav-dock.svelte.d.ts +23 -0
  81. package/dist/utils/chapter-nav-dock.svelte.js +65 -0
  82. package/dist/utils/cn.d.ts +2 -0
  83. package/dist/utils/cn.js +27 -0
  84. package/package.json +79 -0
@@ -0,0 +1,80 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { type NavItem } from './nav.js';
4
+ interface Props {
5
+ /**
6
+ * Escape hatch: a snippet rendering custom mark content in place of the
7
+ * bundled, re-theming deer mark (e.g. a division-specific logo). Most
8
+ * consumers should leave this unset and get the guide's own mark.
9
+ */
10
+ logo?: Snippet;
11
+ logoAlt?: string;
12
+ /**
13
+ * Where the logo/mark links to. Defaults to `/`, the guide's own
14
+ * literal href (partials/header.html:3) — set this when the consuming
15
+ * app is not rooted at `/`, e.g. mounted under a base path.
16
+ */
17
+ logoHref?: string;
18
+ subtitle?: string;
19
+ items?: NavItem[];
20
+ active?: string;
21
+ /**
22
+ * Set when a `ChapterNav` is rendered directly below this header, to
23
+ * reproduce the guide's joined-panel seam (jelinek.css:604-620): the
24
+ * header loses its bottom border/corners/shadow so it flows into the
25
+ * nav strip, until the reader scrolls past 50px and it detaches into
26
+ * its own rounded, shadowed bar (assets/js/jelinek.js:354). SiteHeader
27
+ * tracks that scroll threshold itself — a consumer never has to wire
28
+ * up a listener. Leave unset (default `false`) for the normal,
29
+ * always-detached header, e.g. the rozcestník, which has no chapter
30
+ * nav at all.
31
+ */
32
+ hasChapterNav?: boolean;
33
+ /**
34
+ * Accessible name for the `<nav>` landmark holding the top-level
35
+ * links. Czech by default, like every other user-facing string this
36
+ * kit ships (see Pager's `prevLabel`/`nextLabel`).
37
+ */
38
+ navLabel?: string;
39
+ /**
40
+ * Chapter links to repeat inside the phone menu panel. Below 640px the
41
+ * guide's chapter bar is `display: none` (see ChapterNav.svelte), so the
42
+ * chapters have nowhere else to live on a phone; the guide's own script
43
+ * clones them out of the rendered bar, and this prop is that same content
44
+ * passed declaratively instead. Leave unset on pages with no chapter nav
45
+ * (the rozcestník) — the divider and the block disappear with it. They
46
+ * are hidden again from 640px up, where the bar itself is back.
47
+ */
48
+ chapterItems?: NavItem[];
49
+ /**
50
+ * Accessible name for the phone hamburger button while the menu is SHUT.
51
+ * It has no visible text — it is three bars that fold into a cross — so
52
+ * this is the only name a screen reader gets. See BURGER below.
53
+ */
54
+ menuLabel?: string;
55
+ /** Accessible name for the same button while the menu is OPEN. */
56
+ menuLabelClose?: string;
57
+ /**
58
+ * Task 37 — TRAILING CONTENT INSIDE THE NAV, which the guide requires and
59
+ * this component previously refused.
60
+ *
61
+ * partials/header.html:6-13 nests the theme toggle as the LAST CHILD of
62
+ * `.site-header__nav`, so it sits in the nav row on a desktop and moves
63
+ * into the burger panel on a phone along with the links. The earlier
64
+ * decision here was to keep ThemeToggle separate and let a consumer
65
+ * "render both together in its own layout" — but a consumer cannot put
66
+ * anything inside this component's nav from outside it, so what that
67
+ * actually produced was a toggle stranded in the page body, at a different
68
+ * place from the guide and gone from the phone menu entirely.
69
+ *
70
+ * Rendered into BOTH navs, after the links, exactly like `items`. Keep it
71
+ * to controls that belong to the header (the toggle, a division switch) —
72
+ * it is not a general slot.
73
+ */
74
+ actions?: Snippet;
75
+ class?: string;
76
+ }
77
+ type $$ComponentProps = Props & HTMLAttributes<HTMLElement>;
78
+ declare const SiteHeader: import("svelte").Component<$$ComponentProps, {}, "">;
79
+ type SiteHeader = ReturnType<typeof SiteHeader>;
80
+ export default SiteHeader;
@@ -0,0 +1,55 @@
1
+ <script lang="ts">
2
+ import type { HTMLInputAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils/cn.js';
4
+ import Label from './Label.svelte';
5
+
6
+ interface Props {
7
+ value?: number;
8
+ min?: number;
9
+ max?: number;
10
+ step?: number;
11
+ label?: string;
12
+ id?: string;
13
+ class?: string;
14
+ }
15
+
16
+ let {
17
+ value = $bindable(0),
18
+ min = 0,
19
+ max = 100,
20
+ step = 1,
21
+ label,
22
+ id,
23
+ class: className,
24
+ ...rest
25
+ }: Props & Omit<HTMLInputAttributes, 'value' | 'min' | 'max' | 'step' | 'type'> = $props();
26
+
27
+ const uid = $props.id();
28
+ const inputId = $derived(id ?? uid);
29
+
30
+ // Same reasoning as Checkbox.svelte/Radio.svelte: jelinek.css has NO rule
31
+ // at all for range inputs. ui/komponenty.html's two sliders do carry an
32
+ // inline `accent-color: var(--JELINEK-MAGENTA)`, but both are
33
+ // mattress-width demos (90 cm, 120 cm) — a mattress-CONTEXT instance, not
34
+ // a stylesheet rule for sliders in general. Treating that literal magenta
35
+ // as the default would leak the mattress accent onto furniture-division
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.
41
+ </script>
42
+
43
+ {#if label}
44
+ <Label for={inputId}>{label}</Label>
45
+ {/if}
46
+ <input
47
+ {...rest}
48
+ id={inputId}
49
+ type="range"
50
+ {min}
51
+ {max}
52
+ {step}
53
+ bind:value
54
+ class={cn('w-full accent-jelinek-brown', className)}
55
+ />
@@ -0,0 +1,14 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ interface Props {
3
+ value?: number;
4
+ min?: number;
5
+ max?: number;
6
+ step?: number;
7
+ label?: string;
8
+ id?: string;
9
+ class?: string;
10
+ }
11
+ type $$ComponentProps = Props & Omit<HTMLInputAttributes, 'value' | 'min' | 'max' | 'step' | 'type'>;
12
+ declare const Slider: import("svelte").Component<$$ComponentProps, {}, "value">;
13
+ type Slider = ReturnType<typeof Slider>;
14
+ export default Slider;
@@ -0,0 +1,174 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils/cn.js';
4
+ import CodeCopy from './CodeCopy.svelte';
5
+
6
+ interface Props {
7
+ color: string;
8
+ name?: string;
9
+ /**
10
+ * A single copyable string under the name. Use this when the swatch has
11
+ * one value regardless of mode. For a token that changes with the mode,
12
+ * use `light`/`dark` instead — see below.
13
+ */
14
+ value?: string;
15
+ /**
16
+ * Task 37 — THE TWO-MODE ROWS, restored from the guide.
17
+ *
18
+ * jelinek.css:1006-1014 (`.swatch__mode` / `.swatch__mode-lbl`) exists for
19
+ * one purpose: printing a token's LIGHT and DARK value on the same swatch,
20
+ * one labelled row each, because a colour token in this system is a pair
21
+ * and showing only the current mode's half tells a reader half the truth.
22
+ * ui-old/barvy.html used it on every one of its swatches; the kit shipped
23
+ * the chip and the token NAME and nothing else, so the chapter could not
24
+ * answer "what colour is this" at all.
25
+ *
26
+ * Pass both to get the two rows. Each value is copyable on click, and what
27
+ * lands on the clipboard is the VALUE — the previous behaviour, copying
28
+ * the token name from `value`, is what made the copy button useless for
29
+ * anyone reaching for a colour rather than for CSS.
30
+ *
31
+ * A row whose value is a bare em dash renders as a normal row: that is how
32
+ * a token defined in only one mode says so (--border-highlight-white is
33
+ * dark-only), and it is deliberately not silently dropped.
34
+ */
35
+ light?: string;
36
+ dark?: string;
37
+ /** One line under the values — what the token is for. */
38
+ note?: string;
39
+ /** Row labels, Czech by default like every other string this kit ships. */
40
+ lightLabel?: string;
41
+ darkLabel?: string;
42
+ /**
43
+ * 1px BORDER-SUBTLE hairline on the colour chip itself. ui-old/barvy.html
44
+ * sets it inline on the swatches whose chip would otherwise dissolve into
45
+ * the card behind it (white or near-white halves); reproduced as a prop
46
+ * because which chips need it is knowledge about the COLOUR, not about
47
+ * this component.
48
+ */
49
+ chipBorder?: boolean;
50
+ /**
51
+ * The archive's copy-icon button next to each value (jelinek.js:146-160
52
+ * builds it, jelinek.css:1028-1039 styles it). In the guide it exists only
53
+ * on pages with the colour-format switcher — initColorSwitch() bails out
54
+ * without one — so it is opt-in here too; the Barvy chapter turns it on.
55
+ */
56
+ copyIcon?: boolean;
57
+ class?: string;
58
+ }
59
+
60
+ let {
61
+ color,
62
+ name,
63
+ value,
64
+ light,
65
+ dark,
66
+ note,
67
+ lightLabel = 'Light mode',
68
+ darkLabel = 'Dark mode',
69
+ chipBorder = false,
70
+ copyIcon = false,
71
+ class: className,
72
+ ...rest
73
+ }: Props & HTMLAttributes<HTMLDivElement> = $props();
74
+
75
+ // jelinek.css:1008-1012 (`.swatch__mode-lbl`): the secondary family at weight
76
+ // 500, 0.68rem, 0.05em tracking, muted2, and a 62px minimum so both labels
77
+ // line their values up in a column instead of ragging.
78
+ const MODE_LABEL =
79
+ 'font-secondary text-[0.68rem] font-medium tracking-[0.05em] text-fg-muted2 ' +
80
+ 'min-w-[62px] flex-none whitespace-nowrap';
81
+ // jelinek.css:1007 (`.swatch__mode`): the label and the value on one line.
82
+ const MODE_ROW = 'flex items-center gap-2.5';
83
+
84
+ // CSS FIDELITY
85
+ // jelinek.css:474-482 (shared box system with .card/.panel/.demo/.tile):
86
+ // --box-accent is never overridden for .swatch (no `.swatch--*` colour
87
+ // modifier exists anywhere in jelinek.css), so — same reasoning as
88
+ // Card/Tile — the two-stop border gradient collapses to a flat 1px
89
+ // border-border-subtle, reproduced directly.
90
+ // jelinek.css:665 `.swatch { overflow: hidden }` — clips the color chip's
91
+ // square corners to the card's rounded ones.
92
+ // jelinek.css:666 `.swatch__color { height: 92px }` — the brief's `h-20`
93
+ // (80px) was wrong; 92px isn't on Tailwind's default scale, kept as
94
+ // h-[92px].
95
+ // jelinek.css:667 `.swatch__meta { padding: 12px 14px; font-size: 0.82rem
96
+ // }` — 12px/14px map exactly to Tailwind's py-3/px-3.5; 0.82rem isn't a
97
+ // --step token (no rung of the scale equals it: --step-down2 is 0.75rem
98
+ // and --step-down1 is 0.875rem) so it's an arbitrary text-[0.82rem].
99
+ // jelinek.css:668 `.swatch__meta strong { display: block; font-size:
100
+ // 0.88rem }` — block text-[0.88rem] on the name element directly (this
101
+ // component owns its own markup here, unlike Card/Tile's opaque Snippet
102
+ // children, so no [&_strong] selector trick is needed).
103
+ // jelinek.css:327-331 (shared strong/b→font-medium rule) also covers this
104
+ // strong (`.swatch strong` is in that selector list) — folded into the
105
+ // same class list as font-medium.
106
+ // jelinek.css:322-325 (`.swatch` on the shared list): font-secondary
107
+ // font-light for the swatch's own text.
108
+ // jelinek.css:669-676 `.swatch__meta code` — this is CodeCopy's job; see
109
+ // CodeCopy.svelte for the full fidelity note including the `.is-empty`
110
+ // state. Composing CodeCopy here (rather than a bare <code>) is what
111
+ // reproduces jelinek.css's real click-to-copy behaviour for the value
112
+ // (assets/js/jelinek.js:142-193 wires exactly this element to
113
+ // navigator.clipboard).
114
+ // Standing rule 5 (margin policy): `.panel, .card, .demo { ... margin-
115
+ // bottom: 1.5rem }` (jelinek.css:483) does NOT list .swatch at all — .swatch
116
+ // never had an outer margin to begin with, so there is nothing to drop
117
+ // here (unlike Card/Alert/Panel/Demo).
118
+ // TASK 34 — the dark-mode inset top highlight, jelinek.css:251-257:
119
+ // `:root[data-theme="dark"] .panel, .card, .demo, .swatch, .logo-tool {
120
+ // box-shadow: inset 0 2px 1px -1px var(--box-primary) }`. `.swatch` is in
121
+ // that selector list and this was missing. See Panel.svelte for the full
122
+ // note on the false "nothing consumes --box-primary" reasoning it was
123
+ // dropped under.
124
+ //
125
+ // Mostly INVISIBLE here, in the guide exactly as much as in the kit: an
126
+ // inset box-shadow paints above the element's own background but below its
127
+ // descendants, and `.swatch__color` (the 92px chip, reproduced below) covers
128
+ // the whole top edge the highlight sits on. It shows only on a swatch with
129
+ // no chip. Reproduced anyway — the divergence in the computed value is what
130
+ // the css-lock's green is promising, and "you cannot see it on this
131
+ // component" is not a reason for the two stylesheets to disagree.
132
+ // No `.swatch--*` colour modifier exists, so --box-primary is always the
133
+ // :root 50% grey here.
134
+ const BASE =
135
+ 'overflow-hidden rounded-card border border-border-subtle bg-surface ' +
136
+ 'dark:shadow-[inset_0_2px_1px_-1px_var(--box-primary)]';
137
+ </script>
138
+
139
+ <div {...rest} class={cn(BASE, className)}>
140
+ <div
141
+ data-swatch-chip
142
+ style="background: {color};"
143
+ class="h-[92px] w-full {chipBorder ? 'border border-border-subtle' : ''}"
144
+ ></div>
145
+ {#if name || value || light || dark || note}
146
+ <div class="px-3.5 py-3 font-secondary text-[0.82rem] font-light">
147
+ {#if name}
148
+ <strong class="block text-[0.88rem] font-medium">{name}</strong>
149
+ {/if}
150
+ {#if value}
151
+ <CodeCopy {value} icon={copyIcon} />
152
+ {/if}
153
+ <!-- jelinek.css:1025 (`.swatch__mode .swatch__value`): inside a mode row
154
+ the value row flexes to fill and carries `margin: 3px 0` instead of
155
+ the standalone row's `2px 0 4px`. Only meaningful with the icon —
156
+ a bare CodeCopy is a plain inline button, not a row. -->
157
+ {#if light}
158
+ <div class="{MODE_ROW} mt-1.5">
159
+ <span class={MODE_LABEL}>{lightLabel}</span>
160
+ <CodeCopy value={light} icon={copyIcon} class={copyIcon ? 'flex-1 my-[3px]' : undefined} />
161
+ </div>
162
+ {/if}
163
+ {#if dark}
164
+ <div class={MODE_ROW}>
165
+ <span class={MODE_LABEL}>{darkLabel}</span>
166
+ <CodeCopy value={dark} icon={copyIcon} class={copyIcon ? 'flex-1 my-[3px]' : undefined} />
167
+ </div>
168
+ {/if}
169
+ {#if note}
170
+ <p class="mt-1.5 mb-0 text-fg-muted1">{note}</p>
171
+ {/if}
172
+ </div>
173
+ {/if}
174
+ </div>
@@ -0,0 +1,58 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ interface Props {
3
+ color: string;
4
+ name?: string;
5
+ /**
6
+ * A single copyable string under the name. Use this when the swatch has
7
+ * one value regardless of mode. For a token that changes with the mode,
8
+ * use `light`/`dark` instead — see below.
9
+ */
10
+ value?: string;
11
+ /**
12
+ * Task 37 — THE TWO-MODE ROWS, restored from the guide.
13
+ *
14
+ * jelinek.css:1006-1014 (`.swatch__mode` / `.swatch__mode-lbl`) exists for
15
+ * one purpose: printing a token's LIGHT and DARK value on the same swatch,
16
+ * one labelled row each, because a colour token in this system is a pair
17
+ * and showing only the current mode's half tells a reader half the truth.
18
+ * ui-old/barvy.html used it on every one of its swatches; the kit shipped
19
+ * the chip and the token NAME and nothing else, so the chapter could not
20
+ * answer "what colour is this" at all.
21
+ *
22
+ * Pass both to get the two rows. Each value is copyable on click, and what
23
+ * lands on the clipboard is the VALUE — the previous behaviour, copying
24
+ * the token name from `value`, is what made the copy button useless for
25
+ * anyone reaching for a colour rather than for CSS.
26
+ *
27
+ * A row whose value is a bare em dash renders as a normal row: that is how
28
+ * a token defined in only one mode says so (--border-highlight-white is
29
+ * dark-only), and it is deliberately not silently dropped.
30
+ */
31
+ light?: string;
32
+ dark?: string;
33
+ /** One line under the values — what the token is for. */
34
+ note?: string;
35
+ /** Row labels, Czech by default like every other string this kit ships. */
36
+ lightLabel?: string;
37
+ darkLabel?: string;
38
+ /**
39
+ * 1px BORDER-SUBTLE hairline on the colour chip itself. ui-old/barvy.html
40
+ * sets it inline on the swatches whose chip would otherwise dissolve into
41
+ * the card behind it (white or near-white halves); reproduced as a prop
42
+ * because which chips need it is knowledge about the COLOUR, not about
43
+ * this component.
44
+ */
45
+ chipBorder?: boolean;
46
+ /**
47
+ * The archive's copy-icon button next to each value (jelinek.js:146-160
48
+ * builds it, jelinek.css:1028-1039 styles it). In the guide it exists only
49
+ * on pages with the colour-format switcher — initColorSwitch() bails out
50
+ * without one — so it is opt-in here too; the Barvy chapter turns it on.
51
+ */
52
+ copyIcon?: boolean;
53
+ class?: string;
54
+ }
55
+ type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
56
+ declare const Swatch: import("svelte").Component<$$ComponentProps, {}, "">;
57
+ type Swatch = ReturnType<typeof Swatch>;
58
+ export default Swatch;
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ import { getContext, type Snippet } from 'svelte';
3
+ import type { HTMLButtonAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+ import { TABS_CONTEXT, type TabsContext } from './tabs-context.js';
6
+
7
+ interface Props {
8
+ value: string;
9
+ class?: string;
10
+ children: Snippet;
11
+ }
12
+
13
+ let {
14
+ value,
15
+ class: className,
16
+ children,
17
+ ...rest
18
+ }: Props & HTMLButtonAttributes = $props();
19
+
20
+ const tabs = getContext<TabsContext>(TABS_CONTEXT);
21
+ if (!tabs) throw new Error('<Tab> must be rendered inside <Tabs>.');
22
+
23
+ const selected = $derived(tabs.current() === value);
24
+
25
+ // jelinek.css:877-884 (.tab, shared with .logo-tool__bgtab/.logo-tool__lang):
26
+ // text-align:center + white-space:nowrap — both absent from the original
27
+ // draft. font-family is
28
+ // --font-secondary (Avenir), NOT --font-primary (GT Walsheim) as first
29
+ // written. font-weight is 700 (font-bold), also missing. Padding is
30
+ // 10px/16px: px-4 (16px) is correct but the vertical 10px is py-2.5, not
31
+ // py-2 (8px). font-size is a fixed 0.9rem literal, not on the --step scale
32
+ // (--step0 is a constant 1rem, unrelated) — kept as text-[0.9rem], with
33
+ // leading-[1.2] for the CSS's line-height:1.2 (same arbitrary-leading
34
+ // pattern as Button's text-[0.98rem] leading-[1.2]).
35
+ //
36
+ // TASK 31 — `flex: 1` BECAME `flex: 1 0 auto` (jelinek.css:880), which is
37
+ // `flex-[1_0_auto]` here and NOT Tailwind's `flex-1` (that is `flex: 1 1 0%`,
38
+ // a different thing: grow, shrink, zero basis). The guide's own inline
39
+ // comment on that line says what it is for — "roztáhne se, ale nikdy se
40
+ // nesmrskne pod text", it stretches but never shrinks below its label.
41
+ //
42
+ // This is the other half of Tabs' change from a wrapping row to a sideways
43
+ // scroller; see Tabs.svelte's comment for the full pairing. Short version:
44
+ // with a 0% basis, segments in a `flex-nowrap` row shrink instead of
45
+ // overflowing, so the scroller never engages and long labels squash. Both
46
+ // values have to move together or neither does anything.
47
+ //
48
+ // `transition-all` is NOT from the guide — `.tab` carries no transition at
49
+ // all there, so hover snaps rather than fades. Left in place because it
50
+ // predates this task and removing it is a visual change with no rule behind
51
+ // it either way; noted in the Task 31 report so the call is visible rather
52
+ // than buried.
53
+ const BASE =
54
+ 'flex-[1_0_auto] cursor-pointer whitespace-nowrap text-center rounded-pill border-0 ' +
55
+ 'px-4 py-2.5 font-secondary text-[0.9rem] leading-[1.2] font-bold transition-all';
56
+
57
+ // jelinek.css:885-887 (.tab:hover): background flips to --text-muted1 with
58
+ // --text-white text on top — not just a text-colour change as first
59
+ // written. jelinek.css:889-891 (.tab.is-active): unchanged, bg-jelinek-black
60
+ // + text-on-jelinek-black is correct. .tab.is-active is declared after
61
+ // .tab:hover in the source with equal specificity, so an active tab keeps
62
+ // its solid styling even while hovered — reproduced here by only adding the
63
+ // hover: classes to the unselected branch, never to the selected one.
64
+ // Neither branch needs an explicit dark: override: bg-jelinek-black /
65
+ // text-on-jelinek-black / text-fg-muted1 / bg-fg-muted1 / text-fg-inverse
66
+ // all resolve through theme-aware CSS custom properties that already flip
67
+ // under [data-theme="dark"]/.dark.
68
+ </script>
69
+
70
+ <button
71
+ {...rest}
72
+ type={rest.type ?? 'button'}
73
+ role={rest.role ?? 'tab'}
74
+ aria-selected={selected}
75
+ onclick={(event) => {
76
+ tabs.select(value);
77
+ rest.onclick?.(event);
78
+ }}
79
+ class={cn(
80
+ BASE,
81
+ selected
82
+ ? 'bg-jelinek-black text-on-jelinek-black'
83
+ : 'bg-transparent text-fg-muted1 hover:bg-fg-muted1 hover:text-fg-inverse',
84
+ className
85
+ )}
86
+ >
87
+ {@render children()}
88
+ </button>
@@ -0,0 +1,11 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ interface Props {
4
+ value: string;
5
+ class?: string;
6
+ children: Snippet;
7
+ }
8
+ type $$ComponentProps = Props & HTMLButtonAttributes;
9
+ declare const Tab: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type Tab = ReturnType<typeof Tab>;
11
+ export default Tab;