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