stoop 0.2.1 → 0.3.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/api/create-theme.js +43 -0
- package/dist/api/css.js +20 -0
- package/dist/api/global-css.d.ts +0 -11
- package/dist/api/global-css.js +89 -0
- package/dist/api/keyframes.js +95 -0
- package/dist/api/provider.d.ts +3 -3
- package/dist/api/provider.js +109 -0
- package/dist/api/styled.js +170 -0
- package/dist/api/use-theme.js +21 -0
- package/dist/constants.d.ts +2 -3
- package/dist/constants.js +144 -0
- package/dist/core/cache.d.ts +5 -9
- package/dist/core/cache.js +68 -0
- package/dist/core/compiler.js +198 -0
- package/dist/core/theme-manager.d.ts +0 -8
- package/dist/core/theme-manager.js +97 -0
- package/dist/core/variants.js +32 -0
- package/dist/create-stoop-server.d.ts +33 -0
- package/dist/create-stoop-server.js +130 -0
- package/dist/create-stoop.d.ts +2 -5
- package/dist/create-stoop.js +147 -0
- package/dist/index.js +5 -13
- package/dist/inject/browser.d.ts +2 -3
- package/dist/inject/browser.js +149 -0
- package/dist/inject/dedup.js +38 -0
- package/dist/inject/index.d.ts +0 -1
- package/dist/inject/index.js +75 -0
- package/dist/inject/ssr.d.ts +0 -1
- package/dist/inject/ssr.js +46 -0
- package/dist/ssr.d.ts +21 -0
- package/dist/ssr.js +19 -0
- package/dist/types/index.d.ts +10 -5
- package/dist/types/index.js +5 -0
- package/dist/types/react-polymorphic-types.d.ts +4 -8
- package/dist/utils/environment.d.ts +6 -0
- package/dist/utils/environment.js +12 -0
- package/dist/utils/string.d.ts +16 -9
- package/dist/utils/string.js +253 -0
- package/dist/utils/theme-map.d.ts +0 -3
- package/dist/utils/theme-map.js +97 -0
- package/dist/utils/theme-validation.js +36 -0
- package/dist/utils/theme.d.ts +3 -3
- package/dist/utils/theme.js +279 -0
- package/dist/utils/type-guards.js +38 -0
- package/dist/utils/utilities.js +43 -0
- package/package.json +24 -25
- package/LICENSE.md +0 -21
- package/README.md +0 -180
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme token resolution utilities.
|
|
3
|
+
* Converts theme tokens to CSS variables for runtime theme switching.
|
|
4
|
+
* Uses cached token index for efficient lookups and theme comparison.
|
|
5
|
+
*/
|
|
6
|
+
import { escapeCSSVariableValue, sanitizeCSSVariableName } from "./string";
|
|
7
|
+
import { getScaleForProperty } from "./theme-map";
|
|
8
|
+
import { isCSSObject, isThemeObject } from "./type-guards";
|
|
9
|
+
const tokenIndexCache = new WeakMap();
|
|
10
|
+
// Pre-compiled regex for token replacement (matches $primary, -$medium, $colors.primary, etc.)
|
|
11
|
+
const TOKEN_REGEX = /(-?\$[a-zA-Z][a-zA-Z0-9]*(?:\$[a-zA-Z][a-zA-Z0-9]*)?(?:\.[a-zA-Z][a-zA-Z0-9]*)?)/g;
|
|
12
|
+
/**
|
|
13
|
+
* Builds an index of all tokens in a theme for fast lookups.
|
|
14
|
+
* Caches the result to avoid rebuilding on every token resolution.
|
|
15
|
+
*
|
|
16
|
+
* @param theme - Theme to index
|
|
17
|
+
* @returns Map of token names to their paths in the theme
|
|
18
|
+
*/
|
|
19
|
+
function buildTokenIndex(theme) {
|
|
20
|
+
const cached = tokenIndexCache.get(theme);
|
|
21
|
+
if (cached) {
|
|
22
|
+
return cached;
|
|
23
|
+
}
|
|
24
|
+
const index = new Map();
|
|
25
|
+
function processThemeObject(obj, path = []) {
|
|
26
|
+
const keys = Object.keys(obj);
|
|
27
|
+
for (const key of keys) {
|
|
28
|
+
const value = obj[key];
|
|
29
|
+
const currentPath = [...path, key];
|
|
30
|
+
if (isThemeObject(value)) {
|
|
31
|
+
processThemeObject(value, currentPath);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
const existing = index.get(key);
|
|
35
|
+
if (existing) {
|
|
36
|
+
existing.push(currentPath);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
index.set(key, [currentPath]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
processThemeObject(theme);
|
|
45
|
+
for (const [, paths] of index.entries()) {
|
|
46
|
+
if (paths.length > 1) {
|
|
47
|
+
paths.sort((a, b) => {
|
|
48
|
+
const depthDiff = a.length - b.length;
|
|
49
|
+
if (depthDiff !== 0) {
|
|
50
|
+
return depthDiff;
|
|
51
|
+
}
|
|
52
|
+
const pathA = a.join(".");
|
|
53
|
+
const pathB = b.join(".");
|
|
54
|
+
return pathA.localeCompare(pathB);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
tokenIndexCache.set(theme, index);
|
|
59
|
+
return index;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Checks if two themes have the same top-level keys (excluding 'media').
|
|
63
|
+
*
|
|
64
|
+
* @param theme1 - First theme
|
|
65
|
+
* @param theme2 - Second theme
|
|
66
|
+
* @returns True if themes have same keys
|
|
67
|
+
*/
|
|
68
|
+
function themesHaveSameKeys(theme1, theme2) {
|
|
69
|
+
const keys1 = Object.keys(theme1).filter((key) => key !== "media");
|
|
70
|
+
const keys2 = Object.keys(theme2).filter((key) => key !== "media");
|
|
71
|
+
if (keys1.length !== keys2.length) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
for (const key of keys1) {
|
|
75
|
+
if (!(key in theme2)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Compares two themes for structural and value equality.
|
|
83
|
+
* Excludes 'media' property from comparison.
|
|
84
|
+
*
|
|
85
|
+
* @param theme1 - First theme to compare
|
|
86
|
+
* @param theme2 - Second theme to compare
|
|
87
|
+
* @returns True if themes are equal
|
|
88
|
+
*/
|
|
89
|
+
export function themesAreEqual(theme1, theme2) {
|
|
90
|
+
if (theme1 === theme2) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
if (!theme1 || !theme2) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (!themesHaveSameKeys(theme1, theme2)) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const theme1WithoutMedia = { ...theme1 };
|
|
100
|
+
const theme2WithoutMedia = { ...theme2 };
|
|
101
|
+
delete theme1WithoutMedia.media;
|
|
102
|
+
delete theme2WithoutMedia.media;
|
|
103
|
+
return JSON.stringify(theme1WithoutMedia) === JSON.stringify(theme2WithoutMedia);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Finds a token in the theme, optionally scoped to a specific scale.
|
|
107
|
+
*
|
|
108
|
+
* @param theme - Theme to search
|
|
109
|
+
* @param tokenName - Token name to find
|
|
110
|
+
* @param scale - Optional scale to search within first
|
|
111
|
+
* @returns Path to token or null if not found
|
|
112
|
+
*/
|
|
113
|
+
function findTokenInTheme(theme, tokenName, scale) {
|
|
114
|
+
if (scale && scale in theme) {
|
|
115
|
+
const scaleValue = theme[scale];
|
|
116
|
+
if (scaleValue &&
|
|
117
|
+
typeof scaleValue === "object" &&
|
|
118
|
+
!Array.isArray(scaleValue) &&
|
|
119
|
+
tokenName in scaleValue) {
|
|
120
|
+
return [scale, tokenName];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const index = buildTokenIndex(theme);
|
|
124
|
+
const paths = index.get(tokenName);
|
|
125
|
+
if (!paths || paths.length === 0) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return paths[0];
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Converts a theme token string to a CSS variable reference.
|
|
132
|
+
*
|
|
133
|
+
* @param token - Token string (e.g., "$primary" or "$colors$primary")
|
|
134
|
+
* @param theme - Optional theme for token resolution
|
|
135
|
+
* @param property - Optional CSS property name for scale detection
|
|
136
|
+
* @param themeMap - Optional theme scale mappings
|
|
137
|
+
* @returns CSS variable reference string
|
|
138
|
+
*/
|
|
139
|
+
export function tokenToCSSVar(token, theme, property, themeMap) {
|
|
140
|
+
if (!token.startsWith("$")) {
|
|
141
|
+
return token;
|
|
142
|
+
}
|
|
143
|
+
const tokenName = token.slice(1);
|
|
144
|
+
// Handle explicit scale: $colors$primary or $colors.primary
|
|
145
|
+
if (tokenName.includes("$") || tokenName.includes(".")) {
|
|
146
|
+
const parts = tokenName.includes("$") ? tokenName.split("$") : tokenName.split(".");
|
|
147
|
+
const sanitizedParts = parts.map((part) => sanitizeCSSVariableName(part));
|
|
148
|
+
const cssVarName = `--${sanitizedParts.join("-")}`;
|
|
149
|
+
return `var(${cssVarName})`;
|
|
150
|
+
}
|
|
151
|
+
// Handle shorthand token: $primary
|
|
152
|
+
if (theme && property) {
|
|
153
|
+
const scale = getScaleForProperty(property, themeMap);
|
|
154
|
+
if (scale) {
|
|
155
|
+
const foundPath = findTokenInTheme(theme, tokenName, scale);
|
|
156
|
+
if (foundPath) {
|
|
157
|
+
const sanitizedParts = foundPath.map((part) => sanitizeCSSVariableName(part));
|
|
158
|
+
const cssVarName = `--${sanitizedParts.join("-")}`;
|
|
159
|
+
return `var(${cssVarName})`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const index = buildTokenIndex(theme);
|
|
163
|
+
const paths = index.get(tokenName);
|
|
164
|
+
if (paths && paths.length > 1) {
|
|
165
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
166
|
+
const scaleInfo = scale
|
|
167
|
+
? `Property "${property}" maps to "${scale}" scale, but token not found there. `
|
|
168
|
+
: `No scale mapping found for property "${property}". `;
|
|
169
|
+
// eslint-disable-next-line no-console
|
|
170
|
+
console.warn(`[Stoop] Ambiguous token "$${tokenName}" found in multiple categories: ${paths.map((p) => p.join(".")).join(", ")}. ` +
|
|
171
|
+
`${scaleInfo}` +
|
|
172
|
+
`Using "${paths[0].join(".")}" (deterministic: shorter paths first, then alphabetical). ` +
|
|
173
|
+
`Use full path "$${paths[0].join(".")}" to be explicit.`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const foundPath = findTokenInTheme(theme, tokenName);
|
|
177
|
+
if (foundPath) {
|
|
178
|
+
const sanitizedParts = foundPath.map((part) => sanitizeCSSVariableName(part));
|
|
179
|
+
const cssVarName = `--${sanitizedParts.join("-")}`;
|
|
180
|
+
return `var(${cssVarName})`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (theme) {
|
|
184
|
+
const index = buildTokenIndex(theme);
|
|
185
|
+
const paths = index.get(tokenName);
|
|
186
|
+
if (paths && paths.length > 1) {
|
|
187
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
188
|
+
// eslint-disable-next-line no-console
|
|
189
|
+
console.warn(`[Stoop] Ambiguous token "$${tokenName}" found in multiple categories: ${paths.map((p) => p.join(".")).join(", ")}. ` +
|
|
190
|
+
`Using "${paths[0].join(".")}" (deterministic: shorter paths first, then alphabetical). ` +
|
|
191
|
+
`Use full path "$${paths[0].join(".")}" to be explicit, or use with a CSS property for automatic resolution.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const foundPath = findTokenInTheme(theme, tokenName);
|
|
195
|
+
if (foundPath) {
|
|
196
|
+
const sanitizedParts = foundPath.map((part) => sanitizeCSSVariableName(part));
|
|
197
|
+
const cssVarName = `--${sanitizedParts.join("-")}`;
|
|
198
|
+
return `var(${cssVarName})`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const sanitizedTokenName = sanitizeCSSVariableName(tokenName);
|
|
202
|
+
const cssVarName = `--${sanitizedTokenName}`;
|
|
203
|
+
return `var(${cssVarName})`;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Generates CSS custom properties from a theme object.
|
|
207
|
+
*
|
|
208
|
+
* @param theme - Theme object to convert to CSS variables
|
|
209
|
+
* @param prefix - Optional prefix for CSS variable names
|
|
210
|
+
* @returns CSS string with :root selector and CSS variables
|
|
211
|
+
*/
|
|
212
|
+
export function generateCSSVariables(theme, prefix = "stoop") {
|
|
213
|
+
const rootSelector = ":root";
|
|
214
|
+
const variables = [];
|
|
215
|
+
function processThemeObject(obj, path = []) {
|
|
216
|
+
const keys = Object.keys(obj).sort();
|
|
217
|
+
for (const key of keys) {
|
|
218
|
+
// Media queries cannot be CSS variables
|
|
219
|
+
if (key === "media") {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const value = obj[key];
|
|
223
|
+
const currentPath = [...path, key];
|
|
224
|
+
if (isThemeObject(value)) {
|
|
225
|
+
processThemeObject(value, currentPath);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const sanitizedParts = currentPath.map((part) => sanitizeCSSVariableName(part));
|
|
229
|
+
const varName = `--${sanitizedParts.join("-")}`;
|
|
230
|
+
const escapedValue = typeof value === "string" || typeof value === "number"
|
|
231
|
+
? escapeCSSVariableValue(value)
|
|
232
|
+
: String(value);
|
|
233
|
+
variables.push(` ${varName}: ${escapedValue};`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
processThemeObject(theme);
|
|
238
|
+
if (variables.length === 0) {
|
|
239
|
+
return "";
|
|
240
|
+
}
|
|
241
|
+
return `${rootSelector} {\n${variables.join("\n")}\n}`;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Recursively replaces theme tokens with CSS variable references in a CSS object.
|
|
245
|
+
*
|
|
246
|
+
* @param obj - CSS object to process
|
|
247
|
+
* @param theme - Optional theme for token resolution
|
|
248
|
+
* @param themeMap - Optional theme scale mappings
|
|
249
|
+
* @param property - Optional CSS property name for scale detection
|
|
250
|
+
* @returns CSS object with tokens replaced by CSS variables
|
|
251
|
+
*/
|
|
252
|
+
export function replaceThemeTokensWithVars(obj, theme, themeMap, property) {
|
|
253
|
+
if (!obj || typeof obj !== "object") {
|
|
254
|
+
return obj;
|
|
255
|
+
}
|
|
256
|
+
const result = {};
|
|
257
|
+
const keys = Object.keys(obj).sort();
|
|
258
|
+
for (const key of keys) {
|
|
259
|
+
const value = obj[key];
|
|
260
|
+
if (isCSSObject(value)) {
|
|
261
|
+
result[key] = replaceThemeTokensWithVars(value, theme, themeMap, undefined);
|
|
262
|
+
}
|
|
263
|
+
else if (typeof value === "string" && value.includes("$")) {
|
|
264
|
+
const cssProperty = property || key;
|
|
265
|
+
result[key] = value.replace(TOKEN_REGEX, (token) => {
|
|
266
|
+
if (token.startsWith("-$")) {
|
|
267
|
+
const positiveToken = token.slice(1);
|
|
268
|
+
const cssVar = tokenToCSSVar(positiveToken, theme, cssProperty, themeMap);
|
|
269
|
+
return `calc(-1 * ${cssVar})`;
|
|
270
|
+
}
|
|
271
|
+
return tokenToCSSVar(token, theme, cssProperty, themeMap);
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
result[key] = value;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type guard utilities.
|
|
3
|
+
* Provides runtime type checking for CSS objects, themes, and styled component references.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Type guard for CSS objects.
|
|
7
|
+
*
|
|
8
|
+
* @param value - Value to check
|
|
9
|
+
* @returns True if value is a CSS object
|
|
10
|
+
*/
|
|
11
|
+
export function isCSSObject(value) {
|
|
12
|
+
return typeof value === "object" && value !== null;
|
|
13
|
+
}
|
|
14
|
+
function isStyledComponentRef(value) {
|
|
15
|
+
return (typeof value === "object" &&
|
|
16
|
+
value !== null &&
|
|
17
|
+
"__isStoopStyled" in value &&
|
|
18
|
+
"__stoopClassName" in value &&
|
|
19
|
+
value.__isStoopStyled === true);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Type guard for valid CSS objects (excludes styled component references).
|
|
23
|
+
*
|
|
24
|
+
* @param value - Value to check
|
|
25
|
+
* @returns True if value is a valid CSS object
|
|
26
|
+
*/
|
|
27
|
+
export function isValidCSSObject(value) {
|
|
28
|
+
return isCSSObject(value) && !isStyledComponentRef(value);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Type guard for theme objects.
|
|
32
|
+
*
|
|
33
|
+
* @param value - Value to check
|
|
34
|
+
* @returns True if value is a theme object
|
|
35
|
+
*/
|
|
36
|
+
export function isThemeObject(value) {
|
|
37
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
38
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility function application.
|
|
3
|
+
* Applies utility functions (e.g., px, py) to transform shorthand properties into CSS.
|
|
4
|
+
* Recursively processes nested CSS objects.
|
|
5
|
+
*/
|
|
6
|
+
import { isCSSObject } from "./type-guards";
|
|
7
|
+
/**
|
|
8
|
+
* Applies utility functions to transform shorthand properties into CSS.
|
|
9
|
+
*
|
|
10
|
+
* @param styles - CSS object to process
|
|
11
|
+
* @param utils - Optional utility functions object
|
|
12
|
+
* @returns CSS object with utilities applied
|
|
13
|
+
*/
|
|
14
|
+
export function applyUtilities(styles, utils) {
|
|
15
|
+
if (!utils || !styles || typeof styles !== "object") {
|
|
16
|
+
return styles;
|
|
17
|
+
}
|
|
18
|
+
const result = {};
|
|
19
|
+
const utilityKeys = Object.keys(utils);
|
|
20
|
+
for (const key in styles) {
|
|
21
|
+
const value = styles[key];
|
|
22
|
+
if (utilityKeys.includes(key) && utils[key]) {
|
|
23
|
+
try {
|
|
24
|
+
const utilityResult = utils[key](value);
|
|
25
|
+
if (utilityResult && typeof utilityResult === "object") {
|
|
26
|
+
for (const utilKey in utilityResult) {
|
|
27
|
+
result[utilKey] = utilityResult[utilKey];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
result[key] = value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (isCSSObject(value)) {
|
|
36
|
+
result[key] = applyUtilities(value, utils);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
result[key] = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stoop",
|
|
3
3
|
"description": "CSS-in-JS library with type inference, theme creation, and variants support.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"author": "Jackson Dolman <mail@dolmios.com>",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"repository": {
|
|
@@ -16,22 +16,24 @@
|
|
|
16
16
|
"react-polymorphic-types": "^2.0.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@types/node": "^22.
|
|
20
|
-
"@types/react": "^19.
|
|
21
|
-
"@types/react-dom": "^19.
|
|
22
|
-
"bun-types": "^1.2.
|
|
23
|
-
"
|
|
24
|
-
"eslint-config-dolmios": "^2.0.10",
|
|
25
|
-
"prettier": "^3.6.2",
|
|
26
|
-
"react": "^19.1.1",
|
|
27
|
-
"react-dom": "^19.1.1",
|
|
28
|
-
"typescript": "^5.9.2"
|
|
19
|
+
"@types/node": "^22.10.5",
|
|
20
|
+
"@types/react": "^19.0.6",
|
|
21
|
+
"@types/react-dom": "^19.0.2",
|
|
22
|
+
"bun-types": "^1.2.6",
|
|
23
|
+
"typescript": "^5.7.2"
|
|
29
24
|
},
|
|
30
25
|
"exports": {
|
|
31
26
|
".": {
|
|
32
27
|
"types": "./dist/index.d.ts",
|
|
33
28
|
"import": "./dist/index.js",
|
|
34
|
-
"require": "./dist/index.js"
|
|
29
|
+
"require": "./dist/index.js",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./ssr": {
|
|
33
|
+
"types": "./dist/ssr.d.ts",
|
|
34
|
+
"import": "./dist/ssr.js",
|
|
35
|
+
"require": "./dist/ssr.js",
|
|
36
|
+
"default": "./dist/ssr.js"
|
|
35
37
|
}
|
|
36
38
|
},
|
|
37
39
|
"files": [
|
|
@@ -69,21 +71,18 @@
|
|
|
69
71
|
"react": ">=16.8.0",
|
|
70
72
|
"react-dom": ">=16.8.0"
|
|
71
73
|
},
|
|
72
|
-
"prettier": "eslint-config-dolmios/configs/prettier",
|
|
73
|
-
"sideEffects": false,
|
|
74
|
-
"type": "module",
|
|
75
74
|
"scripts": {
|
|
76
|
-
"build": "bun run build:clean && bun run build:
|
|
75
|
+
"build": "bun run build:clean && bun run build:types",
|
|
77
76
|
"build:clean": "rm -rf dist",
|
|
78
|
-
"build:copy-declarations": "mkdir -p dist/types && cp src/types/react-polymorphic-types.d.ts dist/types/react-polymorphic-types.d.ts 2>/dev/null ||
|
|
79
|
-
"build:js": "bun build ./src/index.ts --outdir ./dist --target
|
|
80
|
-
"build:types": "bunx tsc
|
|
81
|
-
"
|
|
82
|
-
"format": "prettier --write src preview",
|
|
83
|
-
"kill": "lsof -ti:3000,420 | xargs kill -9 2>/dev/null || true",
|
|
84
|
-
"lint": "eslint . --ext .ts,.tsx --fix",
|
|
77
|
+
"build:copy-declarations": "mkdir -p dist/types && cp src/types/react-polymorphic-types.d.ts dist/types/react-polymorphic-types.d.ts 2>/dev/null || :",
|
|
78
|
+
"build:js": "NODE_ENV=production bun build ./src/index.ts --outdir ./dist --target node --minify --jsx=automatic --splitting --external react --external react-dom --external react/jsx-runtime --define process.env.NODE_ENV='\"production\"'",
|
|
79
|
+
"build:types": "bunx tsc --project tsconfig.build.json && bun run build:copy-declarations",
|
|
80
|
+
"prepare": "bun run build",
|
|
85
81
|
"test": "bun test",
|
|
86
82
|
"test:coverage": "bun test --coverage",
|
|
87
83
|
"test:watch": "bun test --watch"
|
|
88
|
-
}
|
|
89
|
-
|
|
84
|
+
},
|
|
85
|
+
"sideEffects": false,
|
|
86
|
+
"type": "module"
|
|
87
|
+
}
|
|
88
|
+
|
package/LICENSE.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Jackson Dolman
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/README.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
# Stoop
|
|
2
|
-
|
|
3
|
-
CSS-in-JS library with type inference, theme creation, and variants support.
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/stoop)
|
|
6
|
-
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
|
|
8
|
-
> **Warning: Not Production Ready**
|
|
9
|
-
> Stoop is currently in active development and is not recommended for production use. The API may change, and there may be bugs or missing features. Use at your own risk.
|
|
10
|
-
|
|
11
|
-
## About
|
|
12
|
-
|
|
13
|
-
Stoop is a CSS-in-JS library—a TypeScript-first approach to styling that provides type-safe CSS objects with full type inference. Similar to [Stitches](https://stitches.dev) and [Vanilla Extract](https://vanilla-extract.style), Stoop focuses on type safety and developer experience.
|
|
14
|
-
|
|
15
|
-
Stoop is a minimalist implementation of Stitches' high-level features. It provides a similar API for `styled`, `css`, and variants, but omits several Stitches features.
|
|
16
|
-
|
|
17
|
-
**What's missing compared to Stitches:**
|
|
18
|
-
- Compound variants
|
|
19
|
-
- Build-time CSS extraction (runtime-only)
|
|
20
|
-
- Advanced utility functions (basic support only)
|
|
21
|
-
- Additional Stitches APIs
|
|
22
|
-
|
|
23
|
-
If you need these features, consider [Vanilla Extract](https://vanilla-extract.style) or [styled-components](https://styled-components.com).
|
|
24
|
-
|
|
25
|
-
## Features
|
|
26
|
-
|
|
27
|
-
- Type-safe theming with TypeScript inference
|
|
28
|
-
- CSS variables for theme tokens
|
|
29
|
-
- Variant system for component variations
|
|
30
|
-
- Utility functions for custom CSS transformations
|
|
31
|
-
- Multiple themes with `createTheme()`
|
|
32
|
-
- SSR support via `getCssText()`
|
|
33
|
-
- React 19+ required (Next.js Pages & App Router supported)
|
|
34
|
-
|
|
35
|
-
## Installation
|
|
36
|
-
|
|
37
|
-
```sh
|
|
38
|
-
npm install stoop
|
|
39
|
-
# or
|
|
40
|
-
bun add stoop
|
|
41
|
-
# or
|
|
42
|
-
yarn add stoop
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Quick Start
|
|
46
|
-
|
|
47
|
-
```tsx
|
|
48
|
-
import { createStoop } from "stoop";
|
|
49
|
-
|
|
50
|
-
const { styled, css, createTheme, globalCss, keyframes, ThemeContext } = createStoop({
|
|
51
|
-
theme: {
|
|
52
|
-
colors: {
|
|
53
|
-
primary: "#0070f3",
|
|
54
|
-
background: "#ffffff",
|
|
55
|
-
text: "#000000",
|
|
56
|
-
},
|
|
57
|
-
space: {
|
|
58
|
-
small: "8px",
|
|
59
|
-
medium: "16px",
|
|
60
|
-
large: "24px",
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
const Button = styled("button", {
|
|
66
|
-
padding: "$medium",
|
|
67
|
-
backgroundColor: "$primary",
|
|
68
|
-
}, {
|
|
69
|
-
variant: {
|
|
70
|
-
primary: { backgroundColor: "$primary" },
|
|
71
|
-
secondary: { backgroundColor: "$secondary" },
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
<Button variant="primary">Click me</Button>
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
See [GUIDE.md](./docs/GUIDE.md) for complete setup instructions.
|
|
79
|
-
|
|
80
|
-
## Documentation
|
|
81
|
-
|
|
82
|
-
- **[GUIDE.md](./docs/GUIDE.md)** - Step-by-step setup and usage guide
|
|
83
|
-
- **[API.md](./docs/API.md)** - Complete API reference
|
|
84
|
-
- **[ARCHITECTURE.md](./docs/ARCHITECTURE.md)** - Internal implementation details
|
|
85
|
-
|
|
86
|
-
## API Overview
|
|
87
|
-
|
|
88
|
-
### `createStoop(config)`
|
|
89
|
-
|
|
90
|
-
Creates a Stoop instance. Returns: `styled`, `css`, `createTheme`, `globalCss`, `keyframes`, `getCssText`, `warmCache`, `ThemeContext`, `theme`, `config`.
|
|
91
|
-
|
|
92
|
-
See [API.md](./docs/API.md) for complete API documentation.
|
|
93
|
-
|
|
94
|
-
### Theme Tokens
|
|
95
|
-
|
|
96
|
-
Use `$` prefix for theme tokens. Shorthand `$token` searches all scales; explicit `$scale.token` specifies the scale.
|
|
97
|
-
|
|
98
|
-
```tsx
|
|
99
|
-
{
|
|
100
|
-
color: "$primary", // Shorthand (preferred)
|
|
101
|
-
padding: "$medium", // Property-aware resolution
|
|
102
|
-
fontSize: "$fontSizes.small", // Explicit scale
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Tokens resolve to CSS variables (`var(--colors-primary)`), enabling instant theme switching without recompiling CSS.
|
|
107
|
-
|
|
108
|
-
### Variants
|
|
109
|
-
|
|
110
|
-
Variants create component variations via props:
|
|
111
|
-
|
|
112
|
-
```tsx
|
|
113
|
-
const Button = styled("button", {}, {
|
|
114
|
-
variant: {
|
|
115
|
-
primary: { backgroundColor: "$primary" },
|
|
116
|
-
secondary: { backgroundColor: "$secondary" },
|
|
117
|
-
},
|
|
118
|
-
size: {
|
|
119
|
-
small: { padding: "$small" },
|
|
120
|
-
large: { padding: "$large" },
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
<Button variant="primary" size="small" />
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
## Migration from Stitches
|
|
128
|
-
|
|
129
|
-
Stoop provides a similar API for the features it implements. Key differences:
|
|
130
|
-
- CSS variables for theme tokens
|
|
131
|
-
- Simple theme system with `createTheme()`
|
|
132
|
-
- Full TypeScript inference
|
|
133
|
-
|
|
134
|
-
See [GUIDE.md](./docs/GUIDE.md) for migration examples.
|
|
135
|
-
|
|
136
|
-
## Related Projects
|
|
137
|
-
|
|
138
|
-
**CSS-in-JS Libraries:**
|
|
139
|
-
- [Stitches](https://stitches.dev) - Original library Stoop is based on (no longer maintained)
|
|
140
|
-
- [Stitches](https://stitches.dev) - CSS-in-JS library (original inspiration)
|
|
141
|
-
- [Vanilla Extract](https://vanilla-extract.style) - Zero-runtime CSS-in-JS
|
|
142
|
-
- [styled-components](https://styled-components.com) - CSS-in-JS library
|
|
143
|
-
- [Emotion](https://emotion.sh) - CSS-in-JS library
|
|
144
|
-
- [Goober](https://goober.rocks) - Lightweight CSS-in-JS library
|
|
145
|
-
- [JSS](https://cssinjs.org) - Framework-agnostic CSS-in-JS
|
|
146
|
-
- [Compiled](https://compiledcssinjs.com) - Compile-time CSS-in-JS
|
|
147
|
-
- [Stylex](https://stylexjs.com) - Facebook's build-time CSS-in-JS
|
|
148
|
-
- [Panda CSS](https://panda-css.com) - CSS-in-JS with build-time generation
|
|
149
|
-
- [Linaria](https://linaria.dev) - Zero-runtime CSS-in-JS
|
|
150
|
-
- [Treat](https://seek-oss.github.io/treat) - Themeable CSS-in-JS
|
|
151
|
-
|
|
152
|
-
**Variant Systems:**
|
|
153
|
-
- [CVA](https://cva.style) - Class Variance Authority for component variants
|
|
154
|
-
- [clsx](https://github.com/lukeed/clsx) - Tiny utility for constructing className strings
|
|
155
|
-
|
|
156
|
-
**Utility-First:**
|
|
157
|
-
- [Tailwind CSS](https://tailwindcss.com) - Utility-first CSS framework
|
|
158
|
-
- [UnoCSS](https://unocss.dev) - Instant atomic CSS engine
|
|
159
|
-
|
|
160
|
-
**Component Libraries:**
|
|
161
|
-
- [Radix UI](https://www.radix-ui.com) - Unstyled, accessible component primitives
|
|
162
|
-
- [Chakra UI](https://chakra-ui.com) - Component library built on Emotion
|
|
163
|
-
- [Mantine](https://mantine.dev) - React components library with Emotion
|
|
164
|
-
|
|
165
|
-
## Development
|
|
166
|
-
|
|
167
|
-
```sh
|
|
168
|
-
bun install
|
|
169
|
-
bun run dev
|
|
170
|
-
bun run build
|
|
171
|
-
bun run lint
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
## Contributing
|
|
175
|
-
|
|
176
|
-
Contributions welcome. See [ARCHITECTURE.md](./docs/ARCHITECTURE.md) for implementation details.
|
|
177
|
-
|
|
178
|
-
## License
|
|
179
|
-
|
|
180
|
-
MIT © [Jackson Dolman](https://github.com/dolmios)
|