@jelinek/ui 0.4.1 → 0.5.1

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 CHANGED
@@ -464,24 +464,48 @@ cascade that `pnpm test`'s jsdom environment structurally cannot check
464
464
  The `publish` job is separate and gated on a tag matching `ui-v*` — it
465
465
  does not run on ordinary merges to `master`, only when someone pushes a
466
466
  `ui-v*` tag, and it also depends on `verify` passing first. It publishes
467
- to **npmjs.org with `--access public`**, authenticating with the
468
- `NPM_TOKEN` repo secret (a granular token for the `jelinek` npm org — a
469
- different namespace from the GitHub org, which is named
470
- `JELINEK-nabytek-a-matrace`). No provenance (`--provenance`) is
471
- generated: npm rejects it for builds from a private repository.
472
-
473
- **That job does not currently work, and 0.3.0 was released by hand.** npm
474
- requires a second factor for every publish, and a granular token only
475
- satisfies that if "Bypass two-factor authentication (2FA)" was ticked
476
- *when the token was created* — the setting cannot be added to an existing
477
- token, and npm is in the middle of restricting bypass tokens outright
478
- (account changes Aug 2026, direct publishing Jan 2027). Without it the
479
- registry answers a `PUT` with a misleading `E404 Not Found` in CI, and a
480
- clearer `E403 … Two-factor authentication or granular access token with
481
- bypass 2fa enabled is required` locally.
482
-
483
- So the working release procedure is manual, from a machine that can reach
484
- a browser:
467
+ to **npmjs.org with `--access public`**.
468
+
469
+ ### Releasing
470
+
471
+ 1. Bump `version` in `svelte/package.json`, commit, push `master`.
472
+ 2. `git tag ui-v<version> && git push origin ui-v<version>`.
473
+ 3. `verify` runs, then `publish` — nothing else to do.
474
+
475
+ The tag is what records which commit a version came from, so push it even
476
+ if a release has to be finished by hand.
477
+
478
+ ### How the publish authenticates
479
+
480
+ **Trusted publishing (GitHub Actions OIDC), no secret.** The job asks
481
+ GitHub for an OIDC token and npm exchanges it for a short-lived publish
482
+ credential. Registered on npmjs under the package's *Trusted publisher*:
483
+ org `JELINEK-nabytek-a-matrace`, repo `brand-guide`, workflow filename
484
+ `ui.yml`, environment left empty. All three are checked on every publish
485
+ — **renaming `ui.yml`, or adding an `environment:` to the publish job,
486
+ breaks releases** until the publisher entry is edited to match.
487
+
488
+ Two consequences worth knowing:
489
+
490
+ - The job runs `npm publish`, not `pnpm publish`. The OIDC exchange lives
491
+ in the npm CLI (11.5.1+, which the job installs explicitly); pnpm would
492
+ look for an auth token, find none, and fail.
493
+ - Provenance stays off (`NPM_CONFIG_PROVENANCE=false`). npm can only
494
+ attest a build from a public repository and this one is private
495
+ (licensed fonts). Trusted publishing does not require provenance.
496
+
497
+ There is no `NPM_TOKEN` secret any more. It never worked: npm requires a
498
+ second factor for every publish, and a granular token only satisfies that
499
+ if "Bypass two-factor authentication (2FA)" was ticked *when the token was
500
+ created* — a setting that cannot be added afterwards, on a mechanism npm
501
+ is retiring outright (account changes Aug 2026, direct publishing Jan
502
+ 2027). Without it the registry answered a `PUT` with a misleading `E404
503
+ Not Found` in CI. 0.3.0 through 0.5.0 were released by hand because of it.
504
+
505
+ ### Fallback: publishing by hand
506
+
507
+ If the job fails, the release is not stuck — publish from a machine that
508
+ can reach a browser:
485
509
 
486
510
  ```
487
511
  cd svelte && npm publish --access public --auth-type=web
@@ -490,13 +514,7 @@ cd svelte && npm publish --access public --auth-type=web
490
514
  That prints a `npmjs.com/auth/cli/…` URL; approving it there with a
491
515
  passkey (or an authenticator app) completes the publish. `--otp=<code>`
492
516
  works too, but only with an authenticator app — a passkey produces no
493
- code. Push the `ui-v*` tag anyway: `verify` still gates the release, and
494
- the tag is what records which commit a version came from.
495
-
496
- Fixing CI means either a bypass-enabled token while npm still issues
497
- them, or trusted publishing via GitHub Actions OIDC — the latter is
498
- untested here and may be unavailable for the same reason provenance is,
499
- namely that this repo is private.
517
+ code.
500
518
 
501
519
  ## Known design debt
502
520
 
@@ -0,0 +1,206 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLButtonAttributes } from 'svelte/elements';
4
+ import { cn } from '../utils/cn.js';
5
+
6
+ type Color = 'brown' | 'magenta';
7
+
8
+ interface Props {
9
+ label: string;
10
+ selected?: boolean;
11
+ disabled?: boolean;
12
+ /** Short text shown over the disabled tile explaining WHY it cannot be
13
+ * picked ("Nelze kombinovat s výškou Komfort"). A disabled option with
14
+ * no reason reads as broken, not constrained. */
15
+ disabledNote?: string;
16
+ /** Accent of the selected border and the meter fill — same division
17
+ * pair as Tile. The utilities resolve through --JELINEK-BROWN /
18
+ * --JELINEK-MAGENTA, so an app remapping those per division section
19
+ * re-colours selected tiles without touching this prop. */
20
+ color?: Color;
21
+ /** Photo header (a fabric, a wood finish). Aspect 4:3, top corners
22
+ * follow the tile radius. */
23
+ image?: string;
24
+ /** Empty by default — the label below already names the option. */
25
+ imageAlt?: string;
26
+ /** CSS colour for a swatch-strip header (a lacquer tone, a weight
27
+ * category band) — the no-photograph alternative to `image`. */
28
+ swatch?: string;
29
+ /** Text centred on the swatch strip (matrace-fe's weight label). */
30
+ swatchLabel?: string;
31
+ /** PRE-FORMATTED price delta ("+1 670 Kč"). A string on purpose: money
32
+ * formatting is the app's business (locale, currency, sign), not this
33
+ * kit's — passing a number here would force one formatter on every
34
+ * consumer. Rendered in the accent colour beside the label; wraps to
35
+ * its own right-aligned line on narrow tiles instead of overflowing. */
36
+ price?: string;
37
+ /** Pre-formatted crossed-out price above `price` (a running discount). */
38
+ oldPrice?: string;
39
+ description?: string;
40
+ /** A small labelled level meter (matrace-fe's "Tuhost"): `value` of
41
+ * `max` bars filled with the accent colour. */
42
+ meter?: { label: string; value: number; max?: number };
43
+ /** A Tag ("Novinka", "Volitelné"). Over an image header it floats
44
+ * top-left on the photo; on a tile with no media it renders at the
45
+ * top of the body, above the label row. */
46
+ badge?: Snippet;
47
+ onselect?: () => void;
48
+ /** Extra content at the body's end. Anything interactive inside must
49
+ * carry `data-stop-card-select` so its clicks don't also select the
50
+ * tile. */
51
+ children?: Snippet;
52
+ class?: string;
53
+ }
54
+
55
+ let {
56
+ label,
57
+ selected = false,
58
+ disabled = false,
59
+ disabledNote,
60
+ color = 'brown',
61
+ image,
62
+ imageAlt = '',
63
+ swatch,
64
+ swatchLabel,
65
+ price,
66
+ oldPrice,
67
+ description,
68
+ meter,
69
+ badge,
70
+ onselect,
71
+ children,
72
+ class: className,
73
+ ...rest
74
+ }: Props & Omit<HTMLButtonAttributes, 'disabled'> = $props();
75
+
76
+ function handleClick(event: MouseEvent) {
77
+ if (disabled) return;
78
+ if ((event.target as HTMLElement).closest('[data-stop-card-select]')) return;
79
+ onselect?.();
80
+ }
81
+
82
+ // A selectable option in a configurator — Tile's form-control sibling,
83
+ // first built twice over (matrace-fe's OptionCard, then the e-shop's) and
84
+ // promoted here so the third app doesn't roll a fourth copy. The guide has
85
+ // no .option-tile rule; the geometry is Tile's (rounded-card, the
86
+ // border-subtle resting frame), the selected state is the division-accent
87
+ // border the e-shop shipped, and — unlike Tile — there is NO hover lift:
88
+ // this is a form control, and its feedback is the border, not motion.
89
+ //
90
+ // A real <button aria-pressed>, not a clickable <div>: keyboard and screen
91
+ // readers get the option for free (matrace-fe's div needed two a11y-lint
92
+ // waivers; this needs none).
93
+ const SELECTED: Record<Color, string> = {
94
+ brown: 'border-jelinek-brown shadow-resting',
95
+ magenta: 'border-jelinek-magenta shadow-resting'
96
+ };
97
+ const HOVER: Record<Color, string> = {
98
+ brown: 'hover:border-jelinek-brown-highlight',
99
+ magenta: 'hover:border-jelinek-magenta-highlight'
100
+ };
101
+ const METER_FILL: Record<Color, string> = {
102
+ brown: 'bg-jelinek-brown',
103
+ magenta: 'bg-jelinek-magenta'
104
+ };
105
+ const PRICE_TEXT: Record<Color, string> = {
106
+ brown: 'text-jelinek-brown',
107
+ magenta: 'text-jelinek-magenta'
108
+ };
109
+
110
+ const BASE =
111
+ 'w-full cursor-pointer rounded-card border-2 bg-surface text-left ' +
112
+ 'font-secondary font-light text-fg ' +
113
+ 'transition-[border-color,box-shadow] duration-[var(--dur-base)] ease-[var(--ease)]';
114
+
115
+ const classes = $derived(
116
+ cn(
117
+ BASE,
118
+ selected ? SELECTED[color] : cn('border-border-subtle', HOVER[color]),
119
+ disabled && 'pointer-events-none opacity-60',
120
+ className
121
+ )
122
+ );
123
+
124
+ const meterMax = $derived(meter?.max ?? 5);
125
+ </script>
126
+
127
+ <div class="relative">
128
+ <button type="button" onclick={handleClick} aria-pressed={selected} {disabled} {...rest} class={classes}>
129
+ {#if image}
130
+ <div class="relative">
131
+ <img
132
+ src={image}
133
+ alt={imageAlt}
134
+ loading="lazy"
135
+ class="aspect-[4/3] w-full rounded-t-[calc(var(--radius-card)-2px)] object-cover"
136
+ />
137
+ {#if badge}<div class="absolute top-2 left-2">{@render badge()}</div>{/if}
138
+ </div>
139
+ {:else if swatch}
140
+ <div
141
+ class="flex aspect-[4/1] w-full items-center justify-center rounded-t-[calc(var(--radius-card)-2px)]"
142
+ style="background:{swatch}"
143
+ >
144
+ {#if swatchLabel}
145
+ <!-- Literal white + a soft shadow: the swatch is an arbitrary
146
+ consumer colour, so no theme token can promise contrast.
147
+ `font-bold`, not `font-semibold`: GT Walsheim Pro ships
148
+ 300/400/500/700/900 and no 600, so semibold silently
149
+ renders as Medium 500 (e2e/font-faces.spec.ts caught it).
150
+ `font-semibold` is only legal on the logo subtitle, which
151
+ switches to Avenir Next, whose Demi *is* 600. -->
152
+ <span class="font-bold text-white [text-shadow:0_1px_2px_rgba(0,0,0,0.35)]"
153
+ >{swatchLabel}</span
154
+ >
155
+ {/if}
156
+ </div>
157
+ {/if}
158
+
159
+ <div class="flex flex-col gap-1 p-4">
160
+ {#if badge && !image && !swatch}<div>{@render badge()}</div>{/if}
161
+ <!-- flex-wrap + ml-auto: the price is one unbreakable chunk (nbsp
162
+ grouping); on a narrow tile it drops to its own right-aligned
163
+ line instead of overflowing the border. -->
164
+ <div class="flex flex-wrap items-baseline justify-between gap-x-2">
165
+ <span class="font-bold">{label}</span>
166
+ {#if price || oldPrice}
167
+ <span class="ml-auto flex flex-col items-end">
168
+ {#if oldPrice}<span class="text-step-down2 text-fg-muted2 line-through"
169
+ >{oldPrice}</span
170
+ >{/if}
171
+ {#if price}<span
172
+ class={cn('text-step-down1 font-medium whitespace-nowrap', PRICE_TEXT[color])}
173
+ >{price}</span
174
+ >{/if}
175
+ </span>
176
+ {/if}
177
+ </div>
178
+ {#if description}<p class="text-step-down1 text-fg-muted1 m-0">{description}</p>{/if}
179
+ {#if meter}
180
+ <div class="mt-1 flex items-center gap-2">
181
+ <span class="text-step-down2 text-fg-muted2 font-medium tracking-wide uppercase"
182
+ >{meter.label}</span
183
+ >
184
+ <div class="flex gap-[3px]" role="img" aria-label={`${meter.label}: ${meter.value} z ${meterMax}`}>
185
+ {#each Array(meterMax) as _, i (i)}
186
+ <div
187
+ class={cn(
188
+ 'h-1 w-3 rounded-[2px]',
189
+ i < meter.value ? METER_FILL[color] : 'bg-border-subtle'
190
+ )}
191
+ ></div>
192
+ {/each}
193
+ </div>
194
+ </div>
195
+ {/if}
196
+ {#if children}<div class="mt-1">{@render children()}</div>{/if}
197
+ </div>
198
+ </button>
199
+ {#if disabled && disabledNote}
200
+ <div
201
+ class="rounded-card text-step-down1 absolute inset-0 grid place-items-center bg-[var(--glass-fill)] p-4 text-center font-medium backdrop-blur-sm"
202
+ >
203
+ {disabledNote}
204
+ </div>
205
+ {/if}
206
+ </div>
@@ -0,0 +1,57 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLButtonAttributes } from 'svelte/elements';
3
+ type Color = 'brown' | 'magenta';
4
+ interface Props {
5
+ label: string;
6
+ selected?: boolean;
7
+ disabled?: boolean;
8
+ /** Short text shown over the disabled tile explaining WHY it cannot be
9
+ * picked ("Nelze kombinovat s výškou Komfort"). A disabled option with
10
+ * no reason reads as broken, not constrained. */
11
+ disabledNote?: string;
12
+ /** Accent of the selected border and the meter fill — same division
13
+ * pair as Tile. The utilities resolve through --JELINEK-BROWN /
14
+ * --JELINEK-MAGENTA, so an app remapping those per division section
15
+ * re-colours selected tiles without touching this prop. */
16
+ color?: Color;
17
+ /** Photo header (a fabric, a wood finish). Aspect 4:3, top corners
18
+ * follow the tile radius. */
19
+ image?: string;
20
+ /** Empty by default — the label below already names the option. */
21
+ imageAlt?: string;
22
+ /** CSS colour for a swatch-strip header (a lacquer tone, a weight
23
+ * category band) — the no-photograph alternative to `image`. */
24
+ swatch?: string;
25
+ /** Text centred on the swatch strip (matrace-fe's weight label). */
26
+ swatchLabel?: string;
27
+ /** PRE-FORMATTED price delta ("+1 670 Kč"). A string on purpose: money
28
+ * formatting is the app's business (locale, currency, sign), not this
29
+ * kit's — passing a number here would force one formatter on every
30
+ * consumer. Rendered in the accent colour beside the label; wraps to
31
+ * its own right-aligned line on narrow tiles instead of overflowing. */
32
+ price?: string;
33
+ /** Pre-formatted crossed-out price above `price` (a running discount). */
34
+ oldPrice?: string;
35
+ description?: string;
36
+ /** A small labelled level meter (matrace-fe's "Tuhost"): `value` of
37
+ * `max` bars filled with the accent colour. */
38
+ meter?: {
39
+ label: string;
40
+ value: number;
41
+ max?: number;
42
+ };
43
+ /** A Tag ("Novinka", "Volitelné"). Over an image header it floats
44
+ * top-left on the photo; on a tile with no media it renders at the
45
+ * top of the body, above the label row. */
46
+ badge?: Snippet;
47
+ onselect?: () => void;
48
+ /** Extra content at the body's end. Anything interactive inside must
49
+ * carry `data-stop-card-select` so its clicks don't also select the
50
+ * tile. */
51
+ children?: Snippet;
52
+ class?: string;
53
+ }
54
+ type $$ComponentProps = Props & Omit<HTMLButtonAttributes, 'disabled'>;
55
+ declare const OptionTile: import("svelte").Component<$$ComponentProps, {}, "">;
56
+ type OptionTile = ReturnType<typeof OptionTile>;
57
+ export default OptionTile;
@@ -0,0 +1,109 @@
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 Level = 'h2' | 'h3';
7
+
8
+ interface Props {
9
+ /** With `href` the tile is a link; without it an inert block — same
10
+ * contract as Tile, and the same reasoning: an unlinked tile has
11
+ * nowhere to go, so it should not be keyboard-reachable either. */
12
+ href?: string;
13
+ /** The photograph filling the tile. */
14
+ image: string;
15
+ /** Empty by default: the visible title already names the destination,
16
+ * so the image is decorative unless a consumer says otherwise. */
17
+ imageAlt?: string;
18
+ title: string;
19
+ description?: string;
20
+ /** Heading tag for the title. `h3` matches Tile's own convention; an
21
+ * app using photo tiles as top-level section links (the e-shop's two
22
+ * division tiles) passes `h2`. */
23
+ level?: Level;
24
+ /** Passed straight to the <img>. Photo tiles usually sit below the
25
+ * fold, so lazy is the right default; a consumer rendering one above
26
+ * the fold passes `eager`. */
27
+ loading?: 'lazy' | 'eager';
28
+ class?: string;
29
+ /** Extra content under the description — a Tag, a small CTA line. */
30
+ children?: Snippet;
31
+ }
32
+
33
+ let {
34
+ href,
35
+ image,
36
+ imageAlt = '',
37
+ title,
38
+ description,
39
+ level = 'h3',
40
+ loading = 'lazy',
41
+ class: className,
42
+ children,
43
+ ...rest
44
+ }: Props & Omit<HTMLAnchorAttributes & HTMLAttributes<HTMLDivElement>, 'href' | 'title'> =
45
+ $props();
46
+
47
+ // A photo tile is Tile's full-bleed photographic sibling — first built in
48
+ // the e-shop storefront (its home-page division tiles), promoted here so
49
+ // consumers stop hand-rolling it. The guide itself has no .photo-tile
50
+ // rule; every value below is either a shared token the guide's own
51
+ // surfaces use, or is forced by the photograph:
52
+ //
53
+ // - Geometry/motion are Tile's: rounded-section (a full-bleed photo card
54
+ // is section-scale, not card-scale), shadow-resting → shadow-interactive
55
+ // with the 2px hover lift, both on the guide's own
56
+ // box-shadow/transform transition pair (jelinek.css:924 — see Tile for
57
+ // why transition-all is wrong here).
58
+ // - `no-underline`: base.css underlines every anchor on hover; on a tile
59
+ // that underline bleeds onto the caption text (the exact defect the
60
+ // e-shop shipped with).
61
+ // - The scrim and the text are LITERAL black/white, not theme tokens, on
62
+ // purpose: they sit on a photograph, which does not re-theme.
63
+ // --JELINEK-WHITE inverts to hsl(0,0%,0%) in dark mode (tokens.css:96)
64
+ // — white-on-scrim text routed through it would go invisible on its own
65
+ // photo, the same trap SiteFooter documents for its logo.
66
+ const BASE =
67
+ 'group relative flex flex-col justify-end overflow-hidden aspect-[4/3] ' +
68
+ 'rounded-section shadow-resting no-underline ' +
69
+ 'transition-[box-shadow,transform] duration-[var(--dur-base)] ease-[var(--ease)] ' +
70
+ 'hover:-translate-y-0.5 hover:shadow-interactive ' +
71
+ 'dark:shadow-[var(--shadow-resting),inset_0_2px_1px_-1px_var(--box-primary)] ' +
72
+ 'dark:hover:shadow-[var(--shadow-interactive),inset_0_2px_1px_-1px_var(--box-primary)]';
73
+
74
+ // The photo zooms slowly under the pointer while the tile itself lifts on
75
+ // --dur-base — two speeds on purpose: the lift is feedback ("this is a
76
+ // control"), the zoom is atmosphere. 700ms is deliberate and NOT a --dur
77
+ // token: the scale is 8× the size of the 2px lift, and at --dur-slow
78
+ // (0.2s) it reads as a twitch, not a drift.
79
+ const IMG =
80
+ 'absolute inset-0 h-full w-full object-cover ' +
81
+ 'transition-transform duration-700 ease-[var(--ease)] group-hover:scale-105';
82
+
83
+ // Bottom-weighted scrim so white text passes contrast over any photo —
84
+ // strongest where the text sits, gone by mid-frame so the photo stays a
85
+ // photo.
86
+ const SCRIM = 'absolute inset-0 bg-gradient-to-t from-black/70 via-black/10 to-transparent';
87
+
88
+ const TEXT = 'relative p-6 sm:p-8';
89
+ const TITLE = 'font-primary text-step3 mb-1 font-bold text-white';
90
+ const DESC = 'text-step0 m-0 max-w-xs text-white/90';
91
+
92
+ const classes = $derived(cn(BASE, href && 'cursor-pointer', className));
93
+ </script>
94
+
95
+ {#snippet content()}
96
+ <img src={image} alt={imageAlt} {loading} class={IMG} />
97
+ <div class={SCRIM}></div>
98
+ <div class={TEXT}>
99
+ <svelte:element this={level} class={TITLE}>{title}</svelte:element>
100
+ {#if description}<p class={DESC}>{description}</p>{/if}
101
+ {#if children}{@render children()}{/if}
102
+ </div>
103
+ {/snippet}
104
+
105
+ {#if href}
106
+ <a {...rest} {href} class={classes}>{@render content()}</a>
107
+ {:else}
108
+ <div {...rest} class={classes}>{@render content()}</div>
109
+ {/if}
@@ -0,0 +1,31 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAnchorAttributes, HTMLAttributes } from 'svelte/elements';
3
+ type Level = 'h2' | 'h3';
4
+ interface Props {
5
+ /** With `href` the tile is a link; without it an inert block — same
6
+ * contract as Tile, and the same reasoning: an unlinked tile has
7
+ * nowhere to go, so it should not be keyboard-reachable either. */
8
+ href?: string;
9
+ /** The photograph filling the tile. */
10
+ image: string;
11
+ /** Empty by default: the visible title already names the destination,
12
+ * so the image is decorative unless a consumer says otherwise. */
13
+ imageAlt?: string;
14
+ title: string;
15
+ description?: string;
16
+ /** Heading tag for the title. `h3` matches Tile's own convention; an
17
+ * app using photo tiles as top-level section links (the e-shop's two
18
+ * division tiles) passes `h2`. */
19
+ level?: Level;
20
+ /** Passed straight to the <img>. Photo tiles usually sit below the
21
+ * fold, so lazy is the right default; a consumer rendering one above
22
+ * the fold passes `eager`. */
23
+ loading?: 'lazy' | 'eager';
24
+ class?: string;
25
+ /** Extra content under the description — a Tag, a small CTA line. */
26
+ children?: Snippet;
27
+ }
28
+ type $$ComponentProps = Props & Omit<HTMLAnchorAttributes & HTMLAttributes<HTMLDivElement>, 'href' | 'title'>;
29
+ declare const PhotoTile: import("svelte").Component<$$ComponentProps, {}, "">;
30
+ type PhotoTile = ReturnType<typeof PhotoTile>;
31
+ export default PhotoTile;
package/dist/index.d.ts CHANGED
@@ -15,9 +15,11 @@ export { default as Glass } from './components/Glass.svelte';
15
15
  export { default as Grid } from './components/Grid.svelte';
16
16
  export { default as Label } from './components/Label.svelte';
17
17
  export { isActive, type NavItem, type NavGroup } from './components/nav.js';
18
+ export { default as OptionTile } from './components/OptionTile.svelte';
18
19
  export { default as PageHero } from './components/PageHero.svelte';
19
20
  export { default as Pager } from './components/Pager.svelte';
20
21
  export { default as Panel } from './components/Panel.svelte';
22
+ export { default as PhotoTile } from './components/PhotoTile.svelte';
21
23
  export { default as ProductCard } from './components/ProductCard.svelte';
22
24
  export { default as Radio } from './components/Radio.svelte';
23
25
  export { RADIO_CONTEXT, type RadioContext } from './components/radio-context.js';
package/dist/index.js CHANGED
@@ -15,9 +15,11 @@ export { default as Glass } from './components/Glass.svelte';
15
15
  export { default as Grid } from './components/Grid.svelte';
16
16
  export { default as Label } from './components/Label.svelte';
17
17
  export { isActive } from './components/nav.js';
18
+ export { default as OptionTile } from './components/OptionTile.svelte';
18
19
  export { default as PageHero } from './components/PageHero.svelte';
19
20
  export { default as Pager } from './components/Pager.svelte';
20
21
  export { default as Panel } from './components/Panel.svelte';
22
+ export { default as PhotoTile } from './components/PhotoTile.svelte';
21
23
  export { default as ProductCard } from './components/ProductCard.svelte';
22
24
  export { default as Radio } from './components/Radio.svelte';
23
25
  export { RADIO_CONTEXT } from './components/radio-context.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jelinek/ui",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
4
4
  "description": "JELÍNEK Svelte 5 component kit, generated from the brand guide",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",