@krizaka/ui 3.7.1 → 3.9.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 +14 -12
- 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-HCNOOPZE.js → chunk-KVDB4GXV.js} +2 -2
- 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/{chunk-JZPVUJIX.js → chunk-UB2BI3AW.js} +1 -1
- package/dist/{chunk-JZPVUJIX.js.map → chunk-UB2BI3AW.js.map} +1 -1
- 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/{geometry-CbOU4rtL.d.ts → geometry-B2E8oFhg.d.ts} +1 -1
- package/dist/image-cropper.js +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +26 -24
- package/dist/index.js.map +1 -1
- package/dist/list.js +1 -1
- package/dist/map.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/mascot-quote.d.ts +1 -1
- package/dist/mascot-quote.js.map +1 -1
- package/dist/mascot-state.d.ts +1 -1
- package/dist/mascot-state.js +1 -1
- package/dist/mascots.d.ts +2 -2
- package/dist/mascots.js +1 -1
- 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/map.js +1 -1
- package/dist/native/map.js.map +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 +10 -14
- package/dist/native.js +383 -119
- package/dist/native.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/status-page.d.ts +1 -1
- package/dist/status-page.js.map +1 -1
- 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 +49 -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/landing-page/krizaka.js +1 -1
- package/registry/examples/landing-page/krizaka.tsx +1 -1
- 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/mascots/flock.tsx +1 -1
- package/registry/examples/profile-card/match.js +1 -1
- package/registry/examples/profile-card/match.tsx +1 -1
- 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/status-page/building.js +1 -1
- package/registry/examples/status-page/building.tsx +1 -1
- 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 +77 -17
- package/registry/iso.json +1 -1
- package/registry/kanban.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/landing-page.json +3 -3
- 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 +4 -4
- 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 +5 -5
- 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 +1 -1
- 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 +3 -3
- package/registry/profile-card.json +2 -2
- 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/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 +7 -7
- 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
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { TV } from 'tailwind-variants';
|
|
2
|
+
|
|
3
|
+
/** What a recipe is made of: the `tv()` options — `base`, `slots`, `variants`, `defaultVariants`, `compoundVariants`. */
|
|
4
|
+
type RecipeConfig = Parameters<TV>[0];
|
|
5
|
+
/**
|
|
6
|
+
* A recipe is data: the variants of a component (a `variant`, a `size`, a `tone`…) as classes of the Krizaka tokens.
|
|
7
|
+
* `defineRecipe` only keeps the literal types, so `tv(recipe)` is typed exactly as `tv({ … })` would be (the web side) and
|
|
8
|
+
* `nativeRecipe(recipe)` reads the same classes into styles on the theme's roles (the native side, `./native`).
|
|
9
|
+
*
|
|
10
|
+
* Sizes are a variant dimension (`size: { sm: "h-8 px-3 text-xs" }`); states are the prefixes (`hover:`, `disabled:`,
|
|
11
|
+
* `data-[state=on]:`). Write colours as roles (`bg-accent`, `text-fg-secondary`, `border-border-default`, `ring-accent/40`),
|
|
12
|
+
* spacing as Tailwind units (`h-10`, `px-4`), type as `text-sm` / `text-[11px]` / `font-semibold`.
|
|
13
|
+
*/
|
|
14
|
+
declare function defineRecipe<const C extends RecipeConfig>(config: C): C;
|
|
15
|
+
|
|
16
|
+
/** The colour roles of a native theme (`Theme` of `@krizaka/ui/native`): a key, a colour. */
|
|
17
|
+
type ThemeColors = Readonly<Record<string, string>>;
|
|
18
|
+
type NativeFrame = {
|
|
19
|
+
backgroundColor?: string;
|
|
20
|
+
borderColor?: string;
|
|
21
|
+
borderWidth?: number;
|
|
22
|
+
height?: number;
|
|
23
|
+
width?: number;
|
|
24
|
+
minWidth?: number;
|
|
25
|
+
paddingHorizontal?: number;
|
|
26
|
+
paddingVertical?: number;
|
|
27
|
+
padding?: number;
|
|
28
|
+
gap?: number;
|
|
29
|
+
opacity?: number;
|
|
30
|
+
};
|
|
31
|
+
type NativeText = {
|
|
32
|
+
color?: string;
|
|
33
|
+
fontSize?: number;
|
|
34
|
+
fontWeight?: "400" | "500" | "600" | "700" | "800" | "900";
|
|
35
|
+
letterSpacing?: number;
|
|
36
|
+
textTransform?: "uppercase" | "lowercase" | "capitalize";
|
|
37
|
+
};
|
|
38
|
+
/** What one part of a component looks like: its frame (a View), its text, and the states that change them. */
|
|
39
|
+
type NativeLook = {
|
|
40
|
+
frame: NativeFrame;
|
|
41
|
+
text: NativeText;
|
|
42
|
+
/** `bg-<role>` of the part's dot (`[&>[data-dot]]:bg-accent`): the colour of a status dot. */
|
|
43
|
+
dot?: string;
|
|
44
|
+
/** `bg-linear-to-r from-<role> to-<role>`: the two stops of a gradient fill. */
|
|
45
|
+
gradient?: readonly [string, string];
|
|
46
|
+
/** `disabled:` — the frame and text while disabled. */
|
|
47
|
+
disabled: {
|
|
48
|
+
frame: NativeFrame;
|
|
49
|
+
text: NativeText;
|
|
50
|
+
};
|
|
51
|
+
/** `data-[state=on]:` — the frame and text while selected / pressed on. */
|
|
52
|
+
on: {
|
|
53
|
+
frame: NativeFrame;
|
|
54
|
+
text: NativeText;
|
|
55
|
+
};
|
|
56
|
+
/** The classes with no native meaning (the web's pointer states, shadows, layout…). */
|
|
57
|
+
ignored: string[];
|
|
58
|
+
};
|
|
59
|
+
type Props = Readonly<Record<string, string | number | boolean | null | undefined>>;
|
|
60
|
+
type Slot = string | readonly unknown[] | null | undefined;
|
|
61
|
+
type Config = {
|
|
62
|
+
base?: Slot;
|
|
63
|
+
slots?: Readonly<Record<string, Slot>>;
|
|
64
|
+
variants?: Readonly<Record<string, Readonly<Record<string, unknown>>>>;
|
|
65
|
+
defaultVariants?: Readonly<Record<string, unknown>>;
|
|
66
|
+
compoundVariants?: readonly Readonly<Record<string, unknown>>[];
|
|
67
|
+
};
|
|
68
|
+
/** `#rrggbb` at an opacity (`rgba()`); any other form passes through. */
|
|
69
|
+
declare function withAlpha(color: string, opacity: number): string;
|
|
70
|
+
type NativeRecipe = {
|
|
71
|
+
/** The looks of each slot (`base` for a recipe without slots) for these variants, on this theme. */
|
|
72
|
+
resolve: (props: Props, theme: ThemeColors) => Record<string, NativeLook>;
|
|
73
|
+
};
|
|
74
|
+
/** The native side of a recipe (`defineRecipe`): what its variants look like on a theme. */
|
|
75
|
+
declare function nativeRecipe(config: Config): NativeRecipe;
|
|
76
|
+
|
|
77
|
+
declare const ROLES: {
|
|
78
|
+
readonly "surface-0": "surface0";
|
|
79
|
+
readonly "surface-1": "surface1";
|
|
80
|
+
readonly "surface-2": "surface2";
|
|
81
|
+
readonly "surface-3": "surface3";
|
|
82
|
+
readonly media: "media";
|
|
83
|
+
readonly scrim: "scrim";
|
|
84
|
+
readonly "scrim-strong": "scrimStrong";
|
|
85
|
+
readonly overlay: "overlay";
|
|
86
|
+
readonly "border-subtle": "borderSubtle";
|
|
87
|
+
readonly "border-default": "borderDefault";
|
|
88
|
+
readonly "border-strong": "borderStrong";
|
|
89
|
+
readonly fg: "textPrimary";
|
|
90
|
+
readonly "fg-secondary": "textSecondary";
|
|
91
|
+
readonly "fg-muted": "textMuted";
|
|
92
|
+
readonly "fg-on-media": "textOnMedia";
|
|
93
|
+
readonly "fg-accent": "accentText";
|
|
94
|
+
readonly "fg-success": "successText";
|
|
95
|
+
readonly "fg-warning": "warningText";
|
|
96
|
+
readonly "fg-danger": "dangerText";
|
|
97
|
+
readonly "fg-info": "infoText";
|
|
98
|
+
readonly accent: "accent";
|
|
99
|
+
readonly "accent-hover": "accentHover";
|
|
100
|
+
readonly "accent-soft": "accentSoft";
|
|
101
|
+
readonly "accent-2": "accent2";
|
|
102
|
+
readonly "on-accent": "onAccent";
|
|
103
|
+
readonly ring: "ring";
|
|
104
|
+
readonly success: "success";
|
|
105
|
+
readonly warning: "warning";
|
|
106
|
+
readonly danger: "danger";
|
|
107
|
+
readonly info: "info";
|
|
108
|
+
readonly "success-soft": "successSoft";
|
|
109
|
+
readonly "warning-soft": "warningSoft";
|
|
110
|
+
readonly "danger-soft": "dangerSoft";
|
|
111
|
+
readonly glass: "glassBg";
|
|
112
|
+
readonly "glass-border": "glassBorder";
|
|
113
|
+
};
|
|
114
|
+
type RoleName = keyof typeof ROLES;
|
|
115
|
+
/** Tailwind's type scale, in points (`text-xs` → 12). */
|
|
116
|
+
declare const TEXT_SIZES: {
|
|
117
|
+
readonly xs: 12;
|
|
118
|
+
readonly sm: 14;
|
|
119
|
+
readonly base: 16;
|
|
120
|
+
readonly lg: 18;
|
|
121
|
+
readonly xl: 20;
|
|
122
|
+
readonly "2xl": 24;
|
|
123
|
+
readonly "3xl": 30;
|
|
124
|
+
};
|
|
125
|
+
declare const FONT_WEIGHTS: {
|
|
126
|
+
readonly normal: "400";
|
|
127
|
+
readonly medium: "500";
|
|
128
|
+
readonly semibold: "600";
|
|
129
|
+
readonly bold: "700";
|
|
130
|
+
readonly extrabold: "800";
|
|
131
|
+
readonly black: "900";
|
|
132
|
+
};
|
|
133
|
+
/** `tracking-*`, in em (React Native takes points: the resolver multiplies by the font size). */
|
|
134
|
+
declare const TRACKING: {
|
|
135
|
+
readonly tighter: -0.05;
|
|
136
|
+
readonly tight: -0.025;
|
|
137
|
+
readonly normal: 0;
|
|
138
|
+
readonly wide: 0.025;
|
|
139
|
+
readonly wider: 0.05;
|
|
140
|
+
readonly widest: 0.1;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export { FONT_WEIGHTS, type NativeFrame, type NativeLook, type NativeRecipe, type NativeText, ROLES, type RecipeConfig, type RoleName, TEXT_SIZES, TRACKING, type ThemeColors, defineRecipe, nativeRecipe, withAlpha };
|
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
package/dist/status-page.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ type StatusPageProps = Omit<React.HTMLAttributes<HTMLElement>, "title"> & {
|
|
|
10
10
|
};
|
|
11
11
|
/** A mono line above the title. */
|
|
12
12
|
eyebrow?: React.ReactNode;
|
|
13
|
-
/** The page's `h1` (`Status`, `
|
|
13
|
+
/** The page's `h1` (`Status`, `Yasakia is on its way`). */
|
|
14
14
|
title: React.ReactNode;
|
|
15
15
|
/** One sentence under it. */
|
|
16
16
|
lead?: React.ReactNode;
|
package/dist/status-page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/status-page/status-page.tsx"],"sourcesContent":["// Server-safe. Where things stand (blocks.css `.kz-tpl-state`, `.kz-tpl-services`): the overall state in words and\n// colour, each service on its line, the incidents under them — or, for something not built yet (`state=\"building\"`),\n// what is coming, its progress and how to hear about it, the bird beside it. A state is always said in words.\nimport { Page, Section } from \"@krizaka/ui/page\";\nimport type { LinkComponent } from \"@krizaka/ui/page-hero\";\nimport { PageHero } from \"@krizaka/ui/page-hero\";\nimport type * as React from \"react\";\n\nexport type ServiceState = \"operational\" | \"degraded\" | \"outage\" | \"building\";\n\nexport type StatusPageProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** The way back. */\n back?: { href: string; label: string };\n /** A mono line above the title. */\n eyebrow?: React.ReactNode;\n /** The page's `h1` (`Status`, `
|
|
1
|
+
{"version":3,"sources":["../src/status-page/status-page.tsx"],"sourcesContent":["// Server-safe. Where things stand (blocks.css `.kz-tpl-state`, `.kz-tpl-services`): the overall state in words and\n// colour, each service on its line, the incidents under them — or, for something not built yet (`state=\"building\"`),\n// what is coming, its progress and how to hear about it, the bird beside it. A state is always said in words.\nimport { Page, Section } from \"@krizaka/ui/page\";\nimport type { LinkComponent } from \"@krizaka/ui/page-hero\";\nimport { PageHero } from \"@krizaka/ui/page-hero\";\nimport type * as React from \"react\";\n\nexport type ServiceState = \"operational\" | \"degraded\" | \"outage\" | \"building\";\n\nexport type StatusPageProps = Omit<React.HTMLAttributes<HTMLElement>, \"title\"> & {\n /** The way back. */\n back?: { href: string; label: string };\n /** A mono line above the title. */\n eyebrow?: React.ReactNode;\n /** The page's `h1` (`Status`, `Yasakia is on its way`). */\n title: React.ReactNode;\n /** One sentence under it. */\n lead?: React.ReactNode;\n /** Beside the hero: a bird (the pigeon that delivers, the duck that builds). */\n aside?: React.ReactNode;\n /** The overall state. */\n state: ServiceState;\n /** The overall state, in words (`All systems operational`). */\n stateLabel: React.ReactNode;\n /** When it was checked (`Checked 2 min ago`). */\n checked?: React.ReactNode;\n /** The services and their states, in words: `{ id, name, state, label }`. */\n services?: readonly { id: string; name: React.ReactNode; state: ServiceState; label: React.ReactNode }[];\n /** The services list's accessible name (`Services`). */\n servicesLabel?: string;\n /** Under the services: the incidents (a ChangelogPage's entries, a list), or for `building` the progress and the way to follow. */\n children?: React.ReactNode;\n /** The app's link component (Next's `Link`). */\n linkAs?: LinkComponent;\n};\n\n/** A status page — or an « under construction » one: the overall state, each service, then incidents or progress. */\nexport function StatusPage({ back, eyebrow, title, lead, aside, state, stateLabel, checked, services, servicesLabel = \"Services\", linkAs, children, ...props }: StatusPageProps) {\n return (\n <div {...props}>\n <PageHero back={back} linkAs={linkAs} eyebrow={eyebrow} title={title} lead={lead} aside={aside} backdrop=\"plain\" />\n <Page className=\"kz-tpl-longform\">\n <div className=\"kz-tpl-state\" data-state={state} role=\"status\">\n <span className=\"kz-tpl-dot\" aria-hidden />\n <strong>{stateLabel}</strong>\n {checked && <span className=\"kz-tpl-count\">{checked}</span>}\n </div>\n {services && services.length > 0 && (\n <ul className=\"kz-tpl-services\" aria-label={servicesLabel}>\n {services.map((s) => (\n <li key={s.id}>\n <span>{s.name}</span>\n <small>\n <span className=\"kz-tpl-dot\" data-state={s.state} aria-hidden />\n {s.label}\n </small>\n </li>\n ))}\n </ul>\n )}\n </Page>\n {children && <Section spacing=\"sm\">{children}</Section>}\n </div>\n );\n}\n"],"mappings":";AAGA,SAAS,MAAM,eAAe;AAE9B,SAAS,gBAAgB;AAoCnB,cAEE,YAFF;AAHC,SAAS,WAAW,EAAE,MAAM,SAAS,OAAO,MAAM,OAAO,OAAO,YAAY,SAAS,UAAU,gBAAgB,YAAY,QAAQ,UAAU,GAAG,MAAM,GAAoB;AAC/K,SACE,qBAAC,SAAK,GAAG,OACP;AAAA,wBAAC,YAAS,MAAY,QAAgB,SAAkB,OAAc,MAAY,OAAc,UAAS,SAAQ;AAAA,IACjH,qBAAC,QAAK,WAAU,mBACd;AAAA,2BAAC,SAAI,WAAU,gBAAe,cAAY,OAAO,MAAK,UACpD;AAAA,4BAAC,UAAK,WAAU,cAAa,eAAW,MAAC;AAAA,QACzC,oBAAC,YAAQ,sBAAW;AAAA,QACnB,WAAW,oBAAC,UAAK,WAAU,gBAAgB,mBAAQ;AAAA,SACtD;AAAA,MACC,YAAY,SAAS,SAAS,KAC7B,oBAAC,QAAG,WAAU,mBAAkB,cAAY,eACzC,mBAAS,IAAI,CAAC,MACb,qBAAC,QACC;AAAA,4BAAC,UAAM,YAAE,MAAK;AAAA,QACd,qBAAC,WACC;AAAA,8BAAC,UAAK,WAAU,cAAa,cAAY,EAAE,OAAO,eAAW,MAAC;AAAA,UAC7D,EAAE;AAAA,WACL;AAAA,WALO,EAAE,EAMX,CACD,GACH;AAAA,OAEJ;AAAA,IACC,YAAY,oBAAC,WAAQ,SAAQ,MAAM,UAAS;AAAA,KAC/C;AAEJ;","names":[]}
|
package/dist/swipe-deck.js
CHANGED
package/dist/tag-input.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import {
|
|
3
3
|
Combobox
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-CKSHLSDH.js";
|
|
5
|
+
import "./chunk-7UCPGXKN.js";
|
|
6
|
+
import "./chunk-D3T54RSL.js";
|
|
6
7
|
import "./chunk-ZIXB33X7.js";
|
|
7
8
|
import {
|
|
8
9
|
useControllable
|
|
9
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-FZBWEDQI.js";
|
|
10
11
|
import "./chunk-CPA5CF35.js";
|
|
11
12
|
|
|
12
13
|
// src/tag-input/tag-input.tsx
|
package/dist/tag-input.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tag-input/tag-input.tsx"],"sourcesContent":["// Client. Free words as chips — the multiple, creatable `Combobox` on strings: Enter or a separator adds the word,\n// a paste of \"a, b, c\" adds three, Backspace on an empty input removes the last, suggestions complete as you type.\n// `normalize` cleans a word (lower case, no `#`), `validate` refuses one with a reason read aloud.\nimport * as React from \"react\";\n\nimport { Combobox, type ComboboxLabels, type ComboboxOption } from \"../combobox\";\nimport { useControllable } from \"../media\";\n\nexport type TagInputProps = {\n /** The visible label, passed translated (also the accessible name). */\n label: string;\n /** Hides the label visually, keeping it as the accessible name. */\n hideLabel?: boolean;\n /** The tags (controlled). */\n value?: readonly string[];\n /** The tags at first, uncontrolled. */\n defaultValue?: readonly string[];\n /** Called with every tag after a change. */\n onValueChange?: (tags: string[]) => void;\n /** Words offered as the person types (their own history, a curated list). */\n suggestions?: readonly string[];\n /** Cleans a word before it is added (`(t) => t.toLowerCase().replace(/^#/, \"\")`). An empty result is ignored. */\n normalize?: (tag: string) => string;\n /** Refuses a word: return the reason (passed translated), shown and read as the field's error; `null` accepts it. */\n validate?: (tag: string, tags: readonly string[]) => string | null;\n /** Most tags; the field stops offering more past it. */\n max?: number;\n /** Keys that add the word, besides Enter. Default `[\",\"]`. */\n separators?: readonly string[];\n /** The input's placeholder, passed translated. */\n placeholder?: string;\n /** A help text under the field (\"Up to 10 tags, separated by commas\"). */\n hint?: string;\n /** An error from the app (a server refusal); `validate`'s own errors show here too. */\n error?: string;\n /** Not available. */\n disabled?: boolean;\n /** The name of the hidden inputs, for a plain `<form>` post (one per tag). */\n name?: string;\n /** The words of the field, passed translated; English by default. */\n labels?: Partial<Pick<ComboboxLabels, \"create\" | \"remove\" | \"empty\" | \"results\" | \"toggle\">>;\n /** Classes merged on the root. */\n className?: string;\n};\n\nconst toOption = (tag: string): ComboboxOption => ({ value: tag, label: tag });\n\n/** Words as removable chips — tags, keywords, e-mail recipients — with suggestions, cleaning and validation. */\nexport function TagInput({\n value,\n defaultValue = [],\n onValueChange,\n suggestions = [],\n normalize = (tag) => tag.trim(),\n validate,\n max,\n separators = [\",\"],\n error,\n labels,\n ...props\n}: TagInputProps) {\n const [tags, setTags] = useControllable<readonly string[]>(value, defaultValue, onValueChange as (next: readonly string[]) => void);\n const [refusal, setRefusal] = React.useState<string | null>(null);\n\n const change = (options: ComboboxOption[]) => {\n const next: string[] = [];\n for (const option of options) {\n const tag = normalize(option.value);\n if (!tag || next.includes(tag)) continue;\n if (!tags.includes(tag)) {\n const reason = validate?.(tag, next) ?? null;\n if (reason) {\n setRefusal(reason);\n continue;\n }\n }\n next.push(tag);\n }\n if (next.length >= options.length || options.length < tags.length) setRefusal(null);\n setTags(next);\n };\n\n return (\n <Combobox\n {...props}\n multiple\n creatable\n separators={separators}\n maxSelected={max}\n value={tags.map(toOption)}\n onValueChange={change}\n options={suggestions.map(toOption)}\n error={refusal ?? error}\n labels={{ empty: \"\", results: () => \"\", ...labels }}\n />\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/tag-input/tag-input.tsx"],"sourcesContent":["// Client. Free words as chips — the multiple, creatable `Combobox` on strings: Enter or a separator adds the word,\n// a paste of \"a, b, c\" adds three, Backspace on an empty input removes the last, suggestions complete as you type.\n// `normalize` cleans a word (lower case, no `#`), `validate` refuses one with a reason read aloud.\nimport * as React from \"react\";\n\nimport { Combobox, type ComboboxLabels, type ComboboxOption } from \"../combobox\";\nimport { useControllable } from \"../media\";\n\nexport type TagInputProps = {\n /** The visible label, passed translated (also the accessible name). */\n label: string;\n /** Hides the label visually, keeping it as the accessible name. */\n hideLabel?: boolean;\n /** The tags (controlled). */\n value?: readonly string[];\n /** The tags at first, uncontrolled. */\n defaultValue?: readonly string[];\n /** Called with every tag after a change. */\n onValueChange?: (tags: string[]) => void;\n /** Words offered as the person types (their own history, a curated list). */\n suggestions?: readonly string[];\n /** Cleans a word before it is added (`(t) => t.toLowerCase().replace(/^#/, \"\")`). An empty result is ignored. */\n normalize?: (tag: string) => string;\n /** Refuses a word: return the reason (passed translated), shown and read as the field's error; `null` accepts it. */\n validate?: (tag: string, tags: readonly string[]) => string | null;\n /** Most tags; the field stops offering more past it. */\n max?: number;\n /** Keys that add the word, besides Enter. Default `[\",\"]`. */\n separators?: readonly string[];\n /** The input's placeholder, passed translated. */\n placeholder?: string;\n /** A help text under the field (\"Up to 10 tags, separated by commas\"). */\n hint?: string;\n /** An error from the app (a server refusal); `validate`'s own errors show here too. */\n error?: string;\n /** Not available. */\n disabled?: boolean;\n /** The name of the hidden inputs, for a plain `<form>` post (one per tag). */\n name?: string;\n /** The words of the field, passed translated; English by default. */\n labels?: Partial<Pick<ComboboxLabels, \"create\" | \"remove\" | \"empty\" | \"results\" | \"toggle\">>;\n /** Classes merged on the root. */\n className?: string;\n};\n\nconst toOption = (tag: string): ComboboxOption => ({ value: tag, label: tag });\n\n/** Words as removable chips — tags, keywords, e-mail recipients — with suggestions, cleaning and validation. */\nexport function TagInput({\n value,\n defaultValue = [],\n onValueChange,\n suggestions = [],\n normalize = (tag) => tag.trim(),\n validate,\n max,\n separators = [\",\"],\n error,\n labels,\n ...props\n}: TagInputProps) {\n const [tags, setTags] = useControllable<readonly string[]>(value, defaultValue, onValueChange as (next: readonly string[]) => void);\n const [refusal, setRefusal] = React.useState<string | null>(null);\n\n const change = (options: ComboboxOption[]) => {\n const next: string[] = [];\n for (const option of options) {\n const tag = normalize(option.value);\n if (!tag || next.includes(tag)) continue;\n if (!tags.includes(tag)) {\n const reason = validate?.(tag, next) ?? null;\n if (reason) {\n setRefusal(reason);\n continue;\n }\n }\n next.push(tag);\n }\n if (next.length >= options.length || options.length < tags.length) setRefusal(null);\n setTags(next);\n };\n\n return (\n <Combobox\n {...props}\n multiple\n creatable\n separators={separators}\n maxSelected={max}\n value={tags.map(toOption)}\n onValueChange={change}\n options={suggestions.map(toOption)}\n error={refusal ?? error}\n labels={{ empty: \"\", results: () => \"\", ...labels }}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAGA,YAAY,WAAW;AAgFnB;AAtCJ,IAAM,WAAW,CAAC,SAAiC,EAAE,OAAO,KAAK,OAAO,IAAI;AAGrE,SAAS,SAAS;AAAA,EACvB;AAAA,EACA,eAAe,CAAC;AAAA,EAChB;AAAA,EACA,cAAc,CAAC;AAAA,EACf,YAAY,CAAC,QAAQ,IAAI,KAAK;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,aAAa,CAAC,GAAG;AAAA,EACjB;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAkB;AAChB,QAAM,CAAC,MAAM,OAAO,IAAI,gBAAmC,OAAO,cAAc,aAAkD;AAClI,QAAM,CAAC,SAAS,UAAU,IAAU,eAAwB,IAAI;AAEhE,QAAM,SAAS,CAAC,YAA8B;AAC5C,UAAM,OAAiB,CAAC;AACxB,eAAW,UAAU,SAAS;AAC5B,YAAM,MAAM,UAAU,OAAO,KAAK;AAClC,UAAI,CAAC,OAAO,KAAK,SAAS,GAAG,EAAG;AAChC,UAAI,CAAC,KAAK,SAAS,GAAG,GAAG;AACvB,cAAM,SAAS,WAAW,KAAK,IAAI,KAAK;AACxC,YAAI,QAAQ;AACV,qBAAW,MAAM;AACjB;AAAA,QACF;AAAA,MACF;AACA,WAAK,KAAK,GAAG;AAAA,IACf;AACA,QAAI,KAAK,UAAU,QAAQ,UAAU,QAAQ,SAAS,KAAK,OAAQ,YAAW,IAAI;AAClF,YAAQ,IAAI;AAAA,EACd;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAQ;AAAA,MACR,WAAS;AAAA,MACT;AAAA,MACA,aAAa;AAAA,MACb,OAAO,KAAK,IAAI,QAAQ;AAAA,MACxB,eAAe;AAAA,MACf,SAAS,YAAY,IAAI,QAAQ;AAAA,MACjC,OAAO,WAAW;AAAA,MAClB,QAAQ,EAAE,OAAO,IAAI,SAAS,MAAM,IAAI,GAAG,OAAO;AAAA;AAAA,EACpD;AAEJ;","names":[]}
|
package/dist/tag-selector.js
CHANGED
package/dist/tag-selector.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tag-selector/tag-selector.tsx"],"sourcesContent":["// Client. Interests, languages, what one is looking for: toggle chips with a limit and a count; once it is reached the\n// others rest (disabled) and the count says so.\nimport { Chip } from \"../chip\";\n\nexport type TagOption = { value: string; label: string };\n\nexport type TagSelectorProps = {\n /** The group's name (“Interests”) — passed translated. */\n label: string;\n /** The tags offered. */\n options: readonly TagOption[];\n /** The tags chosen. */\n value: readonly string[];\n /** Called with the tags chosen. */\n onValueChange: (value: string[]) => void;\n /** The most that can be chosen. */\n max?: number;\n /** The count under the tags: `(n, max) => n + \" of \" + max`. */\n countLabel?: (count: number, max: number) => string;\n /** Classes merged on the root. */\n className?: string;\n};\n\n/** Toggle chips with a limit and a count — the others rest once the limit is reached. */\nexport function TagSelector({ label, options, value, onValueChange, max, countLabel, className }: TagSelectorProps) {\n const full = max !== undefined && value.length >= max;\n return (\n <div className={[\"flex flex-col gap-2\", className].filter(Boolean).join(\" \")}>\n <div role=\"group\" aria-label={label} className=\"flex flex-wrap gap-2\">\n {options.map((option) => {\n const on = value.includes(option.value);\n return (\n <Chip key={option.value} selected={on} disabled={!on && full} onSelectedChange={(next) => onValueChange(next ? [...value, option.value] : value.filter((v) => v !== option.value))}>\n {option.label}\n </Chip>\n );\n })}\n </div>\n {max !== undefined && countLabel ? (\n <p className={[\"m-0 text-xs\", full ? \"font-semibold text-fg-accent\" : \"text-fg-secondary\"].join(\" \")} aria-live=\"polite\">\n {countLabel(value.length, max)}\n </p>\n ) : null}\n </div>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/tag-selector/tag-selector.tsx"],"sourcesContent":["// Client. Interests, languages, what one is looking for: toggle chips with a limit and a count; once it is reached the\n// others rest (disabled) and the count says so.\nimport { Chip } from \"../chip\";\n\nexport type TagOption = { value: string; label: string };\n\nexport type TagSelectorProps = {\n /** The group's name (“Interests”) — passed translated. */\n label: string;\n /** The tags offered. */\n options: readonly TagOption[];\n /** The tags chosen. */\n value: readonly string[];\n /** Called with the tags chosen. */\n onValueChange: (value: string[]) => void;\n /** The most that can be chosen. */\n max?: number;\n /** The count under the tags: `(n, max) => n + \" of \" + max`. */\n countLabel?: (count: number, max: number) => string;\n /** Classes merged on the root. */\n className?: string;\n};\n\n/** Toggle chips with a limit and a count — the others rest once the limit is reached. */\nexport function TagSelector({ label, options, value, onValueChange, max, countLabel, className }: TagSelectorProps) {\n const full = max !== undefined && value.length >= max;\n return (\n <div className={[\"flex flex-col gap-2\", className].filter(Boolean).join(\" \")}>\n <div role=\"group\" aria-label={label} className=\"flex flex-wrap gap-2\">\n {options.map((option) => {\n const on = value.includes(option.value);\n return (\n <Chip key={option.value} selected={on} disabled={!on && full} onSelectedChange={(next) => onValueChange(next ? [...value, option.value] : value.filter((v) => v !== option.value))}>\n {option.label}\n </Chip>\n );\n })}\n </div>\n {max !== undefined && countLabel ? (\n <p className={[\"m-0 text-xs\", full ? \"font-semibold text-fg-accent\" : \"text-fg-secondary\"].join(\" \")} aria-live=\"polite\">\n {countLabel(value.length, max)}\n </p>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;AA2BI,SAKQ,KALR;AAHG,SAAS,YAAY,EAAE,OAAO,SAAS,OAAO,eAAe,KAAK,YAAY,UAAU,GAAqB;AAClH,QAAM,OAAO,QAAQ,UAAa,MAAM,UAAU;AAClD,SACE,qBAAC,SAAI,WAAW,CAAC,uBAAuB,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,GACzE;AAAA,wBAAC,SAAI,MAAK,SAAQ,cAAY,OAAO,WAAU,wBAC5C,kBAAQ,IAAI,CAAC,WAAW;AACvB,YAAM,KAAK,MAAM,SAAS,OAAO,KAAK;AACtC,aACE,oBAAC,QAAwB,UAAU,IAAI,UAAU,CAAC,MAAM,MAAM,kBAAkB,CAAC,SAAS,cAAc,OAAO,CAAC,GAAG,OAAO,OAAO,KAAK,IAAI,MAAM,OAAO,CAAC,MAAM,MAAM,OAAO,KAAK,CAAC,GAC9K,iBAAO,SADC,OAAO,KAElB;AAAA,IAEJ,CAAC,GACH;AAAA,IACC,QAAQ,UAAa,aACpB,oBAAC,OAAE,WAAW,CAAC,eAAe,OAAO,iCAAiC,mBAAmB,EAAE,KAAK,GAAG,GAAG,aAAU,UAC7G,qBAAW,MAAM,QAAQ,GAAG,GAC/B,IACE;AAAA,KACN;AAEJ;","names":[]}
|
package/dist/theme.d.ts
CHANGED
package/dist/theme.js
CHANGED
package/dist/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/theme/theme.tsx"],"sourcesContent":["// Client. ONE mechanism for the whole organisation: dark by default, `html.light` for light, `html.theme-<name>` for a\n// named theme, persisted in localStorage (`kz-theme` for the mode, `kz-theme-name` for the named theme).\nimport * as React from \"react\";\n\nimport { type ButtonVariants, buttonVariants } from \"../button/button\";\n\nexport type Mode = \"dark\" | \"light\" | \"system\";\n\nconst MODE_KEY = \"kz-theme\";\nconst NAME_KEY = \"kz-theme-name\";\nconst MODES: readonly Mode[] = [\"dark\", \"light\", \"system\"];\nconst DARK_QUERY = \"(prefers-color-scheme: dark)\";\n\nconst isMode = (value: unknown): value is Mode => MODES.includes(value as Mode);\n\n// Storage blocked (private mode, sandbox): the choice lives in memory for the page.\nconst memory = new Map<string, string>();\n\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return memory.get(key) ?? null;\n }\n}\n\nfunction write(key: string, value: string | null) {\n if (value === null) memory.delete(key);\n else memory.set(key, value);\n try {\n if (value === null) localStorage.removeItem(key);\n else localStorage.setItem(key, value);\n } catch {\n // storage blocked: memory holds it\n }\n}\n\n// The persisted choice is an external store: every provider and tab reads the same value, the server reads none.\nconst listeners = new Set<() => void>();\nconst notify = () => listeners.forEach((listener) => listener());\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n window.addEventListener(\"storage\", listener);\n return () => {\n listeners.delete(listener);\n window.removeEventListener(\"storage\", listener);\n };\n}\n\nfunction applyMode(mode: Mode) {\n const dark = window.matchMedia(DARK_QUERY).matches;\n document.documentElement.classList.toggle(\"light\", mode === \"light\" || (mode === \"system\" && !dark));\n}\n\nfunction applyName(previous: string | null, next: string | null) {\n const classes = document.documentElement.classList;\n if (previous) classes.remove(`theme-${previous}`);\n if (next) classes.add(`theme-${next}`);\n}\n\nexport type ThemeScriptProps = {\n /** The nonce of a strict Content-Security-Policy (`script-src 'nonce-…'`). */\n nonce?: string;\n /**\n * The mode before the visitor chooses one — pass the same value as `ThemeProvider`'s `defaultMode`. A persisted\n * choice (`kz-theme`) always wins.\n * @default \"system\"\n */\n defaultMode?: Mode;\n};\n\n/**\n * In <head>, before anything renders: applies the persisted mode (else `defaultMode`) and named theme, so the page\n * never flashes. `nonce` for a strict Content-Security-Policy.\n */\nexport function ThemeScript({ nonce, defaultMode = \"system\" }: ThemeScriptProps) {\n const fallback = isMode(defaultMode) ? defaultMode : \"system\";\n const js =\n `(function(){try{var m=localStorage.getItem(\"${MODE_KEY}\");if(m!==\"dark\"&&m!==\"light\"&&m!==\"system\")m=\"${fallback}\";` +\n `var n=localStorage.getItem(\"${NAME_KEY}\");var d=matchMedia(\"${DARK_QUERY}\").matches;var c=document.documentElement.classList;` +\n `c.toggle(\"light\",m===\"light\"||(m===\"system\"&&!d));if(n)c.add(\"theme-\"+n)}catch(e){}})()`;\n return <script nonce={nonce} dangerouslySetInnerHTML={{ __html: js }} />;\n}\n\nexport type ThemeContextValue = {\n /** The chosen mode: dark, light or follow the system. */\n mode: Mode;\n setMode: (mode: Mode) => void;\n /** The named theme (`html.theme-<name>`), or null. */\n theme: string | null;\n setTheme: (theme: string | null) => void;\n};\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null);\n\nexport type ThemeProviderProps = {\n children: React.ReactNode;\n /**\n * The mode before the visitor chooses one (nothing persisted as `kz-theme`) — pass the same value to `ThemeScript`.\n * @default \"system\"\n */\n defaultMode?: Mode;\n};\n\nexport function ThemeProvider({ children, defaultMode = \"system\" }: ThemeProviderProps) {\n // Before hydration the server knows nothing persisted (ThemeScript has already applied it to <html>).\n const stored = React.useSyncExternalStore(subscribe, () => read(MODE_KEY), () => null);\n const mode: Mode = isMode(stored) ? stored : defaultMode;\n const theme = React.useSyncExternalStore(subscribe, () => read(NAME_KEY), () => null);\n\n // The same resolution as ThemeScript (a no-op after it), for an app that renders the provider without the script.\n React.useEffect(() => {\n const persisted = read(MODE_KEY);\n applyMode(isMode(persisted) ? persisted : defaultMode);\n }, [defaultMode]);\n\n // \"system\" follows the operating system while the page is open.\n React.useEffect(() => {\n if (mode !== \"system\") return;\n const query = window.matchMedia(DARK_QUERY);\n const onChange = () => applyMode(\"system\");\n query.addEventListener?.(\"change\", onChange);\n return () => query.removeEventListener?.(\"change\", onChange);\n }, [mode]);\n\n const setMode = React.useCallback((next: Mode) => {\n write(MODE_KEY, next);\n applyMode(next);\n notify();\n }, []);\n\n const setTheme = React.useCallback((next: string | null) => {\n applyName(read(NAME_KEY), next);\n write(NAME_KEY, next);\n notify();\n }, []);\n\n const value = React.useMemo(() => ({ mode, setMode, theme, setTheme }), [mode, setMode, theme, setTheme]);\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\nexport function useTheme(): ThemeContextValue {\n const context = React.useContext(ThemeContext);\n if (!context) throw new Error(\"useTheme: wrap the app in <ThemeProvider>\");\n return context;\n}\n\nconst ICONS: Record<Mode, React.ReactNode> = {\n dark: <path d=\"M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5Z\" />,\n light: (\n <>\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4\" />\n </>\n ),\n system: (\n <>\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"12\" rx=\"2\" />\n <path d=\"M8 20h8M12 16v4\" />\n </>\n ),\n};\n\nexport type ThemeToggleProps = Omit<React.ComponentProps<\"button\">, \"onClick\" | \"children\"> &\n Pick<ButtonVariants, \"variant\" | \"shape\"> & {\n /** Any `Button` variant. */\n variant?: ButtonVariants[\"variant\"];\n /**\n * rounded · pill.\n * @default \"rounded\"\n */\n shape?: ButtonVariants[\"shape\"];\n /** The accessible name — a string, or one per current mode (e.g. `(mode) => t(\"theme.\" + mode)`). */\n label: string | ((mode: Mode) => string);\n };\n\n/**\n * An icon button (styled as `IconButton`, without its Slot) that cycles dark → light → system. Needs a\n * <ThemeProvider> above it. State: `data-mode`.\n */\nexport function ThemeToggle({ label, variant = \"ghost\", shape, className, ...props }: ThemeToggleProps) {\n const { mode, setMode } = useTheme();\n const next = MODES[(MODES.indexOf(mode) + 1) % MODES.length] as Mode;\n const name = typeof label === \"function\" ? label(mode) : label;\n return (\n <button\n type=\"button\"\n aria-label={name}\n title={name}\n data-mode={mode}\n onClick={() => setMode(next)}\n className={buttonVariants({ variant, shape, size: \"icon\", className })}\n {...props}\n >\n <svg viewBox=\"0 0 24 24\" className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n {ICONS[mode]}\n </svg>\n </button>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/theme/theme.tsx"],"sourcesContent":["// Client. ONE mechanism for the whole organisation: dark by default, `html.light` for light, `html.theme-<name>` for a\n// named theme, persisted in localStorage (`kz-theme` for the mode, `kz-theme-name` for the named theme).\nimport * as React from \"react\";\n\nimport { type ButtonVariants, buttonVariants } from \"../button/button\";\n\nexport type Mode = \"dark\" | \"light\" | \"system\";\n\nconst MODE_KEY = \"kz-theme\";\nconst NAME_KEY = \"kz-theme-name\";\nconst MODES: readonly Mode[] = [\"dark\", \"light\", \"system\"];\nconst DARK_QUERY = \"(prefers-color-scheme: dark)\";\n\nconst isMode = (value: unknown): value is Mode => MODES.includes(value as Mode);\n\n// Storage blocked (private mode, sandbox): the choice lives in memory for the page.\nconst memory = new Map<string, string>();\n\nfunction read(key: string): string | null {\n try {\n return localStorage.getItem(key);\n } catch {\n return memory.get(key) ?? null;\n }\n}\n\nfunction write(key: string, value: string | null) {\n if (value === null) memory.delete(key);\n else memory.set(key, value);\n try {\n if (value === null) localStorage.removeItem(key);\n else localStorage.setItem(key, value);\n } catch {\n // storage blocked: memory holds it\n }\n}\n\n// The persisted choice is an external store: every provider and tab reads the same value, the server reads none.\nconst listeners = new Set<() => void>();\nconst notify = () => listeners.forEach((listener) => listener());\nfunction subscribe(listener: () => void) {\n listeners.add(listener);\n window.addEventListener(\"storage\", listener);\n return () => {\n listeners.delete(listener);\n window.removeEventListener(\"storage\", listener);\n };\n}\n\nfunction applyMode(mode: Mode) {\n const dark = window.matchMedia(DARK_QUERY).matches;\n document.documentElement.classList.toggle(\"light\", mode === \"light\" || (mode === \"system\" && !dark));\n}\n\nfunction applyName(previous: string | null, next: string | null) {\n const classes = document.documentElement.classList;\n if (previous) classes.remove(`theme-${previous}`);\n if (next) classes.add(`theme-${next}`);\n}\n\nexport type ThemeScriptProps = {\n /** The nonce of a strict Content-Security-Policy (`script-src 'nonce-…'`). */\n nonce?: string;\n /**\n * The mode before the visitor chooses one — pass the same value as `ThemeProvider`'s `defaultMode`. A persisted\n * choice (`kz-theme`) always wins.\n * @default \"system\"\n */\n defaultMode?: Mode;\n};\n\n/**\n * In <head>, before anything renders: applies the persisted mode (else `defaultMode`) and named theme, so the page\n * never flashes. `nonce` for a strict Content-Security-Policy.\n */\nexport function ThemeScript({ nonce, defaultMode = \"system\" }: ThemeScriptProps) {\n const fallback = isMode(defaultMode) ? defaultMode : \"system\";\n const js =\n `(function(){try{var m=localStorage.getItem(\"${MODE_KEY}\");if(m!==\"dark\"&&m!==\"light\"&&m!==\"system\")m=\"${fallback}\";` +\n `var n=localStorage.getItem(\"${NAME_KEY}\");var d=matchMedia(\"${DARK_QUERY}\").matches;var c=document.documentElement.classList;` +\n `c.toggle(\"light\",m===\"light\"||(m===\"system\"&&!d));if(n)c.add(\"theme-\"+n)}catch(e){}})()`;\n return <script nonce={nonce} dangerouslySetInnerHTML={{ __html: js }} />;\n}\n\nexport type ThemeContextValue = {\n /** The chosen mode: dark, light or follow the system. */\n mode: Mode;\n setMode: (mode: Mode) => void;\n /** The named theme (`html.theme-<name>`), or null. */\n theme: string | null;\n setTheme: (theme: string | null) => void;\n};\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null);\n\nexport type ThemeProviderProps = {\n children: React.ReactNode;\n /**\n * The mode before the visitor chooses one (nothing persisted as `kz-theme`) — pass the same value to `ThemeScript`.\n * @default \"system\"\n */\n defaultMode?: Mode;\n};\n\nexport function ThemeProvider({ children, defaultMode = \"system\" }: ThemeProviderProps) {\n // Before hydration the server knows nothing persisted (ThemeScript has already applied it to <html>).\n const stored = React.useSyncExternalStore(subscribe, () => read(MODE_KEY), () => null);\n const mode: Mode = isMode(stored) ? stored : defaultMode;\n const theme = React.useSyncExternalStore(subscribe, () => read(NAME_KEY), () => null);\n\n // The same resolution as ThemeScript (a no-op after it), for an app that renders the provider without the script.\n React.useEffect(() => {\n const persisted = read(MODE_KEY);\n applyMode(isMode(persisted) ? persisted : defaultMode);\n }, [defaultMode]);\n\n // \"system\" follows the operating system while the page is open.\n React.useEffect(() => {\n if (mode !== \"system\") return;\n const query = window.matchMedia(DARK_QUERY);\n const onChange = () => applyMode(\"system\");\n query.addEventListener?.(\"change\", onChange);\n return () => query.removeEventListener?.(\"change\", onChange);\n }, [mode]);\n\n const setMode = React.useCallback((next: Mode) => {\n write(MODE_KEY, next);\n applyMode(next);\n notify();\n }, []);\n\n const setTheme = React.useCallback((next: string | null) => {\n applyName(read(NAME_KEY), next);\n write(NAME_KEY, next);\n notify();\n }, []);\n\n const value = React.useMemo(() => ({ mode, setMode, theme, setTheme }), [mode, setMode, theme, setTheme]);\n return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;\n}\n\nexport function useTheme(): ThemeContextValue {\n const context = React.useContext(ThemeContext);\n if (!context) throw new Error(\"useTheme: wrap the app in <ThemeProvider>\");\n return context;\n}\n\nconst ICONS: Record<Mode, React.ReactNode> = {\n dark: <path d=\"M20 14.5A8 8 0 0 1 9.5 4a8 8 0 1 0 10.5 10.5Z\" />,\n light: (\n <>\n <circle cx=\"12\" cy=\"12\" r=\"4\" />\n <path d=\"M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4\" />\n </>\n ),\n system: (\n <>\n <rect x=\"3\" y=\"4\" width=\"18\" height=\"12\" rx=\"2\" />\n <path d=\"M8 20h8M12 16v4\" />\n </>\n ),\n};\n\nexport type ThemeToggleProps = Omit<React.ComponentProps<\"button\">, \"onClick\" | \"children\"> &\n Pick<ButtonVariants, \"variant\" | \"shape\"> & {\n /** Any `Button` variant. */\n variant?: ButtonVariants[\"variant\"];\n /**\n * rounded · pill.\n * @default \"rounded\"\n */\n shape?: ButtonVariants[\"shape\"];\n /** The accessible name — a string, or one per current mode (e.g. `(mode) => t(\"theme.\" + mode)`). */\n label: string | ((mode: Mode) => string);\n };\n\n/**\n * An icon button (styled as `IconButton`, without its Slot) that cycles dark → light → system. Needs a\n * <ThemeProvider> above it. State: `data-mode`.\n */\nexport function ThemeToggle({ label, variant = \"ghost\", shape, className, ...props }: ThemeToggleProps) {\n const { mode, setMode } = useTheme();\n const next = MODES[(MODES.indexOf(mode) + 1) % MODES.length] as Mode;\n const name = typeof label === \"function\" ? label(mode) : label;\n return (\n <button\n type=\"button\"\n aria-label={name}\n title={name}\n data-mode={mode}\n onClick={() => setMode(next)}\n className={buttonVariants({ variant, shape, size: \"icon\", className })}\n {...props}\n >\n <svg viewBox=\"0 0 24 24\" className=\"h-5 w-5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden>\n {ICONS[mode]}\n </svg>\n </button>\n );\n}\n"],"mappings":";;;;;;;;AAEA,YAAY,WAAW;AA+Ed,SAqEL,UArEK,KAqEL,YArEK;AAzET,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,QAAyB,CAAC,QAAQ,SAAS,QAAQ;AACzD,IAAM,aAAa;AAEnB,IAAM,SAAS,CAAC,UAAkC,MAAM,SAAS,KAAa;AAG9E,IAAM,SAAS,oBAAI,IAAoB;AAEvC,SAAS,KAAK,KAA4B;AACxC,MAAI;AACF,WAAO,aAAa,QAAQ,GAAG;AAAA,EACjC,QAAQ;AACN,WAAO,OAAO,IAAI,GAAG,KAAK;AAAA,EAC5B;AACF;AAEA,SAAS,MAAM,KAAa,OAAsB;AAChD,MAAI,UAAU,KAAM,QAAO,OAAO,GAAG;AAAA,MAChC,QAAO,IAAI,KAAK,KAAK;AAC1B,MAAI;AACF,QAAI,UAAU,KAAM,cAAa,WAAW,GAAG;AAAA,QAC1C,cAAa,QAAQ,KAAK,KAAK;AAAA,EACtC,QAAQ;AAAA,EAER;AACF;AAGA,IAAM,YAAY,oBAAI,IAAgB;AACtC,IAAM,SAAS,MAAM,UAAU,QAAQ,CAAC,aAAa,SAAS,CAAC;AAC/D,SAAS,UAAU,UAAsB;AACvC,YAAU,IAAI,QAAQ;AACtB,SAAO,iBAAiB,WAAW,QAAQ;AAC3C,SAAO,MAAM;AACX,cAAU,OAAO,QAAQ;AACzB,WAAO,oBAAoB,WAAW,QAAQ;AAAA,EAChD;AACF;AAEA,SAAS,UAAU,MAAY;AAC7B,QAAM,OAAO,OAAO,WAAW,UAAU,EAAE;AAC3C,WAAS,gBAAgB,UAAU,OAAO,SAAS,SAAS,WAAY,SAAS,YAAY,CAAC,IAAK;AACrG;AAEA,SAAS,UAAU,UAAyB,MAAqB;AAC/D,QAAM,UAAU,SAAS,gBAAgB;AACzC,MAAI,SAAU,SAAQ,OAAO,SAAS,QAAQ,EAAE;AAChD,MAAI,KAAM,SAAQ,IAAI,SAAS,IAAI,EAAE;AACvC;AAiBO,SAAS,YAAY,EAAE,OAAO,cAAc,SAAS,GAAqB;AAC/E,QAAM,WAAW,OAAO,WAAW,IAAI,cAAc;AACrD,QAAM,KACJ,+CAA+C,QAAQ,kDAAkD,QAAQ,iCAClF,QAAQ,wBAAwB,UAAU;AAE3E,SAAO,oBAAC,YAAO,OAAc,yBAAyB,EAAE,QAAQ,GAAG,GAAG;AACxE;AAWA,IAAM,eAAqB,oBAAwC,IAAI;AAWhE,SAAS,cAAc,EAAE,UAAU,cAAc,SAAS,GAAuB;AAEtF,QAAM,SAAe,2BAAqB,WAAW,MAAM,KAAK,QAAQ,GAAG,MAAM,IAAI;AACrF,QAAM,OAAa,OAAO,MAAM,IAAI,SAAS;AAC7C,QAAM,QAAc,2BAAqB,WAAW,MAAM,KAAK,QAAQ,GAAG,MAAM,IAAI;AAGpF,EAAM,gBAAU,MAAM;AACpB,UAAM,YAAY,KAAK,QAAQ;AAC/B,cAAU,OAAO,SAAS,IAAI,YAAY,WAAW;AAAA,EACvD,GAAG,CAAC,WAAW,CAAC;AAGhB,EAAM,gBAAU,MAAM;AACpB,QAAI,SAAS,SAAU;AACvB,UAAM,QAAQ,OAAO,WAAW,UAAU;AAC1C,UAAM,WAAW,MAAM,UAAU,QAAQ;AACzC,UAAM,mBAAmB,UAAU,QAAQ;AAC3C,WAAO,MAAM,MAAM,sBAAsB,UAAU,QAAQ;AAAA,EAC7D,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,UAAgB,kBAAY,CAAC,SAAe;AAChD,UAAM,UAAU,IAAI;AACpB,cAAU,IAAI;AACd,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAEL,QAAM,WAAiB,kBAAY,CAAC,SAAwB;AAC1D,cAAU,KAAK,QAAQ,GAAG,IAAI;AAC9B,UAAM,UAAU,IAAI;AACpB,WAAO;AAAA,EACT,GAAG,CAAC,CAAC;AAEL,QAAM,QAAc,cAAQ,OAAO,EAAE,MAAM,SAAS,OAAO,SAAS,IAAI,CAAC,MAAM,SAAS,OAAO,QAAQ,CAAC;AACxG,SAAO,oBAAC,aAAa,UAAb,EAAsB,OAAe,UAAS;AACxD;AAEO,SAAS,WAA8B;AAC5C,QAAM,UAAgB,iBAAW,YAAY;AAC7C,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,2CAA2C;AACzE,SAAO;AACT;AAEA,IAAM,QAAuC;AAAA,EAC3C,MAAM,oBAAC,UAAK,GAAE,iDAAgD;AAAA,EAC9D,OACE,iCACE;AAAA,wBAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,KAAI;AAAA,IAC9B,oBAAC,UAAK,GAAE,sGAAqG;AAAA,KAC/G;AAAA,EAEF,QACE,iCACE;AAAA,wBAAC,UAAK,GAAE,KAAI,GAAE,KAAI,OAAM,MAAK,QAAO,MAAK,IAAG,KAAI;AAAA,IAChD,oBAAC,UAAK,GAAE,mBAAkB;AAAA,KAC5B;AAEJ;AAmBO,SAAS,YAAY,EAAE,OAAO,UAAU,SAAS,OAAO,WAAW,GAAG,MAAM,GAAqB;AACtG,QAAM,EAAE,MAAM,QAAQ,IAAI,SAAS;AACnC,QAAM,OAAO,OAAO,MAAM,QAAQ,IAAI,IAAI,KAAK,MAAM,MAAM;AAC3D,QAAM,OAAO,OAAO,UAAU,aAAa,MAAM,IAAI,IAAI;AACzD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,OAAO;AAAA,MACP,aAAW;AAAA,MACX,SAAS,MAAM,QAAQ,IAAI;AAAA,MAC3B,WAAW,eAAe,EAAE,SAAS,OAAO,MAAM,QAAQ,UAAU,CAAC;AAAA,MACpE,GAAG;AAAA,MAEJ,8BAAC,SAAI,SAAQ,aAAY,WAAU,WAAU,MAAK,QAAO,QAAO,gBAAe,aAAY,KAAI,eAAc,SAAQ,gBAAe,SAAQ,eAAW,MACpJ,gBAAM,IAAI,GACb;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
package/dist/tree-view.js
CHANGED