stoop 0.3.0 → 0.4.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.
Files changed (69) hide show
  1. package/README.md +125 -0
  2. package/dist/api/core-api.d.ts +34 -0
  3. package/dist/api/{keyframes.js → core-api.js} +45 -5
  4. package/dist/api/global-css.js +11 -58
  5. package/dist/api/styled.d.ts +0 -1
  6. package/dist/api/styled.js +265 -16
  7. package/dist/api/theme-provider.d.ts +41 -0
  8. package/dist/api/theme-provider.js +223 -0
  9. package/dist/constants.d.ts +11 -1
  10. package/dist/constants.js +11 -1
  11. package/dist/core/compiler.d.ts +11 -0
  12. package/dist/core/compiler.js +15 -7
  13. package/dist/core/theme-manager.d.ts +34 -3
  14. package/dist/core/theme-manager.js +55 -45
  15. package/dist/core/variants.js +9 -3
  16. package/dist/create-stoop-internal.d.ts +30 -0
  17. package/dist/create-stoop-internal.js +123 -0
  18. package/dist/create-stoop-ssr.d.ts +10 -0
  19. package/dist/create-stoop-ssr.js +26 -0
  20. package/dist/create-stoop.d.ts +32 -1
  21. package/dist/create-stoop.js +78 -69
  22. package/dist/inject.d.ts +113 -0
  23. package/dist/inject.js +308 -0
  24. package/dist/types/index.d.ts +147 -12
  25. package/dist/types/react-polymorphic-types.d.ts +13 -2
  26. package/dist/utils/auto-preload.d.ts +45 -0
  27. package/dist/utils/auto-preload.js +167 -0
  28. package/dist/utils/helpers.d.ts +64 -0
  29. package/dist/utils/helpers.js +150 -0
  30. package/dist/utils/storage.d.ts +148 -0
  31. package/dist/utils/storage.js +396 -0
  32. package/dist/utils/{string.d.ts → theme-utils.d.ts} +20 -3
  33. package/dist/utils/{string.js → theme-utils.js} +109 -9
  34. package/dist/utils/theme.d.ts +14 -2
  35. package/dist/utils/theme.js +41 -16
  36. package/package.json +48 -23
  37. package/dist/api/create-theme.d.ts +0 -13
  38. package/dist/api/create-theme.js +0 -43
  39. package/dist/api/css.d.ts +0 -16
  40. package/dist/api/css.js +0 -20
  41. package/dist/api/keyframes.d.ts +0 -16
  42. package/dist/api/provider.d.ts +0 -19
  43. package/dist/api/provider.js +0 -109
  44. package/dist/api/use-theme.d.ts +0 -13
  45. package/dist/api/use-theme.js +0 -21
  46. package/dist/create-stoop-server.d.ts +0 -33
  47. package/dist/create-stoop-server.js +0 -130
  48. package/dist/index.d.ts +0 -6
  49. package/dist/index.js +0 -5
  50. package/dist/inject/browser.d.ts +0 -58
  51. package/dist/inject/browser.js +0 -149
  52. package/dist/inject/dedup.d.ts +0 -29
  53. package/dist/inject/dedup.js +0 -38
  54. package/dist/inject/index.d.ts +0 -40
  55. package/dist/inject/index.js +0 -75
  56. package/dist/inject/ssr.d.ts +0 -27
  57. package/dist/inject/ssr.js +0 -46
  58. package/dist/ssr.d.ts +0 -21
  59. package/dist/ssr.js +0 -19
  60. package/dist/utils/environment.d.ts +0 -6
  61. package/dist/utils/environment.js +0 -12
  62. package/dist/utils/theme-map.d.ts +0 -22
  63. package/dist/utils/theme-map.js +0 -97
  64. package/dist/utils/theme-validation.d.ts +0 -13
  65. package/dist/utils/theme-validation.js +0 -36
  66. package/dist/utils/type-guards.d.ts +0 -26
  67. package/dist/utils/type-guards.js +0 -38
  68. package/dist/utils/utilities.d.ts +0 -14
  69. package/dist/utils/utilities.js +0 -43
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # stoop
2
+
3
+ A lightweight, type-safe CSS-in-JS library for React with theme support, variants, and SSR capabilities.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install stoop
9
+ # or
10
+ bun add stoop
11
+ # or
12
+ yarn add stoop
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```tsx
18
+ import { createStoop } from "stoop";
19
+
20
+ const { styled, css, Provider, useTheme } = createStoop({
21
+ theme: {
22
+ colors: {
23
+ primary: "#0070f3",
24
+ background: "#ffffff",
25
+ text: "#000000",
26
+ },
27
+ space: {
28
+ small: "8px",
29
+ medium: "16px",
30
+ large: "24px",
31
+ },
32
+ },
33
+ themes: {
34
+ light: {
35
+ /* ... */
36
+ },
37
+ dark: {
38
+ /* ... */
39
+ },
40
+ },
41
+ });
42
+
43
+ const Button = styled("button", {
44
+ padding: "$medium",
45
+ backgroundColor: "$primary",
46
+ color: "$text",
47
+ });
48
+
49
+ <Button>Click me</Button>;
50
+ ```
51
+
52
+ ## Features
53
+
54
+ - **Type-safe theming** with TypeScript inference
55
+ - **CSS variables** for instant theme switching
56
+ - **Variant system** for component variations
57
+ - **SSR support** via `getCssText()`
58
+ - **Multiple themes** with built-in Provider
59
+ - **Utility functions** for custom CSS transformations
60
+ - **Zero runtime overhead** for theme switching
61
+
62
+ ## Documentation
63
+
64
+ - **[GUIDE.md](./docs/GUIDE.md)** - Step-by-step setup and usage guide
65
+ - **[API.md](./docs/API.md)** - Complete API reference
66
+ - **[ARCHITECTURE.md](./docs/ARCHITECTURE.md)** - Internal implementation details
67
+ - **[TESTING.md](./docs/TESTING.md)** - Testing guide and test suite documentation
68
+
69
+ ## API Overview
70
+
71
+ ### `createStoop(config)`
72
+
73
+ Creates a Stoop instance. Returns: `styled`, `css`, `createTheme`, `globalCss`, `keyframes`, `getCssText`, `warmCache`, `preloadTheme`, `theme`, `config`. If `themes` config is provided, also returns `Provider` and `useTheme`.
74
+
75
+ ### Theme Tokens
76
+
77
+ Use `$` prefix for theme tokens. Shorthand `$token` uses property-aware resolution (preferred); explicit `$scale.token` specifies the scale.
78
+
79
+ ```tsx
80
+ {
81
+ color: "$primary", // Shorthand (preferred, property-aware)
82
+ padding: "$medium", // Property-aware → space scale
83
+ fontSize: "$fontSizes.small", // Explicit scale
84
+ }
85
+ ```
86
+
87
+ ### Variants
88
+
89
+ Variants create component variations via props:
90
+
91
+ ```tsx
92
+ const Button = styled(
93
+ "button",
94
+ {},
95
+ {
96
+ variant: {
97
+ primary: { backgroundColor: "$primary" },
98
+ secondary: { backgroundColor: "$secondary" },
99
+ },
100
+ size: {
101
+ small: { padding: "$small" },
102
+ large: { padding: "$large" },
103
+ },
104
+ },
105
+ );
106
+
107
+ <Button variant="primary" size="small" />;
108
+ ```
109
+
110
+ ## Development
111
+
112
+ ```sh
113
+ # Build
114
+ bun run build
115
+
116
+ # Test
117
+ bun run test
118
+
119
+ # Watch mode
120
+ bun run test:watch
121
+ ```
122
+
123
+ ## License
124
+
125
+ MIT
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Core API functions.
3
+ * Consolidates theme creation, CSS class generation, and keyframes animation APIs.
4
+ */
5
+ import type { CSS, Theme, ThemeScale, UtilityFunction } from "../types";
6
+ /**
7
+ * Creates a function that extends a base theme with overrides.
8
+ * The returned function deep merges theme overrides with the base theme.
9
+ *
10
+ * @param baseTheme - Base theme to extend
11
+ * @returns Function that accepts theme overrides and returns a merged theme
12
+ */
13
+ export declare function createTheme(baseTheme: Theme): (themeOverrides?: Partial<Theme>) => Theme;
14
+ /**
15
+ * Creates a CSS function that compiles CSS objects into class names.
16
+ *
17
+ * @param defaultTheme - Default theme for token resolution
18
+ * @param prefix - Optional prefix for generated class names
19
+ * @param media - Optional media query breakpoints
20
+ * @param utils - Optional utility functions
21
+ * @param themeMap - Optional theme scale mappings
22
+ * @returns Function that accepts CSS objects and returns class names
23
+ */
24
+ export declare function createCSSFunction(defaultTheme: Theme, prefix?: string, media?: Record<string, string>, utils?: Record<string, UtilityFunction>, themeMap?: Record<string, ThemeScale>): (styles: CSS) => string;
25
+ /**
26
+ * Creates a keyframes animation function.
27
+ * Generates and injects @keyframes rules with caching to prevent duplicates.
28
+ *
29
+ * @param prefix - Optional prefix for animation names
30
+ * @param theme - Optional theme for token resolution
31
+ * @param themeMap - Optional theme scale mappings
32
+ * @returns Function that accepts keyframes objects and returns animation names
33
+ */
34
+ export declare function createKeyframesFunction(prefix?: string, theme?: Theme, themeMap?: Record<string, ThemeScale>): (keyframes: Record<string, CSS>) => string;
@@ -1,12 +1,52 @@
1
1
  /**
2
- * CSS keyframes animation API.
3
- * Creates a function that generates and injects @keyframes rules.
4
- * Caches animations by content hash to prevent duplicates.
2
+ * Core API functions.
3
+ * Consolidates theme creation, CSS class generation, and keyframes animation APIs.
5
4
  */
6
5
  import { LRUCache } from "../core/cache";
6
+ import { compileCSS } from "../core/compiler";
7
+ import { mergeThemes } from "../core/theme-manager";
7
8
  import { injectCSS } from "../inject";
8
- import { hashObject, sanitizeCSSPropertyName, sanitizePrefix, validateKeyframeKey, } from "../utils/string";
9
+ import { validateTheme } from "../utils/helpers";
9
10
  import { replaceThemeTokensWithVars } from "../utils/theme";
11
+ import { hashObject, sanitizeCSSPropertyName, sanitizePrefix, validateKeyframeKey, } from "../utils/theme-utils";
12
+ // ============================================================================
13
+ // Theme Creation API
14
+ // ============================================================================
15
+ /**
16
+ * Creates a function that extends a base theme with overrides.
17
+ * The returned function deep merges theme overrides with the base theme.
18
+ *
19
+ * @param baseTheme - Base theme to extend
20
+ * @returns Function that accepts theme overrides and returns a merged theme
21
+ */
22
+ export function createTheme(baseTheme) {
23
+ return function createTheme(themeOverrides = {}) {
24
+ const validatedOverrides = validateTheme(themeOverrides);
25
+ // Use shared mergeThemes function instead of duplicate deepMerge
26
+ return mergeThemes(baseTheme, validatedOverrides);
27
+ };
28
+ }
29
+ // ============================================================================
30
+ // CSS Class Generation API
31
+ // ============================================================================
32
+ /**
33
+ * Creates a CSS function that compiles CSS objects into class names.
34
+ *
35
+ * @param defaultTheme - Default theme for token resolution
36
+ * @param prefix - Optional prefix for generated class names
37
+ * @param media - Optional media query breakpoints
38
+ * @param utils - Optional utility functions
39
+ * @param themeMap - Optional theme scale mappings
40
+ * @returns Function that accepts CSS objects and returns class names
41
+ */
42
+ export function createCSSFunction(defaultTheme, prefix = "stoop", media, utils, themeMap) {
43
+ return function css(styles) {
44
+ return compileCSS(styles, defaultTheme, prefix, media, utils, themeMap);
45
+ };
46
+ }
47
+ // ============================================================================
48
+ // Keyframes Animation API
49
+ // ============================================================================
10
50
  /**
11
51
  * Converts a keyframes object to a CSS @keyframes rule string.
12
52
  *
@@ -63,7 +103,7 @@ function keyframesToCSS(keyframesObj, animationName, theme, themeMap) {
63
103
  css += " }";
64
104
  return css;
65
105
  }
66
- const KEYFRAME_CACHE_LIMIT = 500;
106
+ import { KEYFRAME_CACHE_LIMIT } from "../constants";
67
107
  /**
68
108
  * Creates a keyframes animation function.
69
109
  * Generates and injects @keyframes rules with caching to prevent duplicates.
@@ -3,12 +3,11 @@
3
3
  * Creates a function that injects global styles into the document.
4
4
  * Supports media queries, nested selectors, and theme tokens.
5
5
  */
6
- import { MAX_CSS_NESTING_DEPTH } from "../constants";
6
+ import { cssObjectToString } from "../core/compiler";
7
7
  import { injectCSS } from "../inject";
8
- import { escapeCSSValue, hashObject, sanitizeCSSPropertyName, sanitizeCSSSelector, sanitizeMediaQuery, sanitizePrefix, } from "../utils/string";
8
+ import { applyUtilities } from "../utils/helpers";
9
9
  import { replaceThemeTokensWithVars } from "../utils/theme";
10
- import { isCSSObject } from "../utils/type-guards";
11
- import { applyUtilities } from "../utils/utilities";
10
+ import { hashObject, sanitizePrefix } from "../utils/theme-utils";
12
11
  /**
13
12
  * Creates a global CSS injection function.
14
13
  * Injects styles directly into the document with deduplication support.
@@ -20,70 +19,24 @@ import { applyUtilities } from "../utils/utilities";
20
19
  * @param themeMap - Optional theme scale mappings
21
20
  * @returns Function that accepts CSS objects and returns a cleanup function
22
21
  */
23
- const globalInjectedStyles = new WeakMap();
24
- function getInjectedSet(theme) {
25
- let set = globalInjectedStyles.get(theme);
26
- if (!set) {
27
- set = new Set();
28
- globalInjectedStyles.set(theme, set);
29
- }
30
- return set;
31
- }
22
+ // Use CSS hash as key instead of theme object reference for better deduplication
23
+ const globalInjectedStyles = new Set();
32
24
  export function createGlobalCSSFunction(defaultTheme, prefix = "stoop", media, utils, themeMap) {
33
25
  return function globalCss(styles) {
34
- const injected = getInjectedSet(defaultTheme);
35
26
  const cssKey = hashObject(styles);
36
- if (injected.has(cssKey)) {
27
+ if (globalInjectedStyles.has(cssKey)) {
37
28
  return () => { };
38
29
  }
39
- injected.add(cssKey);
40
- function generateGlobalCSS(obj, depth = 0) {
41
- if (depth > MAX_CSS_NESTING_DEPTH) {
42
- return "";
43
- }
44
- let result = "";
45
- const sortedEntries = Object.entries(obj).sort(([a], [b]) => a.localeCompare(b));
46
- sortedEntries.forEach(([key, value]) => {
47
- if (isCSSObject(value)) {
48
- if (media && key in media) {
49
- const mediaQuery = sanitizeMediaQuery(media[key]);
50
- if (mediaQuery) {
51
- const nestedCss = generateGlobalCSS(value, depth + 1);
52
- result += `${mediaQuery} { ${nestedCss} }`;
53
- }
54
- }
55
- else if (key.startsWith("@")) {
56
- const sanitizedKey = sanitizeCSSSelector(key);
57
- if (sanitizedKey) {
58
- const nestedCss = generateGlobalCSS(value, depth + 1);
59
- result += `${sanitizedKey} { ${nestedCss} }`;
60
- }
61
- }
62
- else {
63
- const sanitizedKey = sanitizeCSSSelector(key);
64
- if (sanitizedKey) {
65
- const nestedCss = generateGlobalCSS(value, depth + 1);
66
- result += `${sanitizedKey} { ${nestedCss} }`;
67
- }
68
- }
69
- }
70
- else if (value !== undefined) {
71
- const property = sanitizeCSSPropertyName(key);
72
- if (property && (typeof value === "string" || typeof value === "number")) {
73
- const escapedValue = escapeCSSValue(value);
74
- result += `${property}: ${escapedValue}; `;
75
- }
76
- }
77
- });
78
- return result;
79
- }
30
+ globalInjectedStyles.add(cssKey);
80
31
  const sanitizedPrefix = sanitizePrefix(prefix);
81
32
  const stylesWithUtils = applyUtilities(styles, utils);
82
33
  const themedStyles = replaceThemeTokensWithVars(stylesWithUtils, defaultTheme, themeMap);
83
- const cssText = generateGlobalCSS(themedStyles);
34
+ // Use cssObjectToString from compiler.ts instead of duplicate generateGlobalCSS
35
+ // Empty selector for global CSS (no base selector needed)
36
+ const cssText = cssObjectToString(themedStyles, "", 0, media);
84
37
  injectCSS(cssText, sanitizedPrefix, `__global_${cssKey}`);
85
38
  return () => {
86
- injected.delete(cssKey);
39
+ globalInjectedStyles.delete(cssKey);
87
40
  };
88
41
  };
89
42
  }
@@ -16,7 +16,6 @@ type CSSWithVariants = {
16
16
  [K in keyof CSS]: CSS[K];
17
17
  } & {
18
18
  variants: Variants;
19
- compoundVariants?: unknown[];
20
19
  };
21
20
  /**
22
21
  * Creates a styled component factory function.
@@ -8,7 +8,8 @@ import { useMemo, forwardRef, createElement, useContext, createContext, } from "
8
8
  import { EMPTY_CSS, STOOP_COMPONENT_SYMBOL } from "../constants";
9
9
  import { compileCSS } from "../core/compiler";
10
10
  import { applyVariants } from "../core/variants";
11
- import { hash, sanitizeClassName } from "../utils/string";
11
+ import { isStyledComponentRef } from "../utils/helpers";
12
+ import { hash, sanitizeClassName } from "../utils/theme-utils";
12
13
  let defaultThemeContext = null;
13
14
  function getDefaultThemeContext() {
14
15
  if (!defaultThemeContext) {
@@ -33,40 +34,283 @@ export function createStyledComponentRef(className) {
33
34
  }
34
35
  /**
35
36
  * Type guard for styled component references.
37
+ * Uses shared isStyledComponentRef helper for consistency.
36
38
  *
37
39
  * @param value - Value to check
38
40
  * @returns True if value is a styled component reference
39
41
  */
40
42
  function isStyledComponent(value) {
41
- return (typeof value === "object" &&
42
- value !== null &&
43
- "__isStoopStyled" in value &&
44
- value.__isStoopStyled === true);
43
+ return isStyledComponentRef(value);
45
44
  }
46
45
  /**
47
- * Separates component props into variant props and element props.
48
- * Variant props are used for style variants, element props are passed to the DOM element.
46
+ * Set of common CSS properties that should not be passed to DOM elements.
47
+ * These are camelCase CSS properties that React doesn't recognize as valid DOM attributes.
48
+ */
49
+ const CSS_PROPERTIES = new Set([
50
+ "alignContent",
51
+ "alignItems",
52
+ "alignSelf",
53
+ "animation",
54
+ "animationDelay",
55
+ "animationDirection",
56
+ "animationDuration",
57
+ "animationFillMode",
58
+ "animationIterationCount",
59
+ "animationName",
60
+ "animationPlayState",
61
+ "animationTimingFunction",
62
+ "aspectRatio",
63
+ "backdropFilter",
64
+ "backfaceVisibility",
65
+ "background",
66
+ "backgroundAttachment",
67
+ "backgroundBlendMode",
68
+ "backgroundClip",
69
+ "backgroundColor",
70
+ "backgroundImage",
71
+ "backgroundOrigin",
72
+ "backgroundPosition",
73
+ "backgroundRepeat",
74
+ "backgroundSize",
75
+ "border",
76
+ "borderBottom",
77
+ "borderBottomColor",
78
+ "borderBottomLeftRadius",
79
+ "borderBottomRightRadius",
80
+ "borderBottomStyle",
81
+ "borderBottomWidth",
82
+ "borderCollapse",
83
+ "borderColor",
84
+ "borderImage",
85
+ "borderImageOutset",
86
+ "borderImageRepeat",
87
+ "borderImageSlice",
88
+ "borderImageSource",
89
+ "borderImageWidth",
90
+ "borderLeft",
91
+ "borderLeftColor",
92
+ "borderLeftStyle",
93
+ "borderLeftWidth",
94
+ "borderRadius",
95
+ "borderRight",
96
+ "borderRightColor",
97
+ "borderRightStyle",
98
+ "borderRightWidth",
99
+ "borderSpacing",
100
+ "borderStyle",
101
+ "borderTop",
102
+ "borderTopColor",
103
+ "borderTopLeftRadius",
104
+ "borderTopRightRadius",
105
+ "borderTopStyle",
106
+ "borderTopWidth",
107
+ "borderWidth",
108
+ "bottom",
109
+ "boxShadow",
110
+ "boxSizing",
111
+ "captionSide",
112
+ "caretColor",
113
+ "clear",
114
+ "clip",
115
+ "clipPath",
116
+ "color",
117
+ "columnCount",
118
+ "columnFill",
119
+ "columnGap",
120
+ "columnRule",
121
+ "columnRuleColor",
122
+ "columnRuleStyle",
123
+ "columnRuleWidth",
124
+ "columnSpan",
125
+ "columnWidth",
126
+ "columns",
127
+ "content",
128
+ "counterIncrement",
129
+ "counterReset",
130
+ "cursor",
131
+ "direction",
132
+ "display",
133
+ "emptyCells",
134
+ "filter",
135
+ "flex",
136
+ "flexBasis",
137
+ "flexDirection",
138
+ "flexFlow",
139
+ "flexGrow",
140
+ "flexShrink",
141
+ "flexWrap",
142
+ "float",
143
+ "font",
144
+ "fontFamily",
145
+ "fontFeatureSettings",
146
+ "fontKerning",
147
+ "fontLanguageOverride",
148
+ "fontSize",
149
+ "fontSizeAdjust",
150
+ "fontStretch",
151
+ "fontStyle",
152
+ "fontSynthesis",
153
+ "fontVariant",
154
+ "fontVariantAlternates",
155
+ "fontVariantCaps",
156
+ "fontVariantEastAsian",
157
+ "fontVariantLigatures",
158
+ "fontVariantNumeric",
159
+ "fontVariantPosition",
160
+ "fontWeight",
161
+ "gap",
162
+ "grid",
163
+ "gridArea",
164
+ "gridAutoColumns",
165
+ "gridAutoFlow",
166
+ "gridAutoRows",
167
+ "gridColumn",
168
+ "gridColumnEnd",
169
+ "gridColumnGap",
170
+ "gridColumnStart",
171
+ "gridGap",
172
+ "gridRow",
173
+ "gridRowEnd",
174
+ "gridRowGap",
175
+ "gridRowStart",
176
+ "gridTemplate",
177
+ "gridTemplateAreas",
178
+ "gridTemplateColumns",
179
+ "gridTemplateRows",
180
+ "height",
181
+ "hyphens",
182
+ "imageOrientation",
183
+ "imageRendering",
184
+ "imageResolution",
185
+ "imeMode",
186
+ "inlineSize",
187
+ "isolation",
188
+ "justifyContent",
189
+ "justifyItems",
190
+ "justifySelf",
191
+ "left",
192
+ "letterSpacing",
193
+ "lineHeight",
194
+ "listStyle",
195
+ "listStyleImage",
196
+ "listStylePosition",
197
+ "listStyleType",
198
+ "margin",
199
+ "marginBottom",
200
+ "marginLeft",
201
+ "marginRight",
202
+ "marginTop",
203
+ "maxHeight",
204
+ "maxWidth",
205
+ "minHeight",
206
+ "minWidth",
207
+ "objectFit",
208
+ "objectPosition",
209
+ "opacity",
210
+ "order",
211
+ "orphans",
212
+ "outline",
213
+ "outlineColor",
214
+ "outlineOffset",
215
+ "outlineStyle",
216
+ "outlineWidth",
217
+ "overflow",
218
+ "overflowWrap",
219
+ "overflowX",
220
+ "overflowY",
221
+ "padding",
222
+ "paddingBottom",
223
+ "paddingLeft",
224
+ "paddingRight",
225
+ "paddingTop",
226
+ "pageBreakAfter",
227
+ "pageBreakBefore",
228
+ "pageBreakInside",
229
+ "perspective",
230
+ "perspectiveOrigin",
231
+ "placeContent",
232
+ "placeItems",
233
+ "placeSelf",
234
+ "pointerEvents",
235
+ "position",
236
+ "quotes",
237
+ "resize",
238
+ "right",
239
+ "rowGap",
240
+ "scrollBehavior",
241
+ "tabSize",
242
+ "tableLayout",
243
+ "textAlign",
244
+ "textAlignLast",
245
+ "textDecoration",
246
+ "textDecorationColor",
247
+ "textDecorationLine",
248
+ "textDecorationStyle",
249
+ "textIndent",
250
+ "textJustify",
251
+ "textOverflow",
252
+ "textShadow",
253
+ "textTransform",
254
+ "textUnderlinePosition",
255
+ "top",
256
+ "transform",
257
+ "transformOrigin",
258
+ "transformStyle",
259
+ "transition",
260
+ "transitionDelay",
261
+ "transitionDuration",
262
+ "transitionProperty",
263
+ "transitionTimingFunction",
264
+ "unicodeBidi",
265
+ "userSelect",
266
+ "verticalAlign",
267
+ "visibility",
268
+ "whiteSpace",
269
+ "width",
270
+ "wordBreak",
271
+ "wordSpacing",
272
+ "wordWrap",
273
+ "writingMode",
274
+ "zIndex",
275
+ ]);
276
+ /**
277
+ * Checks if a prop name is a CSS property that should be converted to CSS styles.
278
+ *
279
+ * @param propName - The prop name to check
280
+ * @returns True if the prop is a CSS property
281
+ */
282
+ function isCSSProperty(propName) {
283
+ return CSS_PROPERTIES.has(propName);
284
+ }
285
+ /**
286
+ * Separates component props into variant props, CSS props, and element props.
287
+ * Variant props are used for style variants.
288
+ * CSS props are converted to CSS styles.
289
+ * Element props are passed to the DOM element.
49
290
  *
50
291
  * @param props - All component props
51
292
  * @param variants - Variant configuration
52
- * @returns Object with separated elementProps and variantProps
293
+ * @returns Object with separated elementProps, variantProps, and cssProps
53
294
  */
54
295
  function extractVariantProps(props, variants) {
55
- if (!variants) {
56
- return { elementProps: props, variantProps: {} };
57
- }
58
- const variantKeys = new Set(Object.keys(variants));
296
+ const variantKeys = variants ? new Set(Object.keys(variants)) : new Set();
59
297
  const variantProps = {};
298
+ const cssProps = {};
60
299
  const elementProps = {};
61
300
  for (const key in props) {
62
301
  if (variantKeys.has(key)) {
63
302
  variantProps[key] = props[key];
64
303
  }
304
+ else if (isCSSProperty(key)) {
305
+ // Convert CSS properties to CSS styles
306
+ cssProps[key] = props[key];
307
+ }
65
308
  else {
309
+ // Only include non-CSS, non-variant properties in elementProps
66
310
  elementProps[key] = props[key];
67
311
  }
68
312
  }
69
- return { elementProps, variantProps };
313
+ return { cssProps, elementProps, variantProps };
70
314
  }
71
315
  /**
72
316
  * Creates a styled component factory function.
@@ -88,7 +332,7 @@ export function createStyledFunction(defaultTheme, prefix = "stoop", media, util
88
332
  "variants" in baseStylesOrVariants &&
89
333
  typeof baseStylesOrVariants.variants === "object") {
90
334
  actualVariants = baseStylesOrVariants.variants;
91
- const { compoundVariants: __, variants: _, ...rest } = baseStylesOrVariants;
335
+ const { variants: _, ...rest } = baseStylesOrVariants;
92
336
  actualBaseStyles = rest;
93
337
  }
94
338
  let baseElementClassName;
@@ -101,7 +345,7 @@ export function createStyledFunction(defaultTheme, prefix = "stoop", media, util
101
345
  const cssObject = useMemo(() => cssStyles && typeof cssStyles === "object" && cssStyles !== null
102
346
  ? cssStyles
103
347
  : EMPTY_CSS, [cssStyles]);
104
- const { elementProps, variantProps } = extractVariantProps(restProps, actualVariants);
348
+ const { cssProps, elementProps, variantProps } = extractVariantProps(restProps, actualVariants);
105
349
  const contextValue = useContext(themeContext || getDefaultThemeContext());
106
350
  const currentTheme = contextValue?.theme || defaultTheme;
107
351
  const currentMedia = currentTheme.media ? { ...media, ...currentTheme.media } : media;
@@ -123,11 +367,16 @@ export function createStyledFunction(defaultTheme, prefix = "stoop", media, util
123
367
  if (actualVariants && variantKey) {
124
368
  componentStyles = applyVariants(actualVariants, variantProps, actualBaseStyles);
125
369
  }
370
+ // Merge CSS props (CSS properties passed as props) into styles
371
+ if (Object.keys(cssProps).length > 0) {
372
+ componentStyles = Object.assign({}, componentStyles, cssProps);
373
+ }
374
+ // Merge explicit css prop into styles
126
375
  if (cssObject !== EMPTY_CSS) {
127
376
  componentStyles = Object.assign({}, componentStyles, cssObject);
128
377
  }
129
378
  return componentStyles;
130
- }, [variantKey, cssObject, actualBaseStyles, actualVariants, variantProps]);
379
+ }, [variantKey, cssObject, cssProps, actualBaseStyles, actualVariants, variantProps]);
131
380
  const finalClassName = useMemo(() => {
132
381
  const classNames = [];
133
382
  if (baseElementClassName) {