stoop 0.2.1 → 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 (56) hide show
  1. package/README.md +48 -103
  2. package/dist/api/core-api.d.ts +34 -0
  3. package/dist/api/core-api.js +135 -0
  4. package/dist/api/global-css.d.ts +0 -11
  5. package/dist/api/global-css.js +42 -0
  6. package/dist/api/styled.d.ts +0 -1
  7. package/dist/api/styled.js +419 -0
  8. package/dist/api/theme-provider.d.ts +41 -0
  9. package/dist/api/theme-provider.js +223 -0
  10. package/dist/constants.d.ts +13 -4
  11. package/dist/constants.js +154 -0
  12. package/dist/core/cache.d.ts +5 -9
  13. package/dist/core/cache.js +68 -0
  14. package/dist/core/compiler.d.ts +11 -0
  15. package/dist/core/compiler.js +206 -0
  16. package/dist/core/theme-manager.d.ts +27 -4
  17. package/dist/core/theme-manager.js +107 -0
  18. package/dist/core/variants.js +38 -0
  19. package/dist/create-stoop-internal.d.ts +30 -0
  20. package/dist/create-stoop-internal.js +123 -0
  21. package/dist/create-stoop-ssr.d.ts +10 -0
  22. package/dist/create-stoop-ssr.js +26 -0
  23. package/dist/create-stoop.d.ts +32 -4
  24. package/dist/create-stoop.js +156 -0
  25. package/dist/inject.d.ts +113 -0
  26. package/dist/inject.js +308 -0
  27. package/dist/types/index.d.ts +157 -17
  28. package/dist/types/index.js +5 -0
  29. package/dist/types/react-polymorphic-types.d.ts +15 -8
  30. package/dist/utils/auto-preload.d.ts +45 -0
  31. package/dist/utils/auto-preload.js +167 -0
  32. package/dist/utils/helpers.d.ts +64 -0
  33. package/dist/utils/helpers.js +150 -0
  34. package/dist/utils/storage.d.ts +148 -0
  35. package/dist/utils/storage.js +396 -0
  36. package/dist/utils/{string.d.ts → theme-utils.d.ts} +36 -12
  37. package/dist/utils/theme-utils.js +353 -0
  38. package/dist/utils/theme.d.ts +17 -5
  39. package/dist/utils/theme.js +304 -0
  40. package/package.json +48 -24
  41. package/LICENSE.md +0 -21
  42. package/dist/api/create-theme.d.ts +0 -13
  43. package/dist/api/css.d.ts +0 -16
  44. package/dist/api/keyframes.d.ts +0 -16
  45. package/dist/api/provider.d.ts +0 -19
  46. package/dist/api/use-theme.d.ts +0 -13
  47. package/dist/index.d.ts +0 -6
  48. package/dist/index.js +0 -13
  49. package/dist/inject/browser.d.ts +0 -59
  50. package/dist/inject/dedup.d.ts +0 -29
  51. package/dist/inject/index.d.ts +0 -41
  52. package/dist/inject/ssr.d.ts +0 -28
  53. package/dist/utils/theme-map.d.ts +0 -25
  54. package/dist/utils/theme-validation.d.ts +0 -13
  55. package/dist/utils/type-guards.d.ts +0 -26
  56. package/dist/utils/utilities.d.ts +0 -14
package/README.md CHANGED
@@ -1,36 +1,6 @@
1
- # Stoop
1
+ # stoop
2
2
 
3
- CSS-in-JS library with type inference, theme creation, and variants support.
4
-
5
- [![npm version](https://img.shields.io/npm/v/stoop)](https://www.npmjs.com/package/stoop)
6
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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)
3
+ A lightweight, type-safe CSS-in-JS library for React with theme support, variants, and SSR capabilities.
34
4
 
35
5
  ## Installation
36
6
 
@@ -47,7 +17,7 @@ yarn add stoop
47
17
  ```tsx
48
18
  import { createStoop } from "stoop";
49
19
 
50
- const { styled, css, createTheme, globalCss, keyframes, ThemeContext } = createStoop({
20
+ const { styled, css, Provider, useTheme } = createStoop({
51
21
  theme: {
52
22
  colors: {
53
23
  primary: "#0070f3",
@@ -60,121 +30,96 @@ const { styled, css, createTheme, globalCss, keyframes, ThemeContext } = createS
60
30
  large: "24px",
61
31
  },
62
32
  },
33
+ themes: {
34
+ light: {
35
+ /* ... */
36
+ },
37
+ dark: {
38
+ /* ... */
39
+ },
40
+ },
63
41
  });
64
42
 
65
43
  const Button = styled("button", {
66
44
  padding: "$medium",
67
45
  backgroundColor: "$primary",
68
- }, {
69
- variant: {
70
- primary: { backgroundColor: "$primary" },
71
- secondary: { backgroundColor: "$secondary" },
72
- },
46
+ color: "$text",
73
47
  });
74
48
 
75
- <Button variant="primary">Click me</Button>
49
+ <Button>Click me</Button>;
76
50
  ```
77
51
 
78
- See [GUIDE.md](./docs/GUIDE.md) for complete setup instructions.
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
79
61
 
80
62
  ## Documentation
81
63
 
82
64
  - **[GUIDE.md](./docs/GUIDE.md)** - Step-by-step setup and usage guide
83
65
  - **[API.md](./docs/API.md)** - Complete API reference
84
66
  - **[ARCHITECTURE.md](./docs/ARCHITECTURE.md)** - Internal implementation details
67
+ - **[TESTING.md](./docs/TESTING.md)** - Testing guide and test suite documentation
85
68
 
86
69
  ## API Overview
87
70
 
88
71
  ### `createStoop(config)`
89
72
 
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.
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`.
93
74
 
94
75
  ### Theme Tokens
95
76
 
96
- Use `$` prefix for theme tokens. Shorthand `$token` searches all scales; explicit `$scale.token` specifies the scale.
77
+ Use `$` prefix for theme tokens. Shorthand `$token` uses property-aware resolution (preferred); explicit `$scale.token` specifies the scale.
97
78
 
98
79
  ```tsx
99
80
  {
100
- color: "$primary", // Shorthand (preferred)
101
- padding: "$medium", // Property-aware resolution
81
+ color: "$primary", // Shorthand (preferred, property-aware)
82
+ padding: "$medium", // Property-aware → space scale
102
83
  fontSize: "$fontSizes.small", // Explicit scale
103
84
  }
104
85
  ```
105
86
 
106
- Tokens resolve to CSS variables (`var(--colors-primary)`), enabling instant theme switching without recompiling CSS.
107
-
108
87
  ### Variants
109
88
 
110
89
  Variants create component variations via props:
111
90
 
112
91
  ```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" },
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
+ },
121
104
  },
122
- });
105
+ );
123
106
 
124
- <Button variant="primary" size="small" />
107
+ <Button variant="primary" size="small" />;
125
108
  ```
126
109
 
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
110
  ## Development
166
111
 
167
112
  ```sh
168
- bun install
169
- bun run dev
113
+ # Build
170
114
  bun run build
171
- bun run lint
172
- ```
173
115
 
174
- ## Contributing
116
+ # Test
117
+ bun run test
175
118
 
176
- Contributions welcome. See [ARCHITECTURE.md](./docs/ARCHITECTURE.md) for implementation details.
119
+ # Watch mode
120
+ bun run test:watch
121
+ ```
177
122
 
178
123
  ## License
179
124
 
180
- MIT © [Jackson Dolman](https://github.com/dolmios)
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;
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Core API functions.
3
+ * Consolidates theme creation, CSS class generation, and keyframes animation APIs.
4
+ */
5
+ import { LRUCache } from "../core/cache";
6
+ import { compileCSS } from "../core/compiler";
7
+ import { mergeThemes } from "../core/theme-manager";
8
+ import { injectCSS } from "../inject";
9
+ import { validateTheme } from "../utils/helpers";
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
+ // ============================================================================
50
+ /**
51
+ * Converts a keyframes object to a CSS @keyframes rule string.
52
+ *
53
+ * @param keyframesObj - Keyframes object with percentage/from/to keys
54
+ * @param animationName - Name for the animation
55
+ * @param theme - Optional theme for token resolution
56
+ * @param themeMap - Optional theme scale mappings
57
+ * @returns CSS @keyframes rule string
58
+ */
59
+ function keyframesToCSS(keyframesObj, animationName, theme, themeMap) {
60
+ let css = `@keyframes ${animationName} {`;
61
+ const sortedKeys = Object.keys(keyframesObj).sort((a, b) => {
62
+ const aNum = parseFloat(a.replace("%", ""));
63
+ const bNum = parseFloat(b.replace("%", ""));
64
+ if (a === "from") {
65
+ return -1;
66
+ }
67
+ if (b === "from") {
68
+ return 1;
69
+ }
70
+ if (a === "to") {
71
+ return 1;
72
+ }
73
+ if (b === "to") {
74
+ return -1;
75
+ }
76
+ return aNum - bNum;
77
+ });
78
+ for (const key of sortedKeys) {
79
+ if (!validateKeyframeKey(key)) {
80
+ continue;
81
+ }
82
+ const styles = keyframesObj[key];
83
+ if (!styles || typeof styles !== "object") {
84
+ continue;
85
+ }
86
+ css += ` ${key} {`;
87
+ const themedStyles = replaceThemeTokensWithVars(styles, theme, themeMap);
88
+ // Sort properties for deterministic CSS generation
89
+ const sortedProps = Object.keys(themedStyles).sort();
90
+ for (const prop of sortedProps) {
91
+ const value = themedStyles[prop];
92
+ if (value !== undefined && (typeof value === "string" || typeof value === "number")) {
93
+ const sanitizedProp = sanitizeCSSPropertyName(prop);
94
+ if (sanitizedProp) {
95
+ // Don't escape keyframe values - escaping breaks complex CSS functions
96
+ const cssValue = String(value);
97
+ css += ` ${sanitizedProp}: ${cssValue};`;
98
+ }
99
+ }
100
+ }
101
+ css += " }";
102
+ }
103
+ css += " }";
104
+ return css;
105
+ }
106
+ import { KEYFRAME_CACHE_LIMIT } from "../constants";
107
+ /**
108
+ * Creates a keyframes animation function.
109
+ * Generates and injects @keyframes rules with caching to prevent duplicates.
110
+ *
111
+ * @param prefix - Optional prefix for animation names
112
+ * @param theme - Optional theme for token resolution
113
+ * @param themeMap - Optional theme scale mappings
114
+ * @returns Function that accepts keyframes objects and returns animation names
115
+ */
116
+ export function createKeyframesFunction(prefix = "stoop", theme, themeMap) {
117
+ const sanitizedPrefix = sanitizePrefix(prefix);
118
+ const animationCache = new LRUCache(KEYFRAME_CACHE_LIMIT);
119
+ return function keyframes(keyframesObj) {
120
+ const keyframesKey = hashObject(keyframesObj);
121
+ const cachedName = animationCache.get(keyframesKey);
122
+ if (cachedName) {
123
+ return cachedName;
124
+ }
125
+ const hashValue = keyframesKey.slice(0, 8);
126
+ const animationName = sanitizedPrefix
127
+ ? `${sanitizedPrefix}-${hashValue}`
128
+ : `stoop-${hashValue}`;
129
+ const css = keyframesToCSS(keyframesObj, animationName, theme, themeMap);
130
+ const ruleKey = `__keyframes_${animationName}`;
131
+ injectCSS(css, sanitizedPrefix, ruleKey);
132
+ animationCache.set(keyframesKey, animationName);
133
+ return animationName;
134
+ };
135
+ }
@@ -4,15 +4,4 @@
4
4
  * Supports media queries, nested selectors, and theme tokens.
5
5
  */
6
6
  import type { CSS, Theme, ThemeScale, UtilityFunction } from "../types";
7
- /**
8
- * Creates a global CSS injection function.
9
- * Injects styles directly into the document with deduplication support.
10
- *
11
- * @param defaultTheme - Default theme for token resolution
12
- * @param prefix - Optional prefix for CSS rules
13
- * @param media - Optional media query breakpoints
14
- * @param utils - Optional utility functions
15
- * @param themeMap - Optional theme scale mappings
16
- * @returns Function that accepts CSS objects and returns a cleanup function
17
- */
18
7
  export declare function createGlobalCSSFunction(defaultTheme: Theme, prefix?: string, media?: Record<string, string>, utils?: Record<string, UtilityFunction>, themeMap?: Record<string, ThemeScale>): (styles: CSS) => () => void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Global CSS injection API.
3
+ * Creates a function that injects global styles into the document.
4
+ * Supports media queries, nested selectors, and theme tokens.
5
+ */
6
+ import { cssObjectToString } from "../core/compiler";
7
+ import { injectCSS } from "../inject";
8
+ import { applyUtilities } from "../utils/helpers";
9
+ import { replaceThemeTokensWithVars } from "../utils/theme";
10
+ import { hashObject, sanitizePrefix } from "../utils/theme-utils";
11
+ /**
12
+ * Creates a global CSS injection function.
13
+ * Injects styles directly into the document with deduplication support.
14
+ *
15
+ * @param defaultTheme - Default theme for token resolution
16
+ * @param prefix - Optional prefix for CSS rules
17
+ * @param media - Optional media query breakpoints
18
+ * @param utils - Optional utility functions
19
+ * @param themeMap - Optional theme scale mappings
20
+ * @returns Function that accepts CSS objects and returns a cleanup function
21
+ */
22
+ // Use CSS hash as key instead of theme object reference for better deduplication
23
+ const globalInjectedStyles = new Set();
24
+ export function createGlobalCSSFunction(defaultTheme, prefix = "stoop", media, utils, themeMap) {
25
+ return function globalCss(styles) {
26
+ const cssKey = hashObject(styles);
27
+ if (globalInjectedStyles.has(cssKey)) {
28
+ return () => { };
29
+ }
30
+ globalInjectedStyles.add(cssKey);
31
+ const sanitizedPrefix = sanitizePrefix(prefix);
32
+ const stylesWithUtils = applyUtilities(styles, utils);
33
+ const themedStyles = replaceThemeTokensWithVars(stylesWithUtils, defaultTheme, themeMap);
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);
37
+ injectCSS(cssText, sanitizedPrefix, `__global_${cssKey}`);
38
+ return () => {
39
+ globalInjectedStyles.delete(cssKey);
40
+ };
41
+ };
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.