stoop 0.6.1 → 0.6.2
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/core-api.d.ts +2 -0
- package/dist/api/core-api.js +171 -0
- package/dist/api/styled.d.ts +3 -0
- package/dist/api/styled.js +467 -0
- package/dist/api/theme-provider.d.ts +3 -0
- package/dist/api/theme-provider.js +199 -0
- package/dist/constants.js +154 -0
- package/dist/core/cache.js +66 -0
- package/dist/core/compiler.js +408 -0
- package/dist/core/stringify.js +150 -0
- package/dist/core/theme-manager.js +97 -0
- package/dist/create-stoop-internal.d.ts +2 -2
- package/dist/create-stoop-internal.js +119 -0
- package/dist/create-stoop-ssr.d.ts +2 -0
- package/dist/create-stoop-ssr.js +22 -16
- package/dist/create-stoop.d.ts +6 -25
- package/dist/create-stoop.js +48 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -17
- package/dist/inject.js +293 -0
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.js +5 -2
- package/dist/utils/helpers.js +157 -0
- package/dist/utils/storage.js +130 -0
- package/dist/utils/theme-utils.js +328 -0
- package/dist/utils/theme.js +430 -0
- package/package.json +10 -14
package/dist/api/core-api.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Core API functions.
|
|
3
3
|
* Consolidates theme creation, CSS class generation, and keyframes animation APIs.
|
|
4
|
+
*
|
|
5
|
+
* SERVER-SAFE: No React dependencies, can be used in server components and SSR.
|
|
4
6
|
*/
|
|
5
7
|
import type { CSS, Theme, ThemeScale, UtilityFunction } from "../types";
|
|
6
8
|
/**
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core API functions.
|
|
3
|
+
* Consolidates theme creation, CSS class generation, and keyframes animation APIs.
|
|
4
|
+
*
|
|
5
|
+
* SERVER-SAFE: No React dependencies, can be used in server components and SSR.
|
|
6
|
+
*/
|
|
7
|
+
import { LRUCache } from "../core/cache";
|
|
8
|
+
import { compileCSS, cssObjectToString } from "../core/compiler";
|
|
9
|
+
import { sanitizeCSSPropertyName } from "../core/stringify";
|
|
10
|
+
import { mergeThemes } from "../core/theme-manager";
|
|
11
|
+
import { injectCSS } from "../inject";
|
|
12
|
+
import { applyUtilities, validateTheme } from "../utils/helpers";
|
|
13
|
+
import { replaceThemeTokensWithVars } from "../utils/theme";
|
|
14
|
+
import { hashObject, sanitizePrefix, validateKeyframeKey } from "../utils/theme-utils";
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Theme Creation API
|
|
17
|
+
// ============================================================================
|
|
18
|
+
/**
|
|
19
|
+
* Creates a function that extends a base theme with overrides.
|
|
20
|
+
* The returned function deep merges theme overrides with the base theme.
|
|
21
|
+
*
|
|
22
|
+
* @param baseTheme - Base theme to extend
|
|
23
|
+
* @returns Function that accepts theme overrides and returns a merged theme
|
|
24
|
+
*/
|
|
25
|
+
export function createTheme(baseTheme) {
|
|
26
|
+
return function createTheme(themeOverrides = {}) {
|
|
27
|
+
const validatedOverrides = validateTheme(themeOverrides);
|
|
28
|
+
return mergeThemes(baseTheme, validatedOverrides);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// CSS Class Generation API
|
|
33
|
+
// ============================================================================
|
|
34
|
+
/**
|
|
35
|
+
* Creates a CSS function that compiles CSS objects into class names.
|
|
36
|
+
*
|
|
37
|
+
* @param defaultTheme - Default theme for token resolution
|
|
38
|
+
* @param prefix - Optional prefix for generated class names
|
|
39
|
+
* @param media - Optional media query breakpoints
|
|
40
|
+
* @param utils - Optional utility functions
|
|
41
|
+
* @param themeMap - Optional theme scale mappings
|
|
42
|
+
* @returns Function that accepts CSS objects and returns class names
|
|
43
|
+
*/
|
|
44
|
+
export function createCSSFunction(defaultTheme, prefix = "stoop", media, utils, themeMap) {
|
|
45
|
+
return function css(styles) {
|
|
46
|
+
return compileCSS(styles, defaultTheme, prefix, media, utils, themeMap);
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Keyframes Animation API
|
|
51
|
+
// ============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Converts a keyframes object to a CSS @keyframes rule string.
|
|
54
|
+
*
|
|
55
|
+
* @param keyframesObj - Keyframes object with percentage/from/to keys
|
|
56
|
+
* @param animationName - Name for the animation
|
|
57
|
+
* @param theme - Optional theme for token resolution
|
|
58
|
+
* @param themeMap - Optional theme scale mappings
|
|
59
|
+
* @returns CSS @keyframes rule string
|
|
60
|
+
*/
|
|
61
|
+
function keyframesToCSS(keyframesObj, animationName, theme, themeMap) {
|
|
62
|
+
let css = `@keyframes ${animationName} {`;
|
|
63
|
+
const sortedKeys = Object.keys(keyframesObj).sort((a, b) => {
|
|
64
|
+
const aNum = parseFloat(a.replace("%", ""));
|
|
65
|
+
const bNum = parseFloat(b.replace("%", ""));
|
|
66
|
+
if (a === "from") {
|
|
67
|
+
return -1;
|
|
68
|
+
}
|
|
69
|
+
if (b === "from") {
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
if (a === "to") {
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
if (b === "to") {
|
|
76
|
+
return -1;
|
|
77
|
+
}
|
|
78
|
+
return aNum - bNum;
|
|
79
|
+
});
|
|
80
|
+
for (const key of sortedKeys) {
|
|
81
|
+
if (!validateKeyframeKey(key)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const styles = keyframesObj[key];
|
|
85
|
+
if (!styles || typeof styles !== "object") {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
css += ` ${key} {`;
|
|
89
|
+
const themedStyles = replaceThemeTokensWithVars(styles, theme, themeMap);
|
|
90
|
+
// Sort properties for deterministic CSS generation
|
|
91
|
+
const sortedProps = Object.keys(themedStyles).sort();
|
|
92
|
+
for (const prop of sortedProps) {
|
|
93
|
+
const value = themedStyles[prop];
|
|
94
|
+
if (value !== undefined && (typeof value === "string" || typeof value === "number")) {
|
|
95
|
+
const sanitizedProp = sanitizeCSSPropertyName(prop);
|
|
96
|
+
if (sanitizedProp) {
|
|
97
|
+
// Don't escape keyframe values - escaping breaks complex CSS functions
|
|
98
|
+
const cssValue = String(value);
|
|
99
|
+
css += ` ${sanitizedProp}: ${cssValue};`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
css += " }";
|
|
104
|
+
}
|
|
105
|
+
css += " }";
|
|
106
|
+
return css;
|
|
107
|
+
}
|
|
108
|
+
import { KEYFRAME_CACHE_LIMIT } from "../constants";
|
|
109
|
+
/**
|
|
110
|
+
* Creates a keyframes animation function.
|
|
111
|
+
* Generates and injects @keyframes rules with caching to prevent duplicates.
|
|
112
|
+
*
|
|
113
|
+
* @param prefix - Optional prefix for animation names
|
|
114
|
+
* @param theme - Optional theme for token resolution
|
|
115
|
+
* @param themeMap - Optional theme scale mappings
|
|
116
|
+
* @returns Function that accepts keyframes objects and returns animation names
|
|
117
|
+
*/
|
|
118
|
+
export function createKeyframesFunction(prefix = "stoop", theme, themeMap) {
|
|
119
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
120
|
+
const animationCache = new LRUCache(KEYFRAME_CACHE_LIMIT);
|
|
121
|
+
return function keyframes(keyframesObj) {
|
|
122
|
+
const keyframesKey = hashObject(keyframesObj);
|
|
123
|
+
const cachedName = animationCache.get(keyframesKey);
|
|
124
|
+
if (cachedName) {
|
|
125
|
+
return cachedName;
|
|
126
|
+
}
|
|
127
|
+
const hashValue = keyframesKey.slice(0, 8);
|
|
128
|
+
const animationName = sanitizedPrefix
|
|
129
|
+
? `${sanitizedPrefix}-${hashValue}`
|
|
130
|
+
: `stoop-${hashValue}`;
|
|
131
|
+
const css = keyframesToCSS(keyframesObj, animationName, theme, themeMap);
|
|
132
|
+
const ruleKey = `__keyframes_${animationName}`;
|
|
133
|
+
injectCSS(css, sanitizedPrefix, ruleKey);
|
|
134
|
+
animationCache.set(keyframesKey, animationName);
|
|
135
|
+
return animationName;
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// Global CSS Injection API
|
|
140
|
+
// ============================================================================
|
|
141
|
+
/**
|
|
142
|
+
* Creates a global CSS injection function.
|
|
143
|
+
* Injects styles directly into the document with deduplication support.
|
|
144
|
+
* Supports media queries, nested selectors, and theme tokens.
|
|
145
|
+
*
|
|
146
|
+
* @param defaultTheme - Default theme for token resolution
|
|
147
|
+
* @param prefix - Optional prefix for CSS rules
|
|
148
|
+
* @param media - Optional media query breakpoints
|
|
149
|
+
* @param utils - Optional utility functions
|
|
150
|
+
* @param themeMap - Optional theme scale mappings
|
|
151
|
+
* @returns Function that accepts CSS objects and returns a cleanup function
|
|
152
|
+
*/
|
|
153
|
+
const globalInjectedStyles = new Set();
|
|
154
|
+
export function createGlobalCSSFunction(defaultTheme, prefix = "stoop", media, utils, themeMap) {
|
|
155
|
+
return function globalCss(styles) {
|
|
156
|
+
const cssKey = hashObject(styles);
|
|
157
|
+
if (globalInjectedStyles.has(cssKey)) {
|
|
158
|
+
return () => { };
|
|
159
|
+
}
|
|
160
|
+
globalInjectedStyles.add(cssKey);
|
|
161
|
+
const sanitizedPrefix = sanitizePrefix(prefix);
|
|
162
|
+
const stylesWithUtils = applyUtilities(styles, utils);
|
|
163
|
+
const themedStyles = replaceThemeTokensWithVars(stylesWithUtils, defaultTheme, themeMap);
|
|
164
|
+
// Empty selector for global CSS (no base selector needed)
|
|
165
|
+
const cssText = cssObjectToString(themedStyles, "", 0, media);
|
|
166
|
+
injectCSS(cssText, sanitizedPrefix, `__global_${cssKey}`);
|
|
167
|
+
return () => {
|
|
168
|
+
globalInjectedStyles.delete(cssKey);
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
}
|
package/dist/api/styled.d.ts
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Styled component API.
|
|
3
3
|
* Creates polymorphic styled components with variant support, theme awareness,
|
|
4
4
|
* and CSS prop merging. Supports component targeting via selector references.
|
|
5
|
+
*
|
|
6
|
+
* CLIENT-ONLY: This module uses React hooks (useMemo, useContext, forwardRef, createElement)
|
|
7
|
+
* and MUST have "use client" directive for Next.js App Router compatibility.
|
|
5
8
|
*/
|
|
6
9
|
import { forwardRef, type Context } from "react";
|
|
7
10
|
import type { CSS, StyledComponentProps, StyledComponentRef, StylableElement, Theme, ThemeContextValue, ThemeScale, UtilityFunction, Variants } from "../types";
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
/**
|
|
3
|
+
* Styled component API.
|
|
4
|
+
* Creates polymorphic styled components with variant support, theme awareness,
|
|
5
|
+
* and CSS prop merging. Supports component targeting via selector references.
|
|
6
|
+
*
|
|
7
|
+
* CLIENT-ONLY: This module uses React hooks (useMemo, useContext, forwardRef, createElement)
|
|
8
|
+
* and MUST have "use client" directive for Next.js App Router compatibility.
|
|
9
|
+
*/
|
|
10
|
+
import { useMemo, forwardRef, createElement, useContext, createContext, } from "react";
|
|
11
|
+
import { EMPTY_CSS, STOOP_COMPONENT_SYMBOL } from "../constants";
|
|
12
|
+
import { compileCSS } from "../core/compiler";
|
|
13
|
+
import { isStyledComponentRef } from "../utils/helpers";
|
|
14
|
+
import { hash, sanitizeClassName } from "../utils/theme-utils";
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Variant Application Logic
|
|
17
|
+
// ============================================================================
|
|
18
|
+
/**
|
|
19
|
+
* Applies variant styles to base styles based on component props.
|
|
20
|
+
* Optimized to avoid unnecessary object spreads when no variants are applied.
|
|
21
|
+
*
|
|
22
|
+
* @param variants - Variant configuration object
|
|
23
|
+
* @param props - Component props containing variant values
|
|
24
|
+
* @param baseStyles - Base styles to merge with variant styles
|
|
25
|
+
* @returns Merged CSS object
|
|
26
|
+
*/
|
|
27
|
+
function applyVariants(variants, props, baseStyles) {
|
|
28
|
+
const appliedVariantStyles = [];
|
|
29
|
+
for (const variantName in variants) {
|
|
30
|
+
const propValue = props[variantName];
|
|
31
|
+
if (propValue === undefined) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const variantOptions = variants[variantName];
|
|
35
|
+
const key = propValue === true ? "true" : propValue === false ? "false" : String(propValue);
|
|
36
|
+
if (variantOptions[key]) {
|
|
37
|
+
appliedVariantStyles.push(variantOptions[key]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (appliedVariantStyles.length === 0) {
|
|
41
|
+
return baseStyles;
|
|
42
|
+
}
|
|
43
|
+
// Optimize for single variant case
|
|
44
|
+
if (appliedVariantStyles.length === 1) {
|
|
45
|
+
return { ...baseStyles, ...appliedVariantStyles[0] };
|
|
46
|
+
}
|
|
47
|
+
// Merge multiple variant styles
|
|
48
|
+
const mergedVariants = { ...appliedVariantStyles[0] };
|
|
49
|
+
for (let i = 1; i < appliedVariantStyles.length; i++) {
|
|
50
|
+
Object.assign(mergedVariants, appliedVariantStyles[i]);
|
|
51
|
+
}
|
|
52
|
+
return { ...baseStyles, ...mergedVariants };
|
|
53
|
+
}
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Styled Component API
|
|
56
|
+
// ============================================================================
|
|
57
|
+
let defaultThemeContext = null;
|
|
58
|
+
function getDefaultThemeContext() {
|
|
59
|
+
if (!defaultThemeContext) {
|
|
60
|
+
defaultThemeContext = createContext(null);
|
|
61
|
+
}
|
|
62
|
+
return defaultThemeContext;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Creates a styled component reference for selector targeting.
|
|
66
|
+
*
|
|
67
|
+
* @param className - Class name to reference
|
|
68
|
+
* @returns StyledComponentRef for use in CSS selectors
|
|
69
|
+
*/
|
|
70
|
+
export function createStyledComponentRef(className) {
|
|
71
|
+
const ref = {
|
|
72
|
+
__isStoopStyled: true,
|
|
73
|
+
__stoopClassName: className,
|
|
74
|
+
[STOOP_COMPONENT_SYMBOL]: className,
|
|
75
|
+
toString: () => `__STOOP_COMPONENT_${className}`,
|
|
76
|
+
};
|
|
77
|
+
return ref;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Type guard for styled component references.
|
|
81
|
+
*
|
|
82
|
+
* @param value - Value to check
|
|
83
|
+
* @returns True if value is a styled component reference
|
|
84
|
+
*/
|
|
85
|
+
function isStyledComponent(value) {
|
|
86
|
+
return isStyledComponentRef(value);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Set of common CSS properties that should not be passed to DOM elements.
|
|
90
|
+
* These are camelCase CSS properties that React doesn't recognize as valid DOM attributes.
|
|
91
|
+
*/
|
|
92
|
+
const CSS_PROPERTIES = new Set([
|
|
93
|
+
"alignContent",
|
|
94
|
+
"alignItems",
|
|
95
|
+
"alignSelf",
|
|
96
|
+
"animation",
|
|
97
|
+
"animationDelay",
|
|
98
|
+
"animationDirection",
|
|
99
|
+
"animationDuration",
|
|
100
|
+
"animationFillMode",
|
|
101
|
+
"animationIterationCount",
|
|
102
|
+
"animationName",
|
|
103
|
+
"animationPlayState",
|
|
104
|
+
"animationTimingFunction",
|
|
105
|
+
"aspectRatio",
|
|
106
|
+
"backdropFilter",
|
|
107
|
+
"backfaceVisibility",
|
|
108
|
+
"background",
|
|
109
|
+
"backgroundAttachment",
|
|
110
|
+
"backgroundBlendMode",
|
|
111
|
+
"backgroundClip",
|
|
112
|
+
"backgroundColor",
|
|
113
|
+
"backgroundImage",
|
|
114
|
+
"backgroundOrigin",
|
|
115
|
+
"backgroundPosition",
|
|
116
|
+
"backgroundRepeat",
|
|
117
|
+
"backgroundSize",
|
|
118
|
+
"border",
|
|
119
|
+
"borderBottom",
|
|
120
|
+
"borderBottomColor",
|
|
121
|
+
"borderBottomLeftRadius",
|
|
122
|
+
"borderBottomRightRadius",
|
|
123
|
+
"borderBottomStyle",
|
|
124
|
+
"borderBottomWidth",
|
|
125
|
+
"borderCollapse",
|
|
126
|
+
"borderColor",
|
|
127
|
+
"borderImage",
|
|
128
|
+
"borderImageOutset",
|
|
129
|
+
"borderImageRepeat",
|
|
130
|
+
"borderImageSlice",
|
|
131
|
+
"borderImageSource",
|
|
132
|
+
"borderImageWidth",
|
|
133
|
+
"borderLeft",
|
|
134
|
+
"borderLeftColor",
|
|
135
|
+
"borderLeftStyle",
|
|
136
|
+
"borderLeftWidth",
|
|
137
|
+
"borderRadius",
|
|
138
|
+
"borderRight",
|
|
139
|
+
"borderRightColor",
|
|
140
|
+
"borderRightStyle",
|
|
141
|
+
"borderRightWidth",
|
|
142
|
+
"borderSpacing",
|
|
143
|
+
"borderStyle",
|
|
144
|
+
"borderTop",
|
|
145
|
+
"borderTopColor",
|
|
146
|
+
"borderTopLeftRadius",
|
|
147
|
+
"borderTopRightRadius",
|
|
148
|
+
"borderTopStyle",
|
|
149
|
+
"borderTopWidth",
|
|
150
|
+
"borderWidth",
|
|
151
|
+
"bottom",
|
|
152
|
+
"boxShadow",
|
|
153
|
+
"boxSizing",
|
|
154
|
+
"captionSide",
|
|
155
|
+
"caretColor",
|
|
156
|
+
"clear",
|
|
157
|
+
"clip",
|
|
158
|
+
"clipPath",
|
|
159
|
+
"color",
|
|
160
|
+
"columnCount",
|
|
161
|
+
"columnFill",
|
|
162
|
+
"columnGap",
|
|
163
|
+
"columnRule",
|
|
164
|
+
"columnRuleColor",
|
|
165
|
+
"columnRuleStyle",
|
|
166
|
+
"columnRuleWidth",
|
|
167
|
+
"columnSpan",
|
|
168
|
+
"columnWidth",
|
|
169
|
+
"columns",
|
|
170
|
+
"content",
|
|
171
|
+
"counterIncrement",
|
|
172
|
+
"counterReset",
|
|
173
|
+
"cursor",
|
|
174
|
+
"direction",
|
|
175
|
+
"display",
|
|
176
|
+
"emptyCells",
|
|
177
|
+
"filter",
|
|
178
|
+
"flex",
|
|
179
|
+
"flexBasis",
|
|
180
|
+
"flexDirection",
|
|
181
|
+
"flexFlow",
|
|
182
|
+
"flexGrow",
|
|
183
|
+
"flexShrink",
|
|
184
|
+
"flexWrap",
|
|
185
|
+
"float",
|
|
186
|
+
"font",
|
|
187
|
+
"fontFamily",
|
|
188
|
+
"fontFeatureSettings",
|
|
189
|
+
"fontKerning",
|
|
190
|
+
"fontLanguageOverride",
|
|
191
|
+
"fontSize",
|
|
192
|
+
"fontSizeAdjust",
|
|
193
|
+
"fontStretch",
|
|
194
|
+
"fontStyle",
|
|
195
|
+
"fontSynthesis",
|
|
196
|
+
"fontVariant",
|
|
197
|
+
"fontVariantAlternates",
|
|
198
|
+
"fontVariantCaps",
|
|
199
|
+
"fontVariantEastAsian",
|
|
200
|
+
"fontVariantLigatures",
|
|
201
|
+
"fontVariantNumeric",
|
|
202
|
+
"fontVariantPosition",
|
|
203
|
+
"fontWeight",
|
|
204
|
+
"gap",
|
|
205
|
+
"grid",
|
|
206
|
+
"gridArea",
|
|
207
|
+
"gridAutoColumns",
|
|
208
|
+
"gridAutoFlow",
|
|
209
|
+
"gridAutoRows",
|
|
210
|
+
"gridColumn",
|
|
211
|
+
"gridColumnEnd",
|
|
212
|
+
"gridColumnGap",
|
|
213
|
+
"gridColumnStart",
|
|
214
|
+
"gridGap",
|
|
215
|
+
"gridRow",
|
|
216
|
+
"gridRowEnd",
|
|
217
|
+
"gridRowGap",
|
|
218
|
+
"gridRowStart",
|
|
219
|
+
"gridTemplate",
|
|
220
|
+
"gridTemplateAreas",
|
|
221
|
+
"gridTemplateColumns",
|
|
222
|
+
"gridTemplateRows",
|
|
223
|
+
"height",
|
|
224
|
+
"hyphens",
|
|
225
|
+
"imageOrientation",
|
|
226
|
+
"imageRendering",
|
|
227
|
+
"imageResolution",
|
|
228
|
+
"imeMode",
|
|
229
|
+
"inlineSize",
|
|
230
|
+
"isolation",
|
|
231
|
+
"justifyContent",
|
|
232
|
+
"justifyItems",
|
|
233
|
+
"justifySelf",
|
|
234
|
+
"left",
|
|
235
|
+
"letterSpacing",
|
|
236
|
+
"lineHeight",
|
|
237
|
+
"listStyle",
|
|
238
|
+
"listStyleImage",
|
|
239
|
+
"listStylePosition",
|
|
240
|
+
"listStyleType",
|
|
241
|
+
"margin",
|
|
242
|
+
"marginBottom",
|
|
243
|
+
"marginLeft",
|
|
244
|
+
"marginRight",
|
|
245
|
+
"marginTop",
|
|
246
|
+
"maxHeight",
|
|
247
|
+
"maxWidth",
|
|
248
|
+
"minHeight",
|
|
249
|
+
"minWidth",
|
|
250
|
+
"objectFit",
|
|
251
|
+
"objectPosition",
|
|
252
|
+
"opacity",
|
|
253
|
+
"order",
|
|
254
|
+
"orphans",
|
|
255
|
+
"outline",
|
|
256
|
+
"outlineColor",
|
|
257
|
+
"outlineOffset",
|
|
258
|
+
"outlineStyle",
|
|
259
|
+
"outlineWidth",
|
|
260
|
+
"overflow",
|
|
261
|
+
"overflowWrap",
|
|
262
|
+
"overflowX",
|
|
263
|
+
"overflowY",
|
|
264
|
+
"padding",
|
|
265
|
+
"paddingBottom",
|
|
266
|
+
"paddingLeft",
|
|
267
|
+
"paddingRight",
|
|
268
|
+
"paddingTop",
|
|
269
|
+
"pageBreakAfter",
|
|
270
|
+
"pageBreakBefore",
|
|
271
|
+
"pageBreakInside",
|
|
272
|
+
"perspective",
|
|
273
|
+
"perspectiveOrigin",
|
|
274
|
+
"placeContent",
|
|
275
|
+
"placeItems",
|
|
276
|
+
"placeSelf",
|
|
277
|
+
"pointerEvents",
|
|
278
|
+
"position",
|
|
279
|
+
"quotes",
|
|
280
|
+
"resize",
|
|
281
|
+
"right",
|
|
282
|
+
"rowGap",
|
|
283
|
+
"scrollBehavior",
|
|
284
|
+
"tabSize",
|
|
285
|
+
"tableLayout",
|
|
286
|
+
"textAlign",
|
|
287
|
+
"textAlignLast",
|
|
288
|
+
"textDecoration",
|
|
289
|
+
"textDecorationColor",
|
|
290
|
+
"textDecorationLine",
|
|
291
|
+
"textDecorationStyle",
|
|
292
|
+
"textIndent",
|
|
293
|
+
"textJustify",
|
|
294
|
+
"textOverflow",
|
|
295
|
+
"textShadow",
|
|
296
|
+
"textTransform",
|
|
297
|
+
"textUnderlinePosition",
|
|
298
|
+
"top",
|
|
299
|
+
"transform",
|
|
300
|
+
"transformOrigin",
|
|
301
|
+
"transformStyle",
|
|
302
|
+
"transition",
|
|
303
|
+
"transitionDelay",
|
|
304
|
+
"transitionDuration",
|
|
305
|
+
"transitionProperty",
|
|
306
|
+
"transitionTimingFunction",
|
|
307
|
+
"unicodeBidi",
|
|
308
|
+
"userSelect",
|
|
309
|
+
"verticalAlign",
|
|
310
|
+
"visibility",
|
|
311
|
+
"whiteSpace",
|
|
312
|
+
"width",
|
|
313
|
+
"wordBreak",
|
|
314
|
+
"wordSpacing",
|
|
315
|
+
"wordWrap",
|
|
316
|
+
"writingMode",
|
|
317
|
+
"zIndex",
|
|
318
|
+
]);
|
|
319
|
+
/**
|
|
320
|
+
* Checks if a prop name is a CSS property that should be converted to CSS styles.
|
|
321
|
+
*
|
|
322
|
+
* @param propName - The prop name to check
|
|
323
|
+
* @returns True if the prop is a CSS property
|
|
324
|
+
*/
|
|
325
|
+
function isCSSProperty(propName) {
|
|
326
|
+
return CSS_PROPERTIES.has(propName);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Separates component props into variant props, CSS props, and element props.
|
|
330
|
+
* Variant props are used for style variants.
|
|
331
|
+
* CSS props are converted to CSS styles.
|
|
332
|
+
* Element props are passed to the DOM element.
|
|
333
|
+
*
|
|
334
|
+
* @param props - All component props
|
|
335
|
+
* @param variants - Variant configuration
|
|
336
|
+
* @returns Object with separated elementProps, variantProps, and cssProps
|
|
337
|
+
*/
|
|
338
|
+
function extractVariantProps(props, variants) {
|
|
339
|
+
const variantKeys = variants ? new Set(Object.keys(variants)) : new Set();
|
|
340
|
+
const variantProps = {};
|
|
341
|
+
const cssProps = {};
|
|
342
|
+
const elementProps = {};
|
|
343
|
+
for (const key in props) {
|
|
344
|
+
if (variantKeys.has(key)) {
|
|
345
|
+
variantProps[key] = props[key];
|
|
346
|
+
}
|
|
347
|
+
else if (isCSSProperty(key)) {
|
|
348
|
+
cssProps[key] = props[key];
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
elementProps[key] = props[key];
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return { cssProps, elementProps, variantProps };
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Creates a styled component factory function.
|
|
358
|
+
* Supports polymorphic components, variants, theme awareness, and CSS prop merging.
|
|
359
|
+
*
|
|
360
|
+
* @param defaultTheme - Default theme for token resolution
|
|
361
|
+
* @param prefix - Optional prefix for generated class names
|
|
362
|
+
* @param media - Optional media query breakpoints
|
|
363
|
+
* @param utils - Optional utility functions
|
|
364
|
+
* @param themeMap - Optional theme scale mappings
|
|
365
|
+
* @param themeContext - React context for theme values (instance-specific)
|
|
366
|
+
* @returns Styled component factory function
|
|
367
|
+
*/
|
|
368
|
+
export function createStyledFunction(defaultTheme, prefix = "stoop", media, utils, themeMap, themeContext) {
|
|
369
|
+
return function styled(defaultElement, baseStyles) {
|
|
370
|
+
let actualBaseStyles = (baseStyles || EMPTY_CSS);
|
|
371
|
+
let actualVariants;
|
|
372
|
+
// Extract variants if embedded in baseStyles
|
|
373
|
+
if (baseStyles &&
|
|
374
|
+
"variants" in baseStyles &&
|
|
375
|
+
typeof baseStyles.variants === "object" &&
|
|
376
|
+
baseStyles.variants !== null &&
|
|
377
|
+
!Array.isArray(baseStyles.variants)) {
|
|
378
|
+
// Verify it's actually a Variants object (has string keys with CSS values)
|
|
379
|
+
const variantsObj = baseStyles.variants;
|
|
380
|
+
const hasValidVariants = Object.keys(variantsObj).length > 0 &&
|
|
381
|
+
Object.values(variantsObj).every((v) => typeof v === "object" && v !== null && !Array.isArray(v));
|
|
382
|
+
if (hasValidVariants) {
|
|
383
|
+
actualVariants = baseStyles.variants;
|
|
384
|
+
const { variants: _, ...rest } = baseStyles;
|
|
385
|
+
actualBaseStyles = rest;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
let baseElementClassName;
|
|
389
|
+
if (typeof defaultElement !== "string" && isStyledComponent(defaultElement)) {
|
|
390
|
+
baseElementClassName = defaultElement.__stoopClassName;
|
|
391
|
+
}
|
|
392
|
+
const StyledComponent = forwardRef(function StyledComponent(propsWithBase, ref) {
|
|
393
|
+
const { as, className, css: cssStyles, ...restProps } = propsWithBase;
|
|
394
|
+
const element = (as || defaultElement);
|
|
395
|
+
const cssObject = useMemo(() => cssStyles && typeof cssStyles === "object" && cssStyles !== null
|
|
396
|
+
? cssStyles
|
|
397
|
+
: EMPTY_CSS, [cssStyles]);
|
|
398
|
+
const { cssProps, elementProps, variantProps } = extractVariantProps(restProps, actualVariants);
|
|
399
|
+
const contextValue = useContext(themeContext || getDefaultThemeContext());
|
|
400
|
+
const currentTheme = contextValue?.theme || defaultTheme;
|
|
401
|
+
const currentMedia = currentTheme.media ? { ...media, ...currentTheme.media } : media;
|
|
402
|
+
const variantKey = useMemo(() => {
|
|
403
|
+
if (!actualVariants) {
|
|
404
|
+
return "";
|
|
405
|
+
}
|
|
406
|
+
const variantEntries = Object.entries(variantProps);
|
|
407
|
+
if (variantEntries.length === 0) {
|
|
408
|
+
return "";
|
|
409
|
+
}
|
|
410
|
+
return variantEntries
|
|
411
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
412
|
+
.map(([key, value]) => `${key}:${String(value)}`)
|
|
413
|
+
.join("|");
|
|
414
|
+
}, [variantProps]);
|
|
415
|
+
const finalStyles = useMemo(() => {
|
|
416
|
+
let componentStyles = actualBaseStyles;
|
|
417
|
+
if (actualVariants && variantKey) {
|
|
418
|
+
componentStyles = applyVariants(actualVariants, variantProps, actualBaseStyles);
|
|
419
|
+
}
|
|
420
|
+
if (Object.keys(cssProps).length > 0) {
|
|
421
|
+
componentStyles = Object.assign({}, componentStyles, cssProps);
|
|
422
|
+
}
|
|
423
|
+
if (cssObject !== EMPTY_CSS) {
|
|
424
|
+
componentStyles = Object.assign({}, componentStyles, cssObject);
|
|
425
|
+
}
|
|
426
|
+
return componentStyles;
|
|
427
|
+
}, [variantKey, cssObject, cssProps, actualBaseStyles, actualVariants, variantProps]);
|
|
428
|
+
const finalClassName = useMemo(() => {
|
|
429
|
+
const classNames = [];
|
|
430
|
+
if (baseElementClassName) {
|
|
431
|
+
classNames.push(baseElementClassName);
|
|
432
|
+
}
|
|
433
|
+
const mergedClass = compileCSS(finalStyles, currentTheme, prefix, currentMedia, utils, themeMap);
|
|
434
|
+
if (mergedClass) {
|
|
435
|
+
classNames.push(mergedClass);
|
|
436
|
+
}
|
|
437
|
+
if (className) {
|
|
438
|
+
const classNameStr = typeof className === "string" ? className : String(className);
|
|
439
|
+
const sanitizedClassName = sanitizeClassName(classNameStr);
|
|
440
|
+
if (sanitizedClassName) {
|
|
441
|
+
classNames.push(sanitizedClassName);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return classNames.length > 0 ? classNames.join(" ") : undefined;
|
|
445
|
+
}, [
|
|
446
|
+
finalStyles,
|
|
447
|
+
className,
|
|
448
|
+
baseElementClassName,
|
|
449
|
+
currentTheme,
|
|
450
|
+
prefix,
|
|
451
|
+
currentMedia,
|
|
452
|
+
utils,
|
|
453
|
+
themeMap,
|
|
454
|
+
]);
|
|
455
|
+
return createElement(element, {
|
|
456
|
+
...elementProps,
|
|
457
|
+
className: finalClassName,
|
|
458
|
+
ref,
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
const selectorHash = hash(JSON.stringify(actualBaseStyles));
|
|
462
|
+
const selectorClassName = `${prefix}-${selectorHash}`;
|
|
463
|
+
const componentWithSelector = StyledComponent;
|
|
464
|
+
componentWithSelector.selector = createStyledComponentRef(selectorClassName);
|
|
465
|
+
return componentWithSelector;
|
|
466
|
+
};
|
|
467
|
+
}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Theme Provider component and hook.
|
|
3
3
|
* Manages theme state, localStorage persistence, cookie sync, and centralized theme variable updates.
|
|
4
4
|
* Includes the useTheme hook for accessing theme management context.
|
|
5
|
+
*
|
|
6
|
+
* CLIENT-ONLY: This module uses React hooks (useState, useLayoutEffect, useCallback, useMemo)
|
|
7
|
+
* and MUST have "use client" directive for Next.js App Router compatibility.
|
|
5
8
|
*/
|
|
6
9
|
import { type ComponentType, type Context } from "react";
|
|
7
10
|
import type { ProviderProps, Theme, ThemeContextValue, ThemeManagementContextValue } from "../types";
|