@marigold/system 0.8.0 → 0.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/dist/index.d.ts CHANGED
@@ -1,18 +1,14 @@
1
- import { PolymorphicPropsWithRef, PolymorphicComponentWithRef, ComponentProps } from '@marigold/types';
1
+ import { Simplify, PolymorphicPropsWithRef, PolymorphicComponentWithRef, ComponentProps } from '@marigold/types';
2
2
  import * as _theme_ui_css from '@theme-ui/css';
3
3
  import { ResponsiveStyleValue as ResponsiveStyleValue$1, ThemeUIStyleObject, ThemeUICSSObject, ThemeUICSSProperties, NestedScaleDict } from '@theme-ui/css';
4
4
  import * as react from 'react';
5
- import { ElementType, ReactNode } from 'react';
5
+ import { ReactNode, ElementType } from 'react';
6
6
  import * as CSS from 'csstype';
7
7
 
8
- /**
9
- * Create type aliases for `theme-ui` so that it doesn't leak too much into our code.
10
- */
11
-
12
8
  declare type ResponsiveStyleValue<T> = ResponsiveStyleValue$1<T>;
13
9
  declare type StyleObject = ThemeUIStyleObject;
14
- declare type CSSObject = ThemeUICSSObject;
15
- declare type CSSProperties = ThemeUICSSProperties;
10
+ declare type CSSObject = Simplify<ThemeUICSSObject>;
11
+ declare type CSSProperties = Simplify<ThemeUICSSProperties>;
16
12
 
17
13
  interface StyleProps extends Pick<CSSObject, 'display' | 'height' | 'width' | 'minWidth' | 'maxWidth' | 'position' | 'top' | 'bottom' | 'right' | 'left' | 'zIndex' | 'p' | 'px' | 'py' | 'pt' | 'pb' | 'pl' | 'pr' | 'm' | 'mx' | 'my' | 'mt' | 'mb' | 'ml' | 'mr' | 'flexDirection' | 'flexWrap' | 'flexShrink' | 'flexGrow' | 'alignItems' | 'justifyContent' | 'bg' | 'border' | 'borderRadius' | 'boxShadow' | 'opacity' | 'overflow' | 'transition'> {
18
14
  }
@@ -31,160 +27,52 @@ declare const Box: PolymorphicComponentWithRef<BoxOwnProps, 'div'>;
31
27
 
32
28
  declare const Global: () => JSX.Element;
33
29
 
30
+ interface SVGProps extends ComponentProps<'svg'> {
31
+ size?: number | string | number[] | string[];
32
+ }
33
+ declare const SVG: ({ size, fill, children, ...props }: SVGProps) => react.ReactSVGElement;
34
+
34
35
  /**
35
- * Normalize styling of certain elements between browsers.
36
- * Based on https://www.joshwcomeau.com/css/custom-css-reset/
36
+ * Props that every component should accepts to change the styling
37
37
  */
38
-
39
- declare const normalize: {
40
- readonly base: {
41
- readonly boxSizing: "border-box";
42
- readonly margin: 0;
43
- readonly minWidth: 0;
44
- };
45
- readonly a: {
46
- readonly textDecoration: "none";
47
- readonly boxSizing: "border-box";
48
- readonly margin: 0;
49
- readonly minWidth: 0;
50
- };
51
- readonly p: {
52
- readonly overflowWrap: "break-word";
53
- readonly boxSizing: "border-box";
54
- readonly margin: 0;
55
- readonly minWidth: 0;
56
- };
57
- readonly h1: {
58
- readonly overflowWrap: "break-word";
59
- readonly boxSizing: "border-box";
60
- readonly margin: 0;
61
- readonly minWidth: 0;
62
- };
63
- readonly h2: {
64
- readonly overflowWrap: "break-word";
65
- readonly boxSizing: "border-box";
66
- readonly margin: 0;
67
- readonly minWidth: 0;
68
- };
69
- readonly h3: {
70
- readonly overflowWrap: "break-word";
71
- readonly boxSizing: "border-box";
72
- readonly margin: 0;
73
- readonly minWidth: 0;
74
- };
75
- readonly h4: {
76
- readonly overflowWrap: "break-word";
77
- readonly boxSizing: "border-box";
78
- readonly margin: 0;
79
- readonly minWidth: 0;
80
- };
81
- readonly h5: {
82
- readonly overflowWrap: "break-word";
83
- readonly boxSizing: "border-box";
84
- readonly margin: 0;
85
- readonly minWidth: 0;
86
- };
87
- readonly h6: {
88
- readonly overflowWrap: "break-word";
89
- readonly boxSizing: "border-box";
90
- readonly margin: 0;
91
- readonly minWidth: 0;
92
- };
93
- readonly img: {
94
- readonly display: "block";
95
- readonly maxWidth: "100%";
96
- readonly boxSizing: "border-box";
97
- readonly margin: 0;
98
- readonly minWidth: 0;
99
- };
100
- readonly picture: {
101
- readonly display: "block";
102
- readonly maxWidth: "100%";
103
- readonly boxSizing: "border-box";
104
- readonly margin: 0;
105
- readonly minWidth: 0;
106
- };
107
- readonly video: {
108
- readonly display: "block";
109
- readonly maxWidth: "100%";
110
- readonly boxSizing: "border-box";
111
- readonly margin: 0;
112
- readonly minWidth: 0;
113
- };
114
- readonly canvas: {
115
- readonly display: "block";
116
- readonly maxWidth: "100%";
117
- readonly boxSizing: "border-box";
118
- readonly margin: 0;
119
- readonly minWidth: 0;
120
- };
121
- readonly svg: {
122
- readonly display: "block";
123
- readonly maxWidth: "100%";
124
- readonly boxSizing: "border-box";
125
- readonly margin: 0;
126
- readonly minWidth: 0;
127
- };
128
- readonly select: {
129
- readonly display: "block";
130
- readonly appearance: "none";
131
- readonly font: "inherit";
132
- readonly '&::-ms-expand': {
133
- readonly display: "none";
134
- };
135
- readonly boxSizing: "border-box";
136
- readonly margin: 0;
137
- readonly minWidth: 0;
138
- };
139
- readonly button: {
140
- readonly display: "block";
141
- readonly appearance: "none";
142
- readonly font: "inherit";
143
- readonly background: "transparent";
144
- readonly textAlign: "center";
145
- readonly boxSizing: "border-box";
146
- readonly margin: 0;
147
- readonly minWidth: 0;
148
- };
149
- readonly textarea: {
150
- readonly display: "block";
151
- readonly appearance: "none";
152
- readonly font: "inherit";
153
- readonly boxSizing: "border-box";
154
- readonly margin: 0;
155
- readonly minWidth: 0;
156
- };
157
- readonly input: {
158
- readonly display: "block";
159
- readonly appearance: "none";
160
- readonly font: "inherit";
161
- readonly '&::-ms-clear': {
162
- readonly display: "none";
38
+ declare type ThemeComponentProps = {
39
+ variant?: string;
40
+ size?: string;
41
+ };
42
+ /**
43
+ * Structure for component styles in a theme.
44
+ */
45
+ declare type ThemeExtension<ComponentName extends string> = {
46
+ [P in ComponentName]?: {
47
+ base?: CSSObject;
48
+ variant?: {
49
+ [key: string]: CSSObject;
163
50
  };
164
- readonly '&::-webkit-search-cancel-button': {
165
- readonly WebkitAppearance: "none";
51
+ size?: {
52
+ [key: string]: CSSObject;
166
53
  };
167
- readonly boxSizing: "border-box";
168
- readonly margin: 0;
169
- readonly minWidth: 0;
170
54
  };
171
55
  };
172
- declare type NormalizedElement = keyof typeof normalize;
173
56
  /**
174
- * Type-safe helper to get normalization. If no normalization is found,
175
- * returns the *base* normalization.
57
+ * Structure for component styles in a theme that consists of multiple parts.
176
58
  */
177
- declare const getNormalizedStyles: (val?: ElementType<any> | undefined) => {
178
- readonly boxSizing: "border-box";
179
- readonly margin: 0;
180
- readonly minWidth: 0;
59
+ declare type ThemeExtensionsWithParts<ComponentName extends string, Parts extends string[]> = {
60
+ [P in ComponentName]?: {
61
+ base?: {
62
+ [Part in Parts[number]]?: CSSObject;
63
+ };
64
+ variant?: {
65
+ [key: string]: {
66
+ [Part in Parts[number]]?: CSSObject;
67
+ };
68
+ };
69
+ size?: {
70
+ [key: string]: {
71
+ [Part in Parts[number]]?: CSSObject;
72
+ };
73
+ };
74
+ };
181
75
  };
182
-
183
- interface SVGProps extends ComponentProps<'svg'> {
184
- size?: number | string | number[] | string[];
185
- }
186
- declare const SVG: ({ size, fill, children, ...props }: SVGProps) => react.ReactSVGElement;
187
-
188
76
  /**
189
77
  * Value used to define a scale.
190
78
  *
@@ -331,6 +219,22 @@ interface Theme {
331
219
  transitions?: Scale<CSS.Property.Transition>;
332
220
  }
333
221
 
222
+ interface ComponentStylesProps {
223
+ variant?: string;
224
+ size?: string;
225
+ }
226
+ declare type ComponentStyleParts<Parts extends string[]> = {
227
+ [P in Parts[number]]: CSSObject;
228
+ };
229
+ declare function useComponentStyles(componentName: string, props?: ComponentStylesProps, options?: {
230
+ parts: never;
231
+ }): CSSObject;
232
+ declare function useComponentStyles<Part extends string, Parts extends ReadonlyArray<Part>>(componentName: string, props?: ComponentStylesProps, options?: {
233
+ parts: Parts;
234
+ }): {
235
+ [P in Parts[number]]: CSSObject;
236
+ };
237
+
334
238
  /**
335
239
  * Hook that can be used to return values based on the current screen size,
336
240
  * using breakpoints from the theme (`theme.breakpoints`). Note that this
@@ -338,6 +242,21 @@ interface Theme {
338
242
  */
339
243
  declare const useResponsiveValue: <T>(values: T[], defaultIndex?: number) => T;
340
244
 
245
+ declare type ComponentState = 'hover' | 'focus' | 'active' | 'visited' | 'disabled' | 'readOnly' | 'checked' | 'indeterminate' | 'error';
246
+ declare type StateAttrKeyProps = `data-${Lowercase<ComponentState>}`;
247
+ declare type StateAttrProps = {
248
+ [key in StateAttrKeyProps]?: '';
249
+ };
250
+ declare type UseStateProps = {
251
+ [key in ComponentState]?: boolean;
252
+ };
253
+ /**
254
+ * Given a map of states (e.g. `{ hover: true, focus: false }`) returns an
255
+ * object that can be used to set state props on a component
256
+ * (e.g. `[data-hover]` and `[data-focus]`).
257
+ */
258
+ declare const useStateProps: (states: UseStateProps) => StateAttrProps;
259
+
341
260
  /**
342
261
  * @internal
343
262
  */
@@ -353,6 +272,155 @@ interface ThemeProviderProps<T extends Theme> {
353
272
  }
354
273
  declare function ThemeProvider<T extends Theme>({ theme, children, }: ThemeProviderProps<T>): JSX.Element;
355
274
 
275
+ /**
276
+ * Normalize styling of certain elements between browsers.
277
+ * Based on https://www.joshwcomeau.com/css/custom-css-reset/
278
+ */
279
+
280
+ declare const normalize: {
281
+ readonly base: {
282
+ readonly boxSizing: "border-box";
283
+ readonly margin: 0;
284
+ readonly minWidth: 0;
285
+ };
286
+ readonly a: {
287
+ readonly textDecoration: "none";
288
+ readonly boxSizing: "border-box";
289
+ readonly margin: 0;
290
+ readonly minWidth: 0;
291
+ };
292
+ readonly p: {
293
+ readonly overflowWrap: "break-word";
294
+ readonly boxSizing: "border-box";
295
+ readonly margin: 0;
296
+ readonly minWidth: 0;
297
+ };
298
+ readonly h1: {
299
+ readonly overflowWrap: "break-word";
300
+ readonly boxSizing: "border-box";
301
+ readonly margin: 0;
302
+ readonly minWidth: 0;
303
+ };
304
+ readonly h2: {
305
+ readonly overflowWrap: "break-word";
306
+ readonly boxSizing: "border-box";
307
+ readonly margin: 0;
308
+ readonly minWidth: 0;
309
+ };
310
+ readonly h3: {
311
+ readonly overflowWrap: "break-word";
312
+ readonly boxSizing: "border-box";
313
+ readonly margin: 0;
314
+ readonly minWidth: 0;
315
+ };
316
+ readonly h4: {
317
+ readonly overflowWrap: "break-word";
318
+ readonly boxSizing: "border-box";
319
+ readonly margin: 0;
320
+ readonly minWidth: 0;
321
+ };
322
+ readonly h5: {
323
+ readonly overflowWrap: "break-word";
324
+ readonly boxSizing: "border-box";
325
+ readonly margin: 0;
326
+ readonly minWidth: 0;
327
+ };
328
+ readonly h6: {
329
+ readonly overflowWrap: "break-word";
330
+ readonly boxSizing: "border-box";
331
+ readonly margin: 0;
332
+ readonly minWidth: 0;
333
+ };
334
+ readonly img: {
335
+ readonly display: "block";
336
+ readonly maxWidth: "100%";
337
+ readonly boxSizing: "border-box";
338
+ readonly margin: 0;
339
+ readonly minWidth: 0;
340
+ };
341
+ readonly picture: {
342
+ readonly display: "block";
343
+ readonly maxWidth: "100%";
344
+ readonly boxSizing: "border-box";
345
+ readonly margin: 0;
346
+ readonly minWidth: 0;
347
+ };
348
+ readonly video: {
349
+ readonly display: "block";
350
+ readonly maxWidth: "100%";
351
+ readonly boxSizing: "border-box";
352
+ readonly margin: 0;
353
+ readonly minWidth: 0;
354
+ };
355
+ readonly canvas: {
356
+ readonly display: "block";
357
+ readonly maxWidth: "100%";
358
+ readonly boxSizing: "border-box";
359
+ readonly margin: 0;
360
+ readonly minWidth: 0;
361
+ };
362
+ readonly svg: {
363
+ readonly display: "block";
364
+ readonly maxWidth: "100%";
365
+ readonly boxSizing: "border-box";
366
+ readonly margin: 0;
367
+ readonly minWidth: 0;
368
+ };
369
+ readonly select: {
370
+ readonly display: "block";
371
+ readonly appearance: "none";
372
+ readonly font: "inherit";
373
+ readonly '&::-ms-expand': {
374
+ readonly display: "none";
375
+ };
376
+ readonly boxSizing: "border-box";
377
+ readonly margin: 0;
378
+ readonly minWidth: 0;
379
+ };
380
+ readonly button: {
381
+ readonly display: "block";
382
+ readonly appearance: "none";
383
+ readonly font: "inherit";
384
+ readonly background: "transparent";
385
+ readonly textAlign: "center";
386
+ readonly boxSizing: "border-box";
387
+ readonly margin: 0;
388
+ readonly minWidth: 0;
389
+ };
390
+ readonly textarea: {
391
+ readonly display: "block";
392
+ readonly appearance: "none";
393
+ readonly font: "inherit";
394
+ readonly boxSizing: "border-box";
395
+ readonly margin: 0;
396
+ readonly minWidth: 0;
397
+ };
398
+ readonly input: {
399
+ readonly display: "block";
400
+ readonly appearance: "none";
401
+ readonly font: "inherit";
402
+ readonly '&::-ms-clear': {
403
+ readonly display: "none";
404
+ };
405
+ readonly '&::-webkit-search-cancel-button': {
406
+ readonly WebkitAppearance: "none";
407
+ };
408
+ readonly boxSizing: "border-box";
409
+ readonly margin: 0;
410
+ readonly minWidth: 0;
411
+ };
412
+ };
413
+ declare type NormalizedElement = keyof typeof normalize;
414
+ /**
415
+ * Type-safe helper to get normalization. If no normalization is found,
416
+ * returns the *base* normalization.
417
+ */
418
+ declare const getNormalizedStyles: (val?: ElementType<any> | undefined) => {
419
+ readonly boxSizing: "border-box";
420
+ readonly margin: 0;
421
+ readonly minWidth: 0;
422
+ };
423
+
356
424
  /**
357
425
  * Ensures that the `val` is an array. Will return an empty array if `val` is falsy.
358
426
  */
@@ -383,4 +451,4 @@ declare const appendVariantState: (variant: string, state: keyof State) => strin
383
451
  */
384
452
  declare const conditional: (variant: string, { disabled, ...states }: State) => string[];
385
453
 
386
- export { Box, BoxOwnProps, BoxProps, CSSObject, CSSProperties, Global, NormalizedElement, ResponsiveStyleValue, SVG, SVGProps, Scale, ScaleValue, SizeScale, State, StyleObject, StyleProps, Theme, ThemeProvider, ThemeProviderProps, ZeroScale, ZeroSizeScale, __defaultTheme, appendVariantState, conditional, ensureArray, ensureArrayVariant, ensureVariantDefault, getNormalizedStyles, normalize, useResponsiveValue, useTheme };
454
+ export { Box, BoxOwnProps, BoxProps, CSSObject, CSSProperties, ComponentState, ComponentStyleParts, ComponentStylesProps, Global, NormalizedElement, ResponsiveStyleValue, SVG, SVGProps, Scale, ScaleValue, SizeScale, State, StateAttrKeyProps, StateAttrProps, StyleObject, StyleProps, Theme, ThemeComponentProps, ThemeExtension, ThemeExtensionsWithParts, ThemeProvider, ThemeProviderProps, UseStateProps, ZeroScale, ZeroSizeScale, __defaultTheme, appendVariantState, conditional, ensureArray, ensureArrayVariant, ensureVariantDefault, getNormalizedStyles, normalize, useComponentStyles, useResponsiveValue, useStateProps, useTheme };
package/dist/index.js CHANGED
@@ -63,15 +63,18 @@ __export(src_exports, {
63
63
  ensureVariantDefault: () => ensureVariantDefault,
64
64
  getNormalizedStyles: () => getNormalizedStyles,
65
65
  normalize: () => normalize,
66
+ useComponentStyles: () => useComponentStyles,
66
67
  useResponsiveValue: () => useResponsiveValue,
68
+ useStateProps: () => useStateProps,
67
69
  useTheme: () => useTheme
68
70
  });
69
71
  module.exports = __toCommonJS(src_exports);
70
72
 
71
- // src/Box.tsx
72
- var import_react = require("@emotion/react");
73
+ // src/components/Box/Box.tsx
74
+ var import_react = require("react");
75
+ var import_react2 = require("@emotion/react");
73
76
  var import_css = require("@theme-ui/css");
74
- var import_react2 = require("react");
77
+ var import_deepmerge = __toESM(require("deepmerge"));
75
78
 
76
79
  // src/normalize.ts
77
80
  var base = {
@@ -157,18 +160,45 @@ var conditional = (variant, _a) => {
157
160
  return [variant, ...stateVariants];
158
161
  };
159
162
 
160
- // src/Box.tsx
161
- var isNotEmpty = (val) => !(val && Object.keys(val).length === 0 && val.constructor === Object);
163
+ // src/components/Box/utils.ts
164
+ var pseudos = {
165
+ "&:hover": "&:hover, &[data-hover]",
166
+ "&:focus": "&:focus, &[data-focus]",
167
+ "&:active": "&:active, &[data-active]",
168
+ "&:disabled": "&[disabled], &[aria-disabled=true], &[data-disabled]",
169
+ "&:read-only": "&[readonly], &[aria-readonly=true], &[data-readonly]",
170
+ "&:checked": "&[aria-checked=true], &[data-checked]",
171
+ "&:indeterminate": "&:indeterminate, &[aria-checked=mixed], &[data-indeterminate]",
172
+ "&:error": "&:invalid, &[aria-invalid=true], &[data-error]"
173
+ };
174
+ var transformPseudos = (styles) => {
175
+ let result = {};
176
+ for (let key in styles) {
177
+ const value = styles[key];
178
+ if (key in pseudos) {
179
+ key = pseudos[key];
180
+ }
181
+ if (typeof value === "object") {
182
+ result[key] = transformPseudos(value);
183
+ continue;
184
+ }
185
+ result[key] = value;
186
+ }
187
+ return result;
188
+ };
189
+
190
+ // src/components/Box/Box.tsx
162
191
  var createThemedStyle = ({ as, __baseCSS, variant, styles, css }) => (theme) => {
163
- return [
192
+ const themedStyles = import_deepmerge.default.all([
164
193
  getNormalizedStyles(as),
165
194
  (0, import_css.css)(__baseCSS)(theme),
166
195
  ...ensureArrayVariant(variant).map((v) => (0, import_css.css)({ variant: v })(theme)),
167
196
  (0, import_css.css)(styles)(theme),
168
197
  (0, import_css.css)(css)(theme)
169
- ].filter(isNotEmpty);
198
+ ]);
199
+ return transformPseudos(themedStyles);
170
200
  };
171
- var Box = (0, import_react2.forwardRef)((_a, ref) => {
201
+ var Box = (0, import_react.forwardRef)((_a, ref) => {
172
202
  var _b = _a, {
173
203
  as = "div",
174
204
  children,
@@ -258,7 +288,7 @@ var Box = (0, import_react2.forwardRef)((_a, ref) => {
258
288
  "overflow",
259
289
  "transition"
260
290
  ]);
261
- return (0, import_react.jsx)(as, __spreadProps(__spreadValues({}, props), {
291
+ return (0, import_react2.jsx)(as, __spreadProps(__spreadValues({}, props), {
262
292
  css: createThemedStyle({
263
293
  as,
264
294
  __baseCSS,
@@ -309,11 +339,16 @@ var Box = (0, import_react2.forwardRef)((_a, ref) => {
309
339
  }), children);
310
340
  });
311
341
 
312
- // src/Global.tsx
313
- var import_react5 = __toESM(require("react"));
314
- var import_react6 = require("@emotion/react");
342
+ // src/components/Global/Global.tsx
343
+ var import_react8 = __toESM(require("react"));
344
+ var import_react9 = require("@emotion/react");
345
+
346
+ // src/hooks/useComponentStyles.ts
347
+ var import_deepmerge2 = __toESM(require("deepmerge"));
348
+ var import_react5 = require("react");
349
+ var import_react_fast_compare = __toESM(require("react-fast-compare"));
315
350
 
316
- // src/useTheme.tsx
351
+ // src/hooks/useTheme.tsx
317
352
  var import_react3 = __toESM(require("react"));
318
353
  var import_css2 = require("@theme-ui/css");
319
354
  var import_react4 = require("@emotion/react");
@@ -322,8 +357,8 @@ var InternalContext = (0, import_react3.createContext)(__defaultTheme);
322
357
  var useTheme = () => {
323
358
  const theme = (0, import_react3.useContext)(InternalContext);
324
359
  const css = (0, import_react3.useCallback)((style) => (0, import_css2.css)(style)(theme), [theme]);
325
- const get = (0, import_react3.useCallback)((path) => (0, import_css2.get)(theme, path), [theme]);
326
- return { theme, css, get };
360
+ const get2 = (0, import_react3.useCallback)((path) => (0, import_css2.get)(theme, path), [theme]);
361
+ return { theme, css, get: get2 };
327
362
  };
328
363
  function ThemeProvider({
329
364
  theme,
@@ -336,7 +371,84 @@ function ThemeProvider({
336
371
  }, children));
337
372
  }
338
373
 
339
- // src/Global.tsx
374
+ // src/hooks/useComponentStyles.ts
375
+ var get = (obj, path, fallback) => {
376
+ const key = path.split(".");
377
+ let result = obj;
378
+ for (let i = 0, length = key.length; i < length; i++) {
379
+ if (!result)
380
+ break;
381
+ result = result[key[i]];
382
+ }
383
+ return result === void 0 ? fallback : result;
384
+ };
385
+ function useComponentStyles(componentName, props = {}, options = {}) {
386
+ var _a, _b;
387
+ const { theme } = useTheme();
388
+ const componentStyles = get(theme, `components.${componentName}`);
389
+ const stylesRef = (0, import_react5.useRef)({});
390
+ if (componentStyles) {
391
+ const base2 = componentStyles.base || {};
392
+ const size = ((_a = componentStyles.size) == null ? void 0 : _a[props.size]) || {};
393
+ const variant = ((_b = componentStyles.variant) == null ? void 0 : _b[props.variant]) || {};
394
+ let styles = import_deepmerge2.default.all([base2, size, variant]);
395
+ if (options.parts) {
396
+ styles = options.parts.reduce((result, part) => {
397
+ result[part] = styles[part] || {};
398
+ return result;
399
+ }, {});
400
+ }
401
+ if (!(0, import_react_fast_compare.default)(stylesRef.current, styles)) {
402
+ stylesRef.current = styles;
403
+ }
404
+ }
405
+ return stylesRef.current;
406
+ }
407
+
408
+ // src/hooks/useResponsiveValue.ts
409
+ var import_react6 = require("react");
410
+ var emptyBreakpoints = ["40em", "52em", "64em"];
411
+ var useResponsiveValue = (values, defaultIndex = 0) => {
412
+ const { theme } = useTheme();
413
+ const breakpoints = theme.breakpoints || emptyBreakpoints;
414
+ if (defaultIndex < 0 || defaultIndex >= breakpoints.length) {
415
+ throw new RangeError(`Default breakpoint index is out of bounds. Theme has ${breakpoints.length} breakpoints, default is ${defaultIndex}.`);
416
+ }
417
+ const [index, setIndex] = (0, import_react6.useState)(defaultIndex);
418
+ (0, import_react6.useEffect)(() => {
419
+ const getIndex = () => breakpoints.filter((breakpoint) => window.matchMedia(`screen and (min-width: ${breakpoint})`).matches).length;
420
+ const handleResize = () => {
421
+ const newIndex = getIndex();
422
+ if (index !== newIndex) {
423
+ setIndex(newIndex);
424
+ }
425
+ };
426
+ handleResize();
427
+ window.addEventListener("resize", handleResize);
428
+ return () => window.removeEventListener("resize", handleResize);
429
+ }, [breakpoints, index]);
430
+ return values[index >= values.length ? values.length - 1 : index];
431
+ };
432
+
433
+ // src/hooks/useStateProps.ts
434
+ var import_react7 = require("react");
435
+ var import_react_fast_compare2 = __toESM(require("react-fast-compare"));
436
+ var useStateProps = (states) => {
437
+ const statePropsRef = (0, import_react7.useRef)({});
438
+ let stateProps = {};
439
+ for (let state in states) {
440
+ if (states[state]) {
441
+ const key = `data-${state.toLocaleLowerCase()}`;
442
+ stateProps[key] = "";
443
+ }
444
+ }
445
+ if (!(0, import_react_fast_compare2.default)(statePropsRef.current, stateProps)) {
446
+ statePropsRef.current = stateProps;
447
+ }
448
+ return statePropsRef.current;
449
+ };
450
+
451
+ // src/components/Global/Global.tsx
340
452
  var reduceMotionStyles = {
341
453
  "@media screen and (prefers-reduced-motion: reduce), (update: slow)": {
342
454
  "*": {
@@ -361,13 +473,13 @@ var Global = () => {
361
473
  variant: "root.body"
362
474
  }
363
475
  });
364
- return /* @__PURE__ */ import_react5.default.createElement(import_react6.Global, {
476
+ return /* @__PURE__ */ import_react8.default.createElement(import_react9.Global, {
365
477
  styles: __spreadValues({ reduceMotionStyles }, styles)
366
478
  });
367
479
  };
368
480
 
369
- // src/SVG.tsx
370
- var import_react7 = require("@emotion/react");
481
+ // src/components/SVG/SVG.tsx
482
+ var import_react10 = require("@emotion/react");
371
483
  var normalizedStyles = getNormalizedStyles("svg");
372
484
  var toDimension = (value) => Array.isArray(value) ? value.map(ensureNumberOrToken) : ensureNumberOrToken(value);
373
485
  var ensureNumberOrToken = (value) => typeof value === "string" && /[0-9]+/.test(value) ? Number(value) : value;
@@ -382,7 +494,7 @@ var SVG = (_a) => {
382
494
  "children"
383
495
  ]);
384
496
  const { css } = useTheme();
385
- return (0, import_react7.jsx)("svg", __spreadValues({
497
+ return (0, import_react10.jsx)("svg", __spreadValues({
386
498
  viewBox: "0 0 24 24",
387
499
  css: css(__spreadProps(__spreadValues({}, normalizedStyles), {
388
500
  fill,
@@ -391,31 +503,6 @@ var SVG = (_a) => {
391
503
  }))
392
504
  }, props), children);
393
505
  };
394
-
395
- // src/useResponsiveValue.ts
396
- var import_react8 = require("react");
397
- var emptyBreakpoints = ["40em", "52em", "64em"];
398
- var useResponsiveValue = (values, defaultIndex = 0) => {
399
- const { theme } = useTheme();
400
- const breakpoints = theme.breakpoints || emptyBreakpoints;
401
- if (defaultIndex < 0 || defaultIndex >= breakpoints.length) {
402
- throw new RangeError(`Default breakpoint index is out of bounds. Theme has ${breakpoints.length} breakpoints, default is ${defaultIndex}.`);
403
- }
404
- const [index, setIndex] = (0, import_react8.useState)(defaultIndex);
405
- (0, import_react8.useEffect)(() => {
406
- const getIndex = () => breakpoints.filter((breakpoint) => window.matchMedia(`screen and (min-width: ${breakpoint})`).matches).length;
407
- const handleResize = () => {
408
- const newIndex = getIndex();
409
- if (index !== newIndex) {
410
- setIndex(newIndex);
411
- }
412
- };
413
- handleResize();
414
- window.addEventListener("resize", handleResize);
415
- return () => window.removeEventListener("resize", handleResize);
416
- }, [breakpoints, index]);
417
- return values[index >= values.length ? values.length - 1 : index];
418
- };
419
506
  // Annotate the CommonJS export names for ESM import in node:
420
507
  0 && (module.exports = {
421
508
  Box,
@@ -430,6 +517,8 @@ var useResponsiveValue = (values, defaultIndex = 0) => {
430
517
  ensureVariantDefault,
431
518
  getNormalizedStyles,
432
519
  normalize,
520
+ useComponentStyles,
433
521
  useResponsiveValue,
522
+ useStateProps,
434
523
  useTheme
435
524
  });
package/dist/index.mjs CHANGED
@@ -30,10 +30,11 @@ var __objRest = (source, exclude) => {
30
30
  return target;
31
31
  };
32
32
 
33
- // src/Box.tsx
33
+ // src/components/Box/Box.tsx
34
+ import { forwardRef } from "react";
34
35
  import { jsx } from "@emotion/react";
35
36
  import { css as transformStyleObject } from "@theme-ui/css";
36
- import { forwardRef } from "react";
37
+ import merge from "deepmerge";
37
38
 
38
39
  // src/normalize.ts
39
40
  var base = {
@@ -119,16 +120,43 @@ var conditional = (variant, _a) => {
119
120
  return [variant, ...stateVariants];
120
121
  };
121
122
 
122
- // src/Box.tsx
123
- var isNotEmpty = (val) => !(val && Object.keys(val).length === 0 && val.constructor === Object);
123
+ // src/components/Box/utils.ts
124
+ var pseudos = {
125
+ "&:hover": "&:hover, &[data-hover]",
126
+ "&:focus": "&:focus, &[data-focus]",
127
+ "&:active": "&:active, &[data-active]",
128
+ "&:disabled": "&[disabled], &[aria-disabled=true], &[data-disabled]",
129
+ "&:read-only": "&[readonly], &[aria-readonly=true], &[data-readonly]",
130
+ "&:checked": "&[aria-checked=true], &[data-checked]",
131
+ "&:indeterminate": "&:indeterminate, &[aria-checked=mixed], &[data-indeterminate]",
132
+ "&:error": "&:invalid, &[aria-invalid=true], &[data-error]"
133
+ };
134
+ var transformPseudos = (styles) => {
135
+ let result = {};
136
+ for (let key in styles) {
137
+ const value = styles[key];
138
+ if (key in pseudos) {
139
+ key = pseudos[key];
140
+ }
141
+ if (typeof value === "object") {
142
+ result[key] = transformPseudos(value);
143
+ continue;
144
+ }
145
+ result[key] = value;
146
+ }
147
+ return result;
148
+ };
149
+
150
+ // src/components/Box/Box.tsx
124
151
  var createThemedStyle = ({ as, __baseCSS, variant, styles, css }) => (theme) => {
125
- return [
152
+ const themedStyles = merge.all([
126
153
  getNormalizedStyles(as),
127
154
  transformStyleObject(__baseCSS)(theme),
128
155
  ...ensureArrayVariant(variant).map((v) => transformStyleObject({ variant: v })(theme)),
129
156
  transformStyleObject(styles)(theme),
130
157
  transformStyleObject(css)(theme)
131
- ].filter(isNotEmpty);
158
+ ]);
159
+ return transformPseudos(themedStyles);
132
160
  };
133
161
  var Box = forwardRef((_a, ref) => {
134
162
  var _b = _a, {
@@ -271,11 +299,16 @@ var Box = forwardRef((_a, ref) => {
271
299
  }), children);
272
300
  });
273
301
 
274
- // src/Global.tsx
302
+ // src/components/Global/Global.tsx
275
303
  import React2 from "react";
276
304
  import { Global as EmotionGlobal } from "@emotion/react";
277
305
 
278
- // src/useTheme.tsx
306
+ // src/hooks/useComponentStyles.ts
307
+ import merge2 from "deepmerge";
308
+ import { useRef } from "react";
309
+ import isEqual from "react-fast-compare";
310
+
311
+ // src/hooks/useTheme.tsx
279
312
  import React, {
280
313
  createContext,
281
314
  useCallback,
@@ -291,8 +324,8 @@ var InternalContext = createContext(__defaultTheme);
291
324
  var useTheme = () => {
292
325
  const theme = useContext(InternalContext);
293
326
  const css = useCallback((style) => transformStyleObject2(style)(theme), [theme]);
294
- const get = useCallback((path) => getfromTheme(theme, path), [theme]);
295
- return { theme, css, get };
327
+ const get2 = useCallback((path) => getfromTheme(theme, path), [theme]);
328
+ return { theme, css, get: get2 };
296
329
  };
297
330
  function ThemeProvider({
298
331
  theme,
@@ -305,7 +338,84 @@ function ThemeProvider({
305
338
  }, children));
306
339
  }
307
340
 
308
- // src/Global.tsx
341
+ // src/hooks/useComponentStyles.ts
342
+ var get = (obj, path, fallback) => {
343
+ const key = path.split(".");
344
+ let result = obj;
345
+ for (let i = 0, length = key.length; i < length; i++) {
346
+ if (!result)
347
+ break;
348
+ result = result[key[i]];
349
+ }
350
+ return result === void 0 ? fallback : result;
351
+ };
352
+ function useComponentStyles(componentName, props = {}, options = {}) {
353
+ var _a, _b;
354
+ const { theme } = useTheme();
355
+ const componentStyles = get(theme, `components.${componentName}`);
356
+ const stylesRef = useRef({});
357
+ if (componentStyles) {
358
+ const base2 = componentStyles.base || {};
359
+ const size = ((_a = componentStyles.size) == null ? void 0 : _a[props.size]) || {};
360
+ const variant = ((_b = componentStyles.variant) == null ? void 0 : _b[props.variant]) || {};
361
+ let styles = merge2.all([base2, size, variant]);
362
+ if (options.parts) {
363
+ styles = options.parts.reduce((result, part) => {
364
+ result[part] = styles[part] || {};
365
+ return result;
366
+ }, {});
367
+ }
368
+ if (!isEqual(stylesRef.current, styles)) {
369
+ stylesRef.current = styles;
370
+ }
371
+ }
372
+ return stylesRef.current;
373
+ }
374
+
375
+ // src/hooks/useResponsiveValue.ts
376
+ import { useEffect, useState } from "react";
377
+ var emptyBreakpoints = ["40em", "52em", "64em"];
378
+ var useResponsiveValue = (values, defaultIndex = 0) => {
379
+ const { theme } = useTheme();
380
+ const breakpoints = theme.breakpoints || emptyBreakpoints;
381
+ if (defaultIndex < 0 || defaultIndex >= breakpoints.length) {
382
+ throw new RangeError(`Default breakpoint index is out of bounds. Theme has ${breakpoints.length} breakpoints, default is ${defaultIndex}.`);
383
+ }
384
+ const [index, setIndex] = useState(defaultIndex);
385
+ useEffect(() => {
386
+ const getIndex = () => breakpoints.filter((breakpoint) => window.matchMedia(`screen and (min-width: ${breakpoint})`).matches).length;
387
+ const handleResize = () => {
388
+ const newIndex = getIndex();
389
+ if (index !== newIndex) {
390
+ setIndex(newIndex);
391
+ }
392
+ };
393
+ handleResize();
394
+ window.addEventListener("resize", handleResize);
395
+ return () => window.removeEventListener("resize", handleResize);
396
+ }, [breakpoints, index]);
397
+ return values[index >= values.length ? values.length - 1 : index];
398
+ };
399
+
400
+ // src/hooks/useStateProps.ts
401
+ import { useRef as useRef2 } from "react";
402
+ import isEqual2 from "react-fast-compare";
403
+ var useStateProps = (states) => {
404
+ const statePropsRef = useRef2({});
405
+ let stateProps = {};
406
+ for (let state in states) {
407
+ if (states[state]) {
408
+ const key = `data-${state.toLocaleLowerCase()}`;
409
+ stateProps[key] = "";
410
+ }
411
+ }
412
+ if (!isEqual2(statePropsRef.current, stateProps)) {
413
+ statePropsRef.current = stateProps;
414
+ }
415
+ return statePropsRef.current;
416
+ };
417
+
418
+ // src/components/Global/Global.tsx
309
419
  var reduceMotionStyles = {
310
420
  "@media screen and (prefers-reduced-motion: reduce), (update: slow)": {
311
421
  "*": {
@@ -335,7 +445,7 @@ var Global = () => {
335
445
  });
336
446
  };
337
447
 
338
- // src/SVG.tsx
448
+ // src/components/SVG/SVG.tsx
339
449
  import { jsx as jsx2 } from "@emotion/react";
340
450
  var normalizedStyles = getNormalizedStyles("svg");
341
451
  var toDimension = (value) => Array.isArray(value) ? value.map(ensureNumberOrToken) : ensureNumberOrToken(value);
@@ -360,31 +470,6 @@ var SVG = (_a) => {
360
470
  }))
361
471
  }, props), children);
362
472
  };
363
-
364
- // src/useResponsiveValue.ts
365
- import { useEffect, useState } from "react";
366
- var emptyBreakpoints = ["40em", "52em", "64em"];
367
- var useResponsiveValue = (values, defaultIndex = 0) => {
368
- const { theme } = useTheme();
369
- const breakpoints = theme.breakpoints || emptyBreakpoints;
370
- if (defaultIndex < 0 || defaultIndex >= breakpoints.length) {
371
- throw new RangeError(`Default breakpoint index is out of bounds. Theme has ${breakpoints.length} breakpoints, default is ${defaultIndex}.`);
372
- }
373
- const [index, setIndex] = useState(defaultIndex);
374
- useEffect(() => {
375
- const getIndex = () => breakpoints.filter((breakpoint) => window.matchMedia(`screen and (min-width: ${breakpoint})`).matches).length;
376
- const handleResize = () => {
377
- const newIndex = getIndex();
378
- if (index !== newIndex) {
379
- setIndex(newIndex);
380
- }
381
- };
382
- handleResize();
383
- window.addEventListener("resize", handleResize);
384
- return () => window.removeEventListener("resize", handleResize);
385
- }, [breakpoints, index]);
386
- return values[index >= values.length ? values.length - 1 : index];
387
- };
388
473
  export {
389
474
  Box,
390
475
  Global,
@@ -398,6 +483,8 @@ export {
398
483
  ensureVariantDefault,
399
484
  getNormalizedStyles,
400
485
  normalize,
486
+ useComponentStyles,
401
487
  useResponsiveValue,
488
+ useStateProps,
402
489
  useTheme
403
490
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marigold/system",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "Marigold System Library",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -27,9 +27,11 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@emotion/react": "11.8.2",
30
- "@marigold/types": "0.4.0",
30
+ "@marigold/types": "0.4.1",
31
31
  "@theme-ui/css": "0.14.2",
32
- "csstype": "3.0.11"
32
+ "csstype": "3.0.11",
33
+ "deepmerge": "^4.2.2",
34
+ "react-fast-compare": "^3.2.0"
33
35
  },
34
36
  "peerDependencies": {
35
37
  "react": "^16.x || ^17.0.0",
@@ -37,7 +39,7 @@
37
39
  },
38
40
  "devDependencies": {
39
41
  "@marigold/tsconfig": "0.3.0",
40
- "tsup": "5.12.4"
42
+ "tsup": "5.12.5"
41
43
  },
42
44
  "scripts": {
43
45
  "build": "tsup src/index.ts",