@krizaka/ui 3.51.0 → 3.53.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/dist/action-bar.js +1 -1
- package/dist/argument-card.js +4 -4
- package/dist/blocks.css +293 -0
- package/dist/chapter.d.ts +89 -0
- package/dist/chapter.js +123 -0
- package/dist/chapter.js.map +1 -0
- package/dist/choice-grid.js +1 -1
- package/dist/choice-grid.js.map +1 -1
- package/dist/{chunk-IBDTAMPF.js → chunk-7JG3VR4C.js} +2 -2
- package/dist/{chunk-C7LTF5CC.js → chunk-ENOS6ETI.js} +2 -2
- package/dist/{chunk-C7LTF5CC.js.map → chunk-ENOS6ETI.js.map} +1 -1
- package/dist/{chunk-2AGSO3WH.js → chunk-FVGFPB73.js} +2 -2
- package/dist/{chunk-2AGSO3WH.js.map → chunk-FVGFPB73.js.map} +1 -1
- package/dist/chunk-YATTFMDV.js +45 -0
- package/dist/chunk-YATTFMDV.js.map +1 -0
- package/dist/cover-hero.js +1 -1
- package/dist/glossary-term.d.ts +12 -0
- package/dist/glossary-term.js +16 -0
- package/dist/glossary-term.js.map +1 -0
- package/dist/glossary.d.ts +20 -0
- package/dist/glossary.js +17 -0
- package/dist/glossary.js.map +1 -0
- package/dist/index.js +7 -7
- package/dist/kpi-explained.d.ts +66 -0
- package/dist/kpi-explained.js +84 -0
- package/dist/kpi-explained.js.map +1 -0
- package/dist/link-card.js +2 -2
- package/dist/media-grid.js +1 -1
- package/dist/media-grid.js.map +1 -1
- package/dist/next-actions.d.ts +27 -0
- package/dist/next-actions.js +27 -0
- package/dist/next-actions.js.map +1 -0
- package/dist/page-hero.js +1 -1
- package/dist/page-index.js +1 -1
- package/dist/service-card.js +1 -1
- package/dist/slot-picker.js +1 -1
- package/dist/text-style/server.d.ts +63 -0
- package/dist/text-style/server.js +109 -0
- package/dist/text-style/server.js.map +1 -0
- package/dist/text-style.d.ts +149 -0
- package/dist/text-style.js +399 -0
- package/dist/text-style.js.map +1 -0
- package/dist/tooltip.js +5 -37
- package/dist/tooltip.js.map +1 -1
- package/package.json +86 -8
- package/registry/account-menu.json +1 -1
- package/registry/account-settings.json +1 -1
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +2 -2
- package/registry/activity-feed.json +1 -1
- package/registry/agenda.json +1 -1
- package/registry/alert.json +1 -1
- package/registry/anatomy-plate.json +1 -1
- package/registry/app-header.json +1 -1
- package/registry/app-shell.json +1 -1
- package/registry/argument-card.json +2 -2
- package/registry/audio-player.json +1 -1
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/bird-plate.json +1 -1
- package/registry/button.json +1 -1
- package/registry/captcha.json +1 -1
- package/registry/card-grid.json +1 -1
- package/registry/card.json +1 -1
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +1 -1
- package/registry/chapter.json +478 -0
- package/registry/chat.json +1 -1
- package/registry/checkbox.json +1 -1
- package/registry/chip-link.json +1 -1
- package/registry/chip-row.json +1 -1
- package/registry/chip.json +1 -1
- package/registry/choice-grid.json +2 -2
- package/registry/code-block.json +1 -1
- package/registry/combobox.json +1 -1
- package/registry/command.json +1 -1
- package/registry/comparison-table.json +1 -1
- package/registry/component-page.json +1 -1
- 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/cover-hero.json +2 -2
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +1 -1
- package/registry/date-picker.json +2 -2
- 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/docs-nav.json +1 -1
- package/registry/document-viewer.json +1 -1
- package/registry/drop-overlay.json +1 -1
- package/registry/dropdown-menu.json +1 -1
- package/registry/elephant.json +2 -2
- package/registry/empty-state.json +1 -1
- package/registry/engine-flow.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/chapter/detail.d.ts +4 -0
- package/registry/examples/chapter/detail.js +21 -0
- package/registry/examples/chapter/detail.tsx +16 -0
- package/registry/examples/chapter/foot.d.ts +4 -0
- package/registry/examples/chapter/foot.js +20 -0
- package/registry/examples/chapter/foot.tsx +15 -0
- package/registry/examples/chapter/head.d.ts +4 -0
- package/registry/examples/chapter/head.js +22 -0
- package/registry/examples/chapter/head.tsx +17 -0
- package/registry/examples/chapter/index.d.ts +4 -0
- package/registry/examples/chapter/index.js +34 -0
- package/registry/examples/chapter/index.tsx +30 -0
- package/registry/examples/chapter/reading-page.d.ts +4 -0
- package/registry/examples/chapter/reading-page.js +34 -0
- package/registry/examples/chapter/reading-page.tsx +25 -0
- package/registry/examples/glossary/shop-terms.d.ts +4 -0
- package/registry/examples/glossary/shop-terms.js +11 -0
- package/registry/examples/glossary/shop-terms.tsx +5 -0
- package/registry/examples/glossary-term/inline.d.ts +4 -0
- package/registry/examples/glossary-term/inline.js +15 -0
- package/registry/examples/glossary-term/inline.tsx +5 -0
- package/registry/examples/kpi-explained/founder-cockpit.d.ts +4 -0
- package/registry/examples/kpi-explained/founder-cockpit.js +11 -0
- package/registry/examples/kpi-explained/founder-cockpit.tsx +5 -0
- package/registry/examples/kpi-explained/salon-month.d.ts +4 -0
- package/registry/examples/kpi-explained/salon-month.js +11 -0
- package/registry/examples/kpi-explained/salon-month.tsx +5 -0
- package/registry/examples/next-actions/shop-owner.d.ts +4 -0
- package/registry/examples/next-actions/shop-owner.js +11 -0
- package/registry/examples/next-actions/shop-owner.tsx +5 -0
- package/registry/examples/text-style/compact-toolbar.d.ts +4 -0
- package/registry/examples/text-style/compact-toolbar.js +28 -0
- package/registry/examples/text-style/compact-toolbar.tsx +21 -0
- package/registry/examples/text-style/product-price.d.ts +4 -0
- package/registry/examples/text-style/product-price.js +26 -0
- package/registry/examples/text-style/product-price.tsx +17 -0
- package/registry/examples/text-style/storefront-title.d.ts +4 -0
- package/registry/examples/text-style/storefront-title.js +36 -0
- package/registry/examples/text-style/storefront-title.tsx +22 -0
- package/registry/field.json +1 -1
- package/registry/file-upload.json +2 -2
- package/registry/filter-bar.json +1 -1
- package/registry/flock.json +1 -1
- package/registry/footnote.json +1 -1
- package/registry/force-update-screen.json +1 -1
- package/registry/form.json +2 -2
- package/registry/gallery-page.json +1 -1
- package/registry/genui.json +2 -2
- package/registry/glossary-term.json +144 -0
- package/registry/glossary.json +137 -0
- 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 +1 -1
- package/registry/index.json +199 -11
- package/registry/iso.json +1 -1
- package/registry/kanban.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/kpi-explained.json +364 -0
- package/registry/landing-page.json +1 -1
- package/registry/landscape.json +1 -1
- package/registry/language-switch.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +2 -2
- package/registry/list.json +1 -1
- package/registry/loader.json +1 -1
- package/registry/longform-page.json +1 -1
- package/registry/maintenance-screen.json +1 -1
- package/registry/map.json +2 -2
- package/registry/markdown.json +1 -1
- 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 +1 -1
- package/registry/media-gallery.json +1 -1
- package/registry/media-grid.json +2 -2
- package/registry/meta-row.json +1 -1
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +2 -2
- package/registry/next-actions.json +142 -0
- package/registry/number-ticker.json +1 -1
- package/registry/opening-hours.json +1 -1
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +2 -2
- package/registry/page-index.json +2 -2
- 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/rail-nav.json +2 -2
- package/registry/recipes.json +1 -1
- package/registry/router.json +1 -1
- package/registry/scroll-stage.json +1 -1
- package/registry/search-field.json +1 -1
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/service-card.json +2 -2
- package/registry/settings-layout.json +1 -1
- package/registry/showcase.json +1 -1
- package/registry/site-footer.json +2 -2
- package/registry/sites.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-picker.json +2 -2
- package/registry/slot.json +1 -1
- package/registry/specimen-tag.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/step-showcase.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/sticky-action.json +1 -1
- package/registry/swipe-deck.json +1 -1
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/tag-input.json +1 -1
- package/registry/tag-selector.json +1 -1
- package/registry/taped-photo.json +1 -1
- package/registry/terminal-replay.json +1 -1
- package/registry/text-style.json +378 -0
- 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 +1 -1
- package/registry/update-prompt.json +1 -1
- package/registry/upload-queue.json +1 -1
- package/registry/verified-badge.json +1 -1
- package/registry/vertical-video.json +2 -2
- package/registry/video-player.json +1 -1
- package/registry/video-trimmer.json +1 -1
- package/registry/virtual.json +1 -1
- package/registry/waveform.json +1 -1
- /package/dist/{chunk-IBDTAMPF.js.map → chunk-7JG3VR4C.js.map} +0 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
|
|
4
|
+
type StyledTextProps<T extends React.ElementType = "p"> = {
|
|
5
|
+
/** The element drawn: `h1`…`h6`, `p`, `span`, `div`. */
|
|
6
|
+
as?: T;
|
|
7
|
+
/** The stored text style — validated here: anything invalid draws the default. */
|
|
8
|
+
value?: unknown;
|
|
9
|
+
/** Merged after the style's own class. */
|
|
10
|
+
className?: string;
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
} & Omit<React.ComponentPropsWithoutRef<T>, "as" | "value" | "className" | "style" | "children">;
|
|
13
|
+
/** A text role drawn from a `TextStyle`: its font, size, weight, spacing, alignment, case and tone, all from CSS variables. */
|
|
14
|
+
declare function StyledText<T extends React.ElementType = "p">({ as, value, className, children, ...rest }: StyledTextProps<T>): React.JSX.Element;
|
|
15
|
+
|
|
16
|
+
/** The curated families of `@krizaka/fonts`, by role: `display` (the headlines), `text` (reading), `mono` (the typewriter), `cahier` (the pen). */
|
|
17
|
+
declare const fontIds: readonly ["display", "text", "mono", "cahier"];
|
|
18
|
+
type FontId = (typeof fontIds)[number];
|
|
19
|
+
/** The steps of the type scale, smallest to largest. Never pixels. */
|
|
20
|
+
declare const sizeSteps: readonly ["xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl"];
|
|
21
|
+
type SizeStep = (typeof sizeSteps)[number];
|
|
22
|
+
declare const textWeights: readonly [400, 500, 600, 700];
|
|
23
|
+
type TextWeight = (typeof textWeights)[number];
|
|
24
|
+
declare const leadings: readonly ["tight", "normal", "relaxed"];
|
|
25
|
+
type Leading = (typeof leadings)[number];
|
|
26
|
+
declare const trackings: readonly ["tight", "normal", "wide"];
|
|
27
|
+
type Tracking = (typeof trackings)[number];
|
|
28
|
+
declare const aligns: readonly ["start", "center", "end"];
|
|
29
|
+
type Align = (typeof aligns)[number];
|
|
30
|
+
declare const cases: readonly ["none", "upper", "small-caps"];
|
|
31
|
+
type Case = (typeof cases)[number];
|
|
32
|
+
/** The text roles of the tokens, and the brand hue. */
|
|
33
|
+
declare const toneIds: readonly ["primary", "secondary", "muted", "accent"];
|
|
34
|
+
type ToneId = (typeof toneIds)[number];
|
|
35
|
+
/** A text style: what a shop owner chooses, stored as it is. */
|
|
36
|
+
type TextStyle = {
|
|
37
|
+
font: FontId;
|
|
38
|
+
size: SizeStep;
|
|
39
|
+
weight: TextWeight;
|
|
40
|
+
leading: Leading;
|
|
41
|
+
tracking: Tracking;
|
|
42
|
+
align: Align;
|
|
43
|
+
case: Case;
|
|
44
|
+
italic: boolean;
|
|
45
|
+
tone: ToneId;
|
|
46
|
+
};
|
|
47
|
+
/** What `parseTextStyle` answers for anything that is not a text style. */
|
|
48
|
+
declare const defaultTextStyle: Readonly<TextStyle>;
|
|
49
|
+
/** The weights each face is cut in: a picker offers these, never a synthesised bold. */
|
|
50
|
+
declare const fontWeights: Readonly<Record<FontId, readonly TextWeight[]>>;
|
|
51
|
+
/**
|
|
52
|
+
* Validates what was stored (or sent): the text style, or `defaultTextStyle` for anything invalid — a free font name, a
|
|
53
|
+
* hex colour, a pixel size, CSS text, a value that is not an object. Keys a text style does not have are dropped; keys it
|
|
54
|
+
* lacks (a value stored before one was added) take their default. Usable on a server.
|
|
55
|
+
*/
|
|
56
|
+
declare function parseTextStyle(input: unknown): TextStyle;
|
|
57
|
+
/** The `className` and `style` that draw a text style. Built from fixed tables of CSS variables only. */
|
|
58
|
+
declare function textStyleProps(style: unknown): {
|
|
59
|
+
className: string;
|
|
60
|
+
style: CSSProperties;
|
|
61
|
+
};
|
|
62
|
+
/** A colour read from the page, as `[r, g, b]` in 0–255, or `null` when it cannot be read (the check then says nothing). */
|
|
63
|
+
declare function parseColor(text: string): [number, number, number] | null;
|
|
64
|
+
/** The WCAG contrast ratio of two colours (1 – 21). */
|
|
65
|
+
declare function contrastRatio(foreground: [number, number, number], background: [number, number, number]): number;
|
|
66
|
+
/** The ratio WCAG asks of large text. */
|
|
67
|
+
declare const largeTextRatio = 3;
|
|
68
|
+
/** The ratio WCAG asks of body text. */
|
|
69
|
+
declare const bodyTextRatio = 4.5;
|
|
70
|
+
/** Large text for WCAG: 24 px and over, or bold from 18.66 px (`xl`, 20 px, in bold). */
|
|
71
|
+
declare function isLargeText(style: Pick<TextStyle, "size" | "weight">): boolean;
|
|
72
|
+
/** The ratio a style's text must reach on its surface. */
|
|
73
|
+
declare const requiredContrast: (style: Pick<TextStyle, "size" | "weight">) => number;
|
|
74
|
+
|
|
75
|
+
/** The controls of the picker, in the order they are drawn. */
|
|
76
|
+
declare const textStyleFields: readonly ["font", "size", "weight", "leading", "tracking", "align", "case", "italic", "tone"];
|
|
77
|
+
type TextStyleField = (typeof textStyleFields)[number];
|
|
78
|
+
type TextStyleLabels = {
|
|
79
|
+
/** The name of the whole control. */
|
|
80
|
+
picker: string;
|
|
81
|
+
font: string;
|
|
82
|
+
size: string;
|
|
83
|
+
weight: string;
|
|
84
|
+
leading: string;
|
|
85
|
+
tracking: string;
|
|
86
|
+
align: string;
|
|
87
|
+
case: string;
|
|
88
|
+
italic: string;
|
|
89
|
+
tone: string;
|
|
90
|
+
presets: string;
|
|
91
|
+
preview: string;
|
|
92
|
+
fonts: Record<FontId, string>;
|
|
93
|
+
weights: Record<TextWeight, string>;
|
|
94
|
+
leadings: Record<Leading, string>;
|
|
95
|
+
trackings: Record<Tracking, string>;
|
|
96
|
+
aligns: Record<Align, string>;
|
|
97
|
+
cases: Record<Case, string>;
|
|
98
|
+
tones: Record<ToneId, string>;
|
|
99
|
+
/** The contrast warning: `{ratio}` is what the tone reaches, `{min}` what it must. */
|
|
100
|
+
contrastWarning: string;
|
|
101
|
+
/** The small-text warning: `{size}` is the smallest step advised for body text. */
|
|
102
|
+
minSizeWarning: string;
|
|
103
|
+
};
|
|
104
|
+
/** The English words. */
|
|
105
|
+
declare const textStyleLabels: TextStyleLabels;
|
|
106
|
+
/** The French words. */
|
|
107
|
+
declare const textStyleLabelsFr: TextStyleLabels;
|
|
108
|
+
type TextStylePreset = {
|
|
109
|
+
/** A stable key. */
|
|
110
|
+
id: string;
|
|
111
|
+
/** The name on the button, passed translated (« Titre », « Prix »…). */
|
|
112
|
+
label: string;
|
|
113
|
+
/** The style it applies; what it does not say takes the default. Validated when applied. */
|
|
114
|
+
value: Partial<TextStyle>;
|
|
115
|
+
};
|
|
116
|
+
type TextStylePickerProps = {
|
|
117
|
+
/** The text style, controlled. Validated by `parseTextStyle`: anything invalid shows the default. */
|
|
118
|
+
value: TextStyle;
|
|
119
|
+
/** Called with the whole new text style at each change. */
|
|
120
|
+
onChange: (value: TextStyle) => void;
|
|
121
|
+
/** The sample the preview shows, passed translated ("Boulangerie Martin"). */
|
|
122
|
+
previewText: string;
|
|
123
|
+
/** Styles the person can start from, as data. */
|
|
124
|
+
presets?: readonly TextStylePreset[];
|
|
125
|
+
/** The controls to show, in the order of `textStyleFields` (just `["font", "size"]`). A preset then changes only these. */
|
|
126
|
+
fields?: readonly TextStyleField[];
|
|
127
|
+
/** A denser form for a popover from a toolbar. */
|
|
128
|
+
compact?: boolean;
|
|
129
|
+
/** The words, passed translated: `textStyleLabels` (EN) or `textStyleLabelsFr`, or the host's own. */
|
|
130
|
+
labels?: Partial<TextStyleLabels>;
|
|
131
|
+
/** The colour the text will sit on, for the preview and the contrast check (a CSS colour). By default the page's own surface. */
|
|
132
|
+
surface?: string;
|
|
133
|
+
/**
|
|
134
|
+
* What the text is for: `body` (reading text, which `minBodySize` guards) or `title` (a heading, which may be small).
|
|
135
|
+
* @default "body"
|
|
136
|
+
*/
|
|
137
|
+
role?: "body" | "title";
|
|
138
|
+
/**
|
|
139
|
+
* The smallest step advised for body text: under it, a warning shows — the size is never forced.
|
|
140
|
+
* @default "sm"
|
|
141
|
+
*/
|
|
142
|
+
minBodySize?: SizeStep;
|
|
143
|
+
/** Merged last on the root. */
|
|
144
|
+
className?: string;
|
|
145
|
+
};
|
|
146
|
+
/** The control of a text style: font, size, weight, spacing, alignment, case, italic and tone, with a live preview. */
|
|
147
|
+
declare function TextStylePicker({ value, onChange, previewText, presets, fields, compact, labels: given, surface, role, minBodySize, className, }: TextStylePickerProps): React.JSX.Element;
|
|
148
|
+
|
|
149
|
+
export { type Align, type Case, type FontId, type Leading, type SizeStep, StyledText, type StyledTextProps, type TextStyle, type TextStyleField, type TextStyleLabels, TextStylePicker, type TextStylePickerProps, type TextStylePreset, type TextWeight, type ToneId, type Tracking, aligns, bodyTextRatio, cases, contrastRatio, defaultTextStyle, fontIds, fontWeights, isLargeText, largeTextRatio, leadings, parseColor, parseTextStyle, requiredContrast, sizeSteps, textStyleFields, textStyleLabels, textStyleLabelsFr, textStyleProps, textWeights, toneIds, trackings };
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/cx.ts
|
|
4
|
+
function cx(...parts) {
|
|
5
|
+
return parts.filter(Boolean).join(" ");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// src/text-style/text-style.ts
|
|
9
|
+
import * as z from "zod/mini";
|
|
10
|
+
var fontIds = ["display", "text", "mono", "cahier"];
|
|
11
|
+
var sizeSteps = ["xs", "sm", "md", "lg", "xl", "2xl", "3xl", "4xl"];
|
|
12
|
+
var textWeights = [400, 500, 600, 700];
|
|
13
|
+
var leadings = ["tight", "normal", "relaxed"];
|
|
14
|
+
var trackings = ["tight", "normal", "wide"];
|
|
15
|
+
var aligns = ["start", "center", "end"];
|
|
16
|
+
var cases = ["none", "upper", "small-caps"];
|
|
17
|
+
var toneIds = ["primary", "secondary", "muted", "accent"];
|
|
18
|
+
var defaultTextStyle = Object.freeze({
|
|
19
|
+
font: "text",
|
|
20
|
+
size: "md",
|
|
21
|
+
weight: 400,
|
|
22
|
+
leading: "normal",
|
|
23
|
+
tracking: "normal",
|
|
24
|
+
align: "start",
|
|
25
|
+
case: "none",
|
|
26
|
+
italic: false,
|
|
27
|
+
tone: "primary"
|
|
28
|
+
});
|
|
29
|
+
var fontWeights = {
|
|
30
|
+
display: [400],
|
|
31
|
+
text: [400, 600, 700],
|
|
32
|
+
mono: [400, 700],
|
|
33
|
+
cahier: [500]
|
|
34
|
+
};
|
|
35
|
+
var schema = z.object({
|
|
36
|
+
font: z.enum(fontIds),
|
|
37
|
+
size: z.enum(sizeSteps),
|
|
38
|
+
weight: z.union([z.literal(400), z.literal(500), z.literal(600), z.literal(700)]),
|
|
39
|
+
leading: z.enum(leadings),
|
|
40
|
+
tracking: z.enum(trackings),
|
|
41
|
+
align: z.enum(aligns),
|
|
42
|
+
case: z.enum(cases),
|
|
43
|
+
italic: z.boolean(),
|
|
44
|
+
tone: z.enum(toneIds)
|
|
45
|
+
});
|
|
46
|
+
function parseTextStyle(input) {
|
|
47
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) return { ...defaultTextStyle };
|
|
48
|
+
const own = Object.fromEntries(Object.entries(input).filter(([, v]) => v !== void 0));
|
|
49
|
+
const parsed = schema.safeParse({ ...defaultTextStyle, ...own });
|
|
50
|
+
return parsed.success ? { ...parsed.data } : { ...defaultTextStyle };
|
|
51
|
+
}
|
|
52
|
+
var rem = { xs: "0.75rem", sm: "0.875rem", md: "1rem", lg: "1.125rem", xl: "1.25rem", "2xl": "1.5rem", "3xl": "1.875rem", "4xl": "2.25rem" };
|
|
53
|
+
var lineHeight = { tight: "1.15", normal: "1.4", relaxed: "1.7" };
|
|
54
|
+
var letterSpacing = { tight: "-0.02em", normal: "0em", wide: "0.08em" };
|
|
55
|
+
var textAlign = { start: "start", center: "center", end: "end" };
|
|
56
|
+
var tone = {
|
|
57
|
+
primary: "var(--kz-text-primary)",
|
|
58
|
+
secondary: "var(--kz-text-secondary)",
|
|
59
|
+
muted: "var(--kz-text-muted)",
|
|
60
|
+
accent: "var(--kz-accent-text)"
|
|
61
|
+
};
|
|
62
|
+
var fontVariable = {
|
|
63
|
+
display: "var(--kz-font-display)",
|
|
64
|
+
text: "var(--kz-font-text)",
|
|
65
|
+
mono: "var(--kz-font-mono)",
|
|
66
|
+
cahier: "var(--kz-font-cahier)"
|
|
67
|
+
};
|
|
68
|
+
var sizeVariable = (step) => `var(--kz-text-style-${step}, ${rem[step]})`;
|
|
69
|
+
var toneVariable = (id) => tone[id];
|
|
70
|
+
function textStyleProps(style) {
|
|
71
|
+
const v = parseTextStyle(style);
|
|
72
|
+
return {
|
|
73
|
+
className: "kz-text-style",
|
|
74
|
+
style: {
|
|
75
|
+
fontFamily: fontVariable[v.font],
|
|
76
|
+
fontSize: sizeVariable(v.size),
|
|
77
|
+
fontWeight: v.weight,
|
|
78
|
+
lineHeight: `var(--kz-text-style-leading-${v.leading}, ${lineHeight[v.leading]})`,
|
|
79
|
+
letterSpacing: `var(--kz-text-style-tracking-${v.tracking}, ${letterSpacing[v.tracking]})`,
|
|
80
|
+
textAlign: textAlign[v.align],
|
|
81
|
+
textTransform: v.case === "upper" ? "uppercase" : "none",
|
|
82
|
+
fontVariantCaps: v.case === "small-caps" ? "small-caps" : "normal",
|
|
83
|
+
fontStyle: v.italic ? "italic" : "normal",
|
|
84
|
+
color: toneVariable(v.tone)
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function parseColor(text) {
|
|
89
|
+
const value = text.trim().toLowerCase();
|
|
90
|
+
const hex = /^#([0-9a-f]{3,8})$/.exec(value)?.[1];
|
|
91
|
+
if (hex && [3, 4, 6, 8].includes(hex.length)) {
|
|
92
|
+
const full = hex.length <= 4 ? [...hex].map((c) => c + c).join("") : hex;
|
|
93
|
+
return [0, 2, 4].map((i) => Number.parseInt(full.slice(i, i + 2), 16));
|
|
94
|
+
}
|
|
95
|
+
const rgb = /^rgba?\(([^)]+)\)$/.exec(value)?.[1];
|
|
96
|
+
if (rgb) {
|
|
97
|
+
const parts = rgb.split(/[\s,/]+/).filter(Boolean).slice(0, 3).map((p) => p.endsWith("%") ? Number.parseFloat(p) / 100 * 255 : Number.parseFloat(p));
|
|
98
|
+
return parts.length === 3 && parts.every(Number.isFinite) ? parts : null;
|
|
99
|
+
}
|
|
100
|
+
const srgb = /^color\(srgb\s+([^)]+)\)$/.exec(value)?.[1];
|
|
101
|
+
if (srgb) {
|
|
102
|
+
const parts = srgb.split(/[\s/]+/).filter(Boolean).slice(0, 3).map((p) => Number.parseFloat(p) * 255);
|
|
103
|
+
return parts.length === 3 && parts.every(Number.isFinite) ? parts : null;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
var channel = (c) => {
|
|
108
|
+
const s = Math.min(Math.max(c, 0), 255) / 255;
|
|
109
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
110
|
+
};
|
|
111
|
+
var luminance = ([r, g, b]) => 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
|
112
|
+
function contrastRatio(foreground, background) {
|
|
113
|
+
const a = luminance(foreground);
|
|
114
|
+
const b = luminance(background);
|
|
115
|
+
return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);
|
|
116
|
+
}
|
|
117
|
+
var largeTextRatio = 3;
|
|
118
|
+
var bodyTextRatio = 4.5;
|
|
119
|
+
function isLargeText(style) {
|
|
120
|
+
const at = sizeSteps.indexOf(style.size);
|
|
121
|
+
return at >= sizeSteps.indexOf("2xl") || at >= sizeSteps.indexOf("xl") && style.weight >= 700;
|
|
122
|
+
}
|
|
123
|
+
var requiredContrast = (style) => isLargeText(style) ? largeTextRatio : bodyTextRatio;
|
|
124
|
+
var isUnderMinimumSize = (size, minimum) => sizeSteps.indexOf(size) < sizeSteps.indexOf(minimum);
|
|
125
|
+
|
|
126
|
+
// src/text-style/styled-text.tsx
|
|
127
|
+
import { jsx } from "react/jsx-runtime";
|
|
128
|
+
function StyledText({ as, value, className, children, ...rest }) {
|
|
129
|
+
const Tag = as ?? "p";
|
|
130
|
+
const props = textStyleProps(value);
|
|
131
|
+
return /* @__PURE__ */ jsx(Tag, { ...rest, className: cx(props.className, className), style: props.style, children });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/text-style/text-style-picker.tsx
|
|
135
|
+
import { Button } from "@krizaka/ui/button";
|
|
136
|
+
import { RadioGroup } from "@krizaka/ui/radio-group";
|
|
137
|
+
import { Slider } from "@krizaka/ui/slider";
|
|
138
|
+
import { Switch } from "@krizaka/ui/switch";
|
|
139
|
+
import { RadioGroup as RadioGroupPrimitive } from "radix-ui";
|
|
140
|
+
import * as React from "react";
|
|
141
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
142
|
+
var textStyleFields = ["font", "size", "weight", "leading", "tracking", "align", "case", "italic", "tone"];
|
|
143
|
+
var textStyleLabels = {
|
|
144
|
+
picker: "Text style",
|
|
145
|
+
font: "Font",
|
|
146
|
+
size: "Size",
|
|
147
|
+
weight: "Weight",
|
|
148
|
+
leading: "Line height",
|
|
149
|
+
tracking: "Letter spacing",
|
|
150
|
+
align: "Alignment",
|
|
151
|
+
case: "Case",
|
|
152
|
+
italic: "Italic",
|
|
153
|
+
tone: "Colour",
|
|
154
|
+
presets: "Presets",
|
|
155
|
+
preview: "Preview",
|
|
156
|
+
fonts: { display: "Display", text: "Text", mono: "Typewriter", cahier: "Pen" },
|
|
157
|
+
weights: { 400: "Regular", 500: "Medium", 600: "Semibold", 700: "Bold" },
|
|
158
|
+
leadings: { tight: "Tight", normal: "Normal", relaxed: "Relaxed" },
|
|
159
|
+
trackings: { tight: "Tight", normal: "Normal", wide: "Wide" },
|
|
160
|
+
aligns: { start: "Start", center: "Centre", end: "End" },
|
|
161
|
+
cases: { none: "Aa", upper: "AA", "small-caps": "Small caps" },
|
|
162
|
+
tones: { primary: "Primary", secondary: "Secondary", muted: "Muted", accent: "Accent" },
|
|
163
|
+
contrastWarning: "This colour reaches only {ratio}:1 on the background; {min}:1 is needed to read it comfortably.",
|
|
164
|
+
minSizeWarning: "Body text under \u201C{size}\u201D is hard to read."
|
|
165
|
+
};
|
|
166
|
+
var textStyleLabelsFr = {
|
|
167
|
+
picker: "Style du texte",
|
|
168
|
+
font: "Police",
|
|
169
|
+
size: "Taille",
|
|
170
|
+
weight: "Graisse",
|
|
171
|
+
leading: "Interligne",
|
|
172
|
+
tracking: "Espacement des lettres",
|
|
173
|
+
align: "Alignement",
|
|
174
|
+
case: "Casse",
|
|
175
|
+
italic: "Italique",
|
|
176
|
+
tone: "Couleur",
|
|
177
|
+
presets: "Pr\xE9r\xE9glages",
|
|
178
|
+
preview: "Aper\xE7u",
|
|
179
|
+
fonts: { display: "Titre", text: "Texte", mono: "Machine \xE0 \xE9crire", cahier: "Stylo" },
|
|
180
|
+
weights: { 400: "Normal", 500: "Moyen", 600: "Demi-gras", 700: "Gras" },
|
|
181
|
+
leadings: { tight: "Serr\xE9", normal: "Normal", relaxed: "A\xE9r\xE9" },
|
|
182
|
+
trackings: { tight: "Serr\xE9", normal: "Normal", wide: "Large" },
|
|
183
|
+
aligns: { start: "D\xE9but", center: "Centr\xE9", end: "Fin" },
|
|
184
|
+
cases: { none: "Aa", upper: "AA", "small-caps": "Petites capitales" },
|
|
185
|
+
tones: { primary: "Principale", secondary: "Secondaire", muted: "Discr\xE8te", accent: "Accent" },
|
|
186
|
+
contrastWarning: "Cette couleur n\u2019atteint que {ratio}:1 sur le fond ; {min}:1 est n\xE9cessaire pour la lire confortablement.",
|
|
187
|
+
minSizeWarning: "Un texte courant sous \xAB {size} \xBB se lit mal."
|
|
188
|
+
};
|
|
189
|
+
function parseColorWithAlpha(text) {
|
|
190
|
+
const value = text.trim().toLowerCase();
|
|
191
|
+
if (value === "transparent") return [0, 0, 0, 0];
|
|
192
|
+
const color = parseColor(value);
|
|
193
|
+
if (!color) return null;
|
|
194
|
+
const hex = /^#([0-9a-f]{4}|[0-9a-f]{8})$/.exec(value)?.[1];
|
|
195
|
+
if (hex) {
|
|
196
|
+
const alpha2 = hex.length === 4 ? Number.parseInt(hex[3] + hex[3], 16) : Number.parseInt(hex.slice(6, 8), 16);
|
|
197
|
+
return [...color, alpha2 / 255];
|
|
198
|
+
}
|
|
199
|
+
const components = /^rgba?\(([^)]+)\)$/.exec(value)?.[1] ?? /^color\(srgb\s+([^)]+)\)$/.exec(value)?.[1];
|
|
200
|
+
if (!components) return [...color, 1];
|
|
201
|
+
const [channels, slashAlpha] = components.split("/");
|
|
202
|
+
const parts = channels.trim().split(/[\s,]+/).filter(Boolean);
|
|
203
|
+
const alpha = slashAlpha?.trim() ?? (parts.length === 4 ? parts[3] : void 0);
|
|
204
|
+
if (alpha === void 0) return [...color, 1];
|
|
205
|
+
const parsed = alpha.endsWith("%") ? Number.parseFloat(alpha) / 100 : Number.parseFloat(alpha);
|
|
206
|
+
return Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? [...color, parsed] : null;
|
|
207
|
+
}
|
|
208
|
+
var over = (foreground, background) => {
|
|
209
|
+
const alpha = foreground[3] + background[3] * (1 - foreground[3]);
|
|
210
|
+
if (alpha === 0) return [0, 0, 0, 0];
|
|
211
|
+
return [
|
|
212
|
+
(foreground[0] * foreground[3] + background[0] * background[3] * (1 - foreground[3])) / alpha,
|
|
213
|
+
(foreground[1] * foreground[3] + background[1] * background[3] * (1 - foreground[3])) / alpha,
|
|
214
|
+
(foreground[2] * foreground[3] + background[2] * background[3] * (1 - foreground[3])) / alpha,
|
|
215
|
+
alpha
|
|
216
|
+
];
|
|
217
|
+
};
|
|
218
|
+
function surfaceOf(element) {
|
|
219
|
+
const layers = [];
|
|
220
|
+
for (let node = element; node; node = node.parentElement) {
|
|
221
|
+
layers.push(node);
|
|
222
|
+
if (node === document.documentElement) break;
|
|
223
|
+
}
|
|
224
|
+
let background = [0, 0, 0, 0];
|
|
225
|
+
for (const node of layers.reverse()) {
|
|
226
|
+
const color = window.getComputedStyle(node).backgroundColor;
|
|
227
|
+
const layer = color ? parseColorWithAlpha(color) : [0, 0, 0, 0];
|
|
228
|
+
if (!layer) return null;
|
|
229
|
+
background = over(layer, background);
|
|
230
|
+
}
|
|
231
|
+
return background[3] >= 1 ? [background[0], background[1], background[2]] : null;
|
|
232
|
+
}
|
|
233
|
+
function useContrast(ref, key) {
|
|
234
|
+
const [measurement, setMeasurement] = React.useState(null);
|
|
235
|
+
React.useEffect(() => {
|
|
236
|
+
const element = ref.current;
|
|
237
|
+
if (!element) return;
|
|
238
|
+
const measure = () => {
|
|
239
|
+
const sample = element.querySelector("[data-text-style-sample]") ?? element;
|
|
240
|
+
const computed = window.getComputedStyle(sample);
|
|
241
|
+
const foreground = parseColor(computed.color);
|
|
242
|
+
const background = surfaceOf(sample);
|
|
243
|
+
const fontSize = Number.parseFloat(computed.fontSize);
|
|
244
|
+
const fontWeight = Number.parseFloat(computed.fontWeight);
|
|
245
|
+
const required = fontSize >= 24 || fontSize >= 18.66 && fontWeight >= 700 ? 3 : 4.5;
|
|
246
|
+
setMeasurement(foreground && background && Number.isFinite(fontSize) && Number.isFinite(fontWeight) ? { ratio: contrastRatio(foreground, background), required } : null);
|
|
247
|
+
};
|
|
248
|
+
measure();
|
|
249
|
+
if (typeof MutationObserver === "undefined") return;
|
|
250
|
+
const observer = new MutationObserver(measure);
|
|
251
|
+
for (let node = element; node; node = node.parentElement) {
|
|
252
|
+
observer.observe(node, { attributes: true, attributeFilter: ["class", "style", "data-theme"] });
|
|
253
|
+
if (node === document.documentElement) break;
|
|
254
|
+
}
|
|
255
|
+
return () => observer.disconnect();
|
|
256
|
+
}, [ref, key]);
|
|
257
|
+
return measurement;
|
|
258
|
+
}
|
|
259
|
+
var nearest = (weights, weight) => weights.reduce((best, w) => Math.abs(w - weight) < Math.abs(best - weight) ? w : best, weights[0]);
|
|
260
|
+
function TextStylePicker({
|
|
261
|
+
value,
|
|
262
|
+
onChange,
|
|
263
|
+
previewText,
|
|
264
|
+
presets,
|
|
265
|
+
fields = textStyleFields,
|
|
266
|
+
compact,
|
|
267
|
+
labels: given,
|
|
268
|
+
surface,
|
|
269
|
+
role = "body",
|
|
270
|
+
minBodySize = "sm",
|
|
271
|
+
className
|
|
272
|
+
}) {
|
|
273
|
+
const labels = { ...textStyleLabels, ...given };
|
|
274
|
+
const style = parseTextStyle(value);
|
|
275
|
+
const shown = (field) => fields.includes(field);
|
|
276
|
+
const id = React.useId();
|
|
277
|
+
const previewRef = React.useRef(null);
|
|
278
|
+
const measurement = useContrast(previewRef, `${style.font}|${style.size}|${style.weight}|${style.tone}|${surface ?? ""}`);
|
|
279
|
+
const set = (patch) => onChange(parseTextStyle({ ...style, ...patch }));
|
|
280
|
+
const setFont = (font) => set({ font, weight: nearest(fontWeights[font], style.weight) });
|
|
281
|
+
const applyPreset = (preset) => {
|
|
282
|
+
const next = parseTextStyle(preset.value);
|
|
283
|
+
const patch = Object.fromEntries(textStyleFields.filter(shown).map((f) => [f, next[f]]));
|
|
284
|
+
if (patch.font !== void 0 || patch.weight !== void 0) {
|
|
285
|
+
const font = patch.font ?? style.font;
|
|
286
|
+
const weight = patch.weight ?? style.weight;
|
|
287
|
+
set({ ...patch, weight: nearest(fontWeights[font], weight) });
|
|
288
|
+
} else {
|
|
289
|
+
set(patch);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const warnings = [];
|
|
293
|
+
if (measurement && measurement.ratio < measurement.required) {
|
|
294
|
+
warnings.push(labels.contrastWarning.replace("{ratio}", measurement.ratio.toFixed(1)).replace("{min}", String(measurement.required)));
|
|
295
|
+
}
|
|
296
|
+
if (role === "body" && isUnderMinimumSize(style.size, minBodySize)) warnings.push(labels.minSizeWarning.replace("{size}", minBodySize));
|
|
297
|
+
const group = (field, title, control, className2) => /* @__PURE__ */ jsxs("div", { className: cx("flex min-w-0 flex-col gap-2", className2), "data-field": field, children: [
|
|
298
|
+
/* @__PURE__ */ jsx2("span", { id: `${id}-${field}`, className: "text-xs font-semibold text-fg-secondary", children: title }),
|
|
299
|
+
control
|
|
300
|
+
] }, field);
|
|
301
|
+
const segmented = (field, title, items, current, text, choose, disabled) => group(
|
|
302
|
+
field,
|
|
303
|
+
title,
|
|
304
|
+
/* @__PURE__ */ jsx2(RadioGroup.Root, { variant: "segmented", "aria-labelledby": `${id}-${field}`, value: String(current), onValueChange: (next) => choose(items.find((item) => String(item) === next)), className: "w-full max-w-full", children: items.map((item) => /* @__PURE__ */ jsx2(RadioGroup.Segment, { value: String(item), disabled: disabled?.(item), className: "min-w-0 px-1 text-xs", children: text(item) }, item)) }),
|
|
305
|
+
"sm:col-span-2"
|
|
306
|
+
);
|
|
307
|
+
const controls = {
|
|
308
|
+
font: () => group(
|
|
309
|
+
"font",
|
|
310
|
+
labels.font,
|
|
311
|
+
/* @__PURE__ */ jsx2(RadioGroup.Root, { variant: "cards", "aria-labelledby": `${id}-font`, value: style.font, onValueChange: (next) => setFont(next), className: "grid-cols-[repeat(auto-fit,minmax(9rem,1fr))]", children: fontIds.map((font) => /* @__PURE__ */ jsx2(RadioGroup.Card, { value: font, style: { fontFamily: fontVariable[font] }, children: labels.fonts[font] }, font)) })
|
|
312
|
+
),
|
|
313
|
+
size: () => group(
|
|
314
|
+
"size",
|
|
315
|
+
labels.size,
|
|
316
|
+
/* @__PURE__ */ jsx2(
|
|
317
|
+
Slider,
|
|
318
|
+
{
|
|
319
|
+
label: labels.size,
|
|
320
|
+
min: 0,
|
|
321
|
+
max: sizeSteps.length - 1,
|
|
322
|
+
step: 1,
|
|
323
|
+
ticks: true,
|
|
324
|
+
value: sizeSteps.indexOf(style.size),
|
|
325
|
+
onValueChange: (index) => set({ size: sizeSteps[index] }),
|
|
326
|
+
formatValue: (index) => sizeSteps[index],
|
|
327
|
+
floatingValue: true
|
|
328
|
+
}
|
|
329
|
+
)
|
|
330
|
+
),
|
|
331
|
+
weight: () => segmented("weight", labels.weight, textWeights, style.weight, (w) => labels.weights[w], (weight) => set({ weight }), (w) => !fontWeights[style.font].includes(w)),
|
|
332
|
+
leading: () => segmented("leading", labels.leading, leadings, style.leading, (l) => labels.leadings[l], (leading) => set({ leading })),
|
|
333
|
+
tracking: () => segmented("tracking", labels.tracking, trackings, style.tracking, (t) => labels.trackings[t], (tracking) => set({ tracking })),
|
|
334
|
+
align: () => segmented("align", labels.align, aligns, style.align, (a) => labels.aligns[a], (align) => set({ align })),
|
|
335
|
+
case: () => segmented("case", labels.case, cases, style.case, (c) => labels.cases[c], (next) => set({ case: next })),
|
|
336
|
+
italic: () => /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center justify-between gap-3", "data-field": "italic", children: [
|
|
337
|
+
/* @__PURE__ */ jsx2("span", { id: `${id}-italic`, className: "text-xs font-semibold text-fg-secondary", children: labels.italic }),
|
|
338
|
+
/* @__PURE__ */ jsx2(Switch, { "aria-labelledby": `${id}-italic`, checked: style.italic, onCheckedChange: (italic) => set({ italic }) })
|
|
339
|
+
] }, "italic"),
|
|
340
|
+
tone: () => group(
|
|
341
|
+
"tone",
|
|
342
|
+
labels.tone,
|
|
343
|
+
/* @__PURE__ */ jsx2(RadioGroupPrimitive.Root, { "aria-labelledby": `${id}-tone`, value: style.tone, onValueChange: (next) => set({ tone: next }), orientation: "horizontal", className: "flex flex-wrap gap-3", children: toneIds.map((tone2) => /* @__PURE__ */ jsx2(
|
|
344
|
+
RadioGroupPrimitive.Item,
|
|
345
|
+
{
|
|
346
|
+
value: tone2,
|
|
347
|
+
"aria-label": labels.tones[tone2],
|
|
348
|
+
title: labels.tones[tone2],
|
|
349
|
+
style: { backgroundColor: toneVariable(tone2) },
|
|
350
|
+
className: "h-8 w-8 rounded-full border border-border-strong focus-visible:outline-hidden focus-visible:focus-ring data-[state=checked]:ring-2 data-[state=checked]:ring-accent data-[state=checked]:ring-offset-2 data-[state=checked]:ring-offset-surface-0"
|
|
351
|
+
},
|
|
352
|
+
tone2
|
|
353
|
+
)) })
|
|
354
|
+
)
|
|
355
|
+
};
|
|
356
|
+
return /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": labels.picker, "data-compact": compact ? "" : void 0, className: cx("flex min-w-0 flex-col", compact ? "gap-3" : "gap-5", className), children: [
|
|
357
|
+
/* @__PURE__ */ jsx2(
|
|
358
|
+
"div",
|
|
359
|
+
{
|
|
360
|
+
ref: previewRef,
|
|
361
|
+
"data-preview": "",
|
|
362
|
+
"aria-label": labels.preview,
|
|
363
|
+
role: "group",
|
|
364
|
+
style: surface ? { backgroundColor: surface } : void 0,
|
|
365
|
+
className: cx("min-w-0 overflow-hidden rounded-xl border border-border-default", surface ? "" : "bg-surface-1", compact ? "p-3" : "p-5"),
|
|
366
|
+
children: /* @__PURE__ */ jsx2(StyledText, { as: "p", value: style, className: "break-words", "data-text-style-sample": "", children: previewText })
|
|
367
|
+
}
|
|
368
|
+
),
|
|
369
|
+
/* @__PURE__ */ jsx2("div", { role: "status", "aria-live": "polite", "data-warnings": "", children: warnings.map((warning) => /* @__PURE__ */ jsx2("p", { className: "text-xs font-medium text-fg-warning", children: warning }, warning)) }),
|
|
370
|
+
presets?.length ? /* @__PURE__ */ jsx2("div", { role: "group", "aria-label": labels.presets, className: "flex flex-wrap gap-2", children: presets.map((preset) => /* @__PURE__ */ jsx2(Button, { type: "button", size: "sm", variant: "outline", onClick: () => applyPreset(preset), children: preset.label }, preset.id)) }) : null,
|
|
371
|
+
/* @__PURE__ */ jsx2("div", { className: cx("grid grid-cols-1 gap-x-6", compact ? "gap-y-3" : "gap-y-5 sm:grid-cols-2"), children: textStyleFields.filter(shown).map((field) => /* @__PURE__ */ jsx2(React.Fragment, { children: controls[field]() }, field)) })
|
|
372
|
+
] });
|
|
373
|
+
}
|
|
374
|
+
export {
|
|
375
|
+
StyledText,
|
|
376
|
+
TextStylePicker,
|
|
377
|
+
aligns,
|
|
378
|
+
bodyTextRatio,
|
|
379
|
+
cases,
|
|
380
|
+
contrastRatio,
|
|
381
|
+
defaultTextStyle,
|
|
382
|
+
fontIds,
|
|
383
|
+
fontWeights,
|
|
384
|
+
isLargeText,
|
|
385
|
+
largeTextRatio,
|
|
386
|
+
leadings,
|
|
387
|
+
parseColor,
|
|
388
|
+
parseTextStyle,
|
|
389
|
+
requiredContrast,
|
|
390
|
+
sizeSteps,
|
|
391
|
+
textStyleFields,
|
|
392
|
+
textStyleLabels,
|
|
393
|
+
textStyleLabelsFr,
|
|
394
|
+
textStyleProps,
|
|
395
|
+
textWeights,
|
|
396
|
+
toneIds,
|
|
397
|
+
trackings
|
|
398
|
+
};
|
|
399
|
+
//# sourceMappingURL=text-style.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cx.ts","../src/text-style/text-style.ts","../src/text-style/styled-text.tsx","../src/text-style/text-style-picker.tsx"],"sourcesContent":["/** Joins class names, skipping the falsy ones. */\nexport function cx(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(\" \");\n}\n","// Server-safe (no hook, no React): the value of a text style, its validation and its CSS. A shop owner's choice is stored\n// by the app, so the value is plain data and its CSS is built from fixed tables of CSS variables — what comes from a\n// database never reaches a style sheet as text. The fonts are the curated roles of @krizaka/fonts (the product theme\n// says which family a role is), the sizes are the type scale's steps, the tones are the text roles and the brand hue.\nimport type { CSSProperties } from \"react\";\nimport * as z from \"zod/mini\";\n\n/** The curated families of `@krizaka/fonts`, by role: `display` (the headlines), `text` (reading), `mono` (the typewriter), `cahier` (the pen). */\nexport const fontIds = [\"display\", \"text\", \"mono\", \"cahier\"] as const;\nexport type FontId = (typeof fontIds)[number];\n\n/** The steps of the type scale, smallest to largest. Never pixels. */\nexport const sizeSteps = [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\", \"3xl\", \"4xl\"] as const;\nexport type SizeStep = (typeof sizeSteps)[number];\n\nexport const textWeights = [400, 500, 600, 700] as const;\nexport type TextWeight = (typeof textWeights)[number];\n\nexport const leadings = [\"tight\", \"normal\", \"relaxed\"] as const;\nexport type Leading = (typeof leadings)[number];\n\nexport const trackings = [\"tight\", \"normal\", \"wide\"] as const;\nexport type Tracking = (typeof trackings)[number];\n\nexport const aligns = [\"start\", \"center\", \"end\"] as const;\nexport type Align = (typeof aligns)[number];\n\nexport const cases = [\"none\", \"upper\", \"small-caps\"] as const;\nexport type Case = (typeof cases)[number];\n\n/** The text roles of the tokens, and the brand hue. */\nexport const toneIds = [\"primary\", \"secondary\", \"muted\", \"accent\"] as const;\nexport type ToneId = (typeof toneIds)[number];\n\n/** A text style: what a shop owner chooses, stored as it is. */\nexport type TextStyle = {\n font: FontId;\n size: SizeStep;\n weight: TextWeight;\n leading: Leading;\n tracking: Tracking;\n align: Align;\n case: Case;\n italic: boolean;\n tone: ToneId;\n};\n\n/** What `parseTextStyle` answers for anything that is not a text style. */\nexport const defaultTextStyle: Readonly<TextStyle> = Object.freeze({\n font: \"text\",\n size: \"md\",\n weight: 400,\n leading: \"normal\",\n tracking: \"normal\",\n align: \"start\",\n case: \"none\",\n italic: false,\n tone: \"primary\",\n});\n\n/** The weights each face is cut in: a picker offers these, never a synthesised bold. */\nexport const fontWeights: Readonly<Record<FontId, readonly TextWeight[]>> = {\n display: [400],\n text: [400, 600, 700],\n mono: [400, 700],\n cahier: [500],\n};\n\nconst schema = z.object({\n font: z.enum(fontIds),\n size: z.enum(sizeSteps),\n weight: z.union([z.literal(400), z.literal(500), z.literal(600), z.literal(700)]),\n leading: z.enum(leadings),\n tracking: z.enum(trackings),\n align: z.enum(aligns),\n case: z.enum(cases),\n italic: z.boolean(),\n tone: z.enum(toneIds),\n});\n\n/**\n * Validates what was stored (or sent): the text style, or `defaultTextStyle` for anything invalid — a free font name, a\n * hex colour, a pixel size, CSS text, a value that is not an object. Keys a text style does not have are dropped; keys it\n * lacks (a value stored before one was added) take their default. Usable on a server.\n */\nexport function parseTextStyle(input: unknown): TextStyle {\n if (typeof input !== \"object\" || input === null || Array.isArray(input)) return { ...defaultTextStyle };\n const own = Object.fromEntries(Object.entries(input).filter(([, v]) => v !== undefined));\n const parsed = schema.safeParse({ ...defaultTextStyle, ...own });\n return parsed.success ? { ...parsed.data } : { ...defaultTextStyle };\n}\n\nconst rem: Record<SizeStep, string> = { xs: \"0.75rem\", sm: \"0.875rem\", md: \"1rem\", lg: \"1.125rem\", xl: \"1.25rem\", \"2xl\": \"1.5rem\", \"3xl\": \"1.875rem\", \"4xl\": \"2.25rem\" };\nconst lineHeight: Record<Leading, string> = { tight: \"1.15\", normal: \"1.4\", relaxed: \"1.7\" };\nconst letterSpacing: Record<Tracking, string> = { tight: \"-0.02em\", normal: \"0em\", wide: \"0.08em\" };\nconst textAlign: Record<Align, CSSProperties[\"textAlign\"]> = { start: \"start\", center: \"center\", end: \"end\" };\nconst tone: Record<ToneId, string> = {\n primary: \"var(--kz-text-primary)\",\n secondary: \"var(--kz-text-secondary)\",\n muted: \"var(--kz-text-muted)\",\n accent: \"var(--kz-accent-text)\",\n};\n\n/** The CSS variable of a text role: the product theme's face for `display`, the preset's for the others. */\nexport const fontVariable: Readonly<Record<FontId, string>> = {\n display: \"var(--kz-font-display)\",\n text: \"var(--kz-font-text)\",\n mono: \"var(--kz-font-mono)\",\n cahier: \"var(--kz-font-cahier)\",\n};\n\n/** A step's size as a CSS value: `var(--kz-text-style-<step>, <rem>)` — a product may re-cut the scale with the variable. */\nexport const sizeVariable = (step: SizeStep): string => `var(--kz-text-style-${step}, ${rem[step]})`;\n\n/** A tone's colour as a CSS value: a role of the tokens. */\nexport const toneVariable = (id: ToneId): string => tone[id];\n\n/** The `className` and `style` that draw a text style. Built from fixed tables of CSS variables only. */\nexport function textStyleProps(style: unknown): { className: string; style: CSSProperties } {\n const v = parseTextStyle(style);\n return {\n className: \"kz-text-style\",\n style: {\n fontFamily: fontVariable[v.font],\n fontSize: sizeVariable(v.size),\n fontWeight: v.weight,\n lineHeight: `var(--kz-text-style-leading-${v.leading}, ${lineHeight[v.leading]})`,\n letterSpacing: `var(--kz-text-style-tracking-${v.tracking}, ${letterSpacing[v.tracking]})`,\n textAlign: textAlign[v.align],\n textTransform: v.case === \"upper\" ? \"uppercase\" : \"none\",\n fontVariantCaps: v.case === \"small-caps\" ? \"small-caps\" : \"normal\",\n fontStyle: v.italic ? \"italic\" : \"normal\",\n color: toneVariable(v.tone),\n },\n };\n}\n\n/** A colour read from the page, as `[r, g, b]` in 0–255, or `null` when it cannot be read (the check then says nothing). */\nexport function parseColor(text: string): [number, number, number] | null {\n const value = text.trim().toLowerCase();\n const hex = /^#([0-9a-f]{3,8})$/.exec(value)?.[1];\n if (hex && [3, 4, 6, 8].includes(hex.length)) {\n const full = hex.length <= 4 ? [...hex].map((c) => c + c).join(\"\") : hex;\n return [0, 2, 4].map((i) => Number.parseInt(full.slice(i, i + 2), 16)) as [number, number, number];\n }\n const rgb = /^rgba?\\(([^)]+)\\)$/.exec(value)?.[1];\n if (rgb) {\n const parts = rgb.split(/[\\s,/]+/).filter(Boolean).slice(0, 3).map((p) => (p.endsWith(\"%\") ? (Number.parseFloat(p) / 100) * 255 : Number.parseFloat(p)));\n return parts.length === 3 && parts.every(Number.isFinite) ? (parts as [number, number, number]) : null;\n }\n const srgb = /^color\\(srgb\\s+([^)]+)\\)$/.exec(value)?.[1];\n if (srgb) {\n const parts = srgb.split(/[\\s/]+/).filter(Boolean).slice(0, 3).map((p) => Number.parseFloat(p) * 255);\n return parts.length === 3 && parts.every(Number.isFinite) ? (parts as [number, number, number]) : null;\n }\n return null;\n}\n\nconst channel = (c: number) => {\n const s = Math.min(Math.max(c, 0), 255) / 255;\n return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;\n};\nconst luminance = ([r, g, b]: [number, number, number]) => 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);\n\n/** The WCAG contrast ratio of two colours (1 – 21). */\nexport function contrastRatio(foreground: [number, number, number], background: [number, number, number]): number {\n const a = luminance(foreground);\n const b = luminance(background);\n return (Math.max(a, b) + 0.05) / (Math.min(a, b) + 0.05);\n}\n\n/** The ratio WCAG asks of large text. */\nexport const largeTextRatio = 3;\n/** The ratio WCAG asks of body text. */\nexport const bodyTextRatio = 4.5;\n\n/** Large text for WCAG: 24 px and over, or bold from 18.66 px (`xl`, 20 px, in bold). */\nexport function isLargeText(style: Pick<TextStyle, \"size\" | \"weight\">): boolean {\n const at = sizeSteps.indexOf(style.size);\n return at >= sizeSteps.indexOf(\"2xl\") || (at >= sizeSteps.indexOf(\"xl\") && style.weight >= 700);\n}\n\n/** The ratio a style's text must reach on its surface. */\nexport const requiredContrast = (style: Pick<TextStyle, \"size\" | \"weight\">): number => (isLargeText(style) ? largeTextRatio : bodyTextRatio);\n\n/** True when body text in this step is under the minimum: a warning to show, never a size to force. */\nexport const isUnderMinimumSize = (size: SizeStep, minimum: SizeStep): boolean => sizeSteps.indexOf(size) < sizeSteps.indexOf(minimum);\n","// Server-safe: a text drawn with a stored text style. Whatever is passed as `value` goes through `parseTextStyle`.\nimport * as React from \"react\";\n\nimport { cx } from \"../cx\";\nimport { textStyleProps } from \"./text-style\";\n\nexport type StyledTextProps<T extends React.ElementType = \"p\"> = {\n /** The element drawn: `h1`…`h6`, `p`, `span`, `div`. */\n as?: T;\n /** The stored text style — validated here: anything invalid draws the default. */\n value?: unknown;\n /** Merged after the style's own class. */\n className?: string;\n children?: React.ReactNode;\n} & Omit<React.ComponentPropsWithoutRef<T>, \"as\" | \"value\" | \"className\" | \"style\" | \"children\">;\n\n/** A text role drawn from a `TextStyle`: its font, size, weight, spacing, alignment, case and tone, all from CSS variables. */\nexport function StyledText<T extends React.ElementType = \"p\">({ as, value, className, children, ...rest }: StyledTextProps<T>) {\n const Tag: React.ElementType = as ?? \"p\";\n const props = textStyleProps(value);\n return (\n <Tag {...rest} className={cx(props.className, className)} style={props.style}>\n {children}\n </Tag>\n );\n}\n","// Client (state, an observer). A control for the look of one text role — font, size, weight, spacing, alignment, case,\n// italic, tone — over the primitives: the font as a radio group whose options are drawn in their own face, the size as\n// a step slider, the choices as segmented radio groups, italic as a switch, the tone as swatches. It is controlled, shows\n// a live preview, and warns (never clamps) when the tone lacks contrast on the surface or body text is too small.\nimport { Button } from \"@krizaka/ui/button\";\nimport { RadioGroup } from \"@krizaka/ui/radio-group\";\nimport { Slider } from \"@krizaka/ui/slider\";\nimport { Switch } from \"@krizaka/ui/switch\";\nimport { RadioGroup as RadioGroupPrimitive } from \"radix-ui\";\nimport * as React from \"react\";\n\nimport { cx } from \"../cx\";\nimport { StyledText } from \"./styled-text\";\nimport {\n type Align,\n aligns,\n type Case,\n cases,\n contrastRatio,\n type FontId,\n fontIds,\n fontVariable,\n fontWeights,\n isUnderMinimumSize,\n type Leading,\n leadings,\n parseColor,\n parseTextStyle,\n type SizeStep,\n sizeSteps,\n type TextStyle,\n type TextWeight,\n textWeights,\n type ToneId,\n toneIds,\n toneVariable,\n type Tracking,\n trackings,\n} from \"./text-style\";\n\n/** The controls of the picker, in the order they are drawn. */\nexport const textStyleFields = [\"font\", \"size\", \"weight\", \"leading\", \"tracking\", \"align\", \"case\", \"italic\", \"tone\"] as const;\nexport type TextStyleField = (typeof textStyleFields)[number];\n\nexport type TextStyleLabels = {\n /** The name of the whole control. */\n picker: string;\n font: string;\n size: string;\n weight: string;\n leading: string;\n tracking: string;\n align: string;\n case: string;\n italic: string;\n tone: string;\n presets: string;\n preview: string;\n fonts: Record<FontId, string>;\n weights: Record<TextWeight, string>;\n leadings: Record<Leading, string>;\n trackings: Record<Tracking, string>;\n aligns: Record<Align, string>;\n cases: Record<Case, string>;\n tones: Record<ToneId, string>;\n /** The contrast warning: `{ratio}` is what the tone reaches, `{min}` what it must. */\n contrastWarning: string;\n /** The small-text warning: `{size}` is the smallest step advised for body text. */\n minSizeWarning: string;\n};\n\n/** The English words. */\nexport const textStyleLabels: TextStyleLabels = {\n picker: \"Text style\",\n font: \"Font\",\n size: \"Size\",\n weight: \"Weight\",\n leading: \"Line height\",\n tracking: \"Letter spacing\",\n align: \"Alignment\",\n case: \"Case\",\n italic: \"Italic\",\n tone: \"Colour\",\n presets: \"Presets\",\n preview: \"Preview\",\n fonts: { display: \"Display\", text: \"Text\", mono: \"Typewriter\", cahier: \"Pen\" },\n weights: { 400: \"Regular\", 500: \"Medium\", 600: \"Semibold\", 700: \"Bold\" },\n leadings: { tight: \"Tight\", normal: \"Normal\", relaxed: \"Relaxed\" },\n trackings: { tight: \"Tight\", normal: \"Normal\", wide: \"Wide\" },\n aligns: { start: \"Start\", center: \"Centre\", end: \"End\" },\n cases: { none: \"Aa\", upper: \"AA\", \"small-caps\": \"Small caps\" },\n tones: { primary: \"Primary\", secondary: \"Secondary\", muted: \"Muted\", accent: \"Accent\" },\n contrastWarning: \"This colour reaches only {ratio}:1 on the background; {min}:1 is needed to read it comfortably.\",\n minSizeWarning: \"Body text under “{size}” is hard to read.\",\n};\n\n/** The French words. */\nexport const textStyleLabelsFr: TextStyleLabels = {\n picker: \"Style du texte\",\n font: \"Police\",\n size: \"Taille\",\n weight: \"Graisse\",\n leading: \"Interligne\",\n tracking: \"Espacement des lettres\",\n align: \"Alignement\",\n case: \"Casse\",\n italic: \"Italique\",\n tone: \"Couleur\",\n presets: \"Préréglages\",\n preview: \"Aperçu\",\n fonts: { display: \"Titre\", text: \"Texte\", mono: \"Machine à écrire\", cahier: \"Stylo\" },\n weights: { 400: \"Normal\", 500: \"Moyen\", 600: \"Demi-gras\", 700: \"Gras\" },\n leadings: { tight: \"Serré\", normal: \"Normal\", relaxed: \"Aéré\" },\n trackings: { tight: \"Serré\", normal: \"Normal\", wide: \"Large\" },\n aligns: { start: \"Début\", center: \"Centré\", end: \"Fin\" },\n cases: { none: \"Aa\", upper: \"AA\", \"small-caps\": \"Petites capitales\" },\n tones: { primary: \"Principale\", secondary: \"Secondaire\", muted: \"Discrète\", accent: \"Accent\" },\n contrastWarning: \"Cette couleur n’atteint que {ratio}:1 sur le fond ; {min}:1 est nécessaire pour la lire confortablement.\",\n minSizeWarning: \"Un texte courant sous « {size} » se lit mal.\",\n};\n\nexport type TextStylePreset = {\n /** A stable key. */\n id: string;\n /** The name on the button, passed translated (« Titre », « Prix »…). */\n label: string;\n /** The style it applies; what it does not say takes the default. Validated when applied. */\n value: Partial<TextStyle>;\n};\n\nexport type TextStylePickerProps = {\n /** The text style, controlled. Validated by `parseTextStyle`: anything invalid shows the default. */\n value: TextStyle;\n /** Called with the whole new text style at each change. */\n onChange: (value: TextStyle) => void;\n /** The sample the preview shows, passed translated (\"Boulangerie Martin\"). */\n previewText: string;\n /** Styles the person can start from, as data. */\n presets?: readonly TextStylePreset[];\n /** The controls to show, in the order of `textStyleFields` (just `[\"font\", \"size\"]`). A preset then changes only these. */\n fields?: readonly TextStyleField[];\n /** A denser form for a popover from a toolbar. */\n compact?: boolean;\n /** The words, passed translated: `textStyleLabels` (EN) or `textStyleLabelsFr`, or the host's own. */\n labels?: Partial<TextStyleLabels>;\n /** The colour the text will sit on, for the preview and the contrast check (a CSS colour). By default the page's own surface. */\n surface?: string;\n /**\n * What the text is for: `body` (reading text, which `minBodySize` guards) or `title` (a heading, which may be small).\n * @default \"body\"\n */\n role?: \"body\" | \"title\";\n /**\n * The smallest step advised for body text: under it, a warning shows — the size is never forced.\n * @default \"sm\"\n */\n minBodySize?: SizeStep;\n /** Merged last on the root. */\n className?: string;\n};\n\ntype Rgb = [number, number, number];\ntype Rgba = [number, number, number, number];\n\nfunction parseColorWithAlpha(text: string): Rgba | null {\n const value = text.trim().toLowerCase();\n if (value === \"transparent\") return [0, 0, 0, 0];\n const color = parseColor(value);\n if (!color) return null;\n\n const hex = /^#([0-9a-f]{4}|[0-9a-f]{8})$/.exec(value)?.[1];\n if (hex) {\n const alpha = hex.length === 4 ? Number.parseInt(hex[3]! + hex[3]!, 16) : Number.parseInt(hex.slice(6, 8), 16);\n return [...color, alpha / 255];\n }\n\n const components =\n /^rgba?\\(([^)]+)\\)$/.exec(value)?.[1] ??\n /^color\\(srgb\\s+([^)]+)\\)$/.exec(value)?.[1];\n if (!components) return [...color, 1];\n\n const [channels, slashAlpha] = components.split(\"/\");\n const parts = channels!.trim().split(/[\\s,]+/).filter(Boolean);\n const alpha = slashAlpha?.trim() ?? (parts.length === 4 ? parts[3] : undefined);\n if (alpha === undefined) return [...color, 1];\n const parsed = alpha.endsWith(\"%\") ? Number.parseFloat(alpha) / 100 : Number.parseFloat(alpha);\n return Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? [...color, parsed] : null;\n}\n\nconst over = (foreground: Rgba, background: Rgba): Rgba => {\n const alpha = foreground[3] + background[3] * (1 - foreground[3]);\n if (alpha === 0) return [0, 0, 0, 0];\n return [\n (foreground[0] * foreground[3] + background[0] * background[3] * (1 - foreground[3])) / alpha,\n (foreground[1] * foreground[3] + background[1] * background[3] * (1 - foreground[3])) / alpha,\n (foreground[2] * foreground[3] + background[2] * background[3] * (1 - foreground[3])) / alpha,\n alpha,\n ];\n};\n\n/** The composited colour behind an element, or null when a background layer cannot be read. */\nfunction surfaceOf(element: HTMLElement): Rgb | null {\n const layers: HTMLElement[] = [];\n for (let node: HTMLElement | null = element; node; node = node.parentElement) {\n layers.push(node);\n if (node === document.documentElement) break;\n }\n let background: Rgba = [0, 0, 0, 0];\n for (const node of layers.reverse()) {\n const color = window.getComputedStyle(node).backgroundColor;\n const layer: Rgba | null = color ? parseColorWithAlpha(color) : [0, 0, 0, 0];\n if (!layer) return null;\n background = over(layer, background);\n }\n return background[3] >= 1 ? [background[0], background[1], background[2]] : null;\n}\n\n/** What the tone reaches on its surface, measured on the preview as it is drawn (the theme and the product's tokens included). */\nfunction useContrast(ref: React.RefObject<HTMLElement | null>, key: string): { ratio: number; required: number } | null {\n const [measurement, setMeasurement] = React.useState<{ ratio: number; required: number } | null>(null);\n React.useEffect(() => {\n const element = ref.current;\n if (!element) return;\n const measure = () => {\n const sample = element.querySelector<HTMLElement>(\"[data-text-style-sample]\") ?? element;\n const computed = window.getComputedStyle(sample);\n const foreground = parseColor(computed.color);\n const background = surfaceOf(sample);\n const fontSize = Number.parseFloat(computed.fontSize);\n const fontWeight = Number.parseFloat(computed.fontWeight);\n const required = fontSize >= 24 || (fontSize >= 18.66 && fontWeight >= 700) ? 3 : 4.5;\n setMeasurement(foreground && background && Number.isFinite(fontSize) && Number.isFinite(fontWeight) ? { ratio: contrastRatio(foreground, background), required } : null);\n };\n measure();\n if (typeof MutationObserver === \"undefined\") return;\n const observer = new MutationObserver(measure);\n for (let node: HTMLElement | null = element; node; node = node.parentElement) {\n observer.observe(node, { attributes: true, attributeFilter: [\"class\", \"style\", \"data-theme\"] });\n if (node === document.documentElement) break;\n }\n return () => observer.disconnect();\n }, [ref, key]);\n return measurement;\n}\n\nconst nearest = (weights: readonly TextWeight[], weight: TextWeight): TextWeight =>\n weights.reduce((best, w) => (Math.abs(w - weight) < Math.abs(best - weight) ? w : best), weights[0]!);\n\n/** The control of a text style: font, size, weight, spacing, alignment, case, italic and tone, with a live preview. */\nexport function TextStylePicker({\n value,\n onChange,\n previewText,\n presets,\n fields = textStyleFields,\n compact,\n labels: given,\n surface,\n role = \"body\",\n minBodySize = \"sm\",\n className,\n}: TextStylePickerProps) {\n const labels = { ...textStyleLabels, ...given };\n const style = parseTextStyle(value);\n const shown = (field: TextStyleField) => fields.includes(field);\n const id = React.useId();\n const previewRef = React.useRef<HTMLDivElement>(null);\n const measurement = useContrast(previewRef, `${style.font}|${style.size}|${style.weight}|${style.tone}|${surface ?? \"\"}`);\n\n const set = (patch: Partial<TextStyle>) => onChange(parseTextStyle({ ...style, ...patch }));\n const setFont = (font: FontId) => set({ font, weight: nearest(fontWeights[font], style.weight) });\n const applyPreset = (preset: TextStylePreset) => {\n const next = parseTextStyle(preset.value);\n const patch = Object.fromEntries(textStyleFields.filter(shown).map((f) => [f, next[f]])) as Partial<TextStyle>;\n if (patch.font !== undefined || patch.weight !== undefined) {\n const font = patch.font ?? style.font;\n const weight = patch.weight ?? style.weight;\n set({ ...patch, weight: nearest(fontWeights[font], weight) });\n } else {\n set(patch);\n }\n };\n\n const warnings: string[] = [];\n if (measurement && measurement.ratio < measurement.required) {\n warnings.push(labels.contrastWarning.replace(\"{ratio}\", measurement.ratio.toFixed(1)).replace(\"{min}\", String(measurement.required)));\n }\n if (role === \"body\" && isUnderMinimumSize(style.size, minBodySize)) warnings.push(labels.minSizeWarning.replace(\"{size}\", minBodySize));\n\n const group = (field: TextStyleField, title: string, control: React.ReactNode, className?: string) => (\n <div key={field} className={cx(\"flex min-w-0 flex-col gap-2\", className)} data-field={field}>\n <span id={`${id}-${field}`} className=\"text-xs font-semibold text-fg-secondary\">\n {title}\n </span>\n {control}\n </div>\n );\n const segmented = <T extends string | number>(field: TextStyleField, title: string, items: readonly T[], current: T, text: (item: T) => string, choose: (item: T) => void, disabled?: (item: T) => boolean) =>\n group(\n field,\n title,\n <RadioGroup.Root variant=\"segmented\" aria-labelledby={`${id}-${field}`} value={String(current)} onValueChange={(next) => choose(items.find((item) => String(item) === next)!)} className=\"w-full max-w-full\">\n {items.map((item) => (\n <RadioGroup.Segment key={item} value={String(item)} disabled={disabled?.(item)} className=\"min-w-0 px-1 text-xs\">\n {text(item)}\n </RadioGroup.Segment>\n ))}\n </RadioGroup.Root>,\n \"sm:col-span-2\",\n );\n\n const controls: Record<TextStyleField, () => React.ReactNode> = {\n font: () =>\n group(\n \"font\",\n labels.font,\n <RadioGroup.Root variant=\"cards\" aria-labelledby={`${id}-font`} value={style.font} onValueChange={(next) => setFont(next as FontId)} className=\"grid-cols-[repeat(auto-fit,minmax(9rem,1fr))]\">\n {fontIds.map((font) => (\n <RadioGroup.Card key={font} value={font} style={{ fontFamily: fontVariable[font] }}>\n {labels.fonts[font]}\n </RadioGroup.Card>\n ))}\n </RadioGroup.Root>,\n ),\n size: () =>\n group(\n \"size\",\n labels.size,\n <Slider\n label={labels.size}\n min={0}\n max={sizeSteps.length - 1}\n step={1}\n ticks\n value={sizeSteps.indexOf(style.size)}\n onValueChange={(index) => set({ size: sizeSteps[index]! })}\n formatValue={(index) => sizeSteps[index]!}\n floatingValue\n />,\n ),\n weight: () => segmented(\"weight\", labels.weight, textWeights, style.weight, (w) => labels.weights[w], (weight) => set({ weight }), (w) => !fontWeights[style.font].includes(w)),\n leading: () => segmented(\"leading\", labels.leading, leadings, style.leading, (l) => labels.leadings[l], (leading) => set({ leading })),\n tracking: () => segmented(\"tracking\", labels.tracking, trackings, style.tracking, (t) => labels.trackings[t], (tracking) => set({ tracking })),\n align: () => segmented(\"align\", labels.align, aligns, style.align, (a) => labels.aligns[a], (align) => set({ align })),\n case: () => segmented(\"case\", labels.case, cases, style.case, (c) => labels.cases[c], (next) => set({ case: next })),\n italic: () => (\n <div key=\"italic\" className=\"flex min-w-0 items-center justify-between gap-3\" data-field=\"italic\">\n <span id={`${id}-italic`} className=\"text-xs font-semibold text-fg-secondary\">\n {labels.italic}\n </span>\n <Switch aria-labelledby={`${id}-italic`} checked={style.italic} onCheckedChange={(italic) => set({ italic })} />\n </div>\n ),\n tone: () =>\n group(\n \"tone\",\n labels.tone,\n <RadioGroupPrimitive.Root aria-labelledby={`${id}-tone`} value={style.tone} onValueChange={(next) => set({ tone: next as ToneId })} orientation=\"horizontal\" className=\"flex flex-wrap gap-3\">\n {toneIds.map((tone) => (\n <RadioGroupPrimitive.Item\n key={tone}\n value={tone}\n aria-label={labels.tones[tone]}\n title={labels.tones[tone]}\n style={{ backgroundColor: toneVariable(tone) }}\n className=\"h-8 w-8 rounded-full border border-border-strong focus-visible:outline-hidden focus-visible:focus-ring data-[state=checked]:ring-2 data-[state=checked]:ring-accent data-[state=checked]:ring-offset-2 data-[state=checked]:ring-offset-surface-0\"\n />\n ))}\n </RadioGroupPrimitive.Root>,\n ),\n };\n\n return (\n <div role=\"group\" aria-label={labels.picker} data-compact={compact ? \"\" : undefined} className={cx(\"flex min-w-0 flex-col\", compact ? \"gap-3\" : \"gap-5\", className)}>\n <div\n ref={previewRef}\n data-preview=\"\"\n aria-label={labels.preview}\n role=\"group\"\n style={surface ? { backgroundColor: surface } : undefined}\n className={cx(\"min-w-0 overflow-hidden rounded-xl border border-border-default\", surface ? \"\" : \"bg-surface-1\", compact ? \"p-3\" : \"p-5\")}\n >\n <StyledText as=\"p\" value={style} className=\"break-words\" data-text-style-sample=\"\">\n {previewText}\n </StyledText>\n </div>\n <div role=\"status\" aria-live=\"polite\" data-warnings=\"\">\n {warnings.map((warning) => (\n <p key={warning} className=\"text-xs font-medium text-fg-warning\">\n {warning}\n </p>\n ))}\n </div>\n {presets?.length ? (\n <div role=\"group\" aria-label={labels.presets} className=\"flex flex-wrap gap-2\">\n {presets.map((preset) => (\n <Button key={preset.id} type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => applyPreset(preset)}>\n {preset.label}\n </Button>\n ))}\n </div>\n ) : null}\n <div className={cx(\"grid grid-cols-1 gap-x-6\", compact ? \"gap-y-3\" : \"gap-y-5 sm:grid-cols-2\")}>\n {textStyleFields.filter(shown).map((field) => (\n <React.Fragment key={field}>{controls[field]()}</React.Fragment>\n ))}\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","alpha","className","tone"]}
|