@splenddev/kreativ-core 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +342 -3
- package/dist/common-CxqD2cxB.d.cts +557 -0
- package/dist/common-CxqD2cxB.d.ts +557 -0
- package/dist/hooks.cjs +442 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.cts +121 -0
- package/dist/hooks.d.ts +121 -0
- package/dist/hooks.js +411 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.cjs +1362 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1288 -0
- package/dist/index.js.map +1 -0
- package/dist/theme.cjs +1183 -0
- package/dist/theme.cjs.map +1 -0
- package/dist/theme.d.cts +2724 -0
- package/dist/theme.d.ts +2724 -0
- package/dist/theme.js +1142 -0
- package/dist/theme.js.map +1 -0
- package/dist/types.cjs +4 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +94 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/useColorMode-B7eyS27H.d.ts +10 -0
- package/dist/useColorMode-tvcgvc4V.d.cts +10 -0
- package/dist/utils.cjs +184 -0
- package/dist/utils.cjs.map +1 -0
- package/dist/utils.d.cts +59 -0
- package/dist/utils.d.ts +59 -0
- package/dist/utils.js +152 -0
- package/dist/utils.js.map +1 -0
- package/package.json +89 -4
package/README.md
CHANGED
|
@@ -1,3 +1,342 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# Kreativ Core
|
|
2
|
+
|
|
3
|
+
The design-system engine behind [Kreativ UI](https://github.com/Vincentvirtuoso/kreativ-ui).
|
|
4
|
+
|
|
5
|
+
Define tokens, themes, and recipes. Core validates them and turns them into CSS variables and class names. It knows nothing about specific components or default looks; those live in Kreativ UI.
|
|
6
|
+
|
|
7
|
+
> **Status:** early development. We're extracting this from Kreativ UI, so APIs and package boundaries may still change.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @splenddev/kreativ-core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import {
|
|
19
|
+
defineTokens,
|
|
20
|
+
defineSemanticToken,
|
|
21
|
+
defineTheme,
|
|
22
|
+
ThemeProvider,
|
|
23
|
+
} from "@splenddev/kreativ-core";
|
|
24
|
+
|
|
25
|
+
const tokens = defineTokens({
|
|
26
|
+
colors: { blue: { 500: "#3b82f6", 400: "#60a5fa" } },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const semanticTokens = {
|
|
30
|
+
colors: {
|
|
31
|
+
brand: defineSemanticToken("{colors.blue.500}", "{colors.blue.400}"),
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const theme = defineTheme({
|
|
36
|
+
tokens,
|
|
37
|
+
semanticTokens,
|
|
38
|
+
typography,
|
|
39
|
+
recipes,
|
|
40
|
+
sizes,
|
|
41
|
+
intensity: 50,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
<ThemeProvider theme={theme}>
|
|
45
|
+
<App />
|
|
46
|
+
</ThemeProvider>;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Light and dark values resolve automatically, and every token becomes a CSS variable like `--kui-brand`.
|
|
50
|
+
|
|
51
|
+
Building on Kreativ UI? Use its `KreativUIProvider` instead (see [Providers](#providers)).
|
|
52
|
+
|
|
53
|
+
## Core vs Kreativ UI
|
|
54
|
+
|
|
55
|
+
Core is the machinery. Kreativ UI is the product: the components plus the default tokens, sizes, typography, and recipes that give it its look.
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Kreativ Core tokens · semantic tokens · typography · sizes · recipes
|
|
59
|
+
themes · validation · CSS variables · component metadata · React runtime
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
Kreativ UI components + the default design language
|
|
63
|
+
│
|
|
64
|
+
▼
|
|
65
|
+
Your app
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Use Kreativ UI to build apps. Reach for Core when you're building your own design system or tooling on the same foundation. Defaults (tokens, sizes, typography, recipes) belong to Kreativ UI, never to Core.
|
|
69
|
+
|
|
70
|
+
### Providers
|
|
71
|
+
|
|
72
|
+
The provider is split the same way:
|
|
73
|
+
|
|
74
|
+
- **`ThemeProvider`** (Core) is UI-agnostic. It handles theme composition, color mode, token resolution, CSS variables, and the theme context. It knows nothing about specific components.
|
|
75
|
+
- **`KreativUIProvider`** (Kreativ UI) is a thin wrapper around `ThemeProvider`. It adds the default theme, the overlay root used by popovers, dialogs, and toasts, and typed per-component `defaults`. Those depend on Kreativ UI's own components, so they can't live in Core.
|
|
76
|
+
|
|
77
|
+
## Concepts
|
|
78
|
+
|
|
79
|
+
### Tokens
|
|
80
|
+
|
|
81
|
+
Tokens are the raw values of a design system. They're mode-independent and terminal, so a primitive token can't reference another token.
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const tokens = defineTokens({
|
|
85
|
+
colors: { blue: { 500: "#3b82f6", 600: "#2563eb" } },
|
|
86
|
+
spacing: { sm: "0.5rem", md: "1rem" },
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Built-in groups: `colors`, `spacing`, `radii`, `fonts`, `fontSizes`, `fontWeights`, `lineHeights`, `letterSpacing`, `shadows`, `animations`, and `breakpoints`. Custom groups work too.
|
|
91
|
+
|
|
92
|
+
### Semantic tokens
|
|
93
|
+
|
|
94
|
+
Semantic tokens describe meaning instead of raw values. Components consume `{colors.brand}` rather than `{colors.blue.500}`, so you can re-theme without touching a component.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
defineSemanticToken("{colors.blue.500}", "{colors.blue.400}"); // light, dark
|
|
98
|
+
defineSemanticToken("{colors.white}"); // same in both modes
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
They can reference primitives or other semantic tokens. Keeping all references on the semantic side keeps the token graph predictable and makes cycle detection simple.
|
|
102
|
+
|
|
103
|
+
### Typography
|
|
104
|
+
|
|
105
|
+
Typography presets are defined separately from font tokens and reference them:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
const typography = defineTypography({
|
|
109
|
+
body: {
|
|
110
|
+
fontFamily: "{fonts.body}",
|
|
111
|
+
fontSize: "{fontSizes.md}",
|
|
112
|
+
fontWeight: "{fontWeights.normal}",
|
|
113
|
+
lineHeight: "{lineHeights.normal}",
|
|
114
|
+
},
|
|
115
|
+
heading: {
|
|
116
|
+
fontFamily: "{fonts.heading}",
|
|
117
|
+
fontSize: "{fontSizes.2xl}",
|
|
118
|
+
fontWeight: "{fontWeights.bold}",
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`resolveTypography()` turns a preset into CSS properties at runtime.
|
|
124
|
+
|
|
125
|
+
### Sizes
|
|
126
|
+
|
|
127
|
+
Size scales give components consistent dimensions:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const sizes = defineSizes({
|
|
131
|
+
sm: {
|
|
132
|
+
height: "2rem",
|
|
133
|
+
paddingX: "0.75rem",
|
|
134
|
+
fontSize: "{typography.body}",
|
|
135
|
+
gap: "0.375rem",
|
|
136
|
+
iconSize: "0.875rem",
|
|
137
|
+
radius: "0.8rem",
|
|
138
|
+
},
|
|
139
|
+
md: {
|
|
140
|
+
height: "2.5rem",
|
|
141
|
+
paddingX: "1rem",
|
|
142
|
+
fontSize: "{typography.body}",
|
|
143
|
+
gap: "0.5rem",
|
|
144
|
+
iconSize: "1rem",
|
|
145
|
+
radius: "1rem",
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
A size can define `height`, `width`, `paddingX`, `paddingY`, `fontSize`, `gap`, `iconSize`, `radius`, and `separatorSize`. Its `fontSize` is tied to the typography system and must reference a typography preset.
|
|
151
|
+
|
|
152
|
+
### Recipes
|
|
153
|
+
|
|
154
|
+
A recipe is a component's styling contract: base styles, variants, default variants, and compound variants.
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const buttonRecipe = defineRecipe({
|
|
158
|
+
base: "inline-flex items-center justify-center",
|
|
159
|
+
variants: {
|
|
160
|
+
variant: { solid: "...", outline: "...", ghost: "..." },
|
|
161
|
+
color: { brand: "...", destructive: "..." },
|
|
162
|
+
},
|
|
163
|
+
defaultVariants: { variant: "solid", color: "brand" },
|
|
164
|
+
compoundVariants: [
|
|
165
|
+
{ conditions: { variant: "solid", color: "brand" }, className: "..." },
|
|
166
|
+
],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
resolveRecipe(buttonRecipe, props);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`resolveRecipe` applies default variants, resolves matching variants, resolves matching compound variants (exact prop matching), then combines the class names.
|
|
173
|
+
|
|
174
|
+
### Themes
|
|
175
|
+
|
|
176
|
+
A theme brings every layer together, plus lightweight metadata that tools like the Kreativ CLI and Playground can read.
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
const theme = defineTheme({
|
|
180
|
+
tokens,
|
|
181
|
+
semanticTokens,
|
|
182
|
+
typography,
|
|
183
|
+
recipes,
|
|
184
|
+
sizes,
|
|
185
|
+
intensity: 50,
|
|
186
|
+
metadata: {
|
|
187
|
+
name: "My Theme",
|
|
188
|
+
description: "A custom Kreativ theme",
|
|
189
|
+
author: "Author",
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`defineTheme()` keeps the exact shape you pass in while validating the relationships between layers. Metadata fields are `name`, `description`, `author`, and `source`, all optional.
|
|
195
|
+
|
|
196
|
+
#### Overriding and extending
|
|
197
|
+
|
|
198
|
+
Customize an existing theme with a `ThemeOverride`:
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const override = {
|
|
202
|
+
tokens: { colors: { blue: { 500: "#7c3aed" } } },
|
|
203
|
+
semanticTokens: {
|
|
204
|
+
colors: { brand: defineSemanticToken("{colors.blue.500}") },
|
|
205
|
+
},
|
|
206
|
+
intensity: 60,
|
|
207
|
+
};
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Or extend one layer at a time with `extendTokens()`, `extendSemanticTokens()`, `extendTypography()`, `extendSizes()`, and `extendRecipes()`.
|
|
211
|
+
|
|
212
|
+
### Validation
|
|
213
|
+
|
|
214
|
+
`defineTheme()` checks your theme as you write it:
|
|
215
|
+
|
|
216
|
+
- Token references must resolve to existing paths. `{colors.blue.500}` has to actually exist.
|
|
217
|
+
- Primitive tokens can't contain references. Semantic tokens are the reference layer.
|
|
218
|
+
- Typography fields may reference primitive tokens, and those paths must exist.
|
|
219
|
+
- A size's `fontSize` must reference a typography preset, not a raw CSS value.
|
|
220
|
+
- Semantic cycles (`brand → accent → brand`) are invalid. These are caught at runtime.
|
|
221
|
+
|
|
222
|
+
## Runtime
|
|
223
|
+
|
|
224
|
+
### Resolving tokens and CSS variables
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const resolved = resolveTokens(theme.tokens, theme.semanticTokens, "light");
|
|
228
|
+
// Record<string, string>
|
|
229
|
+
|
|
230
|
+
const cssVars = tokensToCssVars(resolved, "light", theme.intensity);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Semantic values resolve for the active color mode. Primitive tokens are named by path, semantic colors by their semantic name, and the runtime also exposes the current mode and intensity:
|
|
234
|
+
|
|
235
|
+
```
|
|
236
|
+
colors.blue.500 → --kui-colors-blue-500
|
|
237
|
+
colors.brand → --kui-brand
|
|
238
|
+
--kui-mode
|
|
239
|
+
--kui-intensity
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Color values are normalized to RGB channels where appropriate, so the variables work with modern color-manipulation patterns.
|
|
243
|
+
|
|
244
|
+
### ThemeProvider
|
|
245
|
+
|
|
246
|
+
`ThemeProvider` sets up the theme for a React tree. It handles theme composition, color-mode state, system color preference, token resolution, CSS variable generation, a fallback component size, and optional theme transitions.
|
|
247
|
+
|
|
248
|
+
Color mode can be `"light"`, `"dark"`, or `"system"` (which follows `prefers-color-scheme`).
|
|
249
|
+
|
|
250
|
+
The theme context exposes:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
{ theme, mode, resolvedMode, setMode, fallbackSize, tokens }
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
so components can use the active theme without being coupled to any default theme.
|
|
257
|
+
|
|
258
|
+
### createComponent and metadata
|
|
259
|
+
|
|
260
|
+
`createComponent()` builds a ref-forwarding component and attaches Kreativ metadata through `__kui`:
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
const Component = createComponent<Props, HTMLElement>({
|
|
264
|
+
displayName: "Component",
|
|
265
|
+
__kui: { role: "display" },
|
|
266
|
+
render(props, ref) {
|
|
267
|
+
return <div ref={ref} />;
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Metadata gives tooling, generators, and composition systems a machine-readable description of a component:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
interface KuiMeta {
|
|
276
|
+
role?: "action" | "formControl" | "overlay" | "layout"
|
|
277
|
+
| "display" | "disclosure" | "navigation" | string;
|
|
278
|
+
groupSlot?: string; // logical compound group, e.g. "Tooltip"
|
|
279
|
+
formControl?: "single" | "compound";
|
|
280
|
+
supports?: { disabled?: boolean; invalid?: boolean; required?: boolean; [key: string]: boolean | undefined };
|
|
281
|
+
skeleton?: KuiSkeletonHint;
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Use `hasKuiMeta(value)` to detect metadata at runtime.
|
|
286
|
+
|
|
287
|
+
**Compound components.** Parts like `Tooltip.Trigger`, `Tooltip.Content`, and `Tooltip.Arrow` carry metadata (e.g. `groupSlot: "Tooltip"`) so tools can see they belong together. The root doesn't need `createComponent()`. A root that only owns state and context renders no DOM, so it has no ref or metadata to forward.
|
|
288
|
+
|
|
289
|
+
### Prop composition
|
|
290
|
+
|
|
291
|
+
`createFromProps()` merges props deterministically. Later layers win:
|
|
292
|
+
|
|
293
|
+
```
|
|
294
|
+
providerDefaults → defaultProps → generatedProps → consumerProps
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
createFromProps({ providerDefaults, defaultProps, generatedProps, consumerProps });
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
- **`className`**: concatenated in priority order.
|
|
302
|
+
- **`style`**: shallow merged.
|
|
303
|
+
- **Event handlers** (anything matching `on[A-Z]`): composed, not overwritten, running from lowest to highest priority.
|
|
304
|
+
- **Refs**: not handled here. Use `composeRefs(...)`.
|
|
305
|
+
|
|
306
|
+
### Controllable state
|
|
307
|
+
|
|
308
|
+
`useControllableState()` is the standard controlled/uncontrolled pattern:
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
const [value, setValue] = useControllableState({ value, defaultValue, onChange });
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
A component is controlled when `value` is defined. In controlled mode, `setValue()` only calls `onChange()`. In uncontrolled mode it also updates internal state. Updates are skipped when the next value is `Object.is`-equal to the current one.
|
|
315
|
+
|
|
316
|
+
### Responsive styles
|
|
317
|
+
|
|
318
|
+
Responsive styles are generated from the active theme's breakpoint tokens, so components never hard-code breakpoints. The generated rules come back together with a data attribute that scopes them to the element.
|
|
319
|
+
|
|
320
|
+
## Design principles
|
|
321
|
+
|
|
322
|
+
- **Tokens before components.** Components consume the design system; they don't invent their own visual language.
|
|
323
|
+
- **Meaning over raw values.** Prefer `{colors.brand}` to `{colors.blue.500}` when the value is a role.
|
|
324
|
+
- **Strict primitives, flexible semantics.** Primitives stay terminal; semantic tokens do the referencing.
|
|
325
|
+
- **Metadata is part of the contract.** A component's role, relationships, and supported states are described to tooling, not just rendered.
|
|
326
|
+
- **Predictable composition.** Prop merging, theme extension, recipe resolution, and token resolution all follow explicit precedence.
|
|
327
|
+
- **Defaults belong to the implementation.** Core defines the system; Kreativ UI defines the look.
|
|
328
|
+
|
|
329
|
+
## Contributing
|
|
330
|
+
|
|
331
|
+
While we finish extracting Core from Kreativ UI, please prefer:
|
|
332
|
+
|
|
333
|
+
- small, composable primitives
|
|
334
|
+
- explicit types
|
|
335
|
+
- predictable merge behavior
|
|
336
|
+
- compile-time validation where possible, runtime validation where necessary
|
|
337
|
+
- framework-specific behavior kept at the runtime boundary
|
|
338
|
+
- no dependency on Kreativ UI defaults
|
|
339
|
+
|
|
340
|
+
## License
|
|
341
|
+
|
|
342
|
+
See the repository license.
|