@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,18 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props {
4
+ title: string;
5
+ description?: string;
6
+ image?: string;
7
+ imageAlt?: string;
8
+ href?: string;
9
+ ctaLabel?: string;
10
+ color?: 'brown' | 'magenta' | 'black';
11
+ /** Optional badge rendered above the title, e.g. <Tag variant="instock">. */
12
+ tag?: Snippet;
13
+ class?: string;
14
+ }
15
+ type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
16
+ declare const ProductCard: import("svelte").Component<$$ComponentProps, {}, "">;
17
+ type ProductCard = ReturnType<typeof ProductCard>;
18
+ export default ProductCard;
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import { getContext } from 'svelte';
3
+ import type { HTMLInputAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+ import { RADIO_CONTEXT, type RadioContext } from './radio-context.js';
6
+
7
+ interface Props {
8
+ value: string;
9
+ label: string;
10
+ id?: string;
11
+ class?: string;
12
+ }
13
+
14
+ let {
15
+ value,
16
+ label,
17
+ id,
18
+ class: className,
19
+ ...rest
20
+ }: Props & Omit<HTMLInputAttributes, 'value' | 'type' | 'name'> = $props();
21
+
22
+ const group = getContext<RadioContext>(RADIO_CONTEXT);
23
+ if (!group) throw new Error('<Radio> must be rendered inside <RadioGroup>.');
24
+
25
+ const uid = $props.id();
26
+ const inputId = $derived(id ?? uid);
27
+ const checked = $derived(group.current() === value);
28
+
29
+ // Same reasoning as Checkbox.svelte: no CSS rule exists for radio inputs
30
+ // or their label; font-secondary/text-fg reproduces what the guide's own
31
+ // `.demo` wrapper applies ambiently (jelinek.css:322-324), and
32
+ // accent-jelinek-brown (not accent-jelinek-magenta) keeps the mattress
33
+ // accent out of furniture UIs by default while still following the
34
+ // division automatically via the --JELINEK-BROWN re-theme
35
+ // (tokens.css:161-166). Flagged for adjudication.
36
+ const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary text-step0 text-fg';
37
+ </script>
38
+
39
+ <label for={inputId} class={cn(LABEL, className)}>
40
+ <input
41
+ {...rest}
42
+ id={inputId}
43
+ type="radio"
44
+ name={group.name}
45
+ {value}
46
+ {checked}
47
+ onchange={(event) => {
48
+ group.select(value);
49
+ rest.onchange?.(event);
50
+ }}
51
+ class="accent-jelinek-brown"
52
+ />
53
+ {label}
54
+ </label>
@@ -0,0 +1,11 @@
1
+ import type { HTMLInputAttributes } from 'svelte/elements';
2
+ interface Props {
3
+ value: string;
4
+ label: string;
5
+ id?: string;
6
+ class?: string;
7
+ }
8
+ type $$ComponentProps = Props & Omit<HTMLInputAttributes, 'value' | 'type' | 'name'>;
9
+ declare const Radio: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type Radio = ReturnType<typeof Radio>;
11
+ export default Radio;
@@ -0,0 +1,42 @@
1
+ <script lang="ts">
2
+ import { setContext, untrack, type Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+ import { RADIO_CONTEXT, type RadioContext } from './radio-context.js';
6
+
7
+ interface Props {
8
+ name: string;
9
+ value?: string;
10
+ class?: string;
11
+ children: Snippet;
12
+ }
13
+
14
+ let {
15
+ name,
16
+ value = $bindable(''),
17
+ class: className,
18
+ children,
19
+ ...rest
20
+ }: Props & HTMLAttributes<HTMLDivElement> = $props();
21
+
22
+ // RadioContext (radio-context.ts) fixes `name` as a plain string, not a
23
+ // getter like `current`/`select` — the native <input name> a group's
24
+ // radios share isn't meant to change after mount. `untrack` here is
25
+ // exactly that: an explicit, one-time snapshot at setContext time,
26
+ // silencing Svelte's state_referenced_locally warning rather than
27
+ // leaving an unacknowledged one.
28
+ setContext<RadioContext>(RADIO_CONTEXT, {
29
+ name: untrack(() => name),
30
+ current: () => value,
31
+ select: (next) => (value = next)
32
+ });
33
+
34
+ // No CSS rule targets a "radio group" wrapper; ui/komponenty.html just
35
+ // stacks the <label> rows with margin-bottom on all but the last. gap-2.5
36
+ // (10px) reproduces that spacing declaratively instead of a per-item
37
+ // trailing margin.
38
+ </script>
39
+
40
+ <div {...rest} role={rest.role ?? 'radiogroup'} class={cn('flex flex-col gap-2.5', className)}>
41
+ {@render children()}
42
+ </div>
@@ -0,0 +1,12 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ interface Props {
4
+ name: string;
5
+ value?: string;
6
+ class?: string;
7
+ children: Snippet;
8
+ }
9
+ type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
10
+ declare const RadioGroup: import("svelte").Component<$$ComponentProps, {}, "value">;
11
+ type RadioGroup = ReturnType<typeof RadioGroup>;
12
+ export default RadioGroup;
@@ -0,0 +1,65 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLSelectAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+ import Label from './Label.svelte';
6
+
7
+ interface Props {
8
+ value?: string;
9
+ label?: string;
10
+ id?: string;
11
+ /**
12
+ * Invalid state. Reproduces the guide's `.select.is-error`
13
+ * (jelinek.css:988-991) — new in the Task 31 merge — and sets
14
+ * `aria-invalid` alongside it. Same prop and same rule as TextField's;
15
+ * the guide styles both in one selector list.
16
+ */
17
+ error?: boolean;
18
+ class?: string;
19
+ children: Snippet;
20
+ }
21
+
22
+ let {
23
+ value = $bindable(''),
24
+ label,
25
+ id,
26
+ error = false,
27
+ class: className,
28
+ children,
29
+ ...rest
30
+ }: Props & Omit<HTMLSelectAttributes, 'value'> = $props();
31
+
32
+ const uid = $props.id();
33
+ const selectId = $derived(id ?? uid);
34
+
35
+ // jelinek.css:966-975 (`.input, .select`) — same rule, and same
36
+ // corrections against the draft brief, as TextField: border-fg-muted2
37
+ // (not border-border-subtle), px-[14px] py-[11px] (not px-4 py-3),
38
+ // text-[0.98rem] (not text-step0).
39
+ // Task 31 — the focus ring moved onto --focus-width/--focus-color, which
40
+ // fixes a too-dark ring in dark mode, and `.select.is-error`
41
+ // (jelinek.css:987-991) is new. See TextField.svelte for the full note on
42
+ // both; the guide styles `.input` and `.select` in one selector list, so the
43
+ // two components stay deliberately identical here.
44
+ 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 ' +
47
+ 'focus:outline-[length:var(--focus-width)] focus:outline-offset-1 ' +
48
+ 'focus:outline-[var(--focus-color)] focus:border-[var(--focus-color)]';
49
+
50
+ const ERROR =
51
+ 'border-sem-error-strong focus:border-sem-error-strong focus:outline-sem-error-strong';
52
+ </script>
53
+
54
+ {#if label}
55
+ <Label for={selectId}>{label}</Label>
56
+ {/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>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLSelectAttributes } from 'svelte/elements';
3
+ interface Props {
4
+ value?: string;
5
+ label?: string;
6
+ id?: string;
7
+ /**
8
+ * Invalid state. Reproduces the guide's `.select.is-error`
9
+ * (jelinek.css:988-991) — new in the Task 31 merge — and sets
10
+ * `aria-invalid` alongside it. Same prop and same rule as TextField's;
11
+ * the guide styles both in one selector list.
12
+ */
13
+ error?: boolean;
14
+ class?: string;
15
+ children: Snippet;
16
+ }
17
+ type $$ComponentProps = Props & Omit<HTMLSelectAttributes, 'value'>;
18
+ declare const Select: import("svelte").Component<$$ComponentProps, {}, "value">;
19
+ type Select = ReturnType<typeof Select>;
20
+ export default Select;
@@ -0,0 +1,121 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { cn } from '../utils/cn.js';
4
+
5
+ interface Props {
6
+ logo?: string;
7
+ logoAlt?: string;
8
+ slogan?: string;
9
+ meta?: string;
10
+ stamp?: string;
11
+ class?: string;
12
+ }
13
+
14
+ let {
15
+ logo,
16
+ logoAlt = 'JELÍNEK',
17
+ slogan,
18
+ meta,
19
+ stamp,
20
+ class: className,
21
+ ...rest
22
+ }: Props & HTMLAttributes<HTMLElement> = $props();
23
+
24
+ // jelinek.css:759-770 (.site-footer, .site-footer__inner). `margin-top:
25
+ // 60px` (:760) is kept — unlike Card/Alert (Tasks 5/6), which deliberately
26
+ // drop their own external margins on the reasoning that a reusable
27
+ // CONTENT atom shouldn't fight a consumer's grid/flex `gap`, SiteFooter
28
+ // is page CHROME: there is exactly one per page, always at the very
29
+ // bottom, and per this task's direction the guide is reproduced as the
30
+ // default rather than softened for a hypothetical consumer. 60px isn't on
31
+ // Tailwind's default spacing scale (nearest are 56px/64px) — arbitrary
32
+ // `mt-[60px]`. `border-radius: var(--radius-section) var(--radius-section)
33
+ // 0 0` (top corners only, :761) → `rounded-t-section`.
34
+ //
35
+ // partials/footer.html has NO anchor wrapping the logo image (unlike
36
+ // header.html, which wraps its mark in <a href="/">) — reproduced as a
37
+ // bare <img>, not a link.
38
+ //
39
+ // Real footer.html uses a dedicated white PNG asset (`logo-znak-bily.png`)
40
+ // here, not the auto-theming inline SVG header.html uses. That's not
41
+ // incidental: the footer's background is ALWAYS pure black (--bg-black is
42
+ // hsl(0,0%,0%) in both themes, see tokens.css) but --JELINEK-WHITE itself
43
+ // INVERTS in dark mode (tokens.css:91, becomes hsl(0,0%,0%)) — the
44
+ // re-theming mark would go invisible against its own footer in dark mode.
45
+ // So, unlike SiteHeader, no bundled-SVG default was added here for the
46
+ // `logo` prop (kept as a plain URL string, per the brief) — flagged as an
47
+ // open question in the Task 9 report: a theme-invariant white-only mark
48
+ // fallback is a reasonable future addition, just out of this task's
49
+ // explicit scope (the owner's logo direction named SiteHeader only).
50
+ const BASE = 'mt-[60px] rounded-t-section bg-surface-black text-fg-inverse';
51
+ </script>
52
+
53
+ <footer {...rest} class={cn(BASE, className)}>
54
+ <div class="mx-auto flex max-w-page flex-wrap items-center gap-6 px-6 py-10">
55
+ {#if logo}
56
+ <!-- jelinek.css:767 — 52px square, 12px radius. Neither value is on
57
+ Tailwind's default scale (nearest square size is 48/56px;
58
+ nearest radius token, radius-card, clamps 10-15px) — both
59
+ arbitrary. The brief's h-8 w-auto (32px, unconstrained width)
60
+ was wrong on every count. -->
61
+ <img src={logo} alt={logoAlt} class="h-[52px] w-[52px] rounded-[12px]" />
62
+ {/if}
63
+ {#if slogan || meta}
64
+ <div>
65
+ {#if slogan}
66
+ <!-- jelinek.css:769 (.slogan): font-weight 700 only — no
67
+ font-family/size rule of its own, so it inherits body's
68
+ defaults (font-serif/Archer, step0/1rem, leading 1.6,
69
+ jelinek.css:264-267). Set explicitly rather than relying
70
+ on an ambient body reset the consuming app may not have,
71
+ same reasoning Checkbox/Card use for their own inherited
72
+ values. -->
73
+ <div class="font-serif text-step0 font-bold leading-[1.6]">{slogan}</div>
74
+ {/if}
75
+ {#if meta}
76
+ <!-- The real footer.html actually renders a THIRD, distinct
77
+ block here styled `.small.muted2` (jelinek.css:306,304:
78
+ step-down1/1.1/weight 300, text-muted2) for the address
79
+ line, plus a separate `.meta`-classed block (jelinek.css:770)
80
+ for a page-specific build-version stamp that isn't
81
+ meaningful outside this static site. This component's
82
+ `meta` prop maps to the general-purpose one — the
83
+ `.small.muted2` address-line treatment — not the
84
+ name-colliding `.site-footer .meta` CSS class (which pins
85
+ itself to the far right via margin-left:auto and is really
86
+ "wherever the deploy timestamp goes", not a generic
87
+ "meta text" concept). Task 18 adds that third block as its
88
+ own `stamp` prop below — kept a separate prop, not folded
89
+ into `meta`, for exactly the reason this paragraph already
90
+ explains: same CSS class name in the guide, different
91
+ element, different font, different position.
92
+
93
+ Fix round 1: `.small` is also in the shared font-secondary
94
+ selector list (jelinek.css:313-317, `.small` listed
95
+ alongside `.site-header__nav a`/`.chapter-nav a`/`.pager a`
96
+ etc.) — missing from the first pass, which left this line
97
+ inheriting the ambient serif body font instead. -->
98
+ <div class="font-secondary text-step-down1 font-light leading-[1.1] text-fg-muted2">{meta}</div>
99
+ {/if}
100
+ </div>
101
+ {/if}
102
+ {#if stamp}
103
+ <!-- jelinek.css:770 (`.site-footer .meta`): margin-left:auto pins it
104
+ to the far right of `.site-footer__inner`'s flex row — so this
105
+ has to be a sibling of the logo/slogan-wrapper above, not nested
106
+ inside either, or margin-left:auto pins it right only within that
107
+ inner wrapper instead of the whole footer. `0.85rem` is a literal
108
+ in the guide, not on the --step scale — arbitrary `text-[0.85rem]`,
109
+ not the nearest step. No font-family rule of its own (unlike
110
+ `.small`, this class is NOT in the jelinek.css:313-317
111
+ font-secondary list), so — same reasoning as `slogan` above — it
112
+ inherits body's ambient Archer, set explicitly here rather than
113
+ relying on a consuming app's own reset to provide it. No
114
+ `flex-wrap` breakpoint needed: `.site-footer__inner`'s own
115
+ `flex-wrap: wrap` (already `flex-wrap` below) already drops this
116
+ onto its own line under the logo/text on narrow screens, same as
117
+ the guide. -->
118
+ <div class="ml-auto font-serif text-[0.85rem] text-fg-muted2">{stamp}</div>
119
+ {/if}
120
+ </div>
121
+ </footer>
@@ -0,0 +1,13 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ interface Props {
3
+ logo?: string;
4
+ logoAlt?: string;
5
+ slogan?: string;
6
+ meta?: string;
7
+ stamp?: string;
8
+ class?: string;
9
+ }
10
+ type $$ComponentProps = Props & HTMLAttributes<HTMLElement>;
11
+ declare const SiteFooter: import("svelte").Component<$$ComponentProps, {}, "">;
12
+ type SiteFooter = ReturnType<typeof SiteFooter>;
13
+ export default SiteFooter;