@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,294 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils/cn.js';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
value: string;
|
|
7
|
+
/**
|
|
8
|
+
* Reproduces jelinek.css:675-676 (`.swatch__meta code.is-empty`): a
|
|
9
|
+
* structurally-present but non-interactive, permanently-muted state for
|
|
10
|
+
* when there is nothing to copy. Native `disabled` handles both the
|
|
11
|
+
* click-suppression and the focus/keyboard-activation removal that the
|
|
12
|
+
* source's plain `cursor: default` alone doesn't give you.
|
|
13
|
+
*/
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Reproduces the archive's `.swatch__value` row (jelinek.css:1028-1039 +
|
|
17
|
+
* assets/js/jelinek.js:146-160): the code value plus a trailing copy-icon
|
|
18
|
+
* button, spread apart by the row. The icon is a second trigger for the
|
|
19
|
+
* same copy, and it is also where the feedback goes — jelinek.js's
|
|
20
|
+
* `flashCopied` turns the ICON --sem-success-strong for 1100ms and leaves
|
|
21
|
+
* the code text alone, so with the icon on, the text-swap feedback below
|
|
22
|
+
* is off. Default off: bare CodeCopy (prose `code[data-copy]`,
|
|
23
|
+
* jelinek.js:329-336) has no icon in the guide either.
|
|
24
|
+
*/
|
|
25
|
+
icon?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The guide's OTHER copy control: `code[data-copy]` in prose
|
|
28
|
+
* (assets/js/jelinek.js:329-336) — the dashed-chip typography of a bare
|
|
29
|
+
* <code> (base.css's `code` rule / jelinek.css:274-279), but clickable,
|
|
30
|
+
* with the same "zkopírováno ✓" text-swap this component already does.
|
|
31
|
+
* The DECISION note below predates this prop and stands for the
|
|
32
|
+
* DEFAULT look only: a chip without data-copy stays a bare <code>;
|
|
33
|
+
* this mode exists for the chips the archive marked copyable.
|
|
34
|
+
*/
|
|
35
|
+
chip?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* The guide's `.code-line code` (jelinek.css:985-992): a full-width grey
|
|
38
|
+
* block for one-or-more lines of example code, pre-wrap, muted text that
|
|
39
|
+
* darkens on hover, click copies the whole thing. Use for the multi-line
|
|
40
|
+
* snippets the archive rendered as `p.code-line`.
|
|
41
|
+
*/
|
|
42
|
+
block?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Visible text when it differs from what lands on the clipboard — the
|
|
45
|
+
* archive's `<code data-copy="33px">33 px</code>` shape. Defaults to
|
|
46
|
+
* `value`.
|
|
47
|
+
*/
|
|
48
|
+
label?: string;
|
|
49
|
+
class?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let {
|
|
53
|
+
value,
|
|
54
|
+
disabled = false,
|
|
55
|
+
icon = false,
|
|
56
|
+
chip = false,
|
|
57
|
+
block = false,
|
|
58
|
+
label,
|
|
59
|
+
class: className,
|
|
60
|
+
...rest
|
|
61
|
+
}: Props & Omit<HTMLButtonAttributes, 'type' | 'disabled'> = $props();
|
|
62
|
+
|
|
63
|
+
let copied = $state(false);
|
|
64
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
65
|
+
|
|
66
|
+
// Ports assets/js/jelinek.js:109-121 (`copyText`) verbatim in shape: the
|
|
67
|
+
// Clipboard API only when `navigator.clipboard` exists AND the page is a
|
|
68
|
+
// secure context (both required — `navigator.clipboard` is `undefined`
|
|
69
|
+
// outside one, e.g. plain http:// or an unsupported browser), else a
|
|
70
|
+
// hidden <textarea> + `document.execCommand('copy')` fallback. The
|
|
71
|
+
// previous version called `navigator.clipboard.writeText` with no guard:
|
|
72
|
+
// in a non-secure context that throws inside an async function (an
|
|
73
|
+
// unhandled rejection), `copied` never flips, and the button silently
|
|
74
|
+
// looks dead.
|
|
75
|
+
function writeToClipboard(text: string): Promise<void> {
|
|
76
|
+
// `!== false` rather than a plain truthy check: real browsers always
|
|
77
|
+
// give `isSecureContext` a genuine boolean, so this is identical to the
|
|
78
|
+
// guide's own check there. jsdom (this file's test environment) never
|
|
79
|
+
// implements the property at all and leaves it `undefined` — treated
|
|
80
|
+
// here as "not known to be insecure", not as falling back.
|
|
81
|
+
if (navigator.clipboard && window.isSecureContext !== false) {
|
|
82
|
+
return navigator.clipboard.writeText(text);
|
|
83
|
+
}
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
try {
|
|
86
|
+
const ta = document.createElement('textarea');
|
|
87
|
+
ta.value = text;
|
|
88
|
+
ta.style.position = 'fixed';
|
|
89
|
+
ta.style.opacity = '0';
|
|
90
|
+
document.body.appendChild(ta);
|
|
91
|
+
ta.focus();
|
|
92
|
+
ta.select();
|
|
93
|
+
document.execCommand('copy');
|
|
94
|
+
document.body.removeChild(ta);
|
|
95
|
+
resolve();
|
|
96
|
+
} catch (e) {
|
|
97
|
+
reject(e);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function copy(event: MouseEvent & { currentTarget: EventTarget & HTMLButtonElement }) {
|
|
103
|
+
if (disabled) return;
|
|
104
|
+
// try/catch so a rejection (either path above can reject) can never
|
|
105
|
+
// escape as an unhandled promise rejection — on failure the label is
|
|
106
|
+
// left unchanged rather than falsely showing the success text.
|
|
107
|
+
try {
|
|
108
|
+
await writeToClipboard(value);
|
|
109
|
+
copied = true;
|
|
110
|
+
clearTimeout(timer);
|
|
111
|
+
// assets/js/jelinek.js:319-326 — the text-swap window is 900ms; the
|
|
112
|
+
// icon flash (jelinek.js:123-132, flashCopied) is 1100ms. Neither is a
|
|
113
|
+
// round number, both are the guide's own.
|
|
114
|
+
timer = setTimeout(() => (copied = false), icon ? 1100 : 900);
|
|
115
|
+
} catch {
|
|
116
|
+
// Swallowed deliberately — see the try/catch comment above.
|
|
117
|
+
}
|
|
118
|
+
// Own work first, THEN the consumer's handler — same order as
|
|
119
|
+
// Tab/ThemeToggle/Radio. Calling rest.onclick BEFORE the copy attempt
|
|
120
|
+
// (as an earlier version of this fix did) meant a throwing consumer
|
|
121
|
+
// handler suppressed the copy entirely and reintroduced the unhandled
|
|
122
|
+
// rejection finding #3 removed: copy() is async, so a synchronous
|
|
123
|
+
// throw from rest.onclick before the try/catch was reached would
|
|
124
|
+
// reject copy()'s own returned promise, which nothing here awaits.
|
|
125
|
+
rest.onclick?.(event);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Svelte 5 replacement for onDestroy — never mix the two, it breaks hydration.
|
|
129
|
+
$effect(() => () => clearTimeout(timer));
|
|
130
|
+
|
|
131
|
+
// CSS FIDELITY — the brief's CodeCopy (rounded-button border bg-surface-gray
|
|
132
|
+
// px-2 py-1 hover:bg-surface-brown chip) matches NEITHER of the two `code`
|
|
133
|
+
// treatments jelinek.css actually defines, and is wrong on every property:
|
|
134
|
+
//
|
|
135
|
+
// jelinek.css:274-279, generic inline `code` (used in body copy, e.g.
|
|
136
|
+
// ui/komponenty.html:243 `<code>--tag-h</code>`): 12px monospace,
|
|
137
|
+
// `color:var(--text-muted2)`, `border:1px dashed var(--text-muted2)`,
|
|
138
|
+
// `border-radius:4px`, `padding:1px 6px`. Task 33: those two were a hardcoded
|
|
139
|
+
// #ccc until the designer's b45c536 put them on the token, so base.css's
|
|
140
|
+
// reproduction is `var(--color-fg-muted2)` rather than an arbitrary value. But this rule has NO `cursor:pointer` and NO `:hover`
|
|
141
|
+
// state anywhere — it is inert documentation typography for a plain
|
|
142
|
+
// `<code>` element sitting in prose, not a control. Grepping the guide
|
|
143
|
+
// confirms every real usage is exactly that (a bare property/token name
|
|
144
|
+
// mentioned mid-sentence), never something clickable.
|
|
145
|
+
//
|
|
146
|
+
// jelinek.css:669-676, `.swatch__meta code` (used by every real swatch,
|
|
147
|
+
// e.g. brand/barvy.html:64 `<code>#FFFFFF</code>` and
|
|
148
|
+
// assets/js/jelinek.js:142/319, which wires a document-level click
|
|
149
|
+
// listener to exactly this element to copy its `data-copy`/text content):
|
|
150
|
+
// `font-family: ui-monospace, Menlo, Consolas, monospace`, `color:
|
|
151
|
+
// var(--text-muted1)`, `font-size: 0.8rem`, `cursor: pointer`, `border: 0;
|
|
152
|
+
// padding: 0; border-radius: 0` (explicitly STRIPS the generic code
|
|
153
|
+
// chrome), `:hover { color: var(--text-black) }`, and `.is-empty` /
|
|
154
|
+
// `.is-empty:hover` pinning it to `var(--text-muted2)` and `cursor:
|
|
155
|
+
// default` when there is nothing to copy.
|
|
156
|
+
//
|
|
157
|
+
// DECISION: CodeCopy always renders the `.swatch__meta code` treatment,
|
|
158
|
+
// never the dashed-chip one, and gets no variant toggling between them.
|
|
159
|
+
// Reasoning: CodeCopy's own contract (Task 10 interfaces) is a `<button
|
|
160
|
+
// role>` that copies `value` on click and reports the action via
|
|
161
|
+
// aria-label — it is unconditionally an interactive control. The generic
|
|
162
|
+
// `code` rule's whole visual language (a dashed border) is the opposite
|
|
163
|
+
// signal — every real instance of it in the guide is static, cursor
|
|
164
|
+
// unchanged, no hover feedback, used for words like `backdrop-filter` or
|
|
165
|
+
// `.ai` inline in a sentence. Skinning an actual `<button>` as "inert
|
|
166
|
+
// text you can't act on" is a UX/affordance bug, not a faithful
|
|
167
|
+
// reproduction — the *other* rule is the one describing a clickable code
|
|
168
|
+
// value. The dashed-chip look is real and worth having in the kit, but as
|
|
169
|
+
// a plain, non-interactive `<code>` typography default (applied globally
|
|
170
|
+
// to bare `<code>` in prose, the way jelinek.css itself does it with a
|
|
171
|
+
// bare-element selector), not as a mode of a component whose entire job
|
|
172
|
+
// is being clicked. That is out of scope for Task 10's four components.
|
|
173
|
+
//
|
|
174
|
+
// jelinek.css:322-325/327-331 (shared `.card/.panel/.alert/.tile/.demo/
|
|
175
|
+
// .swatch` block) does not apply here: `.swatch__meta code` is never
|
|
176
|
+
// listed in that selector group, and lives inside `.swatch__meta`, not
|
|
177
|
+
// directly inside `.swatch` — its font-family/size/weight are fully
|
|
178
|
+
// specified by :669-676 alone.
|
|
179
|
+
//
|
|
180
|
+
// `.swatch__meta code.is-copied` does not exist in jelinek.css — only the
|
|
181
|
+
// sibling `.swatch__copy` icon button and `.pal-cell` get a
|
|
182
|
+
// `--sem-success-strong` flash (assets/js/jelinek.js's `flashCopied`), and
|
|
183
|
+
// that pattern doesn't transfer here (no separate icon for CodeCopy to
|
|
184
|
+
// flash). But the guide's *other* copy control — assets/js/jelinek.js:
|
|
185
|
+
// 319-326, `code[data-copy]` outside a swatch-value row — is structurally
|
|
186
|
+
// the same shape as CodeCopy (one element is both the trigger and the
|
|
187
|
+
// display), and it DOES give visible feedback: on copy it swaps its own
|
|
188
|
+
// text content to the literal string "zkopírováno ✓" for 900ms, then
|
|
189
|
+
// restores the original. That's reproduced below (`copied ? 'zkopírováno
|
|
190
|
+
// ✓' : value`), alongside the aria-label swap already in place — the
|
|
191
|
+
// text-swap is the sighted-user feedback, the aria-label is the
|
|
192
|
+
// assistive-tech feedback, both present at once.
|
|
193
|
+
const BASE =
|
|
194
|
+
'rounded-none border-0 bg-transparent p-0 font-[ui-monospace,Menlo,Consolas,monospace] ' +
|
|
195
|
+
'text-[0.8rem] transition-colors';
|
|
196
|
+
const ENABLED = 'cursor-pointer text-fg-muted1 hover:text-fg';
|
|
197
|
+
const DISABLED = 'cursor-default text-fg-muted2 hover:text-fg-muted2';
|
|
198
|
+
|
|
199
|
+
// base.css's `code` rule (jelinek.css:274-279) restated on the button, since
|
|
200
|
+
// a <button> doesn't inherit it: 12px monospace, muted2, 1px dashed border,
|
|
201
|
+
// 4px radius, 6px side padding. No vertical padding and a tight line-height:
|
|
202
|
+
// on the guide's inline <code> the 1px padding doesn't grow the line box,
|
|
203
|
+
// but on an inline-block button it would — the chip was visibly taller than
|
|
204
|
+
// the archive's until both were dropped.
|
|
205
|
+
// leading must come AFTER text-[12px]: tailwind-merge treats a font-size
|
|
206
|
+
// utility as also owning line-height, so the other order strips the leading
|
|
207
|
+
// (the same trap SiteHeader's NAV_LINK documents).
|
|
208
|
+
const CHIP =
|
|
209
|
+
'cursor-pointer rounded-[4px] border border-dashed border-fg-muted2 bg-transparent ' +
|
|
210
|
+
'px-[6px] py-0 font-[ui-monospace,Menlo,Consolas,monospace] text-[12px] leading-[1.3] text-fg-muted2 ' +
|
|
211
|
+
'transition-colors hover:border-fg-muted1 hover:text-fg-muted1';
|
|
212
|
+
|
|
213
|
+
// jelinek.css:985-992 (`.code-line code`): block, pre-wrap + break-word, 8px/
|
|
214
|
+
// 12px padding, solid --border-subtle, grey fill, muted1 text darkening on
|
|
215
|
+
// hover. Radius 4px comes from the generic code rule it inherits there.
|
|
216
|
+
const BLOCK =
|
|
217
|
+
'block w-full cursor-pointer rounded-[4px] border border-solid border-border-subtle ' +
|
|
218
|
+
'bg-surface-gray px-3 py-2 text-left font-[ui-monospace,Menlo,Consolas,monospace] ' +
|
|
219
|
+
'text-[12px] leading-relaxed whitespace-pre-wrap [word-break:break-word] text-fg-muted1 ' +
|
|
220
|
+
'transition-colors hover:text-fg';
|
|
221
|
+
|
|
222
|
+
// jelinek.css:1028-1031 (`.swatch__value`): flex row, centred, value and icon
|
|
223
|
+
// pushed apart, 8px gap (--space-1), `margin: 2px 0 4px`. The `.swatch__mode
|
|
224
|
+
// .swatch__value { flex: 1; margin: 3px 0 }` override is the CALLER's business
|
|
225
|
+
// (Swatch's mode row passes its own class), not hardcoded here.
|
|
226
|
+
const ROW = 'flex items-center justify-between gap-2 my-0.5 mb-1';
|
|
227
|
+
// jelinek.css:1032-1039 (`.swatch__copy`): borderless 4px-padded 6px-radius
|
|
228
|
+
// icon, muted2 → fg + grey wash on hover, --sem-success-strong while the
|
|
229
|
+
// flash lasts, 0.3 opacity when there is nothing to copy.
|
|
230
|
+
const ICON =
|
|
231
|
+
'flex-none cursor-pointer rounded-[6px] border-0 bg-transparent p-1 leading-none ' +
|
|
232
|
+
'text-fg-muted2 transition-colors hover:bg-surface-gray hover:text-fg ' +
|
|
233
|
+
'disabled:cursor-default disabled:opacity-30 disabled:hover:bg-transparent ' +
|
|
234
|
+
'disabled:hover:text-fg-muted2';
|
|
235
|
+
</script>
|
|
236
|
+
|
|
237
|
+
{#if icon}
|
|
238
|
+
<span class={cn(ROW, className)}>
|
|
239
|
+
<button
|
|
240
|
+
{...rest}
|
|
241
|
+
type="button"
|
|
242
|
+
disabled={disabled || undefined}
|
|
243
|
+
onclick={copy}
|
|
244
|
+
aria-label={disabled ? undefined : `Zkopírovat ${value}`}
|
|
245
|
+
class={cn(BASE, disabled ? DISABLED : ENABLED)}
|
|
246
|
+
>
|
|
247
|
+
{label ?? value}
|
|
248
|
+
</button>
|
|
249
|
+
<button
|
|
250
|
+
type="button"
|
|
251
|
+
disabled={disabled || undefined}
|
|
252
|
+
onclick={copy}
|
|
253
|
+
aria-label={disabled ? undefined : copied ? 'Zkopírováno' : `Kopírovat ${value}`}
|
|
254
|
+
class={cn(ICON, copied && 'text-sem-success-strong hover:text-sem-success-strong')}
|
|
255
|
+
>
|
|
256
|
+
<!-- assets/js/jelinek.js:23-28 (COPY_SVG), verbatim geometry. While the
|
|
257
|
+
flash lasts the icon swaps to a tick in the same success green —
|
|
258
|
+
stronger sighted feedback than the archive's colour-only flash. -->
|
|
259
|
+
<svg
|
|
260
|
+
width="15"
|
|
261
|
+
height="15"
|
|
262
|
+
viewBox="0 0 24 24"
|
|
263
|
+
fill="none"
|
|
264
|
+
stroke="currentColor"
|
|
265
|
+
stroke-width="2"
|
|
266
|
+
stroke-linecap="round"
|
|
267
|
+
stroke-linejoin="round"
|
|
268
|
+
aria-hidden="true"
|
|
269
|
+
>
|
|
270
|
+
{#if copied}
|
|
271
|
+
<path d="M20 6L9 17l-5-5"></path>
|
|
272
|
+
{:else}
|
|
273
|
+
<rect x="9" y="9" width="12" height="12" rx="2"></rect>
|
|
274
|
+
<path d="M5 15V5a2 2 0 0 1 2-2h10"></path>
|
|
275
|
+
{/if}
|
|
276
|
+
</svg>
|
|
277
|
+
</button>
|
|
278
|
+
</span>
|
|
279
|
+
{:else}
|
|
280
|
+
<button
|
|
281
|
+
{...rest}
|
|
282
|
+
type="button"
|
|
283
|
+
disabled={disabled || undefined}
|
|
284
|
+
onclick={copy}
|
|
285
|
+
aria-label={disabled ? undefined : copied ? 'Zkopírováno' : `Zkopírovat ${value}`}
|
|
286
|
+
class={block
|
|
287
|
+
? cn(BLOCK, className)
|
|
288
|
+
: chip
|
|
289
|
+
? cn(CHIP, className)
|
|
290
|
+
: cn(BASE, disabled ? DISABLED : ENABLED, className)}
|
|
291
|
+
>
|
|
292
|
+
{copied ? 'zkopírováno ✓' : (label ?? value)}
|
|
293
|
+
</button>
|
|
294
|
+
{/if}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { HTMLButtonAttributes } from 'svelte/elements';
|
|
2
|
+
interface Props {
|
|
3
|
+
value: string;
|
|
4
|
+
/**
|
|
5
|
+
* Reproduces jelinek.css:675-676 (`.swatch__meta code.is-empty`): a
|
|
6
|
+
* structurally-present but non-interactive, permanently-muted state for
|
|
7
|
+
* when there is nothing to copy. Native `disabled` handles both the
|
|
8
|
+
* click-suppression and the focus/keyboard-activation removal that the
|
|
9
|
+
* source's plain `cursor: default` alone doesn't give you.
|
|
10
|
+
*/
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Reproduces the archive's `.swatch__value` row (jelinek.css:1028-1039 +
|
|
14
|
+
* assets/js/jelinek.js:146-160): the code value plus a trailing copy-icon
|
|
15
|
+
* button, spread apart by the row. The icon is a second trigger for the
|
|
16
|
+
* same copy, and it is also where the feedback goes — jelinek.js's
|
|
17
|
+
* `flashCopied` turns the ICON --sem-success-strong for 1100ms and leaves
|
|
18
|
+
* the code text alone, so with the icon on, the text-swap feedback below
|
|
19
|
+
* is off. Default off: bare CodeCopy (prose `code[data-copy]`,
|
|
20
|
+
* jelinek.js:329-336) has no icon in the guide either.
|
|
21
|
+
*/
|
|
22
|
+
icon?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* The guide's OTHER copy control: `code[data-copy]` in prose
|
|
25
|
+
* (assets/js/jelinek.js:329-336) — the dashed-chip typography of a bare
|
|
26
|
+
* <code> (base.css's `code` rule / jelinek.css:274-279), but clickable,
|
|
27
|
+
* with the same "zkopírováno ✓" text-swap this component already does.
|
|
28
|
+
* The DECISION note below predates this prop and stands for the
|
|
29
|
+
* DEFAULT look only: a chip without data-copy stays a bare <code>;
|
|
30
|
+
* this mode exists for the chips the archive marked copyable.
|
|
31
|
+
*/
|
|
32
|
+
chip?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* The guide's `.code-line code` (jelinek.css:985-992): a full-width grey
|
|
35
|
+
* block for one-or-more lines of example code, pre-wrap, muted text that
|
|
36
|
+
* darkens on hover, click copies the whole thing. Use for the multi-line
|
|
37
|
+
* snippets the archive rendered as `p.code-line`.
|
|
38
|
+
*/
|
|
39
|
+
block?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Visible text when it differs from what lands on the clipboard — the
|
|
42
|
+
* archive's `<code data-copy="33px">33 px</code>` shape. Defaults to
|
|
43
|
+
* `value`.
|
|
44
|
+
*/
|
|
45
|
+
label?: string;
|
|
46
|
+
class?: string;
|
|
47
|
+
}
|
|
48
|
+
type $$ComponentProps = Props & Omit<HTMLButtonAttributes, 'type' | 'disabled'>;
|
|
49
|
+
declare const CodeCopy: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
50
|
+
type CodeCopy = ReturnType<typeof CodeCopy>;
|
|
51
|
+
export default CodeCopy;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext, type Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { cn } from '../utils/cn.js';
|
|
5
|
+
import { CONTAINER_CONTEXT } from './container-context.js';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
class?: string;
|
|
9
|
+
children: Snippet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { class: className, children, ...rest }: Props & HTMLAttributes<HTMLDivElement> =
|
|
13
|
+
$props();
|
|
14
|
+
|
|
15
|
+
// Lets a descendant (currently only PageHero) detect "an ancestor already
|
|
16
|
+
// applies .container's max-width/padding" without reaching outside its
|
|
17
|
+
// own subtree — see PageHero.svelte's own comment (review round 3,
|
|
18
|
+
// Critical follow-up) for why it needs to know this at all: rendering its
|
|
19
|
+
// own equivalent wrapper INSIDE an existing Container would double the
|
|
20
|
+
// horizontal padding jelinek.css only ever applies once.
|
|
21
|
+
setContext(CONTAINER_CONTEXT, true);
|
|
22
|
+
|
|
23
|
+
// jelinek.css:287 (.container): width:100%, max-width:var(--page-max),
|
|
24
|
+
// margin:0 auto, padding:0 24px. w-full/max-w-page/mx-auto/px-6 reproduce
|
|
25
|
+
// all four values exactly (max-w-page maps to --page-max per the token
|
|
26
|
+
// map). mx-auto/px-6 alone don't touch the top/bottom margin or padding
|
|
27
|
+
// sides, but a <div>'s browser default for both is already 0, so no
|
|
28
|
+
// my-0/py-0 is needed to match the source rule's `0` values.
|
|
29
|
+
const BASE = 'mx-auto w-full max-w-page px-6';
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div {...rest} class={cn(BASE, className)}>
|
|
33
|
+
{@render children()}
|
|
34
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
interface Props {
|
|
4
|
+
class?: string;
|
|
5
|
+
children: Snippet;
|
|
6
|
+
}
|
|
7
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
|
|
8
|
+
declare const Container: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type Container = ReturnType<typeof Container>;
|
|
10
|
+
export default Container;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils/cn.js';
|
|
4
|
+
import { DEER_MARK_PATH } from './deer-mark-path.js';
|
|
5
|
+
|
|
6
|
+
type Variant = 'brown' | 'magenta';
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
/** jelinek.css:744-745. Left unset for the bare/default rule (mark
|
|
10
|
+
* colour --text-white, jelinek.css:743) — NOT itself a "neutral"
|
|
11
|
+
* variant name, since there is no third `.content-logo--*` modifier
|
|
12
|
+
* in the guide to give it one. */
|
|
13
|
+
variant?: Variant;
|
|
14
|
+
/**
|
|
15
|
+
* Sets `--logo-mark` (default matches jelinek.css:743's own inline
|
|
16
|
+
* default, `96px`). Every other size in this lockup — the gap between
|
|
17
|
+
* mark and text, the mark's own width/height, the title and subtitle
|
|
18
|
+
* font-sizes — is a `calc()` off this one custom property
|
|
19
|
+
* (jelinek.css:743/746/749/754), so resizing the whole lockup is
|
|
20
|
+
* always this one prop, never a font-size or width override on the
|
|
21
|
+
* parts themselves.
|
|
22
|
+
*/
|
|
23
|
+
logoMark?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The verzálkový podnázev (uppercase subtitle) — jelinek.css's own
|
|
26
|
+
* comment above the rule (jelinek.css:740-742) names this as one of
|
|
27
|
+
* the lockup's three required parts (mark + "JELÍNEK" + subtitle),
|
|
28
|
+
* describing "právě prohlížený obsah" (the content currently being
|
|
29
|
+
* viewed: a division, campaign, claim, anniversary...). Every real
|
|
30
|
+
* instance in the guide (ui/logo.html, 7 of them) sets one — there is
|
|
31
|
+
* no documented bare-mark-plus-title case — so this is required
|
|
32
|
+
* rather than optional. Plain text: jelinek.css:755's
|
|
33
|
+
* `text-transform: uppercase` does the casing, this prop is never
|
|
34
|
+
* pre-uppercased.
|
|
35
|
+
*/
|
|
36
|
+
subtitle: string;
|
|
37
|
+
class?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let {
|
|
41
|
+
variant,
|
|
42
|
+
logoMark = '96px',
|
|
43
|
+
subtitle,
|
|
44
|
+
class: className,
|
|
45
|
+
...rest
|
|
46
|
+
}: Props & HTMLAttributes<HTMLDivElement> = $props();
|
|
47
|
+
|
|
48
|
+
// jelinek.css:740-742 (the Czech comment above the rule): "znak : nadpis :
|
|
49
|
+
// podnázev = 170 : 71 : 28" states the DESIGN INTENT, but the rule that
|
|
50
|
+
// actually ships two lines below it (jelinek.css:754) sets the subtitle's
|
|
51
|
+
// font-size to `calc(var(--logo-mark) * 35 / 170)`, not 28 — confirmed by
|
|
52
|
+
// also reading jelinek.css:751-756 directly rather than trusting the
|
|
53
|
+
// comment's numbers, and cross-checked against SiteHeader.svelte's own
|
|
54
|
+
// subtitle, which independently reproduces the exact same 35/170 ratio
|
|
55
|
+
// for its own comparable text (jelinek.css:379-385) — two unrelated rules
|
|
56
|
+
// agreeing on 35 is strong evidence the comment's "28" is simply stale,
|
|
57
|
+
// not that the CSS is wrong. This component reproduces what the stylesheet
|
|
58
|
+
// actually declares (35), since that's what a browser renders, and flags
|
|
59
|
+
// the mismatch here rather than silently "correcting" either number.
|
|
60
|
+
//
|
|
61
|
+
// jelinek.css:743 (.content-logo): inline-flex, centred, gap computed from
|
|
62
|
+
// --logo-mark (the antler's own right-edge margin, per the rule's own
|
|
63
|
+
// trailing comment "mezera = pravý okraj parohů ve znaku") — same formula
|
|
64
|
+
// SiteHeader.svelte's __inner gap already uses for the same reason. Colour
|
|
65
|
+
// defaults to --text-white (text-fg-inverse) and is picked up by the
|
|
66
|
+
// mark's `currentColor` path below — NOT by the title or subtitle, which
|
|
67
|
+
// each set their own explicit colour two rules down (jelinek.css:749/752)
|
|
68
|
+
// and so do not inherit this. A naive implementation that let title/
|
|
69
|
+
// subtitle inherit this colour instead would go genuinely invisible on a
|
|
70
|
+
// light background in the default (text-white) state; verified this one
|
|
71
|
+
// does not, since both keep their own always-legible colours (near-black
|
|
72
|
+
// title, mid-grey subtitle) regardless of variant, and the mark's own
|
|
73
|
+
// antler artwork sits on an unconditionally opaque black square
|
|
74
|
+
// (jelinek.css:743's rect, see SVG_RECT below) rather than directly on
|
|
75
|
+
// whatever page background surrounds the component — so nothing here is
|
|
76
|
+
// actually invisible once the colour scoping below is right.
|
|
77
|
+
const BASE = 'inline-flex items-center gap-[calc(var(--logo-mark)*107/400)] text-fg-inverse';
|
|
78
|
+
|
|
79
|
+
// jelinek.css:744 (--brown, --JELINEK-BROWN, division-tracking) and :745
|
|
80
|
+
// (--magenta, --JELINEK-MAGENTA, fixed regardless of division) — same
|
|
81
|
+
// token pair and same division-tracking-vs-fixed distinction Eyebrow's
|
|
82
|
+
// own VARIANT already documents for the identical reason (tokens.css's
|
|
83
|
+
// mattress re-theme only ever reassigns --JELINEK-BROWN).
|
|
84
|
+
const VARIANT: Record<Variant, string> = {
|
|
85
|
+
brown: 'text-jelinek-brown',
|
|
86
|
+
magenta: 'text-jelinek-magenta'
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// jelinek.css:746 (.content-logo__svg): width/height both var(--logo-mark),
|
|
90
|
+
// block, flex: 0 0 auto (CSS's `none` keyword is defined as exactly
|
|
91
|
+
// `0 0 auto` — flex-none reproduces it, same mapping SiteHeader's own
|
|
92
|
+
// LOGO_LINK wrapper uses for its equivalent rule).
|
|
93
|
+
const SVG = 'block h-[var(--logo-mark)] w-[var(--logo-mark)] flex-none';
|
|
94
|
+
|
|
95
|
+
// jelinek.css:743's markup (ui/logo.html, all 7 instances verified
|
|
96
|
+
// byte-identical): the SVG's <rect> is a LITERAL `fill="#000"` attribute
|
|
97
|
+
// with a baked-in rx/ry of 25 (a rounded square badge, NOT the plain
|
|
98
|
+
// square SiteHeader's own mark uses) — not a class, not `currentColor`,
|
|
99
|
+
// so it never re-themes with the division switch or with this
|
|
100
|
+
// component's own `variant` prop; only the antler <path> below does
|
|
101
|
+
// (`fill="currentColor"`, picking up BASE/VARIANT's `color` from the
|
|
102
|
+
// wrapping element). Reproduced as literal SVG attributes for the same
|
|
103
|
+
// reason the guide itself uses them: this rect's colour is deliberately
|
|
104
|
+
// NOT part of the token system.
|
|
105
|
+
|
|
106
|
+
// jelinek.css:747-750 (.content-logo__title): font-primary (GT Walsheim),
|
|
107
|
+
// weight 700, line-height 1, letter-spacing -0.01em, size
|
|
108
|
+
// calc(logo-mark*71/170), colour --text-black (text-fg) — its OWN colour,
|
|
109
|
+
// confirmed above, not inherited from BASE.
|
|
110
|
+
const TITLE =
|
|
111
|
+
'font-primary text-[calc(var(--logo-mark)*71/170)] leading-none font-bold tracking-[-0.01em] text-fg';
|
|
112
|
+
|
|
113
|
+
// jelinek.css:751-756 (.content-logo__sub): block, colour --text-muted1
|
|
114
|
+
// (text-fg-muted1, its own colour, not inherited), weight 600 (Avenir
|
|
115
|
+
// Demi -> font-semibold), size calc(logo-mark*35/170), tracking 0.064em,
|
|
116
|
+
// uppercase, margin-top 0.3em, line-height 1.1. Font-family is a literal
|
|
117
|
+
// inline "Avenir Next LT Pro"/"Avenir Next"/Arial/sans-serif stack, NOT
|
|
118
|
+
// var(--font-secondary) (which currently also resolves to GT Walsheim Pro
|
|
119
|
+
// per tokens.css) — same fact, same fix, and same reasoning
|
|
120
|
+
// SiteHeader.svelte's own SUBTITLE_STYLE already documents for its
|
|
121
|
+
// byte-identical subtitle rule: kept as a literal inline `style` rather
|
|
122
|
+
// than a Tailwind arbitrary-font-family class, since Tailwind's bracket
|
|
123
|
+
// syntax for a comma-separated, quoted font stack is fragile.
|
|
124
|
+
const SUB =
|
|
125
|
+
'mt-[0.3em] block text-[calc(var(--logo-mark)*35/170)] leading-[1.1] font-semibold ' +
|
|
126
|
+
'tracking-[0.064em] text-fg-muted1 uppercase';
|
|
127
|
+
const SUB_STYLE = "font-family: 'Avenir Next LT Pro', 'Avenir Next', Arial, sans-serif;";
|
|
128
|
+
|
|
129
|
+
// `--logo-mark` itself has to be a real inline style (a Tailwind
|
|
130
|
+
// arbitrary-property class can't take a per-instance runtime value — only
|
|
131
|
+
// the calc()s that REFERENCE var(--logo-mark) above are static enough for
|
|
132
|
+
// that). Composed with a consumer's own `style` (via `rest.style`) rather
|
|
133
|
+
// than overwritten, same technique — and same reason — Tag.svelte's
|
|
134
|
+
// `mergedStyle` already uses for its own per-instance `--tag-h`/`--tag-s`
|
|
135
|
+
// custom properties: a bare `style={...}` placed after `{...rest}` would
|
|
136
|
+
// otherwise silently discard whatever `style` a consumer passed in.
|
|
137
|
+
const mergedStyle = $derived(
|
|
138
|
+
[`--logo-mark: ${logoMark};`, rest.style]
|
|
139
|
+
.filter((s): s is string => Boolean(s))
|
|
140
|
+
.map((s) => s.trim().replace(/;+$/, ''))
|
|
141
|
+
.join('; ')
|
|
142
|
+
);
|
|
143
|
+
</script>
|
|
144
|
+
|
|
145
|
+
<div {...rest} style={mergedStyle} class={cn(BASE, variant && VARIANT[variant], className)}>
|
|
146
|
+
<svg class={SVG} viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
147
|
+
<rect width="400" height="400" rx="25" ry="25" fill="#000" />
|
|
148
|
+
<path d={DEER_MARK_PATH} fill="currentColor" />
|
|
149
|
+
</svg>
|
|
150
|
+
<span class={TITLE}>JELÍNEK<span class={SUB} style={SUB_STYLE}>{subtitle}</span></span>
|
|
151
|
+
</div>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type Variant = 'brown' | 'magenta';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** jelinek.css:744-745. Left unset for the bare/default rule (mark
|
|
5
|
+
* colour --text-white, jelinek.css:743) — NOT itself a "neutral"
|
|
6
|
+
* variant name, since there is no third `.content-logo--*` modifier
|
|
7
|
+
* in the guide to give it one. */
|
|
8
|
+
variant?: Variant;
|
|
9
|
+
/**
|
|
10
|
+
* Sets `--logo-mark` (default matches jelinek.css:743's own inline
|
|
11
|
+
* default, `96px`). Every other size in this lockup — the gap between
|
|
12
|
+
* mark and text, the mark's own width/height, the title and subtitle
|
|
13
|
+
* font-sizes — is a `calc()` off this one custom property
|
|
14
|
+
* (jelinek.css:743/746/749/754), so resizing the whole lockup is
|
|
15
|
+
* always this one prop, never a font-size or width override on the
|
|
16
|
+
* parts themselves.
|
|
17
|
+
*/
|
|
18
|
+
logoMark?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The verzálkový podnázev (uppercase subtitle) — jelinek.css's own
|
|
21
|
+
* comment above the rule (jelinek.css:740-742) names this as one of
|
|
22
|
+
* the lockup's three required parts (mark + "JELÍNEK" + subtitle),
|
|
23
|
+
* describing "právě prohlížený obsah" (the content currently being
|
|
24
|
+
* viewed: a division, campaign, claim, anniversary...). Every real
|
|
25
|
+
* instance in the guide (ui/logo.html, 7 of them) sets one — there is
|
|
26
|
+
* no documented bare-mark-plus-title case — so this is required
|
|
27
|
+
* rather than optional. Plain text: jelinek.css:755's
|
|
28
|
+
* `text-transform: uppercase` does the casing, this prop is never
|
|
29
|
+
* pre-uppercased.
|
|
30
|
+
*/
|
|
31
|
+
subtitle: string;
|
|
32
|
+
class?: string;
|
|
33
|
+
}
|
|
34
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
|
|
35
|
+
declare const ContentLogo: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
36
|
+
type ContentLogo = ReturnType<typeof ContentLogo>;
|
|
37
|
+
export default ContentLogo;
|