@melcanz85/chaincss 1.7.3 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +323 -348
- package/atomic-optimizer.js +275 -0
- package/cache-manager.js +68 -0
- package/chaincss.js +183 -16
- package/index.js +24 -0
- package/index.react.js +4 -0
- package/package.json +25 -2
- package/prefixer.js +2 -2
- package/react-hooks.js +175 -0
- package/tokens.js +256 -0
- package/transpiler.js +155 -204
- package/types.d.ts +148 -0
- package/.github/workflows/publish.yml +0 -22
- package/publish.sh +0 -7
package/react-hooks.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { useMemo, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { $, compile } from './transpiler';
|
|
3
|
+
|
|
4
|
+
// Cache for generated styles to avoid duplication
|
|
5
|
+
const styleCache = new Map();
|
|
6
|
+
let styleSheet = null;
|
|
7
|
+
|
|
8
|
+
// Initialize style sheet (add to document head)
|
|
9
|
+
const initStyleSheet = () => {
|
|
10
|
+
if (typeof document === 'undefined') return null; // SSR safety
|
|
11
|
+
|
|
12
|
+
if (!styleSheet) {
|
|
13
|
+
// Check if already exists
|
|
14
|
+
const existing = document.getElementById('chaincss-styles');
|
|
15
|
+
if (existing) {
|
|
16
|
+
styleSheet = existing;
|
|
17
|
+
return styleSheet;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Create new style element
|
|
21
|
+
const style = document.createElement('style');
|
|
22
|
+
style.id = 'chaincss-styles';
|
|
23
|
+
style.setAttribute('data-chaincss', 'true');
|
|
24
|
+
document.head.appendChild(style);
|
|
25
|
+
styleSheet = style;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return styleSheet;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Update styles in the style sheet
|
|
32
|
+
const updateStyles = (css) => {
|
|
33
|
+
const sheet = initStyleSheet();
|
|
34
|
+
if (sheet) {
|
|
35
|
+
sheet.textContent = css;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Main hook for using ChainCSS styles in React
|
|
40
|
+
export function useChainStyles(styles, options = {}) {
|
|
41
|
+
const {
|
|
42
|
+
cache = true,
|
|
43
|
+
namespace = 'chain',
|
|
44
|
+
watch = false
|
|
45
|
+
} = options;
|
|
46
|
+
|
|
47
|
+
// Generate a unique ID for this component instance
|
|
48
|
+
const id = useRef(`chain-${Math.random().toString(36).substr(2, 9)}`);
|
|
49
|
+
|
|
50
|
+
// Store the generated class names
|
|
51
|
+
const [classNames, setClassNames] = useState({});
|
|
52
|
+
|
|
53
|
+
// Process styles and generate CSS
|
|
54
|
+
const processed = useMemo(() => {
|
|
55
|
+
if (!styles || Object.keys(styles).length === 0) return { classNames: {}, css: '' };
|
|
56
|
+
|
|
57
|
+
// Check cache first
|
|
58
|
+
const cacheKey = JSON.stringify(styles);
|
|
59
|
+
if (cache && styleCache.has(cacheKey)) {
|
|
60
|
+
return styleCache.get(cacheKey);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Generate unique class names for each style
|
|
64
|
+
const newClassNames = {};
|
|
65
|
+
const compiledStyles = {};
|
|
66
|
+
|
|
67
|
+
Object.entries(styles).forEach(([key, styleDef]) => {
|
|
68
|
+
// Generate a unique class name
|
|
69
|
+
const className = `${namespace}-${key}-${id.current}`;
|
|
70
|
+
|
|
71
|
+
// Create a style definition with the unique class
|
|
72
|
+
const styleObj = typeof styleDef === 'function'
|
|
73
|
+
? styleDef()
|
|
74
|
+
: styleDef;
|
|
75
|
+
|
|
76
|
+
// Store the class name mapping
|
|
77
|
+
newClassNames[key] = className;
|
|
78
|
+
|
|
79
|
+
// Create the style rule
|
|
80
|
+
compiledStyles[`${key}_${id.current}`] = {
|
|
81
|
+
selectors: [`.${className}`],
|
|
82
|
+
...styleObj
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Compile to CSS
|
|
87
|
+
compile(compiledStyles);
|
|
88
|
+
const css = chain.cssOutput;
|
|
89
|
+
|
|
90
|
+
const result = { classNames: newClassNames, css };
|
|
91
|
+
|
|
92
|
+
if (cache) {
|
|
93
|
+
styleCache.set(cacheKey, result);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
}, [styles, namespace]);
|
|
98
|
+
|
|
99
|
+
// Update the style sheet when styles change
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (processed.css) {
|
|
102
|
+
// For simple apps, just append
|
|
103
|
+
if (!watch) {
|
|
104
|
+
const sheet = initStyleSheet();
|
|
105
|
+
if (sheet) {
|
|
106
|
+
// Remove old styles for this component
|
|
107
|
+
const existingStyles = sheet.textContent || '';
|
|
108
|
+
const styleRegex = new RegExp(`\\.[\\w-]*${id.current}[\\s\\S]*?}`, 'g');
|
|
109
|
+
const cleanedStyles = existingStyles.replace(styleRegex, '');
|
|
110
|
+
|
|
111
|
+
// Add new styles
|
|
112
|
+
sheet.textContent = cleanedStyles + processed.css;
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// For watch mode, update everything
|
|
116
|
+
updateStyles(processed.css);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Cleanup on unmount
|
|
121
|
+
return () => {
|
|
122
|
+
if (!watch && styleSheet) {
|
|
123
|
+
const existingStyles = styleSheet.textContent || '';
|
|
124
|
+
const styleRegex = new RegExp(`\\.[\\w-]*${id.current}[\\s\\S]*?}`, 'g');
|
|
125
|
+
styleSheet.textContent = existingStyles.replace(styleRegex, '');
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}, [processed.css, watch]);
|
|
129
|
+
|
|
130
|
+
return processed.classNames;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Hook for dynamic styles that depend on props/state
|
|
134
|
+
export function useDynamicChainStyles(styleFactory, deps = [], options = {}) {
|
|
135
|
+
const styles = useMemo(() => {
|
|
136
|
+
return styleFactory();
|
|
137
|
+
}, deps);
|
|
138
|
+
|
|
139
|
+
return useChainStyles(styles, options);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Hook for theme-aware styles
|
|
143
|
+
export function useThemeChainStyles(styles, theme, options = {}) {
|
|
144
|
+
const themedStyles = useMemo(() => {
|
|
145
|
+
if (typeof styles === 'function') {
|
|
146
|
+
return styles(theme);
|
|
147
|
+
}
|
|
148
|
+
return styles;
|
|
149
|
+
}, [styles, theme]);
|
|
150
|
+
|
|
151
|
+
return useChainStyles(themedStyles, options);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Component for injecting global ChainCSS styles
|
|
155
|
+
export function ChainCSSGlobal({ styles }) {
|
|
156
|
+
useChainStyles(styles, { watch: true });
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// HOC for adding ChainCSS styles to components
|
|
161
|
+
export function withChainStyles(styles, options = {}) {
|
|
162
|
+
return function WrappedComponent(props) {
|
|
163
|
+
const classNames = useChainStyles(
|
|
164
|
+
typeof styles === 'function' ? styles(props) : styles,
|
|
165
|
+
options
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
return <WrappedComponent {...props} chainStyles={classNames} />;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Utility to combine multiple class names
|
|
173
|
+
export function cx(...classes) {
|
|
174
|
+
return classes.filter(Boolean).join(' ');
|
|
175
|
+
}
|
package/tokens.js
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
class DesignTokens {
|
|
2
|
+
constructor(tokens = {}) {
|
|
3
|
+
this.tokens = this.deepFreeze({
|
|
4
|
+
colors: {},
|
|
5
|
+
spacing: {},
|
|
6
|
+
typography: {},
|
|
7
|
+
breakpoints: {},
|
|
8
|
+
zIndex: {},
|
|
9
|
+
shadows: {},
|
|
10
|
+
borderRadius: {},
|
|
11
|
+
...tokens
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
this.flattened = this.flattenTokens(this.tokens);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Deep freeze to prevent accidental modifications
|
|
18
|
+
deepFreeze(obj) {
|
|
19
|
+
Object.keys(obj).forEach(key => {
|
|
20
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
21
|
+
this.deepFreeze(obj[key]);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return Object.freeze(obj);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Flatten nested tokens for easy access
|
|
28
|
+
flattenTokens(obj, prefix = '') {
|
|
29
|
+
return Object.keys(obj).reduce((acc, key) => {
|
|
30
|
+
const prefixed = prefix ? `${prefix}.${key}` : key;
|
|
31
|
+
|
|
32
|
+
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
|
33
|
+
Object.assign(acc, this.flattenTokens(obj[key], prefixed));
|
|
34
|
+
} else {
|
|
35
|
+
acc[prefixed] = obj[key];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return acc;
|
|
39
|
+
}, {});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Get token value by path (e.g., 'colors.primary')
|
|
43
|
+
get(path, defaultValue = '') {
|
|
44
|
+
return this.flattened[path] || defaultValue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Generate CSS variables from tokens
|
|
48
|
+
toCSSVariables(prefix = 'chain') {
|
|
49
|
+
let css = ':root {\n';
|
|
50
|
+
|
|
51
|
+
Object.entries(this.flattened).forEach(([key, value]) => {
|
|
52
|
+
const varName = `--${prefix}-${key.replace(/\./g, '-')}`;
|
|
53
|
+
css += ` ${varName}: ${value};\n`;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
css += '}\n';
|
|
57
|
+
return css;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Create a theme variant
|
|
61
|
+
createTheme(name, overrides) {
|
|
62
|
+
const themeTokens = { ...this.flattened };
|
|
63
|
+
|
|
64
|
+
Object.entries(overrides).forEach(([key, value]) => {
|
|
65
|
+
if (themeTokens[key]) {
|
|
66
|
+
themeTokens[key] = value;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return new DesignTokens(this.expandTokens(themeTokens));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Expand flattened tokens back to nested structure
|
|
74
|
+
expandTokens(flattened) {
|
|
75
|
+
const result = {};
|
|
76
|
+
|
|
77
|
+
Object.entries(flattened).forEach(([key, value]) => {
|
|
78
|
+
const parts = key.split('.');
|
|
79
|
+
let current = result;
|
|
80
|
+
|
|
81
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
82
|
+
current[parts[i]] = current[parts[i]] || {};
|
|
83
|
+
current = current[parts[i]];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
current[parts[parts.length - 1]] = value;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Default tokens
|
|
94
|
+
const defaultTokens = {
|
|
95
|
+
colors: {
|
|
96
|
+
primary: '#667eea',
|
|
97
|
+
secondary: '#764ba2',
|
|
98
|
+
success: '#48bb78',
|
|
99
|
+
danger: '#f56565',
|
|
100
|
+
warning: '#ed8936',
|
|
101
|
+
info: '#4299e1',
|
|
102
|
+
light: '#f7fafc',
|
|
103
|
+
dark: '#1a202c',
|
|
104
|
+
white: '#ffffff',
|
|
105
|
+
black: '#000000',
|
|
106
|
+
gray: {
|
|
107
|
+
100: '#f7fafc',
|
|
108
|
+
200: '#edf2f7',
|
|
109
|
+
300: '#e2e8f0',
|
|
110
|
+
400: '#cbd5e0',
|
|
111
|
+
500: '#a0aec0',
|
|
112
|
+
600: '#718096',
|
|
113
|
+
700: '#4a5568',
|
|
114
|
+
800: '#2d3748',
|
|
115
|
+
900: '#1a202c'
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
spacing: {
|
|
120
|
+
0: '0',
|
|
121
|
+
1: '0.25rem',
|
|
122
|
+
2: '0.5rem',
|
|
123
|
+
3: '0.75rem',
|
|
124
|
+
4: '1rem',
|
|
125
|
+
5: '1.25rem',
|
|
126
|
+
6: '1.5rem',
|
|
127
|
+
8: '2rem',
|
|
128
|
+
10: '2.5rem',
|
|
129
|
+
12: '3rem',
|
|
130
|
+
16: '4rem',
|
|
131
|
+
20: '5rem',
|
|
132
|
+
24: '6rem',
|
|
133
|
+
32: '8rem',
|
|
134
|
+
40: '10rem',
|
|
135
|
+
48: '12rem',
|
|
136
|
+
56: '14rem',
|
|
137
|
+
64: '16rem',
|
|
138
|
+
xs: '0.5rem',
|
|
139
|
+
sm: '1rem',
|
|
140
|
+
md: '1.5rem',
|
|
141
|
+
lg: '2rem',
|
|
142
|
+
xl: '3rem',
|
|
143
|
+
'2xl': '4rem',
|
|
144
|
+
'3xl': '6rem'
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
typography: {
|
|
148
|
+
fontFamily: {
|
|
149
|
+
sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
150
|
+
serif: 'Georgia, Cambria, "Times New Roman", Times, serif',
|
|
151
|
+
mono: 'SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace'
|
|
152
|
+
},
|
|
153
|
+
fontSize: {
|
|
154
|
+
xs: '0.75rem',
|
|
155
|
+
sm: '0.875rem',
|
|
156
|
+
base: '1rem',
|
|
157
|
+
lg: '1.125rem',
|
|
158
|
+
xl: '1.25rem',
|
|
159
|
+
'2xl': '1.5rem',
|
|
160
|
+
'3xl': '1.875rem',
|
|
161
|
+
'4xl': '2.25rem',
|
|
162
|
+
'5xl': '3rem'
|
|
163
|
+
},
|
|
164
|
+
fontWeight: {
|
|
165
|
+
hairline: '100',
|
|
166
|
+
thin: '200',
|
|
167
|
+
light: '300',
|
|
168
|
+
normal: '400',
|
|
169
|
+
medium: '500',
|
|
170
|
+
semibold: '600',
|
|
171
|
+
bold: '700',
|
|
172
|
+
extrabold: '800',
|
|
173
|
+
black: '900'
|
|
174
|
+
},
|
|
175
|
+
lineHeight: {
|
|
176
|
+
none: '1',
|
|
177
|
+
tight: '1.25',
|
|
178
|
+
snug: '1.375',
|
|
179
|
+
normal: '1.5',
|
|
180
|
+
relaxed: '1.625',
|
|
181
|
+
loose: '2'
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
|
|
185
|
+
breakpoints: {
|
|
186
|
+
sm: '640px',
|
|
187
|
+
md: '768px',
|
|
188
|
+
lg: '1024px',
|
|
189
|
+
xl: '1280px',
|
|
190
|
+
'2xl': '1536px'
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
zIndex: {
|
|
194
|
+
0: '0',
|
|
195
|
+
10: '10',
|
|
196
|
+
20: '20',
|
|
197
|
+
30: '30',
|
|
198
|
+
40: '40',
|
|
199
|
+
50: '50',
|
|
200
|
+
auto: 'auto',
|
|
201
|
+
dropdown: '1000',
|
|
202
|
+
sticky: '1020',
|
|
203
|
+
fixed: '1030',
|
|
204
|
+
modal: '1040',
|
|
205
|
+
popover: '1050',
|
|
206
|
+
tooltip: '1060'
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
shadows: {
|
|
210
|
+
sm: '0 1px 2px 0 rgba(0, 0, 0, 0.05)',
|
|
211
|
+
base: '0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)',
|
|
212
|
+
md: '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)',
|
|
213
|
+
lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
|
|
214
|
+
xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)',
|
|
215
|
+
'2xl': '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
|
|
216
|
+
inner: 'inset 0 2px 4px 0 rgba(0, 0, 0, 0.06)',
|
|
217
|
+
none: 'none'
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
borderRadius: {
|
|
221
|
+
none: '0',
|
|
222
|
+
sm: '0.125rem',
|
|
223
|
+
base: '0.25rem',
|
|
224
|
+
md: '0.375rem',
|
|
225
|
+
lg: '0.5rem',
|
|
226
|
+
xl: '0.75rem',
|
|
227
|
+
'2xl': '1rem',
|
|
228
|
+
'3xl': '1.5rem',
|
|
229
|
+
full: '9999px'
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Create and export tokens instance
|
|
234
|
+
const tokens = new DesignTokens(defaultTokens);
|
|
235
|
+
|
|
236
|
+
// Token utility functions
|
|
237
|
+
const createTokens = (customTokens) => {
|
|
238
|
+
return new DesignTokens(customTokens);
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// Generate responsive values
|
|
242
|
+
const responsive = (values) => {
|
|
243
|
+
if (typeof values === 'string') return values;
|
|
244
|
+
|
|
245
|
+
return Object.entries(values).map(([breakpoint, value]) => {
|
|
246
|
+
if (breakpoint === 'base') return value;
|
|
247
|
+
return `@media (min-width: ${tokens.get(`breakpoints.${breakpoint}`)}) { ${value} }`;
|
|
248
|
+
}).join(' ');
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
module.exports = {
|
|
252
|
+
tokens,
|
|
253
|
+
createTokens,
|
|
254
|
+
responsive,
|
|
255
|
+
DesignTokens
|
|
256
|
+
};
|