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.
@@ -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
+ }
@@ -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";