@yarcl/react 0.1.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/LICENSE +21 -0
- package/README.md +53 -0
- package/dist/color.d.ts +12 -0
- package/dist/color.js +41 -0
- package/dist/css.d.ts +10 -0
- package/dist/css.js +175 -0
- package/dist/define.d.ts +383 -0
- package/dist/define.js +29 -0
- package/dist/plugin.d.ts +33 -0
- package/dist/plugin.js +80 -0
- package/package.json +66 -0
- package/src/apply.ts +46 -0
- package/src/classes.ts +23 -0
- package/src/color.ts +48 -0
- package/src/components/Accordion.tsx +274 -0
- package/src/components/Alert.tsx +85 -0
- package/src/components/Badge.tsx +79 -0
- package/src/components/Breadcrumb.tsx +239 -0
- package/src/components/Button.tsx +58 -0
- package/src/components/ButtonGroup.tsx +61 -0
- package/src/components/Card.tsx +50 -0
- package/src/components/Checkbox.tsx +49 -0
- package/src/components/Combobox.tsx +522 -0
- package/src/components/CommandPalette.tsx +452 -0
- package/src/components/DatePicker.tsx +432 -0
- package/src/components/Dialog.tsx +39 -0
- package/src/components/Divider.tsx +34 -0
- package/src/components/Drawer.tsx +31 -0
- package/src/components/Field.tsx +61 -0
- package/src/components/Heading.tsx +40 -0
- package/src/components/HoverCard.tsx +108 -0
- package/src/components/IconButton.tsx +59 -0
- package/src/components/Inline.tsx +42 -0
- package/src/components/Input.tsx +28 -0
- package/src/components/Label.tsx +58 -0
- package/src/components/Link.tsx +41 -0
- package/src/components/Menu.tsx +207 -0
- package/src/components/Modal.tsx +164 -0
- package/src/components/NumberInput.tsx +208 -0
- package/src/components/Pagination.tsx +227 -0
- package/src/components/Popover.tsx +140 -0
- package/src/components/Progress.tsx +122 -0
- package/src/components/Radio.tsx +59 -0
- package/src/components/RadioGroup.tsx +114 -0
- package/src/components/Select.tsx +197 -0
- package/src/components/Skeleton.tsx +95 -0
- package/src/components/Slider.tsx +315 -0
- package/src/components/Spinner.tsx +38 -0
- package/src/components/Stack.tsx +57 -0
- package/src/components/Switch.tsx +42 -0
- package/src/components/Table.tsx +99 -0
- package/src/components/Tabs.tsx +159 -0
- package/src/components/Text.tsx +67 -0
- package/src/components/Textarea.tsx +26 -0
- package/src/components/Toast.tsx +190 -0
- package/src/components/ToggleGroup.tsx +227 -0
- package/src/components/Tooltip.tsx +78 -0
- package/src/contrast.ts +82 -0
- package/src/css.ts +228 -0
- package/src/define.check.ts +259 -0
- package/src/define.ts +379 -0
- package/src/field-context.ts +31 -0
- package/src/floating.tsx +61 -0
- package/src/hooks.ts +19 -0
- package/src/index.ts +119 -0
- package/src/plugin.ts +94 -0
- package/src/reference/DesignReference.tsx +374 -0
- package/src/reference/index.ts +2 -0
- package/src/reference/reference.css +58 -0
- package/src/runtime.ts +38 -0
- package/src/styles.css +1971 -0
- package/src/themes/bloom.ts +72 -0
- package/src/themes/brutalist.ts +63 -0
- package/src/themes/compact.ts +63 -0
- package/src/themes/contract.ts +24 -0
- package/src/themes/editorial.ts +55 -0
- package/src/themes/index.ts +47 -0
- package/src/types.ts +69 -0
- package/src/yarcl.config.ts +104 -0
package/dist/define.d.ts
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/** A color with a value for each color scheme. Emitted as CSS `light-dark()`. */
|
|
2
|
+
export interface ColorPair {
|
|
3
|
+
/** Value used when the color scheme is light. */
|
|
4
|
+
light: string;
|
|
5
|
+
/** Value used when the color scheme is dark. */
|
|
6
|
+
dark: string;
|
|
7
|
+
}
|
|
8
|
+
/** A semantic color: one value per color scheme, plus an optional foreground. */
|
|
9
|
+
export interface ColorToken extends ColorPair {
|
|
10
|
+
/**
|
|
11
|
+
* Text color used on top of this color.
|
|
12
|
+
* Computed by contrast (black or white) per scheme when omitted; requires hex values.
|
|
13
|
+
*/
|
|
14
|
+
on?: string | ColorPair;
|
|
15
|
+
/**
|
|
16
|
+
* Color used when this color is drawn as text: outline, soft and ghost variants, colored `Text`,
|
|
17
|
+
* `Link`, menu items. Computed per scheme when omitted: the color itself when it has enough
|
|
18
|
+
* contrast on the page, surface and tinted backgrounds, otherwise mixed toward the neutral text
|
|
19
|
+
* color until it does. Requires hex values.
|
|
20
|
+
*/
|
|
21
|
+
text?: string | ColorPair;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A single step of the shared control size scale.
|
|
25
|
+
* Every sized control reads the same entry, so controls of the same size share the same height.
|
|
26
|
+
*/
|
|
27
|
+
export interface SizeToken {
|
|
28
|
+
/** Control height, e.g. `'2.5rem'`. */
|
|
29
|
+
height: string;
|
|
30
|
+
/** Horizontal padding, e.g. `'1rem'`. */
|
|
31
|
+
paddingX: string;
|
|
32
|
+
/** Font size, e.g. `'0.875rem'`. */
|
|
33
|
+
fontSize: string;
|
|
34
|
+
/** Size of icons inside controls of this size, e.g. `'1rem'`. */
|
|
35
|
+
iconSize: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A style recipe applied on top of a semantic color.
|
|
39
|
+
* Shared by every component that takes a `variant` prop.
|
|
40
|
+
*/
|
|
41
|
+
export interface VariantToken {
|
|
42
|
+
/** `fill`: the color itself; `tint`: a translucent wash of the color; `none`: transparent. */
|
|
43
|
+
background: 'fill' | 'tint' | 'none';
|
|
44
|
+
/** `color`: the semantic color; `neutral`: the neutral border color; `none`: no visible border. */
|
|
45
|
+
border: 'color' | 'neutral' | 'none';
|
|
46
|
+
/** `on`: the color's foreground; `color`: the semantic color; `neutral`: the default text color. */
|
|
47
|
+
text: 'on' | 'color' | 'neutral';
|
|
48
|
+
}
|
|
49
|
+
/** Cell spacing for tables at one density. */
|
|
50
|
+
export interface DensityToken {
|
|
51
|
+
/** Horizontal cell padding, e.g. `'0.75rem'`. */
|
|
52
|
+
paddingX: string;
|
|
53
|
+
/** Vertical cell padding, e.g. `'0.5rem'`. */
|
|
54
|
+
paddingY: string;
|
|
55
|
+
/** Cell font size, e.g. `'0.875rem'`. */
|
|
56
|
+
fontSize: string;
|
|
57
|
+
}
|
|
58
|
+
/** A font file to load, emitted as an `@font-face` rule. */
|
|
59
|
+
export interface FontFaceToken {
|
|
60
|
+
/** Family name to reference from `typography.families`, e.g. `'Inter'`. */
|
|
61
|
+
family: string;
|
|
62
|
+
/**
|
|
63
|
+
* Font file URL(s), e.g. `'/fonts/inter.woff2'`, or `'local(Inter)'`.
|
|
64
|
+
* The format is inferred from the extension.
|
|
65
|
+
*/
|
|
66
|
+
src: string | string[];
|
|
67
|
+
/** Weight or weight range, e.g. `400` or `'100 900'` for variable fonts. */
|
|
68
|
+
weight?: number | string;
|
|
69
|
+
/** Font style of this file. */
|
|
70
|
+
style?: 'normal' | 'italic';
|
|
71
|
+
/**
|
|
72
|
+
* How the font displays while loading.
|
|
73
|
+
* @default 'swap'
|
|
74
|
+
*/
|
|
75
|
+
display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional';
|
|
76
|
+
}
|
|
77
|
+
/** A named text style. */
|
|
78
|
+
export interface TextStyleToken {
|
|
79
|
+
/** A key of `typography.families`. */
|
|
80
|
+
family: string;
|
|
81
|
+
/** Font size, e.g. `'1rem'`. */
|
|
82
|
+
size: string;
|
|
83
|
+
/** Font weight, e.g. `400`. */
|
|
84
|
+
weight: number;
|
|
85
|
+
/** Unitless line height, e.g. `1.5`. */
|
|
86
|
+
lineHeight: number;
|
|
87
|
+
/** Letter spacing, e.g. `'-0.01em'`. */
|
|
88
|
+
letterSpacing?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Token props each component accepts in `components`. A component's defaults can only set
|
|
92
|
+
* props it actually has.
|
|
93
|
+
*/
|
|
94
|
+
export interface ComponentTokenProps {
|
|
95
|
+
Button: 'size' | 'radius' | 'color' | 'variant';
|
|
96
|
+
IconButton: 'size' | 'radius' | 'color' | 'variant';
|
|
97
|
+
ToggleGroup: 'size' | 'radius' | 'color' | 'variant' | 'selectedVariant';
|
|
98
|
+
Input: 'size' | 'radius' | 'color';
|
|
99
|
+
Textarea: 'size' | 'radius' | 'color';
|
|
100
|
+
NumberInput: 'size' | 'radius' | 'color';
|
|
101
|
+
Select: 'size' | 'radius' | 'color';
|
|
102
|
+
Combobox: 'size' | 'radius' | 'color';
|
|
103
|
+
DatePicker: 'size' | 'radius' | 'color' | 'variant';
|
|
104
|
+
Checkbox: 'size' | 'color';
|
|
105
|
+
Radio: 'size' | 'color';
|
|
106
|
+
Switch: 'size' | 'color';
|
|
107
|
+
Slider: 'size' | 'radius' | 'color';
|
|
108
|
+
Badge: 'size' | 'radius' | 'color' | 'variant';
|
|
109
|
+
Alert: 'radius' | 'color' | 'variant';
|
|
110
|
+
Card: 'radius' | 'padding' | 'shadow';
|
|
111
|
+
Popover: 'radius' | 'padding';
|
|
112
|
+
HoverCard: 'radius' | 'padding';
|
|
113
|
+
Dialog: 'radius' | 'size';
|
|
114
|
+
Drawer: 'size';
|
|
115
|
+
CommandPalette: 'size' | 'radius' | 'color';
|
|
116
|
+
Menu: 'size';
|
|
117
|
+
Tabs: 'size' | 'color';
|
|
118
|
+
Pagination: 'size' | 'radius' | 'color' | 'variant' | 'selectedVariant';
|
|
119
|
+
Accordion: 'size' | 'radius' | 'color';
|
|
120
|
+
Table: 'density';
|
|
121
|
+
Stack: 'gap';
|
|
122
|
+
Inline: 'gap';
|
|
123
|
+
Text: 'textStyle' | 'color';
|
|
124
|
+
Label: 'textStyle' | 'color';
|
|
125
|
+
Link: 'color';
|
|
126
|
+
Breadcrumb: 'textStyle' | 'color';
|
|
127
|
+
Spinner: 'size' | 'color';
|
|
128
|
+
Skeleton: 'size' | 'radius';
|
|
129
|
+
Progress: 'size' | 'color' | 'radius';
|
|
130
|
+
Toast: 'color';
|
|
131
|
+
}
|
|
132
|
+
/** Names of components that accept defaults in `components`. */
|
|
133
|
+
export type ComponentName = keyof ComponentTokenProps;
|
|
134
|
+
/**
|
|
135
|
+
* The structure every yarcl config must satisfy.
|
|
136
|
+
*
|
|
137
|
+
* Open groups (`colors`, `sizes`, `radii`, `variants`, `spacing`, `shadows`, `density`, `modalSizes`, `typography`) take any keys;
|
|
138
|
+
* those keys become the valid prop values. Groups with required keys (`neutrals`, `zIndex`,
|
|
139
|
+
* `motion`, `timing`, `borders`) must include the keys the library depends on, and accept any extra
|
|
140
|
+
* keys, which are emitted as CSS variables for the consumer's own styles.
|
|
141
|
+
*/
|
|
142
|
+
export interface YarclShape {
|
|
143
|
+
/** Semantic colors. Keys become the valid values of the `color` prop. */
|
|
144
|
+
colors: Record<string, ColorToken>;
|
|
145
|
+
/** Neutral colors for backgrounds, text and borders. Extra keys allowed. */
|
|
146
|
+
neutrals: Record<string, ColorPair> & {
|
|
147
|
+
/** Page background. */
|
|
148
|
+
bg: ColorPair;
|
|
149
|
+
/** Background of controls and raised surfaces. */
|
|
150
|
+
surface: ColorPair;
|
|
151
|
+
/** Default text. */
|
|
152
|
+
text: ColorPair;
|
|
153
|
+
/** Secondary text and placeholders. */
|
|
154
|
+
muted: ColorPair;
|
|
155
|
+
/** Default border. */
|
|
156
|
+
border: ColorPair;
|
|
157
|
+
};
|
|
158
|
+
/** Control size scale. Keys become the valid values of the `size` prop. */
|
|
159
|
+
sizes: Record<string, SizeToken>;
|
|
160
|
+
/**
|
|
161
|
+
* Border radii. Keys become the valid values of the `radius` prop. Name them after your
|
|
162
|
+
* sizes (`sm`, `md`, `lg` …) so controls can match their size, plus exceptions such as
|
|
163
|
+
* `square` and `rounded`. `size` is reserved.
|
|
164
|
+
*/
|
|
165
|
+
radii: Record<string, string>;
|
|
166
|
+
/** Style recipes. Keys become the valid values of the `variant` prop. */
|
|
167
|
+
variants: Record<string, VariantToken>;
|
|
168
|
+
/** Spacing scale, e.g. for `gap` and padding. */
|
|
169
|
+
spacing: Record<string, string>;
|
|
170
|
+
/** Box shadows. */
|
|
171
|
+
shadows: Record<string, string>;
|
|
172
|
+
/** Table densities. Keys become the valid values of the `density` prop. */
|
|
173
|
+
density: Record<string, DensityToken>;
|
|
174
|
+
/**
|
|
175
|
+
* Widths of `Dialog` and `Drawer`, e.g. `{ sm: '24rem', md: '32rem', full: '100vw' }`.
|
|
176
|
+
* Keys become the valid values of their `size` prop. Separate from `sizes`, which sets control heights.
|
|
177
|
+
*/
|
|
178
|
+
modalSizes: Record<string, string>;
|
|
179
|
+
/** Font files, font families, named text styles and heading levels. */
|
|
180
|
+
typography: {
|
|
181
|
+
/** Font files to load. Reference their `family` names in `families`. */
|
|
182
|
+
fontFaces?: readonly FontFaceToken[];
|
|
183
|
+
/** Font stacks, e.g. `{ sans: 'Inter, system-ui, sans-serif' }`. */
|
|
184
|
+
families: Record<string, string>;
|
|
185
|
+
/** Named text styles. Keys become the valid text style names. */
|
|
186
|
+
styles: Record<string, TextStyleToken>;
|
|
187
|
+
/** Text style for each heading level, used by `Heading`. Each must be a key of `styles`. */
|
|
188
|
+
headings: {
|
|
189
|
+
h1: string;
|
|
190
|
+
h2: string;
|
|
191
|
+
h3: string;
|
|
192
|
+
h4: string;
|
|
193
|
+
h5: string;
|
|
194
|
+
h6: string;
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
/** Stacking order of floating layers. Extra keys allowed. */
|
|
198
|
+
zIndex: Record<string, number> & {
|
|
199
|
+
dropdown: number;
|
|
200
|
+
tooltip: number;
|
|
201
|
+
dialog: number;
|
|
202
|
+
toast: number;
|
|
203
|
+
};
|
|
204
|
+
/** Transition timing. Extra keys allowed. */
|
|
205
|
+
motion: Record<string, string> & {
|
|
206
|
+
/** Short transitions, e.g. hover. */
|
|
207
|
+
fast: string;
|
|
208
|
+
/** Standard transitions, e.g. opening a popover. */
|
|
209
|
+
base: string;
|
|
210
|
+
/** Easing function. */
|
|
211
|
+
easing: string;
|
|
212
|
+
};
|
|
213
|
+
/** Interaction timing in milliseconds, read by components at render time. Extra keys allowed. */
|
|
214
|
+
timing: Record<string, number> & {
|
|
215
|
+
/** Delay before a `Tooltip` opens on hover. */
|
|
216
|
+
tooltipDelay: number;
|
|
217
|
+
/** Delay before a `HoverCard` opens on hover. */
|
|
218
|
+
hoverOpenDelay: number;
|
|
219
|
+
/** Delay before a `HoverCard` closes after the pointer leaves. */
|
|
220
|
+
hoverCloseDelay: number;
|
|
221
|
+
/** How long a toast stays before it dismisses itself. `0` keeps toasts until dismissed. */
|
|
222
|
+
toastDuration: number;
|
|
223
|
+
};
|
|
224
|
+
/** Border widths. Extra keys allowed. */
|
|
225
|
+
borders: Record<string, string> & {
|
|
226
|
+
width: string;
|
|
227
|
+
};
|
|
228
|
+
/** Keyboard focus indicator, shared by every focusable component. */
|
|
229
|
+
focusRing: {
|
|
230
|
+
/** Outline width, e.g. `'2px'`. */
|
|
231
|
+
width: string;
|
|
232
|
+
/** Gap between the element and the outline, e.g. `'2px'`. Negative values draw it inside. */
|
|
233
|
+
offset: string;
|
|
234
|
+
/** A key of `colors`. */
|
|
235
|
+
color: string;
|
|
236
|
+
/**
|
|
237
|
+
* Outline style.
|
|
238
|
+
* @default 'solid'
|
|
239
|
+
*/
|
|
240
|
+
style?: 'solid' | 'dashed' | 'dotted' | 'double';
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* Per-component defaults, e.g. `{ Button: { radius: 'square' } }`. Applied when a prop is omitted,
|
|
244
|
+
* before the global `defaults`. Each value must be a key of its group.
|
|
245
|
+
*/
|
|
246
|
+
components?: {
|
|
247
|
+
[C in ComponentName]?: {
|
|
248
|
+
[P in ComponentTokenProps[C]]?: string;
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
/** Values used when a component prop is omitted. Each must be a key of its group. */
|
|
252
|
+
defaults: {
|
|
253
|
+
size: string;
|
|
254
|
+
/**
|
|
255
|
+
* A key of `radii`, or `'size'` to use the radius named like the control's size
|
|
256
|
+
* (a `lg` button gets `radii.lg`). Components without a size use `defaults.size`.
|
|
257
|
+
*/
|
|
258
|
+
radius: string;
|
|
259
|
+
color: string;
|
|
260
|
+
variant: string;
|
|
261
|
+
/** Color used for invalid fields. A key of `colors`. */
|
|
262
|
+
errorColor: string;
|
|
263
|
+
/** Text style for `Text`. A key of `typography.styles`. */
|
|
264
|
+
textStyle: string;
|
|
265
|
+
/** Text style for form labels and legends. A key of `typography.styles`. */
|
|
266
|
+
labelStyle: string;
|
|
267
|
+
/** Text style for helper and error text below form controls. A key of `typography.styles`. */
|
|
268
|
+
helperStyle: string;
|
|
269
|
+
/** Gap for `Stack` and `Inline`. A key of `spacing`. */
|
|
270
|
+
gap: string;
|
|
271
|
+
/** Padding for `Card`. A key of `spacing`. */
|
|
272
|
+
padding: string;
|
|
273
|
+
/** Elevation of popovers, menus and listboxes. A key of `shadows`. */
|
|
274
|
+
floatingShadow: string;
|
|
275
|
+
/** Density for `Table`. A key of `density`. */
|
|
276
|
+
density: string;
|
|
277
|
+
/** Variant for low-emphasis components such as `Badge` and `Alert`. A key of `variants`. */
|
|
278
|
+
softVariant: string;
|
|
279
|
+
/** Width of `Dialog` and `Drawer`. A key of `modalSizes`. */
|
|
280
|
+
modalSize: string;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
type Whitespace = ' ' | '\n' | '\t';
|
|
284
|
+
type KeyCheck<G> = {
|
|
285
|
+
[K in keyof G as K extends `${string}${Whitespace}${string}` ? K : never]: {
|
|
286
|
+
error: `Key "${K & string}" must not contain whitespace`;
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
interface TokenKeys<T extends YarclShape> {
|
|
290
|
+
size: keyof T['sizes'];
|
|
291
|
+
radius: keyof T['radii'] | 'size';
|
|
292
|
+
color: keyof T['colors'];
|
|
293
|
+
variant: keyof T['variants'];
|
|
294
|
+
selectedVariant: keyof T['variants'];
|
|
295
|
+
gap: keyof T['spacing'];
|
|
296
|
+
padding: keyof T['spacing'];
|
|
297
|
+
shadow: keyof T['shadows'];
|
|
298
|
+
density: keyof T['density'];
|
|
299
|
+
textStyle: keyof T['typography']['styles'];
|
|
300
|
+
}
|
|
301
|
+
type ComponentChecks<T extends YarclShape> = {
|
|
302
|
+
[C in keyof T['components']]: C extends ComponentName ? {
|
|
303
|
+
[P in keyof T['components'][C]]: P extends ComponentTokenProps[C] ? C extends 'Dialog' | 'Drawer' ? P extends 'size' ? keyof T['modalSizes'] : TokenKeys<T>[P] : TokenKeys<T>[P] : never;
|
|
304
|
+
} : {
|
|
305
|
+
error: `Unknown component "${C & string}"`;
|
|
306
|
+
};
|
|
307
|
+
};
|
|
308
|
+
type Checks<T extends YarclShape> = {
|
|
309
|
+
components?: ComponentChecks<T>;
|
|
310
|
+
colors: KeyCheck<T['colors']>;
|
|
311
|
+
neutrals: KeyCheck<T['neutrals']>;
|
|
312
|
+
zIndex: KeyCheck<T['zIndex']>;
|
|
313
|
+
motion: KeyCheck<T['motion']>;
|
|
314
|
+
timing: KeyCheck<T['timing']>;
|
|
315
|
+
borders: KeyCheck<T['borders']>;
|
|
316
|
+
sizes: KeyCheck<T['sizes']>;
|
|
317
|
+
radii: KeyCheck<T['radii']> & {
|
|
318
|
+
size?: {
|
|
319
|
+
error: 'The radius key "size" is reserved';
|
|
320
|
+
};
|
|
321
|
+
};
|
|
322
|
+
variants: KeyCheck<T['variants']>;
|
|
323
|
+
spacing: KeyCheck<T['spacing']>;
|
|
324
|
+
shadows: KeyCheck<T['shadows']>;
|
|
325
|
+
density: KeyCheck<T['density']>;
|
|
326
|
+
modalSizes: KeyCheck<T['modalSizes']>;
|
|
327
|
+
typography: {
|
|
328
|
+
families: KeyCheck<T['typography']['families']>;
|
|
329
|
+
styles: KeyCheck<T['typography']['styles']> & {
|
|
330
|
+
[K in keyof T['typography']['styles']]: {
|
|
331
|
+
family: keyof T['typography']['families'];
|
|
332
|
+
};
|
|
333
|
+
};
|
|
334
|
+
headings: Record<'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6', keyof T['typography']['styles']>;
|
|
335
|
+
};
|
|
336
|
+
focusRing: {
|
|
337
|
+
color: keyof T['colors'];
|
|
338
|
+
};
|
|
339
|
+
defaults: {
|
|
340
|
+
size: keyof T['sizes'];
|
|
341
|
+
radius: keyof T['radii'] | 'size';
|
|
342
|
+
color: keyof T['colors'];
|
|
343
|
+
variant: keyof T['variants'];
|
|
344
|
+
errorColor: keyof T['colors'];
|
|
345
|
+
textStyle: keyof T['typography']['styles'];
|
|
346
|
+
labelStyle: keyof T['typography']['styles'];
|
|
347
|
+
helperStyle: keyof T['typography']['styles'];
|
|
348
|
+
gap: keyof T['spacing'];
|
|
349
|
+
padding: keyof T['spacing'];
|
|
350
|
+
floatingShadow: keyof T['shadows'];
|
|
351
|
+
density: keyof T['density'];
|
|
352
|
+
softVariant: keyof T['variants'];
|
|
353
|
+
modalSize: keyof T['modalSizes'];
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Declares a yarcl design system config.
|
|
358
|
+
*
|
|
359
|
+
* Checks at compile time that:
|
|
360
|
+
* - every color has a `light` and `dark` value
|
|
361
|
+
* - `defaults`, `focusRing.color`, `typography.headings` and each text style's `family` reference existing keys
|
|
362
|
+
* - no key contains whitespace
|
|
363
|
+
* - `components` only names known components, only sets props they have, and only uses existing keys
|
|
364
|
+
*
|
|
365
|
+
* Returns the config unchanged with literal types preserved, so the library can derive
|
|
366
|
+
* its prop types from it. Spread `@yarcl/react/defaults` to extend the library defaults instead
|
|
367
|
+
* of replacing them.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```ts
|
|
371
|
+
* // src/yarcl.config.ts
|
|
372
|
+
* import { defineConfig } from '@yarcl/react/define';
|
|
373
|
+
* import defaults from '@yarcl/react/defaults';
|
|
374
|
+
*
|
|
375
|
+
* export default defineConfig({
|
|
376
|
+
* ...defaults,
|
|
377
|
+
* colors: { ...defaults.colors, brand: { light: '#2d4bb8', dark: '#8aa2ff' } },
|
|
378
|
+
* defaults: { ...defaults.defaults, color: 'brand' },
|
|
379
|
+
* });
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
export declare function defineConfig<const T extends YarclShape>(config: T & Checks<T>): T;
|
|
383
|
+
export {};
|
package/dist/define.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declares a yarcl design system config.
|
|
3
|
+
*
|
|
4
|
+
* Checks at compile time that:
|
|
5
|
+
* - every color has a `light` and `dark` value
|
|
6
|
+
* - `defaults`, `focusRing.color`, `typography.headings` and each text style's `family` reference existing keys
|
|
7
|
+
* - no key contains whitespace
|
|
8
|
+
* - `components` only names known components, only sets props they have, and only uses existing keys
|
|
9
|
+
*
|
|
10
|
+
* Returns the config unchanged with literal types preserved, so the library can derive
|
|
11
|
+
* its prop types from it. Spread `@yarcl/react/defaults` to extend the library defaults instead
|
|
12
|
+
* of replacing them.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* // src/yarcl.config.ts
|
|
17
|
+
* import { defineConfig } from '@yarcl/react/define';
|
|
18
|
+
* import defaults from '@yarcl/react/defaults';
|
|
19
|
+
*
|
|
20
|
+
* export default defineConfig({
|
|
21
|
+
* ...defaults,
|
|
22
|
+
* colors: { ...defaults.colors, brand: { light: '#2d4bb8', dark: '#8aa2ff' } },
|
|
23
|
+
* defaults: { ...defaults.defaults, color: 'brand' },
|
|
24
|
+
* });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function defineConfig(config) {
|
|
28
|
+
return config;
|
|
29
|
+
}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Plugin } from 'vite';
|
|
2
|
+
/** Options for the yarcl Vite plugin. */
|
|
3
|
+
export interface YarclPluginOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Path to the consumer's config file, relative to the Vite root.
|
|
6
|
+
* If the file does not exist, the library's default config is used.
|
|
7
|
+
* @default 'src/yarcl.config.ts'
|
|
8
|
+
*/
|
|
9
|
+
config?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Vite plugin that connects the library to the consumer's config.
|
|
13
|
+
*
|
|
14
|
+
* - Aliases `@yarcl/config` to the consumer's config file (or the library default).
|
|
15
|
+
* - Generates `virtual:yarcl.css` from the config at build time: CSS variables on `:root`
|
|
16
|
+
* and one `yarcl-{group}-{key}` class per key. No inline styles, no runtime cost.
|
|
17
|
+
* - Warns when a color's foreground fails WCAG AA contrast.
|
|
18
|
+
* - Regenerates the CSS when the config file changes.
|
|
19
|
+
*
|
|
20
|
+
* Must be paired with a matching `paths` entry for `@yarcl/config` in the
|
|
21
|
+
* consumer's `tsconfig.json`, so the types resolve to the same file.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* // vite.config.ts
|
|
26
|
+
* import { yarcl } from '@yarcl/react/plugin';
|
|
27
|
+
*
|
|
28
|
+
* export default defineConfig({
|
|
29
|
+
* plugins: [react(), yarcl({ config: 'src/yarcl.config.ts' })],
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function yarcl({ config }?: YarclPluginOptions): Plugin;
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { runnerImport } from 'vite';
|
|
5
|
+
import { generateCss } from "./css.js";
|
|
6
|
+
const VIRTUAL_CSS = 'virtual:yarcl.css';
|
|
7
|
+
const RESOLVED_CSS = '\0' + VIRTUAL_CSS;
|
|
8
|
+
const PACKAGE = '@yarcl/react';
|
|
9
|
+
const defaultConfig = [new URL('./yarcl.config.ts', import.meta.url), new URL('../src/yarcl.config.ts', import.meta.url)]
|
|
10
|
+
.map((url) => fileURLToPath(url))
|
|
11
|
+
.find((file) => existsSync(file));
|
|
12
|
+
/**
|
|
13
|
+
* Vite plugin that connects the library to the consumer's config.
|
|
14
|
+
*
|
|
15
|
+
* - Aliases `@yarcl/config` to the consumer's config file (or the library default).
|
|
16
|
+
* - Generates `virtual:yarcl.css` from the config at build time: CSS variables on `:root`
|
|
17
|
+
* and one `yarcl-{group}-{key}` class per key. No inline styles, no runtime cost.
|
|
18
|
+
* - Warns when a color's foreground fails WCAG AA contrast.
|
|
19
|
+
* - Regenerates the CSS when the config file changes.
|
|
20
|
+
*
|
|
21
|
+
* Must be paired with a matching `paths` entry for `@yarcl/config` in the
|
|
22
|
+
* consumer's `tsconfig.json`, so the types resolve to the same file.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // vite.config.ts
|
|
27
|
+
* import { yarcl } from '@yarcl/react/plugin';
|
|
28
|
+
*
|
|
29
|
+
* export default defineConfig({
|
|
30
|
+
* plugins: [react(), yarcl({ config: 'src/yarcl.config.ts' })],
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export function yarcl({ config = 'src/yarcl.config.ts' } = {}) {
|
|
35
|
+
let root = process.cwd();
|
|
36
|
+
let target = defaultConfig;
|
|
37
|
+
let watched = new Set();
|
|
38
|
+
return {
|
|
39
|
+
name: 'yarcl',
|
|
40
|
+
config(userConfig) {
|
|
41
|
+
root = resolve(userConfig.root ?? process.cwd());
|
|
42
|
+
const consumerConfig = resolve(root, config);
|
|
43
|
+
target = existsSync(consumerConfig) ? consumerConfig : defaultConfig;
|
|
44
|
+
return {
|
|
45
|
+
resolve: { alias: { '@yarcl/config': target } },
|
|
46
|
+
optimizeDeps: {
|
|
47
|
+
exclude: [PACKAGE],
|
|
48
|
+
include: ['@floating-ui/react', 'date-fns', 'date-fns/locale', 'date-fns/locale/en-US'].map((dep) => `${PACKAGE} > ${dep}`),
|
|
49
|
+
},
|
|
50
|
+
ssr: { noExternal: [PACKAGE] },
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
resolveId(id) {
|
|
54
|
+
if (id === VIRTUAL_CSS)
|
|
55
|
+
return RESOLVED_CSS;
|
|
56
|
+
},
|
|
57
|
+
async load(id) {
|
|
58
|
+
if (id !== RESOLVED_CSS)
|
|
59
|
+
return;
|
|
60
|
+
const { module, dependencies } = await runnerImport(target, {
|
|
61
|
+
configFile: false,
|
|
62
|
+
root,
|
|
63
|
+
ssr: { noExternal: [PACKAGE] },
|
|
64
|
+
logLevel: 'error',
|
|
65
|
+
});
|
|
66
|
+
watched = new Set([target, ...dependencies.map((dep) => resolve(root, dep))]);
|
|
67
|
+
watched.forEach((file) => this.addWatchFile(file));
|
|
68
|
+
return generateCss(module.default, (message) => this.warn(message));
|
|
69
|
+
},
|
|
70
|
+
handleHotUpdate({ file, server, modules }) {
|
|
71
|
+
if (!watched.has(file))
|
|
72
|
+
return;
|
|
73
|
+
const css = server.moduleGraph.getModuleById(RESOLVED_CSS);
|
|
74
|
+
if (!css)
|
|
75
|
+
return;
|
|
76
|
+
server.moduleGraph.invalidateModule(css);
|
|
77
|
+
return [...modules, css];
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yarcl/react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Yet another react component library. Your config file is the design system: every key becomes a typed prop and a rendered style.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Eugene Yakhnenko",
|
|
7
|
+
"homepage": "https://yarcl.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/eugenioenko/yarcl.git",
|
|
11
|
+
"directory": "library"
|
|
12
|
+
},
|
|
13
|
+
"bugs": "https://github.com/eugenioenko/yarcl/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react",
|
|
16
|
+
"component-library",
|
|
17
|
+
"design-system",
|
|
18
|
+
"design-tokens",
|
|
19
|
+
"theming",
|
|
20
|
+
"vite-plugin",
|
|
21
|
+
"typescript"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"files": [
|
|
25
|
+
"src",
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./src/index.ts",
|
|
30
|
+
"./define": "./src/define.ts",
|
|
31
|
+
"./defaults": "./src/yarcl.config.ts",
|
|
32
|
+
"./plugin": {
|
|
33
|
+
"types": "./dist/plugin.d.ts",
|
|
34
|
+
"default": "./dist/plugin.js"
|
|
35
|
+
},
|
|
36
|
+
"./reference": "./src/reference/index.ts",
|
|
37
|
+
"./themes": "./src/themes/index.ts",
|
|
38
|
+
"./css": "./src/apply.ts"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -p tsconfig.build.json",
|
|
42
|
+
"prepare": "tsc -p tsconfig.build.json"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"provenance": true
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": ">=19",
|
|
50
|
+
"react-dom": ">=19",
|
|
51
|
+
"vite": ">=6.1"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@floating-ui/react": "^0.27.20",
|
|
55
|
+
"date-fns": "^4.4.0"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/node": "^24.0.0",
|
|
59
|
+
"@types/react": "^19.1.0",
|
|
60
|
+
"@types/react-dom": "^19.3.0",
|
|
61
|
+
"react": "^19.1.0",
|
|
62
|
+
"react-dom": "^19.3.0",
|
|
63
|
+
"typescript": "^6.0.3",
|
|
64
|
+
"vite": "^8.3.1"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/apply.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { generateCss } from './css';
|
|
2
|
+
import type { YarclShape } from './define';
|
|
3
|
+
import { setActiveConfig } from './runtime';
|
|
4
|
+
|
|
5
|
+
export { generateCss };
|
|
6
|
+
|
|
7
|
+
const STYLE_ID = 'yarcl-theme';
|
|
8
|
+
|
|
9
|
+
/** Options for {@link applyTheme}. */
|
|
10
|
+
export interface ApplyThemeOptions {
|
|
11
|
+
/** Receives contrast warnings for the theme's colors. */
|
|
12
|
+
onWarning?: (message: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Switches the whole app to another theme at runtime, without a rebuild.
|
|
17
|
+
*
|
|
18
|
+
* Injects the theme's generated CSS (replacing the build-time styles) and makes components read
|
|
19
|
+
* their defaults (`defaults`, `components`, heading levels) from the theme, re-rendering them.
|
|
20
|
+
* The theme must define the keys your app uses; bundled themes share the defaults' keys
|
|
21
|
+
* (`ThemeContract`), so any of them can replace another.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* import { applyTheme } from '@yarcl/react/css';
|
|
26
|
+
* import { themes } from '@yarcl/react/themes';
|
|
27
|
+
*
|
|
28
|
+
* applyTheme(themes.editorial);
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function applyTheme(theme: YarclShape, { onWarning }: ApplyThemeOptions = {}) {
|
|
32
|
+
let style = document.getElementById(STYLE_ID);
|
|
33
|
+
if (!style) {
|
|
34
|
+
style = document.createElement('style');
|
|
35
|
+
style.id = STYLE_ID;
|
|
36
|
+
}
|
|
37
|
+
document.head.appendChild(style);
|
|
38
|
+
style.textContent = generateCss(theme, onWarning);
|
|
39
|
+
setActiveConfig(theme);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Removes a theme applied with {@link applyTheme} and returns to the build-time config and styles. */
|
|
43
|
+
export function resetTheme() {
|
|
44
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
45
|
+
setActiveConfig(null);
|
|
46
|
+
}
|
package/src/classes.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { activeConfig } from './runtime';
|
|
2
|
+
import type { Color, Density, Radius, Shadow, Size, Spacing, TextStyle, Variant } from './types';
|
|
3
|
+
|
|
4
|
+
export function cx(...parts: (string | false | null | undefined)[]): string {
|
|
5
|
+
return parts.filter(Boolean).join(' ');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const sizeClass = (size: Size = activeConfig().defaults.size) => `yarcl-size-${size}`;
|
|
9
|
+
export function radiusClass(radius?: Radius | 'size', size?: Size): string | undefined {
|
|
10
|
+
const value: Radius | 'size' = radius ?? (activeConfig().defaults.radius as Radius | 'size');
|
|
11
|
+
if (value !== 'size') return `yarcl-radius-${value}`;
|
|
12
|
+
const match = size ?? activeConfig().defaults.size;
|
|
13
|
+
return match in activeConfig().radii ? `yarcl-radius-${match}` : undefined;
|
|
14
|
+
}
|
|
15
|
+
export const colorClass = (color: Color = activeConfig().defaults.color) => `yarcl-color-${color}`;
|
|
16
|
+
export const variantClass = (variant: Variant = activeConfig().defaults.variant) => `yarcl-variant-${variant}`;
|
|
17
|
+
export const gapClass = (gap: Spacing = activeConfig().defaults.gap) => `yarcl-gap-${gap}`;
|
|
18
|
+
export const paddingClass = (padding: Spacing = activeConfig().defaults.padding) => `yarcl-padding-${padding}`;
|
|
19
|
+
export const shadowClass = (shadow?: Shadow) => shadow && `yarcl-shadow-${shadow}`;
|
|
20
|
+
export const typeClass = (style: TextStyle) => `yarcl-type-${style}`;
|
|
21
|
+
export const densityClass = (density: Density = activeConfig().defaults.density) => `yarcl-density-${density}`;
|
|
22
|
+
export const softVariantClass = (variant: Variant = activeConfig().defaults.softVariant) => `yarcl-variant-${variant}`;
|
|
23
|
+
|