@krizaka/ui 3.8.0 → 3.10.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 +13 -9
- package/dist/action-bar.js +2 -1
- package/dist/action-bar.js.map +1 -1
- package/dist/app-shell.d.ts +81 -3
- package/dist/app-shell.js +163 -35
- package/dist/app-shell.js.map +1 -1
- package/dist/audio-player.js +1 -1
- package/dist/badge.d.ts +36 -39
- package/dist/badge.js +11 -2
- package/dist/badge.js.map +1 -1
- package/dist/blocks.css +56 -4
- package/dist/button-BmCrLCq7.d.ts +114 -0
- package/dist/button.d.ts +48 -48
- package/dist/button.js +2 -1
- package/dist/card.d.ts +111 -111
- package/dist/card.js +10 -2
- package/dist/card.js.map +1 -1
- package/dist/chip.d.ts +24 -24
- package/dist/chip.js +10 -2
- package/dist/chip.js.map +1 -1
- package/dist/{chunk-UOMP6VOZ.js → chunk-276PTQMQ.js} +10 -4
- package/dist/chunk-276PTQMQ.js.map +1 -0
- package/dist/{chunk-Z6FUPB3Q.js → chunk-2NB27LM7.js} +11 -3
- package/dist/chunk-2NB27LM7.js.map +1 -0
- package/dist/chunk-2VNYHG4E.js +9 -0
- package/dist/chunk-2VNYHG4E.js.map +1 -0
- package/dist/{chunk-ND3L5VHT.js → chunk-5Z7FWCV2.js} +2 -2
- package/dist/{chunk-YH4LMWRU.js → chunk-7UCPGXKN.js} +11 -3
- package/dist/chunk-7UCPGXKN.js.map +1 -0
- package/dist/{chunk-AZPKXP3O.js → chunk-AFY35UCB.js} +6 -1
- package/dist/{chunk-AZPKXP3O.js.map → chunk-AFY35UCB.js.map} +1 -1
- package/dist/{chunk-LCZXS6AK.js → chunk-CKSHLSDH.js} +5 -5
- package/dist/chunk-CKSHLSDH.js.map +1 -0
- package/dist/chunk-D3T54RSL.js +11 -0
- package/dist/chunk-D3T54RSL.js.map +1 -0
- package/dist/{chunk-ZKVQGXLD.js → chunk-FKBHWS4N.js} +2 -2
- package/dist/{chunk-BRKMQ6JV.js → chunk-FZBWEDQI.js} +1 -23
- package/dist/chunk-FZBWEDQI.js.map +1 -0
- package/dist/chunk-HG65YAV4.js +379 -0
- package/dist/chunk-HG65YAV4.js.map +1 -0
- package/dist/{chunk-HCNOOPZE.js → chunk-KVDB4GXV.js} +2 -2
- package/dist/chunk-OT6HX3PN.js +85 -0
- package/dist/chunk-OT6HX3PN.js.map +1 -0
- package/dist/{chunk-RUPNPMP3.js → chunk-OWC2LZD2.js} +11 -3
- package/dist/chunk-OWC2LZD2.js.map +1 -0
- package/dist/{chunk-FDCMBWUU.js → chunk-REPTKUJ6.js} +4 -4
- package/dist/chunk-REPTKUJ6.js.map +1 -0
- package/dist/code-block.d.ts +32 -10
- package/dist/code-block.js +51 -43
- package/dist/code-block.js.map +1 -1
- package/dist/combobox.d.ts +3 -3
- package/dist/combobox.js +4 -3
- package/dist/command.js +3 -2
- package/dist/command.js.map +1 -1
- package/dist/component-page.js.map +1 -1
- package/dist/confirm-button.d.ts +1 -1
- package/dist/confirm-button.js +2 -1
- package/dist/confirm-button.js.map +1 -1
- package/dist/continue-prompt.js +11 -3
- package/dist/continue-prompt.js.map +1 -1
- package/dist/data-table.js +7 -6
- package/dist/data-table.js.map +1 -1
- package/dist/date-picker.js +1 -1
- package/dist/dialog.js +3 -2
- package/dist/dialog.js.map +1 -1
- package/dist/file-upload.d.ts +12 -1
- package/dist/file-upload.js +73 -89
- package/dist/file-upload.js.map +1 -1
- package/dist/filter-bar.js +3 -2
- package/dist/image-cropper.js +1 -1
- package/dist/index.js +9 -367
- package/dist/index.js.map +1 -1
- package/dist/list.js +1 -1
- package/dist/map.js.map +1 -1
- package/dist/markdown.d.ts +38 -0
- package/dist/markdown.js +150 -0
- package/dist/markdown.js.map +1 -0
- package/dist/match-dialog.js +2 -1
- package/dist/match-dialog.js.map +1 -1
- package/dist/media-capture.js +1 -1
- package/dist/native/markdown.d.ts +24 -0
- package/dist/native/markdown.js +200 -0
- package/dist/native/markdown.js.map +1 -0
- package/dist/native.d.ts +49 -8
- package/dist/native.js +728 -349
- package/dist/native.js.map +1 -1
- package/dist/nav-bar.d.ts +86 -3
- package/dist/nav-bar.js +88 -2
- package/dist/nav-bar.js.map +1 -1
- package/dist/recipes.d.ts +143 -0
- package/dist/recipes.js +180 -0
- package/dist/recipes.js.map +1 -0
- package/dist/search-field.js +2 -2
- package/dist/showcase.d.ts +69 -0
- package/dist/showcase.js +218 -0
- package/dist/showcase.js.map +1 -0
- package/dist/site-footer.d.ts +172 -0
- package/dist/site-footer.js +105 -0
- package/dist/site-footer.js.map +1 -0
- package/dist/sites-CgJrxejx.d.ts +32 -0
- package/dist/sites.d.ts +64 -0
- package/dist/sites.js +69 -0
- package/dist/sites.js.map +1 -0
- package/dist/swipe-deck.js +1 -1
- package/dist/tag-input.js +4 -3
- package/dist/tag-input.js.map +1 -1
- package/dist/tag-selector.js +2 -1
- package/dist/tag-selector.js.map +1 -1
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +2 -1
- package/dist/theme.js.map +1 -1
- package/dist/tree-view.js +1 -1
- package/dist/vertical-video.js +2 -1
- package/dist/vertical-video.js.map +1 -1
- package/dist/video-player.js +1 -1
- package/dist/video-trimmer.js +1 -1
- package/dist/virtual.js +2 -2
- package/package.json +87 -18
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +1 -1
- package/registry/activity-feed.json +1 -1
- package/registry/alert.json +1 -1
- package/registry/app-shell.json +135 -12
- package/registry/argument-card.json +1 -1
- package/registry/audio-player.json +2 -2
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +1 -1
- package/registry/badge.json +9 -3
- package/registry/button.json +7 -2
- package/registry/card-grid.json +1 -1
- package/registry/card.json +8 -3
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +1 -1
- package/registry/chat.json +1 -1
- package/registry/checkbox.json +1 -1
- package/registry/chip-link.json +1 -1
- package/registry/chip.json +9 -3
- package/registry/choice-grid.json +1 -1
- package/registry/code-block.json +65 -21
- package/registry/combobox.json +4 -3
- package/registry/command.json +1 -1
- package/registry/component-page.json +4 -4
- package/registry/confirm-button.json +1 -1
- package/registry/contact-page.json +1 -1
- package/registry/continue-prompt.json +1 -1
- package/registry/countdown.json +1 -1
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +2 -2
- package/registry/date-picker.json +3 -3
- package/registry/decision-list.json +1 -1
- package/registry/description-list.json +1 -1
- package/registry/detail-layout.json +1 -1
- package/registry/dialog.json +1 -1
- package/registry/docs-article-page.json +1 -1
- package/registry/docs-hub-page.json +1 -1
- package/registry/docs-layout.json +1 -1
- package/registry/dropdown-menu.json +1 -1
- package/registry/empty-state.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/app-shell/console.d.ts +4 -0
- package/registry/examples/app-shell/console.js +93 -0
- package/registry/examples/app-shell/console.tsx +103 -0
- package/registry/examples/badge/native/dot.dark.png +0 -0
- package/registry/examples/badge/native/dot.light.png +0 -0
- package/registry/examples/badge/native/scrim.dark.png +0 -0
- package/registry/examples/badge/native/scrim.light.png +0 -0
- package/registry/examples/badge/native/tones.dark.png +0 -0
- package/registry/examples/badge/native/tones.light.png +0 -0
- package/registry/examples/card/native/default.dark.png +0 -0
- package/registry/examples/card/native/default.light.png +0 -0
- package/registry/examples/card/native/elevated.light.png +0 -0
- package/registry/examples/chip/native/multiple.dark.png +0 -0
- package/registry/examples/chip/native/multiple.light.png +0 -0
- package/registry/examples/chip/native/removable.dark.png +0 -0
- package/registry/examples/chip/native/removable.light.png +0 -0
- package/registry/examples/chip/native/single.dark.png +0 -0
- package/registry/examples/chip/native/single.light.png +0 -0
- package/registry/examples/chip/native/toggle.dark.png +0 -0
- package/registry/examples/chip/native/toggle.light.png +0 -0
- package/registry/examples/code-block/annotated.d.ts +4 -0
- package/registry/examples/code-block/annotated.js +31 -0
- package/registry/examples/code-block/annotated.tsx +26 -0
- package/registry/examples/component-page/button.tsx +1 -1
- package/registry/examples/form/native/amount.dark.png +0 -0
- package/registry/examples/form/native/amount.light.png +0 -0
- package/registry/examples/markdown/ai-stream.d.ts +4 -0
- package/registry/examples/markdown/ai-stream.js +31 -0
- package/registry/examples/markdown/ai-stream.tsx +32 -0
- package/registry/examples/markdown/basic.d.ts +4 -0
- package/registry/examples/markdown/basic.js +26 -0
- package/registry/examples/markdown/basic.tsx +22 -0
- package/registry/examples/markdown/native/ai-stream.d.ts +4 -0
- package/registry/examples/markdown/native/ai-stream.dark.png +0 -0
- package/registry/examples/markdown/native/ai-stream.js +17 -0
- package/registry/examples/markdown/native/ai-stream.light.png +0 -0
- package/registry/examples/markdown/native/ai-stream.tsx +17 -0
- package/registry/examples/markdown/native/basic.d.ts +4 -0
- package/registry/examples/markdown/native/basic.dark.png +0 -0
- package/registry/examples/markdown/native/basic.js +28 -0
- package/registry/examples/markdown/native/basic.light.png +0 -0
- package/registry/examples/markdown/native/basic.tsx +27 -0
- package/registry/examples/nav-bar/family-admin.d.ts +4 -0
- package/registry/examples/nav-bar/family-admin.js +21 -0
- package/registry/examples/nav-bar/family-admin.tsx +20 -0
- package/registry/examples/nav-bar/family-java.d.ts +4 -0
- package/registry/examples/nav-bar/family-java.js +33 -0
- package/registry/examples/nav-bar/family-java.tsx +34 -0
- package/registry/examples/nav-bar/family-product.d.ts +4 -0
- package/registry/examples/nav-bar/family-product.js +35 -0
- package/registry/examples/nav-bar/family-product.tsx +39 -0
- package/registry/examples/nav-bar/family-ui.d.ts +4 -0
- package/registry/examples/nav-bar/family-ui.js +34 -0
- package/registry/examples/nav-bar/family-ui.tsx +35 -0
- package/registry/examples/nav-bar/family-www.d.ts +4 -0
- package/registry/examples/nav-bar/family-www.js +34 -0
- package/registry/examples/nav-bar/family-www.tsx +36 -0
- package/registry/examples/recipes/tone-chip.d.ts +4 -0
- package/registry/examples/recipes/tone-chip.js +46 -0
- package/registry/examples/recipes/tone-chip.tsx +32 -0
- package/registry/examples/screen/native/profile.dark.png +0 -0
- package/registry/examples/screen/native/profile.light.png +0 -0
- package/registry/examples/showcase/native/slides.d.ts +4 -0
- package/registry/examples/showcase/native/slides.dark.png +0 -0
- package/registry/examples/showcase/native/slides.js +27 -0
- package/registry/examples/showcase/native/slides.light.png +0 -0
- package/registry/examples/showcase/native/slides.tsx +28 -0
- package/registry/examples/showcase/prompt-image.d.ts +4 -0
- package/registry/examples/showcase/prompt-image.js +29 -0
- package/registry/examples/showcase/prompt-image.tsx +28 -0
- package/registry/examples/showcase/slides.d.ts +4 -0
- package/registry/examples/showcase/slides.js +29 -0
- package/registry/examples/showcase/slides.tsx +24 -0
- package/registry/examples/site-footer/company.d.ts +4 -0
- package/registry/examples/site-footer/company.js +26 -0
- package/registry/examples/site-footer/company.tsx +23 -0
- package/registry/examples/site-footer/open-source-fr.d.ts +4 -0
- package/registry/examples/site-footer/open-source-fr.js +17 -0
- package/registry/examples/site-footer/open-source-fr.tsx +17 -0
- package/registry/examples/site-footer/product.d.ts +4 -0
- package/registry/examples/site-footer/product.js +22 -0
- package/registry/examples/site-footer/product.tsx +19 -0
- package/registry/examples/sites/family.d.ts +4 -0
- package/registry/examples/sites/family.js +20 -0
- package/registry/examples/sites/family.tsx +30 -0
- package/registry/examples/sites/french.d.ts +4 -0
- package/registry/examples/sites/french.js +25 -0
- package/registry/examples/sites/french.tsx +25 -0
- package/registry/examples/tag-selector/native/interests.dark.png +0 -0
- package/registry/examples/tag-selector/native/interests.light.png +0 -0
- package/registry/field.json +1 -1
- package/registry/file-upload.json +8 -3
- package/registry/filter-bar.json +1 -1
- package/registry/form.json +2 -2
- package/registry/gallery-page.json +1 -1
- package/registry/glow-card.json +1 -1
- package/registry/goal-gauge.json +1 -1
- package/registry/how-it-works-page.json +1 -1
- package/registry/image-cropper.json +2 -2
- package/registry/index.json +138 -20
- package/registry/iso.json +1 -1
- package/registry/kanban.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/landing-page.json +1 -1
- package/registry/landscape.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +1 -1
- package/registry/list.json +2 -2
- package/registry/loader.json +1 -1
- package/registry/longform-page.json +1 -1
- package/registry/map.json +3 -3
- package/registry/markdown.json +266 -0
- package/registry/marketing-layout.json +1 -1
- package/registry/marquee.json +1 -1
- package/registry/mascot-quote.json +1 -1
- package/registry/mascot-state.json +1 -1
- package/registry/mascots.json +1 -1
- package/registry/match-dialog.json +1 -1
- package/registry/media-capture.json +2 -2
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +257 -13
- package/registry/number-ticker.json +1 -1
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +1 -1
- package/registry/page.json +1 -1
- package/registry/pagination.json +1 -1
- package/registry/photo-grid.json +1 -1
- package/registry/pointer-light.json +1 -1
- package/registry/popover.json +1 -1
- package/registry/pricing-page.json +1 -1
- package/registry/product-page.json +1 -1
- package/registry/profile-card.json +1 -1
- package/registry/progress.json +1 -1
- package/registry/promise-card.json +1 -1
- package/registry/radio-group.json +1 -1
- package/registry/recipes.json +99 -0
- package/registry/scroll-stage.json +1 -1
- package/registry/search-field.json +4 -3
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/settings-layout.json +1 -1
- package/registry/showcase.json +421 -0
- package/registry/site-footer.json +389 -0
- package/registry/sites.json +93 -0
- package/registry/skeleton.json +1 -1
- package/registry/skip-link.json +1 -1
- package/registry/slider.json +1 -1
- package/registry/slot.json +1 -1
- package/registry/spinner.json +1 -1
- package/registry/split-view.json +1 -1
- package/registry/stage-banner.json +1 -1
- package/registry/stat.json +1 -1
- package/registry/status-page.json +1 -1
- package/registry/step-progress.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/swipe-deck.json +2 -2
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/tag-input.json +2 -2
- package/registry/tag-selector.json +1 -1
- package/registry/terminal-replay.json +1 -1
- package/registry/theme.json +1 -1
- package/registry/toast.json +1 -1
- package/registry/toc.json +1 -1
- package/registry/tooltip.json +1 -1
- package/registry/tree-view.json +2 -2
- package/registry/verified-badge.json +1 -1
- package/registry/vertical-video.json +2 -2
- package/registry/video-player.json +2 -2
- package/registry/video-trimmer.json +2 -2
- package/registry/virtual.json +2 -2
- package/dist/button-BMAO7dKF.d.ts +0 -114
- package/dist/chunk-BRKMQ6JV.js.map +0 -1
- package/dist/chunk-FDCMBWUU.js.map +0 -1
- package/dist/chunk-LCZXS6AK.js.map +0 -1
- package/dist/chunk-RUPNPMP3.js.map +0 -1
- package/dist/chunk-UOMP6VOZ.js.map +0 -1
- package/dist/chunk-YH4LMWRU.js.map +0 -1
- package/dist/chunk-Z6FUPB3Q.js.map +0 -1
- /package/dist/{chunk-ND3L5VHT.js.map → chunk-5Z7FWCV2.js.map} +0 -0
- /package/dist/{chunk-ZKVQGXLD.js.map → chunk-FKBHWS4N.js.map} +0 -0
- /package/dist/{chunk-HCNOOPZE.js.map → chunk-KVDB4GXV.js.map} +0 -0
package/dist/recipes.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineRecipe
|
|
3
|
+
} from "./chunk-2VNYHG4E.js";
|
|
4
|
+
|
|
5
|
+
// src/recipes/roles.ts
|
|
6
|
+
var ROLES = {
|
|
7
|
+
"surface-0": "surface0",
|
|
8
|
+
"surface-1": "surface1",
|
|
9
|
+
"surface-2": "surface2",
|
|
10
|
+
"surface-3": "surface3",
|
|
11
|
+
media: "media",
|
|
12
|
+
scrim: "scrim",
|
|
13
|
+
"scrim-strong": "scrimStrong",
|
|
14
|
+
overlay: "overlay",
|
|
15
|
+
"border-subtle": "borderSubtle",
|
|
16
|
+
"border-default": "borderDefault",
|
|
17
|
+
"border-strong": "borderStrong",
|
|
18
|
+
fg: "textPrimary",
|
|
19
|
+
"fg-secondary": "textSecondary",
|
|
20
|
+
"fg-muted": "textMuted",
|
|
21
|
+
"fg-on-media": "textOnMedia",
|
|
22
|
+
"fg-accent": "accentText",
|
|
23
|
+
"fg-success": "successText",
|
|
24
|
+
"fg-warning": "warningText",
|
|
25
|
+
"fg-danger": "dangerText",
|
|
26
|
+
"fg-info": "infoText",
|
|
27
|
+
accent: "accent",
|
|
28
|
+
"accent-hover": "accentHover",
|
|
29
|
+
"accent-soft": "accentSoft",
|
|
30
|
+
"accent-2": "accent2",
|
|
31
|
+
"on-accent": "onAccent",
|
|
32
|
+
ring: "ring",
|
|
33
|
+
success: "success",
|
|
34
|
+
warning: "warning",
|
|
35
|
+
danger: "danger",
|
|
36
|
+
info: "info",
|
|
37
|
+
"success-soft": "successSoft",
|
|
38
|
+
"warning-soft": "warningSoft",
|
|
39
|
+
"danger-soft": "dangerSoft",
|
|
40
|
+
glass: "glassBg",
|
|
41
|
+
"glass-border": "glassBorder"
|
|
42
|
+
};
|
|
43
|
+
var TEXT_SIZES = { xs: 12, sm: 14, base: 16, lg: 18, xl: 20, "2xl": 24, "3xl": 30 };
|
|
44
|
+
var FONT_WEIGHTS = { normal: "400", medium: "500", semibold: "600", bold: "700", extrabold: "800", black: "900" };
|
|
45
|
+
var TRACKING = { tighter: -0.05, tight: -0.025, normal: 0, wide: 0.025, wider: 0.05, widest: 0.1 };
|
|
46
|
+
|
|
47
|
+
// src/recipes/native.ts
|
|
48
|
+
var BASE = "base";
|
|
49
|
+
var words = (value) => typeof value === "string" ? value.split(/\s+/).filter(Boolean) : Array.isArray(value) ? value.flatMap(words) : [];
|
|
50
|
+
function withAlpha(color, opacity) {
|
|
51
|
+
const hex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
|
|
52
|
+
if (!hex) return color;
|
|
53
|
+
const [r, g, b] = hex.slice(1).map((part) => parseInt(part, 16));
|
|
54
|
+
return `rgba(${r},${g},${b},${opacity})`;
|
|
55
|
+
}
|
|
56
|
+
function slotNames(config) {
|
|
57
|
+
return config.slots ? Object.keys(config.slots) : [BASE];
|
|
58
|
+
}
|
|
59
|
+
function classesFor(config, props) {
|
|
60
|
+
const names = slotNames(config);
|
|
61
|
+
const out = Object.fromEntries(names.map((name) => [name, config.slots ? words(config.slots[name]) : words(config.base)]));
|
|
62
|
+
const add = (value) => {
|
|
63
|
+
if (typeof value === "string" || Array.isArray(value)) (out[BASE] ??= []).push(...words(value));
|
|
64
|
+
else if (value && typeof value === "object") for (const [name, classes] of Object.entries(value)) (out[name] ??= []).push(...words(classes));
|
|
65
|
+
};
|
|
66
|
+
const chosen = { ...config.defaultVariants, ...Object.fromEntries(Object.entries(props).filter(([, v]) => v !== void 0)) };
|
|
67
|
+
for (const [dimension, values] of Object.entries(config.variants ?? {})) {
|
|
68
|
+
const key = String(chosen[dimension]);
|
|
69
|
+
if (key in values) add(values[key]);
|
|
70
|
+
}
|
|
71
|
+
for (const compound of config.compoundVariants ?? []) {
|
|
72
|
+
const { class: a, className: b, ...conditions } = compound;
|
|
73
|
+
const matches = Object.entries(conditions).every(([dimension, expected]) => Array.isArray(expected) ? expected.map(String).includes(String(chosen[dimension])) : String(chosen[dimension]) === String(expected));
|
|
74
|
+
if (matches) add(a ?? b);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
var empty = () => ({ frame: {}, text: {}, disabled: { frame: {}, text: {} }, on: { frame: {}, text: {} }, ignored: [] });
|
|
79
|
+
var unit = (value) => /^\d+(\.\d+)?$/.test(value) ? Number(value) * 4 : void 0;
|
|
80
|
+
function colour(name, theme) {
|
|
81
|
+
const [role, percent] = name.split("/");
|
|
82
|
+
if (role === "transparent") return "transparent";
|
|
83
|
+
if (!role || !(role in ROLES)) return void 0;
|
|
84
|
+
const resolved = theme[ROLES[role]];
|
|
85
|
+
if (resolved === void 0) return void 0;
|
|
86
|
+
return percent === void 0 ? resolved : withAlpha(resolved, Number(percent) / 100);
|
|
87
|
+
}
|
|
88
|
+
function apply(look, cls, theme) {
|
|
89
|
+
let target = look;
|
|
90
|
+
let utility = cls;
|
|
91
|
+
if (cls.startsWith("disabled:")) {
|
|
92
|
+
target = look.disabled;
|
|
93
|
+
utility = cls.slice("disabled:".length);
|
|
94
|
+
} else if (cls.startsWith("data-[state=on]:")) {
|
|
95
|
+
target = look.on;
|
|
96
|
+
utility = cls.slice("data-[state=on]:".length);
|
|
97
|
+
} else if (cls.startsWith("[&>[data-dot]]:bg-")) {
|
|
98
|
+
const dot = colour(cls.slice("[&>[data-dot]]:bg-".length), theme);
|
|
99
|
+
if (dot === void 0) return false;
|
|
100
|
+
look.dot = dot;
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (utility.includes(":") || utility.startsWith("[&")) return false;
|
|
104
|
+
const { frame, text } = target;
|
|
105
|
+
let match;
|
|
106
|
+
if (utility.startsWith("bg-linear-to-")) return true;
|
|
107
|
+
if (match = /^bg-(.+)$/.exec(utility)) {
|
|
108
|
+
const value = colour(match[1], theme);
|
|
109
|
+
if (value === void 0) return false;
|
|
110
|
+
frame.backgroundColor = value;
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (match = /^text-(.+)$/.exec(utility)) {
|
|
114
|
+
const arbitrary = /^\[(\d+(?:\.\d+)?)px\]$/.exec(match[1]);
|
|
115
|
+
if (arbitrary) return text.fontSize = Number(arbitrary[1]), true;
|
|
116
|
+
if (match[1] in TEXT_SIZES) return text.fontSize = TEXT_SIZES[match[1]], true;
|
|
117
|
+
const value = colour(match[1], theme);
|
|
118
|
+
if (value === void 0) return false;
|
|
119
|
+
text.color = value;
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (utility === "border") return frame.borderWidth = 1, true;
|
|
123
|
+
if (match = /^(?:border|ring)-(.+)$/.exec(utility)) {
|
|
124
|
+
if (match[1] === "1") return frame.borderWidth = 1, true;
|
|
125
|
+
if (match[1] === "inset") return true;
|
|
126
|
+
const value = colour(match[1], theme);
|
|
127
|
+
if (value === void 0) return false;
|
|
128
|
+
frame.borderColor = value;
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (utility === "ring-inset") return true;
|
|
132
|
+
if (match = /^(h|w|min-w|px|py|p|gap)-(\d+(?:\.\d+)?)$/.exec(utility)) {
|
|
133
|
+
const points = unit(match[2]);
|
|
134
|
+
const key = { h: "height", w: "width", "min-w": "minWidth", px: "paddingHorizontal", py: "paddingVertical", p: "padding", gap: "gap" }[match[1]];
|
|
135
|
+
frame[key] = points;
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
if (match = /^opacity-(\d+)$/.exec(utility)) return frame.opacity = Number(match[1]) / 100, true;
|
|
139
|
+
if ((match = /^font-(.+)$/.exec(utility)) && match[1] in FONT_WEIGHTS) return text.fontWeight = FONT_WEIGHTS[match[1]], true;
|
|
140
|
+
if ((match = /^tracking-(.+)$/.exec(utility)) && match[1] in TRACKING) return text.letterSpacing = TRACKING[match[1]], true;
|
|
141
|
+
if (utility === "uppercase" || utility === "lowercase" || utility === "capitalize") return text.textTransform = utility, true;
|
|
142
|
+
if (match = /^(from|to)-(.+)$/.exec(utility)) {
|
|
143
|
+
const value = colour(match[2], theme);
|
|
144
|
+
if (value === void 0) return false;
|
|
145
|
+
const stops = [...look.gradient ?? ["transparent", "transparent"]];
|
|
146
|
+
stops[match[1] === "from" ? 0 : 1] = value;
|
|
147
|
+
look.gradient = stops;
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
function finish(look) {
|
|
153
|
+
for (const part of [look, look.disabled, look.on]) {
|
|
154
|
+
if (part.text.letterSpacing !== void 0) part.text.letterSpacing = part.text.letterSpacing * (part.text.fontSize ?? look.text.fontSize ?? 14);
|
|
155
|
+
}
|
|
156
|
+
return look;
|
|
157
|
+
}
|
|
158
|
+
function nativeRecipe(config) {
|
|
159
|
+
return {
|
|
160
|
+
resolve(props, theme) {
|
|
161
|
+
const looks = {};
|
|
162
|
+
for (const [slot, classes] of Object.entries(classesFor(config, props))) {
|
|
163
|
+
const look = empty();
|
|
164
|
+
for (const cls of classes) if (!apply(look, cls, theme)) look.ignored.push(cls);
|
|
165
|
+
looks[slot] = finish(look);
|
|
166
|
+
}
|
|
167
|
+
return looks;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
export {
|
|
172
|
+
FONT_WEIGHTS,
|
|
173
|
+
ROLES,
|
|
174
|
+
TEXT_SIZES,
|
|
175
|
+
TRACKING,
|
|
176
|
+
defineRecipe,
|
|
177
|
+
nativeRecipe,
|
|
178
|
+
withAlpha
|
|
179
|
+
};
|
|
180
|
+
//# sourceMappingURL=recipes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/recipes/roles.ts","../src/recipes/native.ts"],"sourcesContent":["// The colour roles a recipe may name, by the Tailwind utility suffix the preset gives them (`bg-accent`,\n// `text-fg-secondary`, `border-border-default`) → the key of the same role in the native theme (`accent`, `textSecondary`,\n// `borderDefault`). One table: the web class and the native colour cannot name different tokens. `recipes.test.ts`\n// checks that every key of the right-hand side exists in the real native theme (@krizaka/tokens/native).\nexport const ROLES = {\n \"surface-0\": \"surface0\",\n \"surface-1\": \"surface1\",\n \"surface-2\": \"surface2\",\n \"surface-3\": \"surface3\",\n media: \"media\",\n scrim: \"scrim\",\n \"scrim-strong\": \"scrimStrong\",\n overlay: \"overlay\",\n \"border-subtle\": \"borderSubtle\",\n \"border-default\": \"borderDefault\",\n \"border-strong\": \"borderStrong\",\n fg: \"textPrimary\",\n \"fg-secondary\": \"textSecondary\",\n \"fg-muted\": \"textMuted\",\n \"fg-on-media\": \"textOnMedia\",\n \"fg-accent\": \"accentText\",\n \"fg-success\": \"successText\",\n \"fg-warning\": \"warningText\",\n \"fg-danger\": \"dangerText\",\n \"fg-info\": \"infoText\",\n accent: \"accent\",\n \"accent-hover\": \"accentHover\",\n \"accent-soft\": \"accentSoft\",\n \"accent-2\": \"accent2\",\n \"on-accent\": \"onAccent\",\n ring: \"ring\",\n success: \"success\",\n warning: \"warning\",\n danger: \"danger\",\n info: \"info\",\n \"success-soft\": \"successSoft\",\n \"warning-soft\": \"warningSoft\",\n \"danger-soft\": \"dangerSoft\",\n glass: \"glassBg\",\n \"glass-border\": \"glassBorder\",\n} as const;\n\nexport type RoleName = keyof typeof ROLES;\n\n/** Tailwind's type scale, in points (`text-xs` → 12). */\nexport const TEXT_SIZES = { xs: 12, sm: 14, base: 16, lg: 18, xl: 20, \"2xl\": 24, \"3xl\": 30 } as const;\n\nexport const FONT_WEIGHTS = { normal: \"400\", medium: \"500\", semibold: \"600\", bold: \"700\", extrabold: \"800\", black: \"900\" } as const;\n\n/** `tracking-*`, in em (React Native takes points: the resolver multiplies by the font size). */\nexport const TRACKING = { tighter: -0.05, tight: -0.025, normal: 0, wide: 0.025, wider: 0.05, widest: 0.1 } as const;\n","// The native side of a recipe: the classes of its variants, read into the styles React Native takes, on the roles of the\n// theme. Pure (no React Native, no theme import): the components wrap the result in `StyleSheet` and pass the theme.\n// What a phone has no use for (`hover:`, `focus-visible:`, shadows, transitions, the layout classes) is returned in\n// `ignored`, so a test can tell the classes that only the web has from the ones that were forgotten.\nimport { FONT_WEIGHTS, type RoleName, ROLES, TEXT_SIZES, TRACKING } from \"./roles\";\n\n/** The colour roles of a native theme (`Theme` of `@krizaka/ui/native`): a key, a colour. */\nexport type ThemeColors = Readonly<Record<string, string>>;\n\nexport type NativeFrame = {\n backgroundColor?: string;\n borderColor?: string;\n borderWidth?: number;\n height?: number;\n width?: number;\n minWidth?: number;\n paddingHorizontal?: number;\n paddingVertical?: number;\n padding?: number;\n gap?: number;\n opacity?: number;\n};\n\nexport type NativeText = {\n color?: string;\n fontSize?: number;\n fontWeight?: \"400\" | \"500\" | \"600\" | \"700\" | \"800\" | \"900\";\n letterSpacing?: number;\n textTransform?: \"uppercase\" | \"lowercase\" | \"capitalize\";\n};\n\n/** What one part of a component looks like: its frame (a View), its text, and the states that change them. */\nexport type NativeLook = {\n frame: NativeFrame;\n text: NativeText;\n /** `bg-<role>` of the part's dot (`[&>[data-dot]]:bg-accent`): the colour of a status dot. */\n dot?: string;\n /** `bg-linear-to-r from-<role> to-<role>`: the two stops of a gradient fill. */\n gradient?: readonly [string, string];\n /** `disabled:` — the frame and text while disabled. */\n disabled: { frame: NativeFrame; text: NativeText };\n /** `data-[state=on]:` — the frame and text while selected / pressed on. */\n on: { frame: NativeFrame; text: NativeText };\n /** The classes with no native meaning (the web's pointer states, shadows, layout…). */\n ignored: string[];\n};\n\ntype Props = Readonly<Record<string, string | number | boolean | null | undefined>>;\ntype Slot = string | readonly unknown[] | null | undefined;\ntype Config = {\n base?: Slot;\n slots?: Readonly<Record<string, Slot>>;\n variants?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;\n defaultVariants?: Readonly<Record<string, unknown>>;\n compoundVariants?: readonly Readonly<Record<string, unknown>>[];\n};\n\nconst BASE = \"base\";\n\nconst words = (value: unknown): string[] => (typeof value === \"string\" ? value.split(/\\s+/).filter(Boolean) : Array.isArray(value) ? value.flatMap(words) : []);\n\n/** `#rrggbb` at an opacity (`rgba()`); any other form passes through. */\nexport function withAlpha(color: string, opacity: number): string {\n const hex = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);\n if (!hex) return color;\n const [r, g, b] = hex.slice(1).map((part) => parseInt(part, 16));\n return `rgba(${r},${g},${b},${opacity})`;\n}\n\n/** The slots of a config: `slots` when it has some, else the single `base`. */\nfunction slotNames(config: Config): string[] {\n return config.slots ? Object.keys(config.slots) : [BASE];\n}\n\n/** The classes of each slot for some props: base, the chosen variants (defaults first), then the compound ones. */\nfunction classesFor(config: Config, props: Props): Record<string, string[]> {\n const names = slotNames(config);\n const out: Record<string, string[]> = Object.fromEntries(names.map((name) => [name, config.slots ? words(config.slots[name]) : words(config.base)]));\n // A string (or a list) is for the base; an object names the slots it is for.\n const add = (value: unknown) => {\n if (typeof value === \"string\" || Array.isArray(value)) (out[BASE] ??= []).push(...words(value));\n else if (value && typeof value === \"object\") for (const [name, classes] of Object.entries(value)) (out[name] ??= []).push(...words(classes));\n };\n const chosen = { ...config.defaultVariants, ...Object.fromEntries(Object.entries(props).filter(([, v]) => v !== undefined)) } as Record<string, unknown>;\n for (const [dimension, values] of Object.entries(config.variants ?? {})) {\n const key = String(chosen[dimension]);\n if (key in values) add(values[key]);\n }\n for (const compound of config.compoundVariants ?? []) {\n const { class: a, className: b, ...conditions } = compound as Record<string, unknown>;\n const matches = Object.entries(conditions).every(([dimension, expected]) => (Array.isArray(expected) ? expected.map(String).includes(String(chosen[dimension])) : String(chosen[dimension]) === String(expected)));\n if (matches) add(a ?? b);\n }\n return out;\n}\n\nconst empty = (): NativeLook => ({ frame: {}, text: {}, disabled: { frame: {}, text: {} }, on: { frame: {}, text: {} }, ignored: [] });\n\n/** `3` → 12, `0.5` → 2, `3.5` → 14: Tailwind's spacing unit is 4. */\nconst unit = (value: string): number | undefined => (/^\\d+(\\.\\d+)?$/.test(value) ? Number(value) * 4 : undefined);\n\n/** `accent`, `accent/40`, `transparent` → a colour of the theme, or `undefined` when it is not a role. */\nfunction colour(name: string, theme: ThemeColors): string | undefined {\n const [role, percent] = name.split(\"/\");\n if (role === \"transparent\") return \"transparent\";\n if (!role || !(role in ROLES)) return undefined;\n const resolved = theme[ROLES[role as RoleName]];\n if (resolved === undefined) return undefined;\n return percent === undefined ? resolved : withAlpha(resolved, Number(percent) / 100);\n}\n\n/** Reads one class into a look. Returns `false` when the class means nothing natively. */\nfunction apply(look: NativeLook, cls: string, theme: ThemeColors): boolean {\n // States: `disabled:` and `data-[state=on]:` are kept apart; every other prefix is a pointer, focus or media state.\n let target: { frame: NativeFrame; text: NativeText } = look;\n let utility = cls;\n if (cls.startsWith(\"disabled:\")) {\n target = look.disabled;\n utility = cls.slice(\"disabled:\".length);\n } else if (cls.startsWith(\"data-[state=on]:\")) {\n target = look.on;\n utility = cls.slice(\"data-[state=on]:\".length);\n } else if (cls.startsWith(\"[&>[data-dot]]:bg-\")) {\n const dot = colour(cls.slice(\"[&>[data-dot]]:bg-\".length), theme);\n if (dot === undefined) return false;\n look.dot = dot;\n return true;\n }\n if (utility.includes(\":\") || utility.startsWith(\"[&\")) return false;\n\n const { frame, text } = target;\n let match: RegExpExecArray | null;\n if (utility.startsWith(\"bg-linear-to-\")) return true;\n if ((match = /^bg-(.+)$/.exec(utility))) {\n const value = colour(match[1]!, theme);\n if (value === undefined) return false;\n frame.backgroundColor = value;\n return true;\n }\n if ((match = /^text-(.+)$/.exec(utility))) {\n const arbitrary = /^\\[(\\d+(?:\\.\\d+)?)px\\]$/.exec(match[1]!);\n if (arbitrary) return ((text.fontSize = Number(arbitrary[1])), true);\n if (match[1]! in TEXT_SIZES) return ((text.fontSize = TEXT_SIZES[match[1] as keyof typeof TEXT_SIZES]), true);\n const value = colour(match[1]!, theme);\n if (value === undefined) return false;\n text.color = value;\n return true;\n }\n if (utility === \"border\") return ((frame.borderWidth = 1), true);\n if ((match = /^(?:border|ring)-(.+)$/.exec(utility))) {\n if (match[1] === \"1\") return ((frame.borderWidth = 1), true);\n if (match[1] === \"inset\") return true;\n const value = colour(match[1]!, theme);\n if (value === undefined) return false;\n frame.borderColor = value;\n return true;\n }\n if (utility === \"ring-inset\") return true;\n if ((match = /^(h|w|min-w|px|py|p|gap)-(\\d+(?:\\.\\d+)?)$/.exec(utility))) {\n const points = unit(match[2]!)!;\n const key = { h: \"height\", w: \"width\", \"min-w\": \"minWidth\", px: \"paddingHorizontal\", py: \"paddingVertical\", p: \"padding\", gap: \"gap\" }[match[1]!] as keyof NativeFrame;\n (frame as Record<string, number>)[key] = points;\n return true;\n }\n if ((match = /^opacity-(\\d+)$/.exec(utility))) return ((frame.opacity = Number(match[1]) / 100), true);\n if ((match = /^font-(.+)$/.exec(utility)) && match[1]! in FONT_WEIGHTS) return ((text.fontWeight = FONT_WEIGHTS[match[1] as keyof typeof FONT_WEIGHTS]), true);\n if ((match = /^tracking-(.+)$/.exec(utility)) && match[1]! in TRACKING) return ((text.letterSpacing = TRACKING[match[1] as keyof typeof TRACKING]), true);\n if (utility === \"uppercase\" || utility === \"lowercase\" || utility === \"capitalize\") return ((text.textTransform = utility), true);\n if ((match = /^(from|to)-(.+)$/.exec(utility))) {\n const value = colour(match[2]!, theme);\n if (value === undefined) return false;\n const stops = [...(look.gradient ?? [\"transparent\", \"transparent\"])] as [string, string];\n stops[match[1] === \"from\" ? 0 : 1] = value;\n look.gradient = stops;\n return true;\n }\n return false;\n}\n\n/** `tracking-*` is in em; React Native wants points. */\nfunction finish(look: NativeLook): NativeLook {\n for (const part of [look, look.disabled, look.on]) {\n if (part.text.letterSpacing !== undefined) part.text.letterSpacing = part.text.letterSpacing * (part.text.fontSize ?? look.text.fontSize ?? 14);\n }\n return look;\n}\n\nexport type NativeRecipe = {\n /** The looks of each slot (`base` for a recipe without slots) for these variants, on this theme. */\n resolve: (props: Props, theme: ThemeColors) => Record<string, NativeLook>;\n};\n\n/** The native side of a recipe (`defineRecipe`): what its variants look like on a theme. */\nexport function nativeRecipe(config: Config): NativeRecipe {\n return {\n resolve(props, theme) {\n const looks: Record<string, NativeLook> = {};\n for (const [slot, classes] of Object.entries(classesFor(config, props))) {\n const look = empty();\n for (const cls of classes) if (!apply(look, cls, theme)) look.ignored.push(cls);\n looks[slot] = finish(look);\n }\n return looks;\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/search-field.js
CHANGED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ShowcaseLabels {
|
|
4
|
+
/** The previous button's name. */
|
|
5
|
+
previous: string;
|
|
6
|
+
/** The next button's name. */
|
|
7
|
+
next: string;
|
|
8
|
+
/** The pause button's name while it plays. */
|
|
9
|
+
pause: string;
|
|
10
|
+
/** The same button's name while it is paused. */
|
|
11
|
+
play: string;
|
|
12
|
+
/** A progress segment's name: `(3, 17) => "Go to image 3"`. */
|
|
13
|
+
goTo: (n: number, total: number) => string;
|
|
14
|
+
/** The position in words, beside the title: `(1, 17) => "Image 1 of 17"`. */
|
|
15
|
+
counter: (n: number, total: number) => string;
|
|
16
|
+
/** The slide's accessible name: `(1, 17) => "1 of 17"`. */
|
|
17
|
+
slide: (n: number, total: number) => string;
|
|
18
|
+
}
|
|
19
|
+
/** English defaults — pass your own translated `labels`. */
|
|
20
|
+
declare const showcaseLabels: ShowcaseLabels;
|
|
21
|
+
interface ShowcaseSlideState {
|
|
22
|
+
index: number;
|
|
23
|
+
/** The slide in view. */
|
|
24
|
+
active: boolean;
|
|
25
|
+
}
|
|
26
|
+
interface ShowcaseProps<T> extends Omit<React.ComponentProps<"section">, "children" | "onChange"> {
|
|
27
|
+
/** The slideshow's accessible name (“Prompt to image”), passed translated. */
|
|
28
|
+
label: string;
|
|
29
|
+
/** The slides, in order. */
|
|
30
|
+
items: readonly T[];
|
|
31
|
+
/** A stable key for an item. */
|
|
32
|
+
getId: (item: T) => string;
|
|
33
|
+
/** One slide's content. Only the slide in view and its neighbours are mounted (the rest stay empty): give slides the same size. */
|
|
34
|
+
renderItem: (item: T, state: ShowcaseSlideState) => React.ReactNode;
|
|
35
|
+
/** A slide's title, shown before the counter (“Golden-hour editorial”). */
|
|
36
|
+
getTitle?: (item: T) => string;
|
|
37
|
+
/** Milliseconds a slide stays (the time the segment takes to fill). @default 6000 */
|
|
38
|
+
interval?: number;
|
|
39
|
+
/** Advances by itself. Never under `prefers-reduced-motion`. @default true */
|
|
40
|
+
autoplay?: boolean;
|
|
41
|
+
/** `fade` crossfades; `slide` also moves the slides a few pixels. @default "fade" */
|
|
42
|
+
transition?: "fade" | "slide";
|
|
43
|
+
/** After the last slide, go back to the first. @default true */
|
|
44
|
+
loop?: boolean;
|
|
45
|
+
/** The slide in view (controlled). */
|
|
46
|
+
index?: number;
|
|
47
|
+
/** The slide shown first. @default 0 */
|
|
48
|
+
defaultIndex?: number;
|
|
49
|
+
/** Called when the slide in view changes — `user` is true when the visitor asked for it. */
|
|
50
|
+
onIndexChange?: (index: number, user: boolean) => void;
|
|
51
|
+
/** The words of the controls, translated. */
|
|
52
|
+
labels?: ShowcaseLabels;
|
|
53
|
+
}
|
|
54
|
+
/** An auto-advancing slideshow with a visible, clickable progress bar, a counter, prev / next and a pause button. */
|
|
55
|
+
declare function Showcase<T>({ label, items, getId, renderItem, getTitle, interval, autoplay, transition, loop, index: controlled, defaultIndex, onIndexChange, labels, className, onKeyDown, onFocus, onBlur, onPointerEnter, onPointerLeave, ...props }: ShowcaseProps<T>): React.JSX.Element;
|
|
56
|
+
/**
|
|
57
|
+
* The typed reveal of a text, kept minimal: the visible prefix grows at `speed` characters per second while `active`;
|
|
58
|
+
* under reduced motion (or when not active) it is the whole text at once. Render the full text for assistive technology
|
|
59
|
+
* (`sr-only`) and this one `aria-hidden`.
|
|
60
|
+
*/
|
|
61
|
+
declare function useTypedText(text: string, { active, speed }?: {
|
|
62
|
+
active?: boolean;
|
|
63
|
+
speed?: number;
|
|
64
|
+
}): {
|
|
65
|
+
text: string;
|
|
66
|
+
done: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export { Showcase, type ShowcaseLabels, type ShowcaseProps, type ShowcaseSlideState, showcaseLabels, useTypedText };
|
package/dist/showcase.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
cn
|
|
4
|
+
} from "./chunk-2FRWNSRO.js";
|
|
5
|
+
|
|
6
|
+
// src/showcase/showcase.tsx
|
|
7
|
+
import * as React from "react";
|
|
8
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
var FILL_CSS = ".kz-showcase-fill{transform-origin:left;animation:kz-showcase-fill 6s linear forwards}@keyframes kz-showcase-fill{from{transform:scaleX(0)}to{transform:scaleX(1)}}";
|
|
10
|
+
var REDUCED = "(prefers-reduced-motion: reduce)";
|
|
11
|
+
var subscribeNothing = () => () => void 0;
|
|
12
|
+
var readReduced = () => Boolean(typeof window !== "undefined" && window.matchMedia?.(REDUCED).matches);
|
|
13
|
+
function subscribeReduced(notify) {
|
|
14
|
+
const query = window.matchMedia?.(REDUCED);
|
|
15
|
+
query?.addEventListener?.("change", notify);
|
|
16
|
+
return () => query?.removeEventListener?.("change", notify);
|
|
17
|
+
}
|
|
18
|
+
var readHidden = () => document.visibilityState === "hidden";
|
|
19
|
+
function subscribeVisibility(notify) {
|
|
20
|
+
document.addEventListener("visibilitychange", notify);
|
|
21
|
+
return () => document.removeEventListener("visibilitychange", notify);
|
|
22
|
+
}
|
|
23
|
+
var showcaseLabels = {
|
|
24
|
+
previous: "Previous slide",
|
|
25
|
+
next: "Next slide",
|
|
26
|
+
pause: "Pause the slideshow",
|
|
27
|
+
play: "Play the slideshow",
|
|
28
|
+
goTo: (n) => `Go to slide ${n}`,
|
|
29
|
+
counter: (n, total) => `Slide ${n} of ${total}`,
|
|
30
|
+
slide: (n, total) => `${n} of ${total}`
|
|
31
|
+
};
|
|
32
|
+
function Showcase({
|
|
33
|
+
label,
|
|
34
|
+
items,
|
|
35
|
+
getId,
|
|
36
|
+
renderItem,
|
|
37
|
+
getTitle,
|
|
38
|
+
interval = 6e3,
|
|
39
|
+
autoplay = true,
|
|
40
|
+
transition = "fade",
|
|
41
|
+
loop = true,
|
|
42
|
+
index: controlled,
|
|
43
|
+
defaultIndex = 0,
|
|
44
|
+
onIndexChange,
|
|
45
|
+
labels = showcaseLabels,
|
|
46
|
+
className,
|
|
47
|
+
onKeyDown,
|
|
48
|
+
onFocus,
|
|
49
|
+
onBlur,
|
|
50
|
+
onPointerEnter,
|
|
51
|
+
onPointerLeave,
|
|
52
|
+
...props
|
|
53
|
+
}) {
|
|
54
|
+
const total = items.length;
|
|
55
|
+
const root = React.useRef(null);
|
|
56
|
+
const [inner, setInner] = React.useState(defaultIndex);
|
|
57
|
+
const current = Math.min(Math.max(controlled ?? inner, 0), Math.max(total - 1, 0));
|
|
58
|
+
const mounted = React.useSyncExternalStore(subscribeNothing, () => true, () => false);
|
|
59
|
+
const reduced = React.useSyncExternalStore(subscribeReduced, readReduced, () => false);
|
|
60
|
+
const hidden = React.useSyncExternalStore(subscribeVisibility, readHidden, () => false);
|
|
61
|
+
const [paused, setPaused] = React.useState(false);
|
|
62
|
+
const [hover, setHover] = React.useState(false);
|
|
63
|
+
const [focus, setFocus] = React.useState(false);
|
|
64
|
+
const [inView, setInView] = React.useState(true);
|
|
65
|
+
const [announce, setAnnounce] = React.useState(false);
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
const host = root.current;
|
|
68
|
+
if (!host || typeof IntersectionObserver === "undefined") return;
|
|
69
|
+
const observer = new IntersectionObserver((entries) => setInView(entries.some((e) => e.isIntersecting)), { threshold: 0.2 });
|
|
70
|
+
observer.observe(host);
|
|
71
|
+
return () => observer.disconnect();
|
|
72
|
+
}, []);
|
|
73
|
+
const move = (to, user) => {
|
|
74
|
+
if (total === 0) return;
|
|
75
|
+
const next = loop ? (to + total) % total : Math.min(Math.max(to, 0), total - 1);
|
|
76
|
+
if (next === current) return;
|
|
77
|
+
setAnnounce(user);
|
|
78
|
+
if (controlled === void 0) setInner(next);
|
|
79
|
+
onIndexChange?.(next, user);
|
|
80
|
+
};
|
|
81
|
+
const playing = autoplay && mounted && !reduced && total > 1;
|
|
82
|
+
const running = playing && !paused && !hover && !focus && !hidden && inView;
|
|
83
|
+
const lastStop = !loop && current === total - 1;
|
|
84
|
+
const handleKeyDown = (event) => {
|
|
85
|
+
onKeyDown?.(event);
|
|
86
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
87
|
+
const target = event.target;
|
|
88
|
+
if (target.closest("input, textarea, select, [contenteditable='true']")) return;
|
|
89
|
+
const to = event.key === "ArrowRight" ? current + 1 : event.key === "ArrowLeft" ? current - 1 : event.key === "Home" ? 0 : event.key === "End" ? total - 1 : null;
|
|
90
|
+
if (to === null) return;
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
move(to, true);
|
|
93
|
+
};
|
|
94
|
+
const item = items[current];
|
|
95
|
+
const title = item !== void 0 ? getTitle?.(item) : void 0;
|
|
96
|
+
const button = "inline-flex h-9 w-9 items-center justify-center rounded-full border border-border-default bg-surface-1 text-fg-secondary transition-colors hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40";
|
|
97
|
+
return /* @__PURE__ */ jsxs(
|
|
98
|
+
"section",
|
|
99
|
+
{
|
|
100
|
+
ref: root,
|
|
101
|
+
"aria-roledescription": "carousel",
|
|
102
|
+
"aria-label": label,
|
|
103
|
+
"data-running": running ? "" : void 0,
|
|
104
|
+
className: cn("flex flex-col gap-3", className),
|
|
105
|
+
onKeyDown: handleKeyDown,
|
|
106
|
+
onPointerEnter: (e) => {
|
|
107
|
+
onPointerEnter?.(e);
|
|
108
|
+
if (e.pointerType === "mouse") setHover(true);
|
|
109
|
+
},
|
|
110
|
+
onPointerLeave: (e) => {
|
|
111
|
+
onPointerLeave?.(e);
|
|
112
|
+
setHover(false);
|
|
113
|
+
},
|
|
114
|
+
onFocus: (e) => {
|
|
115
|
+
onFocus?.(e);
|
|
116
|
+
let visible = true;
|
|
117
|
+
try {
|
|
118
|
+
visible = e.target.matches(":focus-visible");
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
if (visible) setFocus(true);
|
|
122
|
+
},
|
|
123
|
+
onBlur: (e) => {
|
|
124
|
+
onBlur?.(e);
|
|
125
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setFocus(false);
|
|
126
|
+
},
|
|
127
|
+
...props,
|
|
128
|
+
children: [
|
|
129
|
+
/* @__PURE__ */ jsx("style", { href: "kz-showcase", precedence: "medium", children: FILL_CSS }),
|
|
130
|
+
/* @__PURE__ */ jsx("div", { "aria-live": announce ? "polite" : "off", className: "grid", children: items.map((slide, i) => {
|
|
131
|
+
const active = i === current;
|
|
132
|
+
const gap = Math.abs(i - current);
|
|
133
|
+
const near = gap <= 1 || loop && total - gap <= 1;
|
|
134
|
+
const side = i < current ? "-translate-x-4" : "translate-x-4";
|
|
135
|
+
return /* @__PURE__ */ jsx(
|
|
136
|
+
"div",
|
|
137
|
+
{
|
|
138
|
+
role: "group",
|
|
139
|
+
"aria-roledescription": "slide",
|
|
140
|
+
"aria-label": labels.slide(i + 1, total),
|
|
141
|
+
"aria-hidden": active ? void 0 : true,
|
|
142
|
+
inert: active ? void 0 : true,
|
|
143
|
+
"data-active": active ? "" : void 0,
|
|
144
|
+
className: cn(
|
|
145
|
+
"col-start-1 row-start-1 min-w-0 transition-[opacity,translate,visibility] duration-500 motion-reduce:transition-none",
|
|
146
|
+
active ? "visible opacity-100" : "invisible opacity-0",
|
|
147
|
+
transition === "slide" && (active ? "translate-x-0" : side)
|
|
148
|
+
),
|
|
149
|
+
children: near ? renderItem(slide, { index: i, active }) : null
|
|
150
|
+
},
|
|
151
|
+
getId(slide)
|
|
152
|
+
);
|
|
153
|
+
}) }),
|
|
154
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1", children: items.map((slide, i) => /* @__PURE__ */ jsx(
|
|
155
|
+
"button",
|
|
156
|
+
{
|
|
157
|
+
type: "button",
|
|
158
|
+
"aria-label": labels.goTo(i + 1, total),
|
|
159
|
+
"aria-current": i === current ? "true" : void 0,
|
|
160
|
+
tabIndex: i === current ? 0 : -1,
|
|
161
|
+
onClick: () => move(i, true),
|
|
162
|
+
className: "group/seg relative flex h-6 min-w-0 flex-1 cursor-pointer items-center rounded-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring",
|
|
163
|
+
children: /* @__PURE__ */ jsxs("span", { className: "relative block h-1 w-full overflow-hidden rounded-full bg-surface-3 transition-[height] group-hover/seg:h-1.5", children: [
|
|
164
|
+
i < current || i === current && (!playing || lastStop) ? /* @__PURE__ */ jsx("span", { className: "absolute inset-0 bg-accent" }) : null,
|
|
165
|
+
i === current && playing && !lastStop ? /* @__PURE__ */ jsx(
|
|
166
|
+
"span",
|
|
167
|
+
{
|
|
168
|
+
"data-segment-fill": true,
|
|
169
|
+
className: "kz-showcase-fill absolute inset-0 bg-accent",
|
|
170
|
+
style: { animationDuration: `${interval}ms`, animationPlayState: running ? "running" : "paused" },
|
|
171
|
+
onAnimationEnd: () => running && move(current + 1, false)
|
|
172
|
+
},
|
|
173
|
+
`${current}`
|
|
174
|
+
) : null
|
|
175
|
+
] })
|
|
176
|
+
},
|
|
177
|
+
getId(slide)
|
|
178
|
+
)) }),
|
|
179
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
180
|
+
/* @__PURE__ */ jsxs("p", { className: "m-0 min-w-0 flex-1 truncate text-sm text-fg-secondary", children: [
|
|
181
|
+
title ? /* @__PURE__ */ jsx("span", { className: "font-medium text-fg", children: title }) : null,
|
|
182
|
+
title ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: " \xB7 " }) : null,
|
|
183
|
+
/* @__PURE__ */ jsx("span", { children: labels.counter(current + 1, total) })
|
|
184
|
+
] }),
|
|
185
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
186
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": labels.previous, disabled: !loop && current === 0, onClick: () => move(current - 1, true), className: button, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M15 5l-7 7 7 7" }) }) }),
|
|
187
|
+
playing ? /* @__PURE__ */ jsx("button", { type: "button", "aria-label": paused ? labels.play : labels.pause, onClick: () => setPaused(!paused), className: button, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-3.5 w-3.5", fill: "currentColor", "aria-hidden": true, children: paused ? /* @__PURE__ */ jsx("path", { d: "M8 5.5v13l10-6.5Z" }) : /* @__PURE__ */ jsx("path", { d: "M7 5h3.5v14H7zM13.5 5H17v14h-3.5z" }) }) }) : null,
|
|
188
|
+
/* @__PURE__ */ jsx("button", { type: "button", "aria-label": labels.next, disabled: !loop && current === total - 1, onClick: () => move(current + 1, true), className: button, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M9 5l7 7-7 7" }) }) })
|
|
189
|
+
] })
|
|
190
|
+
] })
|
|
191
|
+
]
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
function useTypedText(text, { active = true, speed = 45 } = {}) {
|
|
196
|
+
const [typed, setTyped] = React.useState({ of: "", count: 0 });
|
|
197
|
+
const reduced = React.useSyncExternalStore(subscribeReduced, readReduced, () => false);
|
|
198
|
+
React.useEffect(() => {
|
|
199
|
+
if (!active || reduced) return;
|
|
200
|
+
const id = window.setInterval(() => {
|
|
201
|
+
setTyped((state) => {
|
|
202
|
+
const count2 = state.of === text ? state.count + 1 : 1;
|
|
203
|
+
if (count2 >= text.length) window.clearInterval(id);
|
|
204
|
+
return { of: text, count: count2 };
|
|
205
|
+
});
|
|
206
|
+
}, Math.max(10, 1e3 / speed));
|
|
207
|
+
return () => window.clearInterval(id);
|
|
208
|
+
}, [text, active, reduced, speed]);
|
|
209
|
+
const count = typed.of === text ? typed.count : 0;
|
|
210
|
+
const done = !active || reduced || count >= text.length;
|
|
211
|
+
return { text: done ? text : text.slice(0, count), done };
|
|
212
|
+
}
|
|
213
|
+
export {
|
|
214
|
+
Showcase,
|
|
215
|
+
showcaseLabels,
|
|
216
|
+
useTypedText
|
|
217
|
+
};
|
|
218
|
+
//# sourceMappingURL=showcase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/showcase/showcase.tsx"],"sourcesContent":["// Client (state, timers' observers, keys). A slideshow that advances by itself — and shows it: a segmented progress bar\n// (one segment per slide, the current one fills over the interval, each one a button) with a counter, previous / next and a\n// visible pause button (WCAG 2.2.2). The time IS the CSS animation of the current segment (`.kz-showcase-fill`):\n// when it ends, the next slide comes — so what you see filling is exactly what decides, and pausing the animation pauses\n// the show. Its two rules travel with the component (a hoisted, deduplicated <style>): no stylesheet to import. It waits while the pointer is over it, while a keyboard focus is inside, while the tab is hidden and while it\n// is off screen; under reduced motion it never advances by itself. Slides are slots: any content.\nimport * as React from \"react\";\n\nimport { cn } from \"../cn\";\n\nconst FILL_CSS = \".kz-showcase-fill{transform-origin:left;animation:kz-showcase-fill 6s linear forwards}@keyframes kz-showcase-fill{from{transform:scaleX(0)}to{transform:scaleX(1)}}\";\nconst REDUCED = \"(prefers-reduced-motion: reduce)\";\nconst subscribeNothing = () => () => undefined;\nconst readReduced = () => Boolean(typeof window !== \"undefined\" && window.matchMedia?.(REDUCED).matches);\nfunction subscribeReduced(notify: () => void) {\n const query = window.matchMedia?.(REDUCED);\n query?.addEventListener?.(\"change\", notify);\n return () => query?.removeEventListener?.(\"change\", notify);\n}\nconst readHidden = () => document.visibilityState === \"hidden\";\nfunction subscribeVisibility(notify: () => void) {\n document.addEventListener(\"visibilitychange\", notify);\n return () => document.removeEventListener(\"visibilitychange\", notify);\n}\n\nexport interface ShowcaseLabels {\n /** The previous button's name. */\n previous: string;\n /** The next button's name. */\n next: string;\n /** The pause button's name while it plays. */\n pause: string;\n /** The same button's name while it is paused. */\n play: string;\n /** A progress segment's name: `(3, 17) => \"Go to image 3\"`. */\n goTo: (n: number, total: number) => string;\n /** The position in words, beside the title: `(1, 17) => \"Image 1 of 17\"`. */\n counter: (n: number, total: number) => string;\n /** The slide's accessible name: `(1, 17) => \"1 of 17\"`. */\n slide: (n: number, total: number) => string;\n}\n\n/** English defaults — pass your own translated `labels`. */\nexport const showcaseLabels: ShowcaseLabels = {\n previous: \"Previous slide\",\n next: \"Next slide\",\n pause: \"Pause the slideshow\",\n play: \"Play the slideshow\",\n goTo: (n) => `Go to slide ${n}`,\n counter: (n, total) => `Slide ${n} of ${total}`,\n slide: (n, total) => `${n} of ${total}`,\n};\n\nexport interface ShowcaseSlideState {\n index: number;\n /** The slide in view. */\n active: boolean;\n}\n\nexport interface ShowcaseProps<T> extends Omit<React.ComponentProps<\"section\">, \"children\" | \"onChange\"> {\n /** The slideshow's accessible name (“Prompt to image”), passed translated. */\n label: string;\n /** The slides, in order. */\n items: readonly T[];\n /** A stable key for an item. */\n getId: (item: T) => string;\n /** One slide's content. Only the slide in view and its neighbours are mounted (the rest stay empty): give slides the same size. */\n renderItem: (item: T, state: ShowcaseSlideState) => React.ReactNode;\n /** A slide's title, shown before the counter (“Golden-hour editorial”). */\n getTitle?: (item: T) => string;\n /** Milliseconds a slide stays (the time the segment takes to fill). @default 6000 */\n interval?: number;\n /** Advances by itself. Never under `prefers-reduced-motion`. @default true */\n autoplay?: boolean;\n /** `fade` crossfades; `slide` also moves the slides a few pixels. @default \"fade\" */\n transition?: \"fade\" | \"slide\";\n /** After the last slide, go back to the first. @default true */\n loop?: boolean;\n /** The slide in view (controlled). */\n index?: number;\n /** The slide shown first. @default 0 */\n defaultIndex?: number;\n /** Called when the slide in view changes — `user` is true when the visitor asked for it. */\n onIndexChange?: (index: number, user: boolean) => void;\n /** The words of the controls, translated. */\n labels?: ShowcaseLabels;\n}\n\n/** An auto-advancing slideshow with a visible, clickable progress bar, a counter, prev / next and a pause button. */\nexport function Showcase<T>({\n label,\n items,\n getId,\n renderItem,\n getTitle,\n interval = 6000,\n autoplay = true,\n transition = \"fade\",\n loop = true,\n index: controlled,\n defaultIndex = 0,\n onIndexChange,\n labels = showcaseLabels,\n className,\n onKeyDown,\n onFocus,\n onBlur,\n onPointerEnter,\n onPointerLeave,\n ...props\n}: ShowcaseProps<T>) {\n const total = items.length;\n const root = React.useRef<HTMLElement>(null);\n const [inner, setInner] = React.useState(defaultIndex);\n const current = Math.min(Math.max(controlled ?? inner, 0), Math.max(total - 1, 0));\n const mounted = React.useSyncExternalStore(subscribeNothing, () => true, () => false);\n const reduced = React.useSyncExternalStore(subscribeReduced, readReduced, () => false);\n const hidden = React.useSyncExternalStore(subscribeVisibility, readHidden, () => false);\n const [paused, setPaused] = React.useState(false);\n const [hover, setHover] = React.useState(false);\n const [focus, setFocus] = React.useState(false);\n const [inView, setInView] = React.useState(true);\n const [announce, setAnnounce] = React.useState(false);\n\n React.useEffect(() => {\n const host = root.current;\n if (!host || typeof IntersectionObserver === \"undefined\") return;\n const observer = new IntersectionObserver((entries) => setInView(entries.some((e) => e.isIntersecting)), { threshold: 0.2 });\n observer.observe(host);\n return () => observer.disconnect();\n }, []);\n\n const move = (to: number, user: boolean) => {\n if (total === 0) return;\n const next = loop ? (to + total) % total : Math.min(Math.max(to, 0), total - 1);\n if (next === current) return;\n setAnnounce(user);\n if (controlled === undefined) setInner(next);\n onIndexChange?.(next, user);\n };\n\n const playing = autoplay && mounted && !reduced && total > 1;\n const running = playing && !paused && !hover && !focus && !hidden && inView;\n const lastStop = !loop && current === total - 1;\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {\n onKeyDown?.(event);\n if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return;\n const target = event.target as HTMLElement;\n if (target.closest(\"input, textarea, select, [contenteditable='true']\")) return;\n const to = event.key === \"ArrowRight\" ? current + 1 : event.key === \"ArrowLeft\" ? current - 1 : event.key === \"Home\" ? 0 : event.key === \"End\" ? total - 1 : null;\n if (to === null) return;\n event.preventDefault();\n move(to, true);\n };\n\n const item = items[current];\n const title = item !== undefined ? getTitle?.(item) : undefined;\n const button = \"inline-flex h-9 w-9 items-center justify-center rounded-full border border-border-default bg-surface-1 text-fg-secondary transition-colors hover:text-fg focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-40\";\n\n return (\n <section\n ref={root}\n aria-roledescription=\"carousel\"\n aria-label={label}\n data-running={running ? \"\" : undefined}\n className={cn(\"flex flex-col gap-3\", className)}\n onKeyDown={handleKeyDown}\n onPointerEnter={(e) => {\n onPointerEnter?.(e);\n if (e.pointerType === \"mouse\") setHover(true);\n }}\n onPointerLeave={(e) => {\n onPointerLeave?.(e);\n setHover(false);\n }}\n onFocus={(e) => {\n onFocus?.(e);\n // A mouse click on a button is not a reason to stop: only a keyboard focus is.\n let visible = true;\n try {\n visible = e.target.matches(\":focus-visible\");\n } catch {\n /* a stand-in without :focus-visible */\n }\n if (visible) setFocus(true);\n }}\n onBlur={(e) => {\n onBlur?.(e);\n if (!e.currentTarget.contains(e.relatedTarget)) setFocus(false);\n }}\n {...props}\n >\n <style href=\"kz-showcase\" precedence=\"medium\">\n {FILL_CSS}\n </style>\n <div aria-live={announce ? \"polite\" : \"off\"} className=\"grid\">\n {items.map((slide, i) => {\n const active = i === current;\n const gap = Math.abs(i - current);\n const near = gap <= 1 || (loop && total - gap <= 1);\n const side = i < current ? \"-translate-x-4\" : \"translate-x-4\";\n return (\n <div\n key={getId(slide)}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={labels.slide(i + 1, total)}\n aria-hidden={active ? undefined : true}\n inert={active ? undefined : true}\n data-active={active ? \"\" : undefined}\n className={cn(\n \"col-start-1 row-start-1 min-w-0 transition-[opacity,translate,visibility] duration-500 motion-reduce:transition-none\",\n active ? \"visible opacity-100\" : \"invisible opacity-0\",\n transition === \"slide\" && (active ? \"translate-x-0\" : side),\n )}\n >\n {near ? renderItem(slide, { index: i, active }) : null}\n </div>\n );\n })}\n </div>\n\n <div className=\"flex gap-1\">\n {items.map((slide, i) => (\n <button\n key={getId(slide)}\n type=\"button\"\n aria-label={labels.goTo(i + 1, total)}\n aria-current={i === current ? \"true\" : undefined}\n tabIndex={i === current ? 0 : -1}\n onClick={() => move(i, true)}\n className=\"group/seg relative flex h-6 min-w-0 flex-1 cursor-pointer items-center rounded-sm focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <span className=\"relative block h-1 w-full overflow-hidden rounded-full bg-surface-3 transition-[height] group-hover/seg:h-1.5\">\n {i < current || (i === current && (!playing || lastStop)) ? <span className=\"absolute inset-0 bg-accent\" /> : null}\n {i === current && playing && !lastStop ? (\n <span\n key={`${current}`}\n data-segment-fill\n className=\"kz-showcase-fill absolute inset-0 bg-accent\"\n style={{ animationDuration: `${interval}ms`, animationPlayState: running ? \"running\" : \"paused\" }}\n onAnimationEnd={() => running && move(current + 1, false)}\n />\n ) : null}\n </span>\n </button>\n ))}\n </div>\n\n <div className=\"flex items-center gap-3\">\n <p className=\"m-0 min-w-0 flex-1 truncate text-sm text-fg-secondary\">\n {title ? <span className=\"font-medium text-fg\">{title}</span> : null}\n {title ? <span aria-hidden> · </span> : null}\n <span>{labels.counter(current + 1, total)}</span>\n </p>\n <div className=\"flex items-center gap-1.5\">\n <button type=\"button\" aria-label={labels.previous} disabled={!loop && current === 0} onClick={() => move(current - 1, true)} className={button}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M15 5l-7 7 7 7\" />\n </svg>\n </button>\n {playing ? (\n <button type=\"button\" aria-label={paused ? labels.play : labels.pause} onClick={() => setPaused(!paused)} className={button}>\n <svg viewBox=\"0 0 24 24\" className=\"h-3.5 w-3.5\" fill=\"currentColor\" aria-hidden>\n {paused ? <path d=\"M8 5.5v13l10-6.5Z\" /> : <path d=\"M7 5h3.5v14H7zM13.5 5H17v14h-3.5z\" />}\n </svg>\n </button>\n ) : null}\n <button type=\"button\" aria-label={labels.next} disabled={!loop && current === total - 1} onClick={() => move(current + 1, true)} className={button}>\n <svg viewBox=\"0 0 24 24\" className=\"h-4 w-4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n <path d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n </div>\n </section>\n );\n}\n\n/**\n * The typed reveal of a text, kept minimal: the visible prefix grows at `speed` characters per second while `active`;\n * under reduced motion (or when not active) it is the whole text at once. Render the full text for assistive technology\n * (`sr-only`) and this one `aria-hidden`.\n */\nexport function useTypedText(text: string, { active = true, speed = 45 }: { active?: boolean; speed?: number } = {}) {\n const [typed, setTyped] = React.useState({ of: \"\", count: 0 });\n const reduced = React.useSyncExternalStore(subscribeReduced, readReduced, () => false);\n React.useEffect(() => {\n if (!active || reduced) return;\n const id = window.setInterval(() => {\n setTyped((state) => {\n const count = state.of === text ? state.count + 1 : 1;\n if (count >= text.length) window.clearInterval(id);\n return { of: text, count };\n });\n }, Math.max(10, 1000 / speed));\n return () => window.clearInterval(id);\n }, [text, active, reduced, speed]);\n const count = typed.of === text ? typed.count : 0;\n const done = !active || reduced || count >= text.length;\n return { text: done ? text : text.slice(0, count), done };\n}\n"],"mappings":"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","names":["count"]}
|