@jelinek/ui 0.9.0 → 0.9.2
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 +33 -10
- package/dist/components/ChapterNav.svelte +1 -1
- package/dist/components/Checkbox.svelte +13 -15
- package/dist/components/Checkbox.svelte.d.ts +1 -0
- package/dist/components/ContentLogo.svelte +18 -13
- package/dist/components/ContentLogo.svelte.d.ts +6 -5
- package/dist/components/Grid.svelte +9 -5
- package/dist/components/Icon.svelte +52 -0
- package/dist/components/Icon.svelte.d.ts +30 -0
- package/dist/components/InfoBar.svelte +254 -0
- package/dist/components/InfoBar.svelte.d.ts +46 -0
- package/dist/components/Label.svelte +12 -1
- package/dist/components/Label.svelte.d.ts +8 -0
- package/dist/components/PageHero.svelte +12 -9
- package/dist/components/Pager.svelte +5 -2
- package/dist/components/PhotoTile.svelte +4 -2
- package/dist/components/ProductCard.svelte +66 -44
- package/dist/components/ProductCard.svelte.d.ts +17 -4
- package/dist/components/Radio.svelte +12 -8
- package/dist/components/Radio.svelte.d.ts +1 -0
- package/dist/components/Select.svelte +70 -12
- package/dist/components/Select.svelte.d.ts +11 -0
- package/dist/components/SiteFooter.svelte +19 -0
- package/dist/components/SiteFooter.svelte.d.ts +8 -0
- package/dist/components/SiteHeader.svelte +70 -9
- package/dist/components/SiteHeader.svelte.d.ts +16 -0
- package/dist/components/Slider.svelte +6 -11
- package/dist/components/Switch.svelte +76 -0
- package/dist/components/Switch.svelte.d.ts +12 -0
- package/dist/components/Tab.svelte +29 -1
- package/dist/components/TextField.svelte +61 -10
- package/dist/components/TextField.svelte.d.ts +13 -0
- package/dist/components/Textarea.svelte +92 -0
- package/dist/components/Textarea.svelte.d.ts +23 -0
- package/dist/components/Tile.svelte +12 -11
- package/dist/components/Tile.svelte.d.ts +8 -2
- package/dist/components/blocks/CmsButton.svelte +36 -0
- package/dist/components/blocks/CmsButton.svelte.d.ts +12 -0
- package/dist/components/blocks/CmsColumns.svelte +20 -0
- package/dist/components/blocks/CmsColumns.svelte.d.ts +9 -0
- package/dist/components/blocks/CmsCover.svelte +71 -0
- package/dist/components/blocks/CmsCover.svelte.d.ts +17 -0
- package/dist/components/blocks/CmsHeading.svelte +50 -0
- package/dist/components/blocks/CmsHeading.svelte.d.ts +14 -0
- package/dist/components/blocks/CmsImage.svelte +43 -0
- package/dist/components/blocks/CmsImage.svelte.d.ts +13 -0
- package/dist/components/blocks/CmsMediaText.svelte +64 -0
- package/dist/components/blocks/CmsMediaText.svelte.d.ts +16 -0
- package/dist/components/blocks/CmsText.svelte +53 -0
- package/dist/components/blocks/CmsText.svelte.d.ts +10 -0
- package/dist/components/commerce/Breadcrumbs.svelte +22 -5
- package/dist/components/commerce/CartDrawer.svelte +7 -4
- package/dist/components/commerce/CartLineItem.svelte +1 -1
- package/dist/components/commerce/CheckoutProgress.svelte +1 -1
- package/dist/components/commerce/ConfigSection.svelte +2 -2
- package/dist/components/commerce/DimensionSlider.svelte +1 -1
- package/dist/components/commerce/Drawer.svelte +38 -24
- package/dist/components/commerce/Drawer.svelte.d.ts +0 -2
- package/dist/components/commerce/EmptyState.svelte +37 -0
- package/dist/components/commerce/EmptyState.svelte.d.ts +13 -0
- package/dist/components/commerce/FilterChip.svelte +35 -0
- package/dist/components/commerce/FilterChip.svelte.d.ts +10 -0
- package/dist/components/commerce/FreeShippingBar.svelte +1 -1
- package/dist/components/commerce/Gallery.svelte +4 -2
- package/dist/components/commerce/ListingToolbar.svelte +62 -0
- package/dist/components/commerce/ListingToolbar.svelte.d.ts +19 -0
- package/dist/components/commerce/Modal.svelte +31 -21
- package/dist/components/commerce/Modal.svelte.d.ts +0 -2
- package/dist/components/commerce/OrderSummary.svelte +1 -1
- package/dist/components/commerce/Pagination.svelte +115 -0
- package/dist/components/commerce/Pagination.svelte.d.ts +14 -0
- package/dist/components/commerce/PriceDisplay.svelte +3 -3
- package/dist/components/commerce/ProductHeader.svelte +2 -2
- package/dist/components/commerce/QuantityStepper.svelte +1 -1
- package/dist/components/commerce/RatingStars.svelte +1 -1
- package/dist/components/commerce/SearchField.svelte +92 -0
- package/dist/components/commerce/SearchField.svelte.d.ts +15 -0
- package/dist/components/commerce/SideToggle.svelte +1 -1
- package/dist/components/commerce/Skeleton.svelte +4 -1
- package/dist/components/commerce/StickyBar.svelte +4 -4
- package/dist/components/commerce/StockBadge.svelte +2 -2
- package/dist/components/commerce/Toaster.svelte +9 -5
- package/dist/components/commerce/TrustBadges.svelte +1 -1
- package/dist/components/commerce/overlay.d.ts +14 -0
- package/dist/components/commerce/overlay.js +49 -0
- package/dist/components/info-bar.d.ts +26 -0
- package/dist/components/info-bar.js +23 -0
- package/dist/icons/material-icons.generated.d.ts +4 -0
- package/dist/icons/material-icons.generated.js +118 -0
- package/dist/icons/material.d.ts +17 -0
- package/dist/icons/material.js +52 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/theme/base.css +22 -0
- package/dist/theme/extras.css +62 -0
- package/dist/theme/fonts.css +9 -42
- package/dist/theme/tokens.css +23 -9
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Packages for a package that is no longer published there:
|
|
|
26
26
|
```json
|
|
27
27
|
{
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@jelinek/ui": "^0.
|
|
29
|
+
"@jelinek/ui": "^0.9.2"
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
```
|
|
@@ -365,10 +365,7 @@ as before; the DOM-seeding only applies when it's left unbound.
|
|
|
365
365
|
|
|
366
366
|
## Components
|
|
367
367
|
|
|
368
|
-
Alert, Button, Card, ChapterNav, Checkbox, CodeCopy, Container, ContentLogo,
|
|
369
|
-
Demo, Eyebrow, Glass, Grid, Label, PageHero, Pager, Panel, ProductCard, Radio,
|
|
370
|
-
RadioGroup, Select, SiteFooter, SiteHeader, Slider, Swatch, Tab, Tabs, Tag,
|
|
371
|
-
TextField, ThemeToggle, Tile, TileScroller.
|
|
368
|
+
Alert, Breadcrumbs, Button, Card, CartDrawer, CartLineItem, ChapterNav, Checkbox, CheckoutProgress, CmsButton, CmsColumns, CmsCover, CmsHeading, CmsImage, CmsMediaText, CmsText, CodeCopy, ConfigSection, Container, ContentLogo, Demo, DimensionSlider, Drawer, EmptyState, Eyebrow, FilterChip, FreeShippingBar, Gallery, Glass, Grid, Icon, Label, ListingToolbar, Modal, OptionTile, OrderSummary, PageHero, Pager, Pagination, Panel, PhotoTile, PriceDisplay, ProductCard, ProductHeader, ProductStickyBar, ProductTabs, QuantityStepper, Radio, RadioGroup, RatingStars, SearchField, Select, SideToggle, SiteFooter, SiteHeader, Skeleton, Slider, StickyBar, StockBadge, Swatch, Switch, Tab, Tabs, Tag, TextField, Textarea, ThemeToggle, Tile, TileScroller, Toaster, TrustBadges.
|
|
372
369
|
|
|
373
370
|
See `src/lib/index.ts` for the exact export list and `src/lib/components/`
|
|
374
371
|
for source. Per-component contracts, including the two below, are printed at
|
|
@@ -395,6 +392,22 @@ showcase's `+layout.svelte` both model it. And below 640px `ChapterNav` renders
|
|
|
395
392
|
nothing at all: pass the same chapter list to `SiteHeader`'s `chapterItems` so
|
|
396
393
|
a phone reader can still reach the chapters from the menu panel.
|
|
397
394
|
|
|
395
|
+
**`SiteHeader` — the way back to the company portal is `homeHref`, not a
|
|
396
|
+
button in `actions`.** Company apps built from the templates link back to the
|
|
397
|
+
rozcestník. Pass its URL as `homeHref` and the header renders a `home` icon
|
|
398
|
+
(from the kit's icon set, no text) as the first item of the bar, at the far
|
|
399
|
+
left before the logo, at every width including phones. It is a 44px square
|
|
400
|
+
— the burger's size — rendered through the kit's own ghost/black `Button`,
|
|
401
|
+
so hover, transition and the global focus-visible ring match the nav links.
|
|
402
|
+
`homeLabel` (default `'Zpět na rozcestník'`) is its `aria-label` and `title`.
|
|
403
|
+
Without `homeHref` the header is unchanged. Do not put a "Zpět na rozcestník"
|
|
404
|
+
`Button` into `actions` any more: it sat on the right next to the nav links at
|
|
405
|
+
a different size (owner's decision, 15. 9. 2026).
|
|
406
|
+
|
|
407
|
+
```svelte
|
|
408
|
+
<SiteHeader subtitle="Objednávky" items={menu} homeHref="https://jlnk.cz/" />
|
|
409
|
+
```
|
|
410
|
+
|
|
398
411
|
## Sync contract — and its limits
|
|
399
412
|
|
|
400
413
|
Two automated checks keep this kit from silently drifting away from the
|
|
@@ -413,6 +426,18 @@ never hand-edit it**; run `pnpm gen:tokens` and commit the result.
|
|
|
413
426
|
Hand-written theme additions belong in `src/lib/theme/extras.css`, which
|
|
414
427
|
the generator never touches.
|
|
415
428
|
|
|
429
|
+
**`pnpm check:icons`** (`scripts/gen-icons.js --check`) does the same for
|
|
430
|
+
the interface icon set: `src/lib/icons/material-icons.generated.ts` is
|
|
431
|
+
generated from `assets/icons/material/` (Google icons as per-icon SVGs plus
|
|
432
|
+
our own drawings in `vlastni/`) and fails if it is stale. The generator also
|
|
433
|
+
refuses any SVG without `fill="currentColor"` on its root or carrying its
|
|
434
|
+
own colours — an icon that would not re-theme. The module is **generated —
|
|
435
|
+
never hand-edit it**; add or remove SVG files, run `pnpm gen:icons`, commit.
|
|
436
|
+
It exists so that `<Icon name="…" />` works from the installed package:
|
|
437
|
+
the earlier `import.meta.glob` reached outside `dist/` and only worked
|
|
438
|
+
inside this repo (review, 1. 9. 2026). The whole set is ~18 kB gzipped and
|
|
439
|
+
ships with the package.
|
|
440
|
+
|
|
416
441
|
**`pnpm check:css-lock`** (`scripts/check-css-lock.js`) hashes the guide's
|
|
417
442
|
CSS rule groups for two fixed, hardcoded allow-lists in that file:
|
|
418
443
|
`GUARDED` — class names (`btn`, `tag`, `alert`, `tile`, `panel`, `input`,
|
|
@@ -525,11 +550,9 @@ code.
|
|
|
525
550
|
|
|
526
551
|
The guide does not yet define everything this kit's consumers need. See:
|
|
527
552
|
|
|
528
|
-
-
|
|
529
|
-
|
|
530
|
-
priority.
|
|
531
|
-
- `docs/chybejici-bloky-cms.md` — the 16 CMS content blocks that exist in
|
|
532
|
-
code but were never designed against the brand guide.
|
|
553
|
+
- `/audit.html#plan-prace` — the single work plan (formerly the two backlog
|
|
554
|
+
documents): missing interface components, the CMS content blocks still to
|
|
555
|
+
be designed, admin components, with priority and deadline.
|
|
533
556
|
|
|
534
557
|
Five components in this kit have **no corresponding CSS in the guide** and
|
|
535
558
|
were necessarily invented rather than reimplemented: `Checkbox`, `Radio`,
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
// while `display: none` holds; from 640px up the bar is `absolute`, which is
|
|
62
62
|
// what makes it the containing block for the scroll-affordance fades below.
|
|
63
63
|
const OUTER =
|
|
64
|
-
'hidden z-
|
|
64
|
+
'hidden z-(--z-chapter-nav) px-6 ' +
|
|
65
65
|
'min-[640px]:block min-[640px]:absolute min-[640px]:inset-x-0 ' +
|
|
66
66
|
'min-[640px]:top-[calc(var(--header-h)-11px)]';
|
|
67
67
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
checked?: boolean;
|
|
7
7
|
label: string;
|
|
8
8
|
id?: string;
|
|
9
|
+
disabled?: boolean;
|
|
9
10
|
class?: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
checked = $bindable(false),
|
|
14
15
|
label,
|
|
15
16
|
id,
|
|
17
|
+
disabled = false,
|
|
16
18
|
class: className,
|
|
17
19
|
...rest
|
|
18
20
|
}: Props & Omit<HTMLInputAttributes, 'checked' | 'type'> = $props();
|
|
@@ -31,22 +33,18 @@
|
|
|
31
33
|
// without depending on an ambient .demo wrapper the consumer may not
|
|
32
34
|
// have. gap-2.5 matches the 10px inline gap.
|
|
33
35
|
//
|
|
34
|
-
// ACCENT COLOUR —
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
// [data-division="mattress"] (tokens.css:161-166) — the same mechanism
|
|
44
|
-
// Button relies on for its brand colours — so this accent still turns
|
|
45
|
-
// magenta automatically on mattress-themed pages, without hardcoding it.
|
|
46
|
-
const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary text-step0 text-fg';
|
|
36
|
+
// ACCENT COLOUR — adjudicated by the owner (14. 8. 2026): forms are
|
|
37
|
+
// colour-neutral. Every emphasis in a form is BLACK (--JELINEK-BLACK,
|
|
38
|
+
// which flips to near-white in dark mode so it stays visible); the only
|
|
39
|
+
// colour a form carries is the error state. No division re-theme here —
|
|
40
|
+
// forms look the same in Nábytek and Zdravý spánek.
|
|
41
|
+
const LABEL = 'flex cursor-pointer items-center gap-2.5 font-secondary font-light text-step0 text-fg';
|
|
42
|
+
// Zakázaný stav mluví jako zakázané tlačítko (Button.svelte:117):
|
|
43
|
+
// utlumený text a cursor-not-allowed; políčko samotné tlumí prohlížeč.
|
|
44
|
+
const DISABLED = 'cursor-not-allowed text-fg-muted2';
|
|
47
45
|
</script>
|
|
48
46
|
|
|
49
|
-
<label for={inputId} class={cn(LABEL, className)}>
|
|
50
|
-
<input {...rest} id={inputId} type="checkbox" bind:checked class="accent-jelinek-
|
|
47
|
+
<label for={inputId} class={cn(LABEL, disabled && DISABLED, className)}>
|
|
48
|
+
<input {...rest} id={inputId} type="checkbox" {disabled} bind:checked class="accent-jelinek-black" />
|
|
51
49
|
{label}
|
|
52
50
|
</label>
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
import { cn } from '../utils/cn.js';
|
|
4
4
|
import { DEER_MARK_PATH } from './deer-mark-path.js';
|
|
5
5
|
|
|
6
|
-
type Variant = 'brown'
|
|
6
|
+
type Variant = 'brown';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
/** Jelen je jen HNĚDÝ, nebo BÍLÝ (brand → Znak, Užití s produktovými
|
|
10
|
+
* barvami; majitel, 21. 8. 2026): `brown` pro divizi Nábytek, bez
|
|
11
|
+
* varianty BÍLÝ — divize Zdravý spánek a neutrální užití (interní
|
|
12
|
+
* aplikace). Purpurová varianta NEEXISTUJE — dřívější `magenta` byla
|
|
13
|
+
* chyba proti manuálu a je odstraněná. */
|
|
13
14
|
variant?: Variant;
|
|
14
15
|
/**
|
|
15
16
|
* Sets `--logo-mark` (default matches jelinek.css:743's own inline
|
|
@@ -76,14 +77,13 @@
|
|
|
76
77
|
// actually invisible once the colour scoping below is right.
|
|
77
78
|
const BASE = 'inline-flex items-center gap-[calc(var(--logo-mark)*107/400)] text-fg-inverse';
|
|
78
79
|
|
|
79
|
-
// jelinek.css
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
80
|
+
// jelinek.css .content-logo--brown: the brown deer. The division re-theme
|
|
81
|
+
// remaps --JELINEK-BROWN to magenta — for the deer that is a FORBIDDEN
|
|
82
|
+
// state, so under [data-division="mattress"] every lockup flips to WHITE
|
|
83
|
+
// instead; the rule lives in extras.css and hooks on the data-content-logo
|
|
84
|
+
// attribute below, mirroring jelinek.css's own division override.
|
|
84
85
|
const VARIANT: Record<Variant, string> = {
|
|
85
|
-
brown: 'text-jelinek-brown'
|
|
86
|
-
magenta: 'text-jelinek-magenta'
|
|
86
|
+
brown: 'text-jelinek-brown'
|
|
87
87
|
};
|
|
88
88
|
|
|
89
89
|
// jelinek.css:746 (.content-logo__svg): width/height both var(--logo-mark),
|
|
@@ -142,7 +142,12 @@
|
|
|
142
142
|
);
|
|
143
143
|
</script>
|
|
144
144
|
|
|
145
|
-
<div
|
|
145
|
+
<div
|
|
146
|
+
{...rest}
|
|
147
|
+
style={mergedStyle}
|
|
148
|
+
data-content-logo=""
|
|
149
|
+
class={cn(BASE, variant && VARIANT[variant], className)}
|
|
150
|
+
>
|
|
146
151
|
<svg class={SVG} viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
147
152
|
<rect width="400" height="400" rx="25" ry="25" fill="#000" />
|
|
148
153
|
<path d={DEER_MARK_PATH} fill="currentColor" />
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
-
type Variant = 'brown'
|
|
2
|
+
type Variant = 'brown';
|
|
3
3
|
interface Props {
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
/** Jelen je jen HNĚDÝ, nebo BÍLÝ (brand → Znak, Užití s produktovými
|
|
5
|
+
* barvami; majitel, 21. 8. 2026): `brown` pro divizi Nábytek, bez
|
|
6
|
+
* varianty BÍLÝ — divize Zdravý spánek a neutrální užití (interní
|
|
7
|
+
* aplikace). Purpurová varianta NEEXISTUJE — dřívější `magenta` byla
|
|
8
|
+
* chyba proti manuálu a je odstraněná. */
|
|
8
9
|
variant?: Variant;
|
|
9
10
|
/**
|
|
10
11
|
* Sets `--logo-mark` (default matches jelinek.css:743's own inline
|
|
@@ -49,10 +49,11 @@
|
|
|
49
49
|
// Behaviour by width (the guide's, verbatim):
|
|
50
50
|
// w < 640 -> 1 column, every cols value
|
|
51
51
|
// 640 <= w < 1024 -> 2 columns, every cols value
|
|
52
|
-
// w >= 1024 -> the cols value itself
|
|
53
|
-
// (
|
|
54
|
-
//
|
|
55
|
-
//
|
|
52
|
+
// w >= 1024 -> the cols value itself for 2/3; cols=4 steps
|
|
53
|
+
// 3 (>=1024) -> 4 (>=1280) -> 5 (>=1600), so the
|
|
54
|
+
// four-column grid climbs 1/2/3/4/5 with width
|
|
55
|
+
// (owner's request) instead of jumping 2 -> 4.
|
|
56
|
+
// (cols=2 has no 1024px rule at all, it is already 2-up from 640px.)
|
|
56
57
|
//
|
|
57
58
|
// Why Tailwind's native `min-*` variant family and NOT arbitrary *media*
|
|
58
59
|
// variants (`[@media(min-width:1024px)]:...`), even though those can express
|
|
@@ -75,10 +76,13 @@
|
|
|
75
76
|
// `min-[1024px]:grid-cols-${n}` template, so Tailwind's static scanner can
|
|
76
77
|
// see each one literally — a template string is invisible to it and the
|
|
77
78
|
// class would never be generated.
|
|
79
|
+
// cols={4} přidává od 1600 px pátý sloupec (jelinek.css .grid--4, majitel:
|
|
80
|
+
// širší web = víc obsahu do řady; trojka a dvojka zůstávají — jsou volené
|
|
81
|
+
// kvůli obsahu, ne kvůli místu).
|
|
78
82
|
const COLS: Record<2 | 3 | 4, string> = {
|
|
79
83
|
2: 'grid-cols-1 min-[640px]:grid-cols-2',
|
|
80
84
|
3: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-3',
|
|
81
|
-
4: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-4'
|
|
85
|
+
4: 'grid-cols-1 min-[640px]:grid-cols-2 min-[1024px]:grid-cols-3 min-[1280px]:grid-cols-4 min-[1600px]:grid-cols-5'
|
|
82
86
|
};
|
|
83
87
|
</script>
|
|
84
88
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { cn } from '../utils/cn.js';
|
|
4
|
+
import { materialIcon } from '../icons/material.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ikona rozhraní — Google icons (Material Symbols Rounded) jako inline SVG
|
|
8
|
+
* (rozhodnutí majitele 28. 8. 2026: soubory, ne ikonové písmo; pravidla
|
|
9
|
+
* v kapitole /ui/ikony).
|
|
10
|
+
*
|
|
11
|
+
* `name` je přesné jméno ikony z fonts.google.com/icons (shopping_cart,
|
|
12
|
+
* search, favorite…) = jméno souboru v assets/icons/material/. Soubor se
|
|
13
|
+
* vloží inline, takže `fill="currentColor"` dědí barvu okolního textu;
|
|
14
|
+
* neexistující jméno spadne hned při vykreslení (loader hlásí překlep
|
|
15
|
+
* i chybějící stažení). Obal .icon (extras.css) dává rozměr 20 px
|
|
16
|
+
* a posazení na účaří; `size` ho přepíše. Plná varianta (`fill`) sahá po
|
|
17
|
+
* souboru `<name>_fill.svg` — existuje jen tam, kde ji stavy potřebují
|
|
18
|
+
* (star, favorite…), jinak loader spadne a fill variantu je potřeba
|
|
19
|
+
* nejdřív stáhnout. Ikona je čtečkám skrytá, ledaže dostane `label`.
|
|
20
|
+
*/
|
|
21
|
+
interface Props {
|
|
22
|
+
name: string;
|
|
23
|
+
/** Plná varianta pro aktivní/vybraný stav — soubor `<name>_fill.svg`. */
|
|
24
|
+
fill?: boolean;
|
|
25
|
+
/** Popisek pro čtečky; bez něj je ikona dekorace (aria-hidden). */
|
|
26
|
+
label?: string;
|
|
27
|
+
/** Velikost v px (šířka i výška). Výchozích 20 px dává utilita .icon. */
|
|
28
|
+
size?: number;
|
|
29
|
+
class?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
name,
|
|
34
|
+
fill = false,
|
|
35
|
+
label,
|
|
36
|
+
size,
|
|
37
|
+
class: className,
|
|
38
|
+
...rest
|
|
39
|
+
}: Props & HTMLAttributes<HTMLSpanElement> = $props();
|
|
40
|
+
|
|
41
|
+
const svg = $derived(materialIcon(fill ? `${name}_fill` : name));
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<span
|
|
45
|
+
{...rest}
|
|
46
|
+
class={cn('icon', className)}
|
|
47
|
+
style:width={size ? `${size}px` : undefined}
|
|
48
|
+
style:height={size ? `${size}px` : undefined}
|
|
49
|
+
role={label ? 'img' : undefined}
|
|
50
|
+
aria-label={label}
|
|
51
|
+
aria-hidden={label ? undefined : 'true'}>{@html svg}</span
|
|
52
|
+
>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
/**
|
|
3
|
+
* Ikona rozhraní — Google icons (Material Symbols Rounded) jako inline SVG
|
|
4
|
+
* (rozhodnutí majitele 28. 8. 2026: soubory, ne ikonové písmo; pravidla
|
|
5
|
+
* v kapitole /ui/ikony).
|
|
6
|
+
*
|
|
7
|
+
* `name` je přesné jméno ikony z fonts.google.com/icons (shopping_cart,
|
|
8
|
+
* search, favorite…) = jméno souboru v assets/icons/material/. Soubor se
|
|
9
|
+
* vloží inline, takže `fill="currentColor"` dědí barvu okolního textu;
|
|
10
|
+
* neexistující jméno spadne hned při vykreslení (loader hlásí překlep
|
|
11
|
+
* i chybějící stažení). Obal .icon (extras.css) dává rozměr 20 px
|
|
12
|
+
* a posazení na účaří; `size` ho přepíše. Plná varianta (`fill`) sahá po
|
|
13
|
+
* souboru `<name>_fill.svg` — existuje jen tam, kde ji stavy potřebují
|
|
14
|
+
* (star, favorite…), jinak loader spadne a fill variantu je potřeba
|
|
15
|
+
* nejdřív stáhnout. Ikona je čtečkám skrytá, ledaže dostane `label`.
|
|
16
|
+
*/
|
|
17
|
+
interface Props {
|
|
18
|
+
name: string;
|
|
19
|
+
/** Plná varianta pro aktivní/vybraný stav — soubor `<name>_fill.svg`. */
|
|
20
|
+
fill?: boolean;
|
|
21
|
+
/** Popisek pro čtečky; bez něj je ikona dekorace (aria-hidden). */
|
|
22
|
+
label?: string;
|
|
23
|
+
/** Velikost v px (šířka i výška). Výchozích 20 px dává utilita .icon. */
|
|
24
|
+
size?: number;
|
|
25
|
+
class?: string;
|
|
26
|
+
}
|
|
27
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLSpanElement>;
|
|
28
|
+
declare const Icon: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
29
|
+
type Icon = ReturnType<typeof Icon>;
|
|
30
|
+
export default Icon;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { cn } from '../utils/cn.js';
|
|
5
|
+
import Button from './Button.svelte';
|
|
6
|
+
import Icon from './Icon.svelte';
|
|
7
|
+
import { INFO_BAR_COLORS, isActive, type InfoBarItem } from './info-bar.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* INFORMAČNÍ LIŠTA (majitel, 4. 9. 2026) — celoplošná sdělení nad menu:
|
|
11
|
+
* celozávodní dovolená, doprava zdarma, otevírací doba o svátcích.
|
|
12
|
+
*
|
|
13
|
+
* - Leží NAD hlavičkou a PŘES hero (absolute, jako hlavička): pozadí
|
|
14
|
+
* úvodního pásu běží pod ní i pod menu, žádný bílý pruh. Při rolování
|
|
15
|
+
* odjede, plovoucí hlavička se přilepí k hornímu okraji jako bez lišty.
|
|
16
|
+
* Lišta hlásí na <html> dvě proměnné: `--info-bar-h` (svou výšku — hero ji
|
|
17
|
+
* přičítá k rezervě pro hlavičku) a `--header-offset` (aktuální `top`
|
|
18
|
+
* hlavičky, od výšky lišty k nule podle rolování).
|
|
19
|
+
* - Tvar jako hlavička: plovoucí karta stejné šířky a zaoblení, na
|
|
20
|
+
* telefonu pruh od kraje ke kraji.
|
|
21
|
+
* - Výška podle obsahu — ale podle NEJVYŠŠÍHO sdělení: všechna jsou vykreslená
|
|
22
|
+
* přes sebe v jedné buňce mřížky a střídá se jen průhlednost, takže obsah pod
|
|
23
|
+
* lištou při přepnutí neposkočí (majitel, 4. 9. 2026). Křížek je u
|
|
24
|
+
* jednořádkového textu uprostřed výšky, u víceřádkového v pravém horním rohu.
|
|
25
|
+
* - Víc sdělení se střídá samo (výchozí 8 s), prolínáním; při najetí myší
|
|
26
|
+
* nebo zaostření se střídání zastaví. Kdo má v systému omezený pohyb,
|
|
27
|
+
* dostane výměnu bez prolínání.
|
|
28
|
+
* - Křížek zavře jen aktuální sdělení; web si to pamatuje po dobu návštěvy
|
|
29
|
+
* (sessionStorage). Lišta zmizí, až když nezbývá co ukázat.
|
|
30
|
+
* - Každé sdělení může mít časové okno od–do včetně času; kontroluje se
|
|
31
|
+
* každou minutu, takže lišta zmizí i bez obnovení stránky.
|
|
32
|
+
* - Odkaz vpravo je TLAČÍTKO v neutrální barvě (majitel, 4. 9. 2026): černé
|
|
33
|
+
* z kapitoly Tlačítka. Na černé liště se neutrální tokeny přemapují jako
|
|
34
|
+
* v tmavém režimu, takže je bílé s černým textem a hover má podle pravidel.
|
|
35
|
+
* Značkový akcent v liště nepoužíváme, sdělení nese barva samotné lišty.
|
|
36
|
+
*/
|
|
37
|
+
interface Props {
|
|
38
|
+
items: InfoBarItem[];
|
|
39
|
+
/** Střídání sdělení v ms. */
|
|
40
|
+
interval?: number;
|
|
41
|
+
/** Klíč v sessionStorage se zavřenými sděleními. */
|
|
42
|
+
storageKey?: string;
|
|
43
|
+
/** Posouvat plovoucí hlavičku pod lištu (vypnout v ukázkách uvnitř stránky). */
|
|
44
|
+
offsetHeader?: boolean;
|
|
45
|
+
/** „Teď“ pro časová okna — přepisuje se jen v testech a ukázkách. */
|
|
46
|
+
now?: Date;
|
|
47
|
+
class?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let {
|
|
51
|
+
items,
|
|
52
|
+
interval = 8000,
|
|
53
|
+
storageKey = 'jelinek-info-bar',
|
|
54
|
+
offsetHeader = true,
|
|
55
|
+
now: nowProp,
|
|
56
|
+
class: className,
|
|
57
|
+
...rest
|
|
58
|
+
}: Props & HTMLAttributes<HTMLDivElement> = $props();
|
|
59
|
+
|
|
60
|
+
let closed = $state<string[]>([]);
|
|
61
|
+
// svelte-ignore state_referenced_locally — výchozí hodnota, dál se řídí hodinami v onMount
|
|
62
|
+
let now = $state(nowProp ?? new Date());
|
|
63
|
+
let index = $state(0);
|
|
64
|
+
let paused = $state(false);
|
|
65
|
+
let single = $state<Record<string, boolean>>({});
|
|
66
|
+
|
|
67
|
+
const visible = $derived(items.filter((i) => !closed.includes(i.id) && isActive(i, now)));
|
|
68
|
+
const current = $derived(visible[Math.min(index, Math.max(visible.length - 1, 0))]);
|
|
69
|
+
|
|
70
|
+
function readClosed(): string[] {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(sessionStorage.getItem(storageKey) ?? '[]');
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function close(id: string) {
|
|
78
|
+
closed = [...closed, id];
|
|
79
|
+
try {
|
|
80
|
+
sessionStorage.setItem(storageKey, JSON.stringify(closed));
|
|
81
|
+
} catch {
|
|
82
|
+
/* soukromý režim bez úložiště — lišta se zavře jen pro tuto stránku */
|
|
83
|
+
}
|
|
84
|
+
index = 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function advance() {
|
|
88
|
+
if (paused || visible.length < 2) return;
|
|
89
|
+
index = (index + 1) % visible.length;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ODHAD VÝŠKY PŘED HYDRATACÍ (majitel, 4. 9. 2026: „menu nejdřív zčerná“).
|
|
93
|
+
// Než doběhne JS a lišta se změří, seděla hlavička na top:0 — tedy PŘES
|
|
94
|
+
// černou lištu, a skleněná karta nad černou vypadá černě. Proto se do <head>
|
|
95
|
+
// vykreslí odhad --header-offset / --info-bar-h už při prerenderu: řádek
|
|
96
|
+
// textu ≈ 20 px, svislé okraje 24 px, na desktopu horní mezera 12 px; na
|
|
97
|
+
// telefonu se odhadne zalomení (~34 znaků na řádek). JS pak hodnotu opraví
|
|
98
|
+
// přesným měřením, případný rozdíl je pár pixelů, žádný černý záblesk.
|
|
99
|
+
const LINE = 20;
|
|
100
|
+
function estimate(phone: boolean): number {
|
|
101
|
+
let lines = 1;
|
|
102
|
+
for (const i of visible) {
|
|
103
|
+
const width = phone ? 34 : 90;
|
|
104
|
+
const titleLines = i.title ? Math.max(1, Math.ceil(i.title.length / (phone ? 30 : 80))) : 0;
|
|
105
|
+
const n = titleLines + Math.max(1, Math.ceil(i.text.length / width));
|
|
106
|
+
lines = Math.max(lines, n);
|
|
107
|
+
}
|
|
108
|
+
return (phone ? 0 : 12) + 24 + LINE * lines;
|
|
109
|
+
}
|
|
110
|
+
const ssrCss = $derived(
|
|
111
|
+
offsetHeader && visible.length
|
|
112
|
+
? `:root{--header-offset:${estimate(false)}px;--info-bar-h:${estimate(false)}px}@media (max-width:639px){:root{--header-offset:${estimate(true)}px;--info-bar-h:${estimate(true)}px}}`
|
|
113
|
+
: ''
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
let wrap = $state<HTMLDivElement | null>(null);
|
|
117
|
+
let textEls = $state<Record<string, HTMLDivElement | null>>({});
|
|
118
|
+
|
|
119
|
+
// Výška lišty → posun hlavičky. Od 640 px je hlavička fixed: posun se
|
|
120
|
+
// zmenšuje s rolováním až na 0 (lišta odjela, menu se přilepilo nahoru).
|
|
121
|
+
// Na telefonu je hlavička absolute a roluje s obsahem: posun = výška lišty.
|
|
122
|
+
function updateOffset() {
|
|
123
|
+
if (!offsetHeader || typeof document === 'undefined') return;
|
|
124
|
+
const root = document.documentElement;
|
|
125
|
+
const h = wrap?.offsetHeight ?? 0;
|
|
126
|
+
if (!h) {
|
|
127
|
+
root.style.removeProperty('--header-offset');
|
|
128
|
+
root.style.removeProperty('--info-bar-h');
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
root.style.setProperty('--info-bar-h', `${h}px`);
|
|
132
|
+
const fixed = matchMedia('(min-width: 640px)').matches;
|
|
133
|
+
root.style.setProperty('--header-offset', `${fixed ? Math.max(0, h - window.scrollY) : h}px`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function updateLines() {
|
|
137
|
+
const next: Record<string, boolean> = {};
|
|
138
|
+
for (const [id, el] of Object.entries(textEls)) {
|
|
139
|
+
if (!el) continue;
|
|
140
|
+
const lh = parseFloat(getComputedStyle(el).lineHeight) || 20;
|
|
141
|
+
next[id] = el.offsetHeight <= lh * 1.5;
|
|
142
|
+
}
|
|
143
|
+
single = next;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
onMount(() => {
|
|
147
|
+
closed = readClosed();
|
|
148
|
+
if (!nowProp) now = new Date();
|
|
149
|
+
const tick = setInterval(advance, interval);
|
|
150
|
+
const clock = nowProp ? null : setInterval(() => (now = new Date()), 60_000);
|
|
151
|
+
const onScroll = () => updateOffset();
|
|
152
|
+
window.addEventListener('scroll', onScroll, { passive: true });
|
|
153
|
+
window.addEventListener('resize', onScroll);
|
|
154
|
+
const ro = typeof ResizeObserver === 'function' ? new ResizeObserver(() => { updateOffset(); updateLines(); }) : null;
|
|
155
|
+
if (ro && wrap) ro.observe(wrap);
|
|
156
|
+
return () => {
|
|
157
|
+
clearInterval(tick);
|
|
158
|
+
if (clock) clearInterval(clock);
|
|
159
|
+
window.removeEventListener('scroll', onScroll);
|
|
160
|
+
window.removeEventListener('resize', onScroll);
|
|
161
|
+
ro?.disconnect();
|
|
162
|
+
if (offsetHeader && typeof document !== 'undefined') {
|
|
163
|
+
document.documentElement.style.removeProperty('--header-offset');
|
|
164
|
+
document.documentElement.style.removeProperty('--info-bar-h');
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
$effect(() => {
|
|
170
|
+
// Nová lišta / jiná sada sdělení → přeměřit.
|
|
171
|
+
wrap; visible; textEls;
|
|
172
|
+
updateOffset();
|
|
173
|
+
updateLines();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Stejná geometrie jako SiteHeader (OUTER/CARD): na telefonu pruh, od 640 px
|
|
177
|
+
// karta se stejným bočním okrajem, stropem šířky a zaoblením.
|
|
178
|
+
// Nad menu, přes hero: absolute jako hlavička (roluje s obsahem). V ukázkách
|
|
179
|
+
// uvnitř stránky (offsetHeader=false) zůstává v toku.
|
|
180
|
+
const WRAP = $derived(
|
|
181
|
+
(offsetHeader ? 'absolute inset-x-0 top-0 ' : 'relative ') + 'z-(--z-bar) min-[640px]:px-6 min-[640px]:pt-3'
|
|
182
|
+
);
|
|
183
|
+
// Všechna sdělení v jedné buňce mřížky (grid-area 1/1): mřížka má výšku
|
|
184
|
+
// nejvyššího z nich, viditelné je jen aktuální (ostatní průhledná a skrytá
|
|
185
|
+
// pro čtečky i klávesnici). Prolínání = přechod průhlednosti; komu systém
|
|
186
|
+
// omezuje pohyb, tomu ho `motion-reduce` vypne.
|
|
187
|
+
// grid-cols minmax(0,1fr) + min-w-0: bez toho by mřížka rostla podle
|
|
188
|
+
// nejdelšího slova a lišta na 320 px přetekla za okraj okna.
|
|
189
|
+
const STACK = 'mx-auto grid w-full max-w-page grid-cols-[minmax(0,1fr)]';
|
|
190
|
+
// flex-wrap + pravý vnitřní okraj pro křížek: na úzkém displeji tlačítko
|
|
191
|
+
// spadne pod text, křížek zůstává v rohu (je absolute).
|
|
192
|
+
const BAR =
|
|
193
|
+
'[grid-area:1/1] relative flex min-w-0 w-full flex-wrap gap-x-3 gap-y-2 py-3 pl-6 pr-14 font-secondary text-[0.9rem] leading-snug ' +
|
|
194
|
+
'shadow-resting transition-opacity duration-[var(--dur-base)] ease-[var(--ease)] motion-reduce:transition-none min-[640px]:rounded-card';
|
|
195
|
+
// Tlačítko v liště: vždy neutrální černé (Komponenty → Tlačítka), jen
|
|
196
|
+
// kompaktnější kvůli výšce lišty. Na černé liště ho čitelným dělá lokální
|
|
197
|
+
// přemapování neutrálních tokenů v INFO_BAR_COLORS.black (stejně jako tmavý
|
|
198
|
+
// režim a .demo--black): černé tlačítko je tam bílé s černým textem a hover
|
|
199
|
+
// jde na 60 % šedou — přesně stav „Černé“ z kapitoly Tlačítka v tmavém režimu.
|
|
200
|
+
const BTN = 'shrink-0 self-center px-4 py-2 text-[0.9rem]';
|
|
201
|
+
const CLOSE =
|
|
202
|
+
'absolute right-2 inline-flex h-11 w-11 cursor-pointer items-center justify-center rounded-button border-0 bg-transparent p-0 ' +
|
|
203
|
+
'text-current opacity-80 hover:opacity-100 focus-visible:outline-2 focus-visible:outline-current';
|
|
204
|
+
</script>
|
|
205
|
+
|
|
206
|
+
<svelte:head>
|
|
207
|
+
<!-- {@html}: literální <style> by Svelte vzal jako styl komponenty, ne jako element. -->
|
|
208
|
+
{#if ssrCss}{@html `<style>${ssrCss}</style>`}{/if}
|
|
209
|
+
</svelte:head>
|
|
210
|
+
|
|
211
|
+
{#if current}
|
|
212
|
+
<div
|
|
213
|
+
{...rest}
|
|
214
|
+
bind:this={wrap}
|
|
215
|
+
class={cn(WRAP, className)}
|
|
216
|
+
data-info-bar
|
|
217
|
+
onmouseenter={() => (paused = true)}
|
|
218
|
+
onmouseleave={() => (paused = false)}
|
|
219
|
+
onfocusin={() => (paused = true)}
|
|
220
|
+
onfocusout={() => (paused = false)}
|
|
221
|
+
>
|
|
222
|
+
<div class={STACK}>
|
|
223
|
+
{#each visible as item, i (item.id)}
|
|
224
|
+
{@const isCurrent = item === current}
|
|
225
|
+
{@const oneLine = single[item.id] ?? true}
|
|
226
|
+
<div
|
|
227
|
+
role="region"
|
|
228
|
+
aria-label="Informační lišta"
|
|
229
|
+
aria-hidden={!isCurrent}
|
|
230
|
+
inert={!isCurrent}
|
|
231
|
+
class={cn(BAR, INFO_BAR_COLORS[item.color ?? 'black'], oneLine ? 'items-center' : 'items-start', !isCurrent && 'pointer-events-none opacity-0')}
|
|
232
|
+
data-color={item.color ?? 'black'}
|
|
233
|
+
data-current={isCurrent || undefined}
|
|
234
|
+
>
|
|
235
|
+
{#if item.icon}
|
|
236
|
+
<span class="shrink-0 {oneLine ? '' : 'mt-0.5'} [&_svg]:h-5 [&_svg]:w-5"><Icon name={item.icon} size={20} /></span>
|
|
237
|
+
{/if}
|
|
238
|
+
<div bind:this={textEls[item.id]} class="min-w-[10rem] flex-1 self-center">
|
|
239
|
+
{#if item.title}<strong class="block font-bold">{item.title}</strong>{/if}
|
|
240
|
+
<span class="font-light">{item.text}</span>
|
|
241
|
+
</div>
|
|
242
|
+
{#if item.href}
|
|
243
|
+
<Button href={item.href} color="black" class={BTN}>
|
|
244
|
+
{item.linkLabel ?? 'Více'}
|
|
245
|
+
</Button>
|
|
246
|
+
{/if}
|
|
247
|
+
<button type="button" class={cn(CLOSE, oneLine ? 'top-1/2 -translate-y-1/2' : 'top-1.5')} aria-label="Zavřít sdělení" title="Zavřít sdělení" onclick={() => close(item.id)}>
|
|
248
|
+
<Icon name="close" size={18} />
|
|
249
|
+
</button>
|
|
250
|
+
</div>
|
|
251
|
+
{/each}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
{/if}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
import { type InfoBarItem } from './info-bar.js';
|
|
3
|
+
/**
|
|
4
|
+
* INFORMAČNÍ LIŠTA (majitel, 4. 9. 2026) — celoplošná sdělení nad menu:
|
|
5
|
+
* celozávodní dovolená, doprava zdarma, otevírací doba o svátcích.
|
|
6
|
+
*
|
|
7
|
+
* - Leží NAD hlavičkou a PŘES hero (absolute, jako hlavička): pozadí
|
|
8
|
+
* úvodního pásu běží pod ní i pod menu, žádný bílý pruh. Při rolování
|
|
9
|
+
* odjede, plovoucí hlavička se přilepí k hornímu okraji jako bez lišty.
|
|
10
|
+
* Lišta hlásí na <html> dvě proměnné: `--info-bar-h` (svou výšku — hero ji
|
|
11
|
+
* přičítá k rezervě pro hlavičku) a `--header-offset` (aktuální `top`
|
|
12
|
+
* hlavičky, od výšky lišty k nule podle rolování).
|
|
13
|
+
* - Tvar jako hlavička: plovoucí karta stejné šířky a zaoblení, na
|
|
14
|
+
* telefonu pruh od kraje ke kraji.
|
|
15
|
+
* - Výška podle obsahu — ale podle NEJVYŠŠÍHO sdělení: všechna jsou vykreslená
|
|
16
|
+
* přes sebe v jedné buňce mřížky a střídá se jen průhlednost, takže obsah pod
|
|
17
|
+
* lištou při přepnutí neposkočí (majitel, 4. 9. 2026). Křížek je u
|
|
18
|
+
* jednořádkového textu uprostřed výšky, u víceřádkového v pravém horním rohu.
|
|
19
|
+
* - Víc sdělení se střídá samo (výchozí 8 s), prolínáním; při najetí myší
|
|
20
|
+
* nebo zaostření se střídání zastaví. Kdo má v systému omezený pohyb,
|
|
21
|
+
* dostane výměnu bez prolínání.
|
|
22
|
+
* - Křížek zavře jen aktuální sdělení; web si to pamatuje po dobu návštěvy
|
|
23
|
+
* (sessionStorage). Lišta zmizí, až když nezbývá co ukázat.
|
|
24
|
+
* - Každé sdělení může mít časové okno od–do včetně času; kontroluje se
|
|
25
|
+
* každou minutu, takže lišta zmizí i bez obnovení stránky.
|
|
26
|
+
* - Odkaz vpravo je TLAČÍTKO v neutrální barvě (majitel, 4. 9. 2026): černé
|
|
27
|
+
* z kapitoly Tlačítka. Na černé liště se neutrální tokeny přemapují jako
|
|
28
|
+
* v tmavém režimu, takže je bílé s černým textem a hover má podle pravidel.
|
|
29
|
+
* Značkový akcent v liště nepoužíváme, sdělení nese barva samotné lišty.
|
|
30
|
+
*/
|
|
31
|
+
interface Props {
|
|
32
|
+
items: InfoBarItem[];
|
|
33
|
+
/** Střídání sdělení v ms. */
|
|
34
|
+
interval?: number;
|
|
35
|
+
/** Klíč v sessionStorage se zavřenými sděleními. */
|
|
36
|
+
storageKey?: string;
|
|
37
|
+
/** Posouvat plovoucí hlavičku pod lištu (vypnout v ukázkách uvnitř stránky). */
|
|
38
|
+
offsetHeader?: boolean;
|
|
39
|
+
/** „Teď“ pro časová okna — přepisuje se jen v testech a ukázkách. */
|
|
40
|
+
now?: Date;
|
|
41
|
+
class?: string;
|
|
42
|
+
}
|
|
43
|
+
type $$ComponentProps = Props & HTMLAttributes<HTMLDivElement>;
|
|
44
|
+
declare const InfoBar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
45
|
+
type InfoBar = ReturnType<typeof InfoBar>;
|
|
46
|
+
export default InfoBar;
|