@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,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { cn } from '../utils/cn.js';
|
|
5
|
+
|
|
6
|
+
type Color = 'brown' | 'magenta';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
href?: string;
|
|
10
|
+
active?: boolean;
|
|
11
|
+
color?: Color;
|
|
12
|
+
class?: string;
|
|
13
|
+
children: Snippet;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
href,
|
|
18
|
+
active = false,
|
|
19
|
+
color = 'brown',
|
|
20
|
+
class: className,
|
|
21
|
+
children,
|
|
22
|
+
...rest
|
|
23
|
+
}: Props & Omit<HTMLAnchorAttributes & HTMLAttributes<HTMLDivElement>, 'href'> = $props();
|
|
24
|
+
|
|
25
|
+
const ACTIVE: Record<Color, string> = {
|
|
26
|
+
brown: 'border-jelinek-brown bg-surface-brown',
|
|
27
|
+
magenta: 'border-jelinek-magenta bg-surface-magenta'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// jelinek.css:474-481 (shared surface, --box-accent unset by .tile so it
|
|
31
|
+
// defaults to --border-subtle at both gradient stops) collapses to a flat
|
|
32
|
+
// 1px border-border-subtle, same reasoning as Card. jelinek.css:641-649
|
|
33
|
+
// (.tile): padding is 22px, NOT 24px — p-6 was wrong, this is the arbitrary
|
|
34
|
+
// p-[22px] (tile deliberately has its own padding, distinct from
|
|
35
|
+
// .panel/.card/.demo's 24px, and — unlike those three — carries no
|
|
36
|
+
// margin-bottom of its own to drop). color:var(--text-black) was missing
|
|
37
|
+
// entirely (text-fg). box-shadow:var(--shadow-resting) at rest was missing
|
|
38
|
+
// entirely; only the :hover shadow-interactive had been kept, with no
|
|
39
|
+
// resting state to raise it from. jelinek.css:322-325 (shared surface
|
|
40
|
+
// block) puts .tile — explicitly listed alongside .card/.panel/.alert/
|
|
41
|
+
// .demo/.swatch — on font-secondary at font-weight 300 (Light), which the
|
|
42
|
+
// draft omitted altogether in favour of a plain "font-normal" (400).
|
|
43
|
+
// That 400 traces to jelinek.css:285's `.tile, .pager a { font-weight:
|
|
44
|
+
// 400 }`, whose real job is neutralising the global `a { font-weight:
|
|
45
|
+
// 700 }` bold — but :322-325 is later in the file at equal specificity
|
|
46
|
+
// (single class selector, same as :285's `.tile`) and so is the value
|
|
47
|
+
// that actually wins the cascade: font-light, not font-normal.
|
|
48
|
+
// jelinek.css:327-331 additionally raises nested strong/b back to
|
|
49
|
+
// font-weight 500, and :648/:652 give a Tile's own h3/p their own spacing
|
|
50
|
+
// and size (h3: 6px bottom margin + --step1 size/1.3 line-height; p: 0
|
|
51
|
+
// margin, 0.9rem, --text-muted1). Tile's children are an opaque Snippet,
|
|
52
|
+
// so these are reproduced as compound arbitrary variants on the root
|
|
53
|
+
// element rather than as JSX/markup this component controls directly —
|
|
54
|
+
// Card did not do this for its own equivalent h3/strong/b rules (Task 5),
|
|
55
|
+
// so this is a deliberate inconsistency between the two components,
|
|
56
|
+
// flagged for adjudication: either Card should get the same treatment
|
|
57
|
+
// later, or this should be dropped from Tile to match Card's precedent.
|
|
58
|
+
// Task 31 — the transition is the guide's own two properties and its own
|
|
59
|
+
// duration/easing tokens (jelinek.css:924: `transition: box-shadow
|
|
60
|
+
// var(--dur-base) var(--ease), transform var(--dur-base) var(--ease)`),
|
|
61
|
+
// replacing a bare `transition-all`. That matters here beyond tidiness:
|
|
62
|
+
// `.tile.is-active` (jelinek.css:927) flips border-color AND background, and
|
|
63
|
+
// `transition-all` was fading both of those too, which the guide never does
|
|
64
|
+
// — an active tile now snaps to its brown state and only the shadow/lift
|
|
65
|
+
// animate. See Button.svelte's own comment for why --ease has to be named
|
|
66
|
+
// explicitly (Tailwind's default curve is not the guide's `ease`) and why
|
|
67
|
+
// this is not solved globally in extras.css.
|
|
68
|
+
const BASE =
|
|
69
|
+
'block rounded-card border border-border-subtle bg-surface p-[22px] ' +
|
|
70
|
+
'font-secondary font-light text-fg no-underline ' +
|
|
71
|
+
'transition-[box-shadow,transform] duration-[var(--dur-base)] ease-[var(--ease)] ' +
|
|
72
|
+
'shadow-resting hover:-translate-y-0.5 hover:shadow-interactive ' +
|
|
73
|
+
'[&_h3]:mb-1.5 [&_h3]:text-step1 [&_h3]:leading-[1.3] ' +
|
|
74
|
+
'[&_p]:m-0 [&_p]:text-[0.9rem] [&_p]:text-fg-muted1 ' +
|
|
75
|
+
'[&_strong]:font-medium [&_b]:font-medium ' +
|
|
76
|
+
// jelinek.css:259-260 (dark mode only): the resting/hover shadow gains an
|
|
77
|
+
// extra inset highlight, `inset 0 2px 1px -1px var(--box-primary)`.
|
|
78
|
+
//
|
|
79
|
+
// TASK 33 — this is now the real token, not a substitute. The designer's
|
|
80
|
+
// 4ec82cd ("jeden neutrální highlight pro tiles i menu") promoted
|
|
81
|
+
// --box-primary from a per-surface local custom property to ONE :root
|
|
82
|
+
// declaration, `--box-primary: hsl(0, 0%, 50%)` (jelinek.css:135), and
|
|
83
|
+
// deleted the local `--box-primary: var(--JELINEK-BLACK)` this rule's
|
|
84
|
+
// shared surface block used to carry. So the generator does emit it now
|
|
85
|
+
// (it is a PASSTHROUGH entry, see scripts/token-map.js for why it is not
|
|
86
|
+
// a Tailwind theme token) and it is referenced by name.
|
|
87
|
+
//
|
|
88
|
+
// The previous stand-in, var(--color-jelinek-black), was correct against
|
|
89
|
+
// the OLD guide and is wrong against this one: --JELINEK-BLACK inverts to
|
|
90
|
+
// hsl(0,0%,95%) in dark mode, so it would paint a near-white hairline
|
|
91
|
+
// where the guide now asks for a flat 50% grey. The css-lock gate caught
|
|
92
|
+
// the source rule moving; this is the component half of that.
|
|
93
|
+
'dark:shadow-[var(--shadow-resting),inset_0_2px_1px_-1px_var(--box-primary)] ' +
|
|
94
|
+
'dark:hover:shadow-[var(--shadow-interactive),inset_0_2px_1px_-1px_var(--box-primary)]';
|
|
95
|
+
|
|
96
|
+
// Only the linked form gets the hand cursor, and only because a tile is a
|
|
97
|
+
// whole-surface click target: the hover lift already promises "this does
|
|
98
|
+
// something", and a promise the cursor doesn't back up reads as a broken
|
|
99
|
+
// card. Every browser's UA sheet already gives `a[href]` cursor:pointer, so
|
|
100
|
+
// this is belt-and-braces against a host page (or an extension) that resets
|
|
101
|
+
// it — Tailwind's own preflight does exactly that to `button`, which is why
|
|
102
|
+
// Button.svelte carries a cursor-pointer too. Deliberately NOT on the `div`
|
|
103
|
+
// branch: a tile without `href` is inert, and a hand cursor over something
|
|
104
|
+
// that cannot be clicked is worse than no affordance at all. The guide's
|
|
105
|
+
// .tile (jelinek.css:917-928) declares no cursor of its own, so this is a
|
|
106
|
+
// kit-only addition, not a reproduction — the css-lock gate watches the
|
|
107
|
+
// guide's rule text and is unaffected.
|
|
108
|
+
const classes = $derived(cn(BASE, href && 'cursor-pointer', active && ACTIVE[color], className));
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
{#if href}
|
|
112
|
+
<a {...rest} {href} class={classes}>{@render children()}</a>
|
|
113
|
+
{:else}
|
|
114
|
+
<div {...rest} class={classes}>{@render children()}</div>
|
|
115
|
+
{/if}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Color = 'brown' | 'magenta';
|
|
4
|
+
interface Props {
|
|
5
|
+
href?: string;
|
|
6
|
+
active?: boolean;
|
|
7
|
+
color?: Color;
|
|
8
|
+
class?: string;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
}
|
|
11
|
+
type $$ComponentProps = Props & Omit<HTMLAnchorAttributes & HTMLAttributes<HTMLDivElement>, 'href'>;
|
|
12
|
+
declare const Tile: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
13
|
+
type Tile = ReturnType<typeof Tile>;
|
|
14
|
+
export default Tile;
|
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
/**
|
|
8
|
+
* Accessible names for the two scroll arrows.
|
|
9
|
+
*
|
|
10
|
+
* DELIBERATELY NOT the guide's strings. assets/js/jelinek.js:502-503
|
|
11
|
+
* labels every scroller's arrows "Posunout na předchozí/další položky",
|
|
12
|
+
* because one `arrow()` helper builds them for `.tabs`,
|
|
13
|
+
* `.logo-tool__bgtabs` and `.tile-scroller` alike (jelinek.js:475). Put a
|
|
14
|
+
* Tabs and a TileScroller on one page — /ui/komponenty does exactly that
|
|
15
|
+
* — and a screen-reader user hears four buttons with two names between
|
|
16
|
+
* them, with nothing to say which row moves. Naming the thing that
|
|
17
|
+
* actually moves is worth the divergence; a caller who wants the guide's
|
|
18
|
+
* wording back can pass it.
|
|
19
|
+
*/
|
|
20
|
+
prevLabel?: string;
|
|
21
|
+
nextLabel?: string;
|
|
22
|
+
class?: string;
|
|
23
|
+
children: Snippet;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
prevLabel = 'Posunout dlaždice zpět',
|
|
28
|
+
nextLabel = 'Posunout dlaždice dál',
|
|
29
|
+
class: className,
|
|
30
|
+
children,
|
|
31
|
+
...rest
|
|
32
|
+
}: Props & HTMLAttributes<HTMLDivElement> = $props();
|
|
33
|
+
|
|
34
|
+
// ─── WHY THIS IS A COMPONENT AND NOT A `class` ON Grid ──────────────────
|
|
35
|
+
//
|
|
36
|
+
// jelinek.css:930-944 (`.tile-scroller`) is a full sibling of `.grid`: a
|
|
37
|
+
// one-line row that scrolls sideways instead of wrapping, for the case where
|
|
38
|
+
// a listing is a *rozcestník* (subcategories, recommended products) rather
|
|
39
|
+
// than the complete offer. The guide documents it as its own component in
|
|
40
|
+
// ui-old/komponenty.html#posuvne-dlazdice, and Task 35 brought that section
|
|
41
|
+
// into /ui/komponenty — which is what this exists to render.
|
|
42
|
+
//
|
|
43
|
+
// It is deliberately NOT a Grid variant. Grid's whole contract is "wrap into
|
|
44
|
+
// as many rows as it takes"; this one's is "never wrap, and let the last
|
|
45
|
+
// child be half-visible so the reader can see the row continues". Those are
|
|
46
|
+
// opposite answers to the same question, and a `scroll` prop on Grid would
|
|
47
|
+
// make every Grid caller carry the scroll machinery below.
|
|
48
|
+
//
|
|
49
|
+
// ROW — jelinek.css:930-940, one to one. gap-6 is var(--space-3) = 24px
|
|
50
|
+
// (theme.css pins --spacing to a literal 4px, so 6 × 4 = 24 exactly; see
|
|
51
|
+
// Tabs.svelte's note on why that pin matters). `scroll-snap-type: x
|
|
52
|
+
// proximity`, the hidden scrollbar and `scroll-behavior: smooth` have no
|
|
53
|
+
// Tailwind shorthand in this setup and stay arbitrary properties.
|
|
54
|
+
//
|
|
55
|
+
// THE PADDING/MARGIN PAIR IS LEVEL WITH THE SHADOWS, and the guide's own
|
|
56
|
+
// comment (jelinek.css:934-936) says why: a scroll container clips its
|
|
57
|
+
// content, so the tiles' shadow needs room — 12px each side, 24px below
|
|
58
|
+
// (how far the hover shadow reaches) and 4px above (the tile lifts by 2px).
|
|
59
|
+
// The negative side margins then cancel the horizontal reserve so the row
|
|
60
|
+
// still lines up with the surrounding text. Change one number without the
|
|
61
|
+
// other and either the shadow is sliced off or the row sits inset.
|
|
62
|
+
//
|
|
63
|
+
// `scroll-padding-inline: 12px` (jelinek.css:938) is the fix for what that
|
|
64
|
+
// pair breaks: without it, snapping ignores the 12px reserve and parks every
|
|
65
|
+
// tile 12px to the left of where the row's content actually starts.
|
|
66
|
+
const ROW =
|
|
67
|
+
'flex flex-nowrap gap-6 overflow-x-auto [scroll-behavior:smooth] ' +
|
|
68
|
+
'[scroll-snap-type:x_proximity] [scrollbar-width:none] [-ms-overflow-style:none] ' +
|
|
69
|
+
'[&::-webkit-scrollbar]:hidden ' +
|
|
70
|
+
'pt-1 pr-3 pb-6 pl-3 -mt-1 -mr-3 -ml-3 [scroll-padding-inline:12px] ' +
|
|
71
|
+
// jelinek.css:941-944 — how many tiles are visible at each breakpoint.
|
|
72
|
+
// Every basis is deliberately short of a whole division of the row (80,
|
|
73
|
+
// not 100; 46, not 50; 31, not 33.3; 23, not 25) so the NEXT tile is
|
|
74
|
+
// always partly on screen. That sliver is the affordance; the arrows are
|
|
75
|
+
// the second one, for people who do not read slivers.
|
|
76
|
+
'[&>*]:flex-[0_0_80%] [&>*]:[scroll-snap-align:start] ' +
|
|
77
|
+
'min-[640px]:[&>*]:flex-[0_0_46%] min-[1024px]:[&>*]:flex-[0_0_31%] ' +
|
|
78
|
+
'min-[1440px]:[&>*]:flex-[0_0_23%]';
|
|
79
|
+
|
|
80
|
+
// WRAPPER — jelinek.css:948 (`.tabs-scroll--below`) reserves 44px under the
|
|
81
|
+
// row for the arrow pair.
|
|
82
|
+
const WRAP = 'relative min-w-0 pb-11';
|
|
83
|
+
|
|
84
|
+
// ARROWS — jelinek.css:949-956. The `--below` variant differs from Tabs' in
|
|
85
|
+
// three ways, all of them deliberate in the guide: they sit UNDER the row
|
|
86
|
+
// rather than over its edges, they are 34px rather than 30, and at the end
|
|
87
|
+
// of the row they only DIM (opacity .35, pointer-events: none) instead of
|
|
88
|
+
// disappearing. The guide's comment gives the reason for the third —
|
|
89
|
+
// "aby dvojice šipek neposkakovala", so the pair does not jump about as you
|
|
90
|
+
// scroll. It also means neither button ever unmounts, so this component
|
|
91
|
+
// needs none of Tabs' focus hand-off: a keyboard user who exhausts one
|
|
92
|
+
// direction still has both buttons under their finger.
|
|
93
|
+
//
|
|
94
|
+
// Colours, radius, shadow and the transition pair are Tabs' ARROW verbatim
|
|
95
|
+
// (jelinek.css:858-866 + :872-873) — one shared rule in the guide, one
|
|
96
|
+
// shared appearance here.
|
|
97
|
+
const ARROW =
|
|
98
|
+
'absolute bottom-0 flex h-[34px] w-[34px] items-center justify-center ' +
|
|
99
|
+
'rounded-pill border-0 p-0 bg-surface text-fg-muted1 shadow-resting cursor-pointer ' +
|
|
100
|
+
'transition-[background-color,color] duration-[var(--dur-base)] ease-[var(--ease)] ' +
|
|
101
|
+
'hover:bg-jelinek-black hover:text-on-jelinek-black ' +
|
|
102
|
+
'disabled:opacity-35 disabled:pointer-events-none disabled:cursor-default';
|
|
103
|
+
|
|
104
|
+
// State comes from the element, never from a breakpoint — a three-tile row
|
|
105
|
+
// on a wide screen does not overflow and correctly shows no arrows. Both
|
|
106
|
+
// start at 0 so the server-rendered HTML has no buttons; hydration adds them
|
|
107
|
+
// only if the row really does overflow. Slack of 2px is the guide's own
|
|
108
|
+
// (jelinek.js:506-508), so sub-pixel layout never leaves an arrow lit that
|
|
109
|
+
// cannot actually move.
|
|
110
|
+
let scroller = $state<HTMLElement | null>(null);
|
|
111
|
+
let scrollLeft = $state(0);
|
|
112
|
+
let scrollMax = $state(0);
|
|
113
|
+
const overflowing = $derived(scrollMax > 2);
|
|
114
|
+
const atStart = $derived(scrollLeft <= 2);
|
|
115
|
+
const atEnd = $derived(scrollLeft >= scrollMax - 2);
|
|
116
|
+
|
|
117
|
+
$effect(() => {
|
|
118
|
+
const el = scroller;
|
|
119
|
+
if (!el) return;
|
|
120
|
+
function measure() {
|
|
121
|
+
if (!el) return;
|
|
122
|
+
scrollLeft = el.scrollLeft;
|
|
123
|
+
scrollMax = el.scrollWidth - el.clientWidth;
|
|
124
|
+
}
|
|
125
|
+
measure();
|
|
126
|
+
el.addEventListener('scroll', measure, { passive: true });
|
|
127
|
+
const ro = new ResizeObserver(measure);
|
|
128
|
+
ro.observe(el);
|
|
129
|
+
return () => {
|
|
130
|
+
el.removeEventListener('scroll', measure);
|
|
131
|
+
ro.disconnect();
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// jelinek.js:493-497 — 80% of the visible width per press, smooth. That is
|
|
136
|
+
// the same 80% as the mobile flex-basis by coincidence of the design, not by
|
|
137
|
+
// derivation: one press advances roughly one tile on a phone and roughly
|
|
138
|
+
// three on a wide screen, because the step is a share of the VIEWPORT, not
|
|
139
|
+
// of the tile.
|
|
140
|
+
function nudge(dir: 'prev' | 'next') {
|
|
141
|
+
const el = scroller;
|
|
142
|
+
if (!el) return;
|
|
143
|
+
const step = Math.round(el.clientWidth * 0.8) * (dir === 'prev' ? -1 : 1);
|
|
144
|
+
el.scrollBy({ left: step, behavior: 'smooth' });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// jelinek.js:459-463 (`chevron()`), byte for byte — see Tabs.svelte.
|
|
148
|
+
const CHEVRON = { prev: 'M15 6l-6 6 6 6', next: 'M9 6l6 6-6 6' };
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<div class={WRAP}>
|
|
152
|
+
<div bind:this={scroller} {...rest} class={cn(ROW, className)}>
|
|
153
|
+
{@render children()}
|
|
154
|
+
</div>
|
|
155
|
+
{#if overflowing}
|
|
156
|
+
<button
|
|
157
|
+
type="button"
|
|
158
|
+
class={cn(ARROW, 'left-[calc(50%-40px)]')}
|
|
159
|
+
aria-label={prevLabel}
|
|
160
|
+
disabled={atStart}
|
|
161
|
+
onclick={() => nudge('prev')}
|
|
162
|
+
>
|
|
163
|
+
<svg
|
|
164
|
+
width="16"
|
|
165
|
+
height="16"
|
|
166
|
+
viewBox="0 0 24 24"
|
|
167
|
+
fill="none"
|
|
168
|
+
stroke="currentColor"
|
|
169
|
+
stroke-width="2.5"
|
|
170
|
+
stroke-linecap="round"
|
|
171
|
+
stroke-linejoin="round"
|
|
172
|
+
aria-hidden="true"
|
|
173
|
+
>
|
|
174
|
+
<path d={CHEVRON.prev} />
|
|
175
|
+
</svg>
|
|
176
|
+
</button>
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
class={cn(ARROW, 'right-[calc(50%-40px)]')}
|
|
180
|
+
aria-label={nextLabel}
|
|
181
|
+
disabled={atEnd}
|
|
182
|
+
onclick={() => nudge('next')}
|
|
183
|
+
>
|
|
184
|
+
<svg
|
|
185
|
+
width="16"
|
|
186
|
+
height="16"
|
|
187
|
+
viewBox="0 0 24 24"
|
|
188
|
+
fill="none"
|
|
189
|
+
stroke="currentColor"
|
|
190
|
+
stroke-width="2.5"
|
|
191
|
+
stroke-linecap="round"
|
|
192
|
+
stroke-linejoin="round"
|
|
193
|
+
aria-hidden="true"
|
|
194
|
+
>
|
|
195
|
+
<path d={CHEVRON.next} />
|
|
196
|
+
</svg>
|
|
197
|
+
</button>
|
|
198
|
+
{/if}
|
|
199
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props {
|
|
4
|
+
/**
|
|
5
|
+
* Accessible names for the two scroll arrows.
|
|
6
|
+
*
|
|
7
|
+
* DELIBERATELY NOT the guide's strings. assets/js/jelinek.js:502-503
|
|
8
|
+
* labels every scroller's arrows "Posunout na předchozí/další položky",
|
|
9
|
+
* because one `arrow()` helper builds them for `.tabs`,
|
|
10
|
+
* `.logo-tool__bgtabs` and `.tile-scroller` alike (jelinek.js:475). Put a
|
|
11
|
+
* Tabs and a TileScroller on one page — /ui/komponenty does exactly that
|
|
12
|
+
* — and a screen-reader user hears four buttons with two names between
|
|
13
|
+
* them, with nothing to say which row moves. Naming the thing that
|
|
14
|
+
* actually moves is worth the divergence; a caller who wants the guide's
|
|
15
|
+
* wording back can pass it.
|
|
16
|
+
*/
|
|
17
|
+
prevLabel?: string;
|
|
18
|
+
nextLabel?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
children: Snippet;
|
|
21
|
+
}
|
|
22
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
|
|
23
|
+
declare const TileScroller: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
24
|
+
type TileScroller = ReturnType<typeof TileScroller>;
|
|
25
|
+
export default TileScroller;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CONTAINER_CONTEXT: unique symbol;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Marker context: "content rendered here already sits inside a `Container`
|
|
2
|
+
// (jelinek.css:287's `.container` — mx-auto/max-w-page/px-6)." No payload
|
|
3
|
+
// beyond presence is needed — unlike TABS_CONTEXT/RADIO_CONTEXT, which carry
|
|
4
|
+
// live state a child reads/writes, this is a one-way "is an ancestor already
|
|
5
|
+
// doing this job" signal, checked once via `hasContext()` (see PageHero.svelte).
|
|
6
|
+
export const CONTAINER_CONTEXT = Symbol('jelinek-container');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JELÍNEK deer-antler mark's SVG path data (the `d` attribute inside a
|
|
3
|
+
* `0 0 400 400` viewBox), verified byte-identical across every place the
|
|
4
|
+
* guide draws it: partials/header.html:4, all seven `.content-logo__svg`
|
|
5
|
+
* instances in ui/logo.html, and the copy SiteHeader.svelte already carried
|
|
6
|
+
* inline. Extracted here (Task 23, ContentLogo) so SiteHeader.svelte and
|
|
7
|
+
* ContentLogo.svelte share one copy of this ~490-character string instead
|
|
8
|
+
* of two that could silently drift apart — the surrounding markup around
|
|
9
|
+
* the path differs between the two components (SiteHeader's <rect> has no
|
|
10
|
+
* corner radius and re-themes via fill-jelinek-black/-white utility
|
|
11
|
+
* classes; ContentLogo's <rect> is a literal fill="#000" rounded square,
|
|
12
|
+
* rx/ry 25, jelinek.css:743-757, and the path itself is a literal
|
|
13
|
+
* fill="currentColor" so only the antlers — not the square behind them —
|
|
14
|
+
* follow the content division), only the path data itself is identical.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DEER_MARK_PATH = "M216.7,187.9h26.5c1,0,2,0,3,0,12.6,0,26.9.1,36.7-9.8,6.8-6.9,10.1-17.4,10.1-32h-12.7c0,10.8-2.1,18.5-6.4,22.8-6.1,6.1-17,6.1-27.7,6-1,0-2.1,0-3.1,0h-13.7v-33.5h-12.7v33.5h-23.2l-55-31v-48.2h-12.7v41l-9.1-5.1h-.3c-.5-.4-53.6-27.6-54.2-69.3l-12.7.2c.7,49,57.1,78.5,61.2,80.5l56.5,31.8h-41.4v6.5c0,12.5,5,19.7,11.5,25.4,6.5,5.7,22.9,5.7,22.9,5.7h19.3v-13h-22.9c-9.6,0-15.4-3.9-17.4-11.7h51l79.9,42.3v27.2l-32.6-12.5-2.4-.5-99,53.6v13.7l2.6-.3,99.6-53.6,34,13.2,10.4-4.7v-39.9l-3.3-5.7-63-32.8Z";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The JELÍNEK deer-antler mark's SVG path data (the `d` attribute inside a
|
|
3
|
+
* `0 0 400 400` viewBox), verified byte-identical across every place the
|
|
4
|
+
* guide draws it: partials/header.html:4, all seven `.content-logo__svg`
|
|
5
|
+
* instances in ui/logo.html, and the copy SiteHeader.svelte already carried
|
|
6
|
+
* inline. Extracted here (Task 23, ContentLogo) so SiteHeader.svelte and
|
|
7
|
+
* ContentLogo.svelte share one copy of this ~490-character string instead
|
|
8
|
+
* of two that could silently drift apart — the surrounding markup around
|
|
9
|
+
* the path differs between the two components (SiteHeader's <rect> has no
|
|
10
|
+
* corner radius and re-themes via fill-jelinek-black/-white utility
|
|
11
|
+
* classes; ContentLogo's <rect> is a literal fill="#000" rounded square,
|
|
12
|
+
* rx/ry 25, jelinek.css:743-757, and the path itself is a literal
|
|
13
|
+
* fill="currentColor" so only the antlers — not the square behind them —
|
|
14
|
+
* follow the content division), only the path data itself is identical.
|
|
15
|
+
*/
|
|
16
|
+
export const DEER_MARK_PATH = 'M216.7,187.9h26.5c1,0,2,0,3,0,12.6,0,26.9.1,36.7-9.8,6.8-6.9,10.1-17.4,10.1-32h-12.7c0,10.8-2.1,18.5-6.4,22.8-6.1,6.1-17,6.1-27.7,6-1,0-2.1,0-3.1,0h-13.7v-33.5h-12.7v33.5h-23.2l-55-31v-48.2h-12.7v41l-9.1-5.1h-.3c-.5-.4-53.6-27.6-54.2-69.3l-12.7.2c.7,49,57.1,78.5,61.2,80.5l56.5,31.8h-41.4v6.5c0,12.5,5,19.7,11.5,25.4,6.5,5.7,22.9,5.7,22.9,5.7h19.3v-13h-22.9c-9.6,0-15.4-3.9-17.4-11.7h51l79.9,42.3v27.2l-32.6-12.5-2.4-.5-99,53.6v13.7l2.6-.3,99.6-53.6,34,13.2,10.4-4.7v-39.9l-3.3-5.7-63-32.8Z';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type NavItem = {
|
|
2
|
+
href: string;
|
|
3
|
+
label: string;
|
|
4
|
+
/**
|
|
5
|
+
* How `active` is compared against this item's `href`.
|
|
6
|
+
*
|
|
7
|
+
* `'exact'` (the default) is right for a chapter link: only that page lights
|
|
8
|
+
* it up. `'prefix'` is for a SECTION link — the guide's own header marks
|
|
9
|
+
* "Brand guide" and "UI guide" active on every page inside them, not just on
|
|
10
|
+
* the section's index (partials/header.html:8-10 does it with the SSI `$nav`
|
|
11
|
+
* variable, which each page sets to `uvod`/`brand`/`ui`). Without this a
|
|
12
|
+
* top-level section link is lit on exactly one URL and dead on the twenty
|
|
13
|
+
* pages the reader actually gets there from.
|
|
14
|
+
*
|
|
15
|
+
* A `'prefix'` item whose href is `/` would match everything, so the root is
|
|
16
|
+
* special-cased to an exact compare — see isActive() in SiteHeader.
|
|
17
|
+
*/
|
|
18
|
+
match?: 'exact' | 'prefix';
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Does `pathname` count as being on `item`? Exported because SiteHeader renders
|
|
22
|
+
* the same list twice (the ≥640px row and the phone panel) and ChapterNav takes
|
|
23
|
+
* the same shape.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isActive(item: NavItem, pathname: string | undefined): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Does `pathname` count as being on `item`? Exported because SiteHeader renders
|
|
3
|
+
* the same list twice (the ≥640px row and the phone panel) and ChapterNav takes
|
|
4
|
+
* the same shape.
|
|
5
|
+
*/
|
|
6
|
+
export function isActive(item, pathname) {
|
|
7
|
+
if (pathname === undefined)
|
|
8
|
+
return false;
|
|
9
|
+
if (item.match !== 'prefix')
|
|
10
|
+
return item.href === pathname;
|
|
11
|
+
// A bare "/" prefix would match every path, which is never what a section
|
|
12
|
+
// link means. Compare exactly instead.
|
|
13
|
+
const base = item.href.replace(/\/$/, '');
|
|
14
|
+
if (base === '')
|
|
15
|
+
return pathname === '/' || pathname === '';
|
|
16
|
+
return pathname === base || pathname.startsWith(`${base}/`);
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const RADIO_CONTEXT = Symbol('jelinek-radio-group');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const TABS_CONTEXT = Symbol('jelinek-tabs');
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export { cn } from './utils/cn.js';
|
|
2
|
+
export { createChapterNavDock } from './utils/chapter-nav-dock.svelte.js';
|
|
3
|
+
export { default as Alert } from './components/Alert.svelte';
|
|
4
|
+
export { default as Button } from './components/Button.svelte';
|
|
5
|
+
export { default as Card } from './components/Card.svelte';
|
|
6
|
+
export { default as ChapterNav } from './components/ChapterNav.svelte';
|
|
7
|
+
export { default as Checkbox } from './components/Checkbox.svelte';
|
|
8
|
+
export { default as CodeCopy } from './components/CodeCopy.svelte';
|
|
9
|
+
export { CONTAINER_CONTEXT } from './components/container-context.js';
|
|
10
|
+
export { default as Container } from './components/Container.svelte';
|
|
11
|
+
export { default as ContentLogo } from './components/ContentLogo.svelte';
|
|
12
|
+
export { default as Demo } from './components/Demo.svelte';
|
|
13
|
+
export { default as Eyebrow } from './components/Eyebrow.svelte';
|
|
14
|
+
export { default as Glass } from './components/Glass.svelte';
|
|
15
|
+
export { default as Grid } from './components/Grid.svelte';
|
|
16
|
+
export { default as Label } from './components/Label.svelte';
|
|
17
|
+
export { isActive, type NavItem } from './components/nav.js';
|
|
18
|
+
export { default as PageHero } from './components/PageHero.svelte';
|
|
19
|
+
export { default as Pager } from './components/Pager.svelte';
|
|
20
|
+
export { default as Panel } from './components/Panel.svelte';
|
|
21
|
+
export { default as ProductCard } from './components/ProductCard.svelte';
|
|
22
|
+
export { default as Radio } from './components/Radio.svelte';
|
|
23
|
+
export { RADIO_CONTEXT, type RadioContext } from './components/radio-context.js';
|
|
24
|
+
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
25
|
+
export { default as Select } from './components/Select.svelte';
|
|
26
|
+
export { default as SiteFooter } from './components/SiteFooter.svelte';
|
|
27
|
+
export { default as SiteHeader } from './components/SiteHeader.svelte';
|
|
28
|
+
export { default as Slider } from './components/Slider.svelte';
|
|
29
|
+
export { default as Swatch } from './components/Swatch.svelte';
|
|
30
|
+
export { default as Tab } from './components/Tab.svelte';
|
|
31
|
+
export { default as Tabs } from './components/Tabs.svelte';
|
|
32
|
+
export { TABS_CONTEXT, type TabsContext } from './components/tabs-context.js';
|
|
33
|
+
export { default as Tag } from './components/Tag.svelte';
|
|
34
|
+
export { default as TextField } from './components/TextField.svelte';
|
|
35
|
+
export { default as ThemeToggle } from './components/ThemeToggle.svelte';
|
|
36
|
+
export { default as Tile } from './components/Tile.svelte';
|
|
37
|
+
export { default as TileScroller } from './components/TileScroller.svelte';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export { cn } from './utils/cn.js';
|
|
2
|
+
export { createChapterNavDock } from './utils/chapter-nav-dock.svelte.js';
|
|
3
|
+
export { default as Alert } from './components/Alert.svelte';
|
|
4
|
+
export { default as Button } from './components/Button.svelte';
|
|
5
|
+
export { default as Card } from './components/Card.svelte';
|
|
6
|
+
export { default as ChapterNav } from './components/ChapterNav.svelte';
|
|
7
|
+
export { default as Checkbox } from './components/Checkbox.svelte';
|
|
8
|
+
export { default as CodeCopy } from './components/CodeCopy.svelte';
|
|
9
|
+
export { CONTAINER_CONTEXT } from './components/container-context.js';
|
|
10
|
+
export { default as Container } from './components/Container.svelte';
|
|
11
|
+
export { default as ContentLogo } from './components/ContentLogo.svelte';
|
|
12
|
+
export { default as Demo } from './components/Demo.svelte';
|
|
13
|
+
export { default as Eyebrow } from './components/Eyebrow.svelte';
|
|
14
|
+
export { default as Glass } from './components/Glass.svelte';
|
|
15
|
+
export { default as Grid } from './components/Grid.svelte';
|
|
16
|
+
export { default as Label } from './components/Label.svelte';
|
|
17
|
+
export { isActive } from './components/nav.js';
|
|
18
|
+
export { default as PageHero } from './components/PageHero.svelte';
|
|
19
|
+
export { default as Pager } from './components/Pager.svelte';
|
|
20
|
+
export { default as Panel } from './components/Panel.svelte';
|
|
21
|
+
export { default as ProductCard } from './components/ProductCard.svelte';
|
|
22
|
+
export { default as Radio } from './components/Radio.svelte';
|
|
23
|
+
export { RADIO_CONTEXT } from './components/radio-context.js';
|
|
24
|
+
export { default as RadioGroup } from './components/RadioGroup.svelte';
|
|
25
|
+
export { default as Select } from './components/Select.svelte';
|
|
26
|
+
export { default as SiteFooter } from './components/SiteFooter.svelte';
|
|
27
|
+
export { default as SiteHeader } from './components/SiteHeader.svelte';
|
|
28
|
+
export { default as Slider } from './components/Slider.svelte';
|
|
29
|
+
export { default as Swatch } from './components/Swatch.svelte';
|
|
30
|
+
export { default as Tab } from './components/Tab.svelte';
|
|
31
|
+
export { default as Tabs } from './components/Tabs.svelte';
|
|
32
|
+
export { TABS_CONTEXT } from './components/tabs-context.js';
|
|
33
|
+
export { default as Tag } from './components/Tag.svelte';
|
|
34
|
+
export { default as TextField } from './components/TextField.svelte';
|
|
35
|
+
export { default as ThemeToggle } from './components/ThemeToggle.svelte';
|
|
36
|
+
export { default as Tile } from './components/Tile.svelte';
|
|
37
|
+
export { default as TileScroller } from './components/TileScroller.svelte';
|