@marigold/system 0.2.0 → 0.3.3
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/CHANGELOG.md +57 -0
- package/dist/Box.d.ts +14 -0
- package/dist/Global.d.ts +2 -0
- package/dist/SVG.d.ts +6 -0
- package/dist/SVG.stories.d.ts +5 -0
- package/dist/index.d.ts +6 -4
- package/dist/normalize.d.ts +101 -67
- package/dist/system.cjs.development.js +299 -294
- package/dist/system.cjs.development.js.map +1 -1
- package/dist/system.cjs.production.min.js +1 -1
- package/dist/system.cjs.production.min.js.map +1 -1
- package/dist/system.esm.js +291 -289
- package/dist/system.esm.js.map +1 -1
- package/dist/theme.d.ts +136 -0
- package/dist/types.d.ts +1 -2
- package/dist/useTheme.d.ts +11 -5
- package/dist/variant.d.ts +29 -0
- package/package.json +5 -6
- package/src/Box.test.tsx +308 -0
- package/src/Box.tsx +199 -0
- package/src/Global.test.tsx +57 -0
- package/src/Global.tsx +34 -0
- package/src/SVG.stories.tsx +48 -0
- package/src/SVG.test.tsx +82 -0
- package/src/SVG.tsx +24 -0
- package/src/index.ts +6 -4
- package/src/normalize.test.tsx +9 -36
- package/src/normalize.ts +51 -82
- package/src/theme.ts +157 -0
- package/src/types.ts +0 -2
- package/src/useTheme.test.tsx +22 -14
- package/src/useTheme.tsx +37 -9
- package/src/variant.test.ts +93 -0
- package/src/variant.ts +54 -0
- package/dist/Element.d.ts +0 -8
- package/dist/cache.d.ts +0 -4
- package/dist/reset.d.ts +0 -24
- package/dist/useClassname.d.ts +0 -2
- package/dist/useStyles.d.ts +0 -15
- package/src/Colors.stories.mdx +0 -623
- package/src/Element.test.tsx +0 -203
- package/src/Element.tsx +0 -59
- package/src/cache.ts +0 -4
- package/src/concepts-principles.mdx +0 -84
- package/src/reset.ts +0 -108
- package/src/useClassname.test.tsx +0 -70
- package/src/useClassname.ts +0 -23
- package/src/useStyles.stories.mdx +0 -24
- package/src/useStyles.test.tsx +0 -286
- package/src/useStyles.ts +0 -63
package/src/Element.test.tsx
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
3
|
-
import { useStyles } from './useStyles';
|
|
4
|
-
import { ThemeProvider } from './useTheme';
|
|
5
|
-
import { Element } from './Element';
|
|
6
|
-
|
|
7
|
-
const theme = {
|
|
8
|
-
text: {
|
|
9
|
-
body: {
|
|
10
|
-
fontSize: 1,
|
|
11
|
-
color: 'black',
|
|
12
|
-
marginTop: '2px',
|
|
13
|
-
},
|
|
14
|
-
heading: {
|
|
15
|
-
fontSize: 3,
|
|
16
|
-
color: 'primary',
|
|
17
|
-
},
|
|
18
|
-
padding: {
|
|
19
|
-
paddingTop: '2px',
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
test('renders a <div> by default', () => {
|
|
25
|
-
render(<Element>Text</Element>);
|
|
26
|
-
const testelem = screen.getByText('Text');
|
|
27
|
-
|
|
28
|
-
expect(testelem instanceof HTMLDivElement).toBeTruthy();
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test('supports as prop', () => {
|
|
32
|
-
render(<Element as="p">Text</Element>);
|
|
33
|
-
const testelem = screen.getByText('Text');
|
|
34
|
-
|
|
35
|
-
expect(testelem instanceof HTMLParagraphElement).toBeTruthy();
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test('supports HTML className attribute', () => {
|
|
39
|
-
render(<Element className="my-custom-class">Text</Element>);
|
|
40
|
-
const element = screen.getByText('Text');
|
|
41
|
-
|
|
42
|
-
expect(element.getAttribute('class')).toMatch('my-custom-class');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('passes down HTML attributes', () => {
|
|
46
|
-
render(
|
|
47
|
-
<Element className="my-custom-class" id="element-id" disabled>
|
|
48
|
-
Text
|
|
49
|
-
</Element>
|
|
50
|
-
);
|
|
51
|
-
const element = screen.getByText('Text');
|
|
52
|
-
|
|
53
|
-
expect(element.getAttribute('id')).toEqual('element-id');
|
|
54
|
-
expect(element.getAttribute('disabled')).toMatch('');
|
|
55
|
-
expect(element.getAttribute('class')).toMatch('my-custom-class');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test('forwards ref', () => {
|
|
59
|
-
const ref = React.createRef<HTMLButtonElement>();
|
|
60
|
-
render(
|
|
61
|
-
<Element as="button" ref={ref}>
|
|
62
|
-
button
|
|
63
|
-
</Element>
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
expect(ref.current instanceof HTMLButtonElement).toBeTruthy();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test('base styles first', () => {
|
|
70
|
-
const { getByText } = render(<Element as="p">Text</Element>);
|
|
71
|
-
const testelem = getByText('Text');
|
|
72
|
-
const style = getComputedStyle(testelem);
|
|
73
|
-
|
|
74
|
-
expect(style.marginTop).toEqual('0px'); // 0px come from base
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('variant styles second', () => {
|
|
78
|
-
const TestComponent: React.FC<{ variant?: 'body' }> = ({
|
|
79
|
-
variant = 'body',
|
|
80
|
-
children,
|
|
81
|
-
...props
|
|
82
|
-
}) => {
|
|
83
|
-
return (
|
|
84
|
-
<Element as="p" variant={`text.${variant}`} {...props}>
|
|
85
|
-
{children}
|
|
86
|
-
</Element>
|
|
87
|
-
);
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const { getByText } = render(
|
|
91
|
-
<ThemeProvider theme={theme}>
|
|
92
|
-
<TestComponent>Text</TestComponent>
|
|
93
|
-
</ThemeProvider>
|
|
94
|
-
);
|
|
95
|
-
const testelem = getByText('Text');
|
|
96
|
-
const style = getComputedStyle(testelem);
|
|
97
|
-
|
|
98
|
-
expect(style.marginTop).not.toEqual('0px'); // 0px come from base
|
|
99
|
-
expect(style.marginBottom).toEqual('0px'); // 0px still come from base
|
|
100
|
-
expect(style.marginTop).toEqual('2px'); // 2px come from variant
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test('array of variant styles', () => {
|
|
104
|
-
const TestComponent: React.FC<{ variant?: 'body' }> = ({
|
|
105
|
-
variant = 'body',
|
|
106
|
-
children,
|
|
107
|
-
...props
|
|
108
|
-
}) => {
|
|
109
|
-
return (
|
|
110
|
-
<Element as="p" variant={[`text.${variant}`, `text.padding`]} {...props}>
|
|
111
|
-
{children}
|
|
112
|
-
</Element>
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const { getByText } = render(
|
|
117
|
-
<ThemeProvider theme={theme}>
|
|
118
|
-
<TestComponent>Text</TestComponent>
|
|
119
|
-
</ThemeProvider>
|
|
120
|
-
);
|
|
121
|
-
const testelem = getByText('Text');
|
|
122
|
-
const style = getComputedStyle(testelem);
|
|
123
|
-
|
|
124
|
-
expect(style.marginTop).not.toEqual('0px'); // 0px come from base
|
|
125
|
-
expect(style.marginBottom).toEqual('0px'); // 0px still come from base
|
|
126
|
-
expect(style.marginTop).toEqual('2px'); // 2px marginTop come from variant
|
|
127
|
-
expect(style.paddingTop).toEqual('2px'); // 2px paddingTop come from variant
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('custom styles with css prop third', () => {
|
|
131
|
-
const TestComponent: React.FC<{ variant?: 'body' }> = ({
|
|
132
|
-
variant = 'body',
|
|
133
|
-
children,
|
|
134
|
-
...props
|
|
135
|
-
}) => {
|
|
136
|
-
return (
|
|
137
|
-
<Element
|
|
138
|
-
as="p"
|
|
139
|
-
variant={`text.${variant}`}
|
|
140
|
-
css={{ marginTop: '4px' }}
|
|
141
|
-
{...props}
|
|
142
|
-
>
|
|
143
|
-
{children}
|
|
144
|
-
</Element>
|
|
145
|
-
);
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const { getByText } = render(
|
|
149
|
-
<ThemeProvider theme={theme}>
|
|
150
|
-
<TestComponent>Text</TestComponent>
|
|
151
|
-
</ThemeProvider>
|
|
152
|
-
);
|
|
153
|
-
const testelem = getByText('Text');
|
|
154
|
-
const style = getComputedStyle(testelem);
|
|
155
|
-
|
|
156
|
-
expect(style.marginTop).not.toEqual('0px'); // do not apply 0px from base
|
|
157
|
-
expect(style.marginTop).not.toEqual('2px'); // do not apply 2px from variant
|
|
158
|
-
expect(style.marginTop).toEqual('4px'); // apply 4px from custom styles
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("don't apply the same reset multiple times", () => {
|
|
162
|
-
const Button = ({ className }: { className?: string }) => {
|
|
163
|
-
const classNames = useStyles({ element: 'button', className });
|
|
164
|
-
return (
|
|
165
|
-
<Element as="button" title="button" className={classNames}>
|
|
166
|
-
Click me!
|
|
167
|
-
</Element>
|
|
168
|
-
);
|
|
169
|
-
};
|
|
170
|
-
const Wrapper = () => <Button />;
|
|
171
|
-
|
|
172
|
-
render(<Wrapper />);
|
|
173
|
-
const button = screen.getByTitle('button');
|
|
174
|
-
const classNames = button.className.split(' ').filter(i => i.length);
|
|
175
|
-
|
|
176
|
-
// Test if applied classnames are unique
|
|
177
|
-
expect(classNames.length).toEqual([...new Set(classNames)].length);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('normalize tag name <a>', () => {
|
|
181
|
-
const TestComponent: React.FC<{ variant?: 'body' }> = ({
|
|
182
|
-
variant = 'body',
|
|
183
|
-
children,
|
|
184
|
-
...props
|
|
185
|
-
}) => {
|
|
186
|
-
return (
|
|
187
|
-
<Element as="a" variant={`text.${variant}`} {...props}>
|
|
188
|
-
{children}
|
|
189
|
-
</Element>
|
|
190
|
-
);
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const { getByText } = render(
|
|
194
|
-
<ThemeProvider theme={theme}>
|
|
195
|
-
<TestComponent>Link</TestComponent>
|
|
196
|
-
</ThemeProvider>
|
|
197
|
-
);
|
|
198
|
-
const testelem = getByText('Link');
|
|
199
|
-
const style = getComputedStyle(testelem);
|
|
200
|
-
|
|
201
|
-
expect(style.boxSizing).toEqual('border-box'); // from base
|
|
202
|
-
expect(style.textDecoration).toEqual('none'); // from a
|
|
203
|
-
});
|
package/src/Element.tsx
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { jsx } from '@emotion/react';
|
|
2
|
-
import { forwardRef } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
PolymorphicPropsWithRef,
|
|
5
|
-
PolymorphicComponentWithRef,
|
|
6
|
-
} from '@marigold/types';
|
|
7
|
-
|
|
8
|
-
import { getNormalizedStyles } from './normalize';
|
|
9
|
-
import { CSSObject } from './types';
|
|
10
|
-
import { useTheme } from './useTheme';
|
|
11
|
-
|
|
12
|
-
export type ElementOwnProps = {
|
|
13
|
-
css?: CSSObject;
|
|
14
|
-
variant?: string | string[];
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type ElementProps = PolymorphicPropsWithRef<ElementOwnProps, 'div'>;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Function expression to check if there is any falsy value or empty object
|
|
21
|
-
*/
|
|
22
|
-
const isNotEmpty = (val: any) =>
|
|
23
|
-
!(val && Object.keys(val).length === 0 && val.constructor === Object);
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Get the normalized base styles
|
|
27
|
-
*/
|
|
28
|
-
const baseStyles = getNormalizedStyles('base');
|
|
29
|
-
|
|
30
|
-
export const Element: PolymorphicComponentWithRef<ElementOwnProps, 'div'> =
|
|
31
|
-
forwardRef(
|
|
32
|
-
({ as = 'div', css: styles = {}, variant, children, ...props }, ref) => {
|
|
33
|
-
const { css } = useTheme();
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Transform variant input for `@theme-ui/css`
|
|
37
|
-
*/
|
|
38
|
-
const variants = Array.isArray(variant)
|
|
39
|
-
? variant.map(v => ({ variant: v }))
|
|
40
|
-
: [{ variant }];
|
|
41
|
-
|
|
42
|
-
return jsx(
|
|
43
|
-
as,
|
|
44
|
-
{
|
|
45
|
-
...props,
|
|
46
|
-
...{
|
|
47
|
-
css: [
|
|
48
|
-
baseStyles,
|
|
49
|
-
getNormalizedStyles(as),
|
|
50
|
-
...variants.map(v => css(v)),
|
|
51
|
-
css(styles),
|
|
52
|
-
].filter(isNotEmpty),
|
|
53
|
-
},
|
|
54
|
-
ref,
|
|
55
|
-
},
|
|
56
|
-
children
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
);
|
package/src/cache.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { Meta } from '@storybook/addon-docs';
|
|
2
|
-
|
|
3
|
-
<Meta title="Guides/Concepts & Principles" />
|
|
4
|
-
|
|
5
|
-
## Design Tokens
|
|
6
|
-
|
|
7
|
-
The term _design tokens_ originates from the [Lightning Design System](https://www.lightningdesignsystem.com/design-tokens/). They are the "visual design atoms" of a design system. Similar to variables in CSS or Sass, design tokens are used to capture low-level values and then used to create the look and feel of your product. This helps with maintaining a consistent and scalable visual system for UI development.
|
|
8
|
-
|
|
9
|
-
```css
|
|
10
|
-
/* button.css */
|
|
11
|
-
.button {
|
|
12
|
-
color: #1f1235;
|
|
13
|
-
background: #ff6e6c;
|
|
14
|
-
height: 24px;
|
|
15
|
-
}
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
```scss
|
|
19
|
-
/* colors.scss */
|
|
20
|
-
$color-primary: #ff6e6c;
|
|
21
|
-
$color-text: #1f1235;
|
|
22
|
-
|
|
23
|
-
/* sizes.scss */
|
|
24
|
-
$size-6: 24px;
|
|
25
|
-
|
|
26
|
-
/* button.scss */
|
|
27
|
-
.button {
|
|
28
|
-
color: $color-text;
|
|
29
|
-
background: $color-primary;
|
|
30
|
-
height: $size-6;
|
|
31
|
-
}
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
_Read more:_
|
|
35
|
-
|
|
36
|
-
- [Lightning Design System: Design Tokens](https://www.lightningdesignsystem.com/design-tokens/)
|
|
37
|
-
- [CSS Tricks: What are Design Tokens?](https://css-tricks.com/what-are-design-tokens/)
|
|
38
|
-
|
|
39
|
-
## Rhythm & Harmony
|
|
40
|
-
|
|
41
|
-
Instead of choosing arbitrary numbers for our typography, we make use of a _modular scale_. "By using culturally relevant, historically pleasing ratios to create modular scales and basing the measurements in our compositions on values from those scales, we can achieve a visual harmony not found in layouts that use arbitrary, conventional, or easily divisible numbers." ([_More Meaningful Typography_](https://alistapart.com/article/more-meaningful-typography/))
|
|
42
|
-
|
|
43
|
-
Applied to web typography, this means we choose a base font size and apply a ratio to it. The ratio is then squared to generate an ascending scale.
|
|
44
|
-
|
|
45
|
-
```scss
|
|
46
|
-
/* perfect fifth */
|
|
47
|
-
$ratio: 1.5;
|
|
48
|
-
|
|
49
|
-
/* browser default */
|
|
50
|
-
$base: 16px;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The formular is always "$base * $ratio ** $level", the level is a integer.
|
|
54
|
-
* The resulting value is usually rounded to avoid poor sub-pixel rendering.
|
|
55
|
-
*
|
|
56
|
-
* Note that in the below example the number is not equal to the $level in
|
|
57
|
-
* the formula.
|
|
58
|
-
*/
|
|
59
|
-
$fontSize-0: 10px;
|
|
60
|
-
$fontSize-1: 16px;
|
|
61
|
-
$fontSize-2: 24px;
|
|
62
|
-
$fontSize-3: 36px;
|
|
63
|
-
/* etc. */
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
A similar concept can be applied to spacing and sizing of elements. To maintain a aesthetically pleasing rhythm between elements, we adopt the _8pt grid_. This means that multiples of 8 are used to define dimensions, paddings and margins. As a result, all of our measurements follow the same rule and we automatically get a more consistent UI.
|
|
67
|
-
|
|
68
|
-
```scss
|
|
69
|
-
/* 8pt grid */
|
|
70
|
-
$size-0: 0px;
|
|
71
|
-
$size-1: 8px;
|
|
72
|
-
$size-2: 16px;
|
|
73
|
-
$size-3: 32px;
|
|
74
|
-
$size-4: 64px;
|
|
75
|
-
$size-5: 128px;
|
|
76
|
-
$size-6: 256px;
|
|
77
|
-
$size-7: 512px;
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Read more:
|
|
81
|
-
|
|
82
|
-
- [A List Apart: More Meaningful Typography](https://alistapart.com/article/more-meaningful-typography/)
|
|
83
|
-
- [Type Scale Calculator](https://type-scale.com/)
|
|
84
|
-
- [Bryn Jackson's 8-Point Grid](https://spec.fm/specifics/8-pt-grid)
|
package/src/reset.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { css } from '@emotion/css';
|
|
2
|
-
|
|
3
|
-
const base = css({
|
|
4
|
-
boxSizing: 'border-box',
|
|
5
|
-
margin: 0,
|
|
6
|
-
padding: 0,
|
|
7
|
-
minWidth: 0,
|
|
8
|
-
fontSize: '100%',
|
|
9
|
-
font: 'inherit',
|
|
10
|
-
verticalAlign: 'baseline',
|
|
11
|
-
WebkitTapHighlightColor: 'transparent',
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
// Content
|
|
15
|
-
// ---------------
|
|
16
|
-
const block = css({
|
|
17
|
-
display: 'block',
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const list = css({
|
|
21
|
-
// empty
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
const table = css({
|
|
25
|
-
borderCollapse: 'collapse',
|
|
26
|
-
borderSpacing: 0,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
// Typography
|
|
30
|
-
// ---------------
|
|
31
|
-
const a = css({
|
|
32
|
-
textDecoration: 'none',
|
|
33
|
-
touchAction: 'manipulation',
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const quote = css({
|
|
37
|
-
quotes: 'none',
|
|
38
|
-
selectors: {
|
|
39
|
-
'&:before, &:after': {
|
|
40
|
-
content: "''",
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
// Form Elements
|
|
46
|
-
// ---------------
|
|
47
|
-
const button = css({
|
|
48
|
-
display: 'block',
|
|
49
|
-
appearance: 'none',
|
|
50
|
-
background: 'transparent',
|
|
51
|
-
textAlign: 'center',
|
|
52
|
-
touchAction: 'manipulation',
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const input = css({
|
|
56
|
-
display: 'block',
|
|
57
|
-
appearance: 'none',
|
|
58
|
-
selectors: {
|
|
59
|
-
'&::-ms-clear': {
|
|
60
|
-
display: 'none',
|
|
61
|
-
},
|
|
62
|
-
'&::-webkit-search-cancel-button': {
|
|
63
|
-
WebkitAppearance: 'none',
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
const select = css({
|
|
69
|
-
display: 'block',
|
|
70
|
-
appearance: 'none',
|
|
71
|
-
selectors: {
|
|
72
|
-
'&::-ms-expand': {
|
|
73
|
-
display: 'none',
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
const textarea = css({
|
|
79
|
-
display: 'block',
|
|
80
|
-
appearance: 'none',
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
// Reset
|
|
84
|
-
// ---------------
|
|
85
|
-
export const reset = {
|
|
86
|
-
article: block,
|
|
87
|
-
aside: block,
|
|
88
|
-
details: block,
|
|
89
|
-
figcaption: block,
|
|
90
|
-
figure: block,
|
|
91
|
-
footer: block,
|
|
92
|
-
header: block,
|
|
93
|
-
hgroup: block,
|
|
94
|
-
menu: block,
|
|
95
|
-
nav: block,
|
|
96
|
-
section: block,
|
|
97
|
-
ul: list,
|
|
98
|
-
ol: list,
|
|
99
|
-
blockquote: quote,
|
|
100
|
-
q: quote,
|
|
101
|
-
a,
|
|
102
|
-
base,
|
|
103
|
-
table,
|
|
104
|
-
select,
|
|
105
|
-
button,
|
|
106
|
-
textarea,
|
|
107
|
-
input,
|
|
108
|
-
} as const;
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { renderHook } from '@testing-library/react-hooks';
|
|
3
|
-
import { useClassname } from './useClassname';
|
|
4
|
-
import { ThemeProvider } from './useTheme';
|
|
5
|
-
|
|
6
|
-
// Setup
|
|
7
|
-
// ---------------
|
|
8
|
-
const theme = {
|
|
9
|
-
colors: {
|
|
10
|
-
primary: 'hotpink',
|
|
11
|
-
black: '#000',
|
|
12
|
-
},
|
|
13
|
-
sizes: [0, 1, 2],
|
|
14
|
-
text: {
|
|
15
|
-
body: {
|
|
16
|
-
fontSize: 1,
|
|
17
|
-
color: 'black',
|
|
18
|
-
},
|
|
19
|
-
heading: {
|
|
20
|
-
fontSize: 3,
|
|
21
|
-
color: 'primary',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const wrapper: React.FC = ({ children }) => (
|
|
27
|
-
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
test('create a string classname', () => {
|
|
31
|
-
const { result } = renderHook(() => useClassname({ color: 'primary' }), {
|
|
32
|
-
wrapper,
|
|
33
|
-
});
|
|
34
|
-
expect(result.current).toEqual(expect.any(String));
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
test('create classnames from multiple intpus', () => {
|
|
38
|
-
const { result } = renderHook(
|
|
39
|
-
() => useClassname({ color: 'primary' }, { p: 2 }),
|
|
40
|
-
{
|
|
41
|
-
wrapper,
|
|
42
|
-
}
|
|
43
|
-
);
|
|
44
|
-
expect(result.current).toEqual(expect.any(String));
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('create a unique classnames', () => {
|
|
48
|
-
const { result: first } = renderHook(
|
|
49
|
-
() => useClassname({ color: 'primary' }),
|
|
50
|
-
{
|
|
51
|
-
wrapper,
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
const { result: second } = renderHook(
|
|
55
|
-
() => useClassname({ color: 'black' }),
|
|
56
|
-
{
|
|
57
|
-
wrapper,
|
|
58
|
-
}
|
|
59
|
-
);
|
|
60
|
-
expect(first.current).not.toEqual(second.current);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('handle empty styles', () => {
|
|
64
|
-
/**
|
|
65
|
-
* emotion will add a lot of `css-0` classes especially
|
|
66
|
-
* when a "variant" is not present in the theme.
|
|
67
|
-
*/
|
|
68
|
-
const { result } = renderHook(() => useClassname({}));
|
|
69
|
-
expect(result.current).toMatchInlineSnapshot(`""`);
|
|
70
|
-
});
|
package/src/useClassname.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { css as emotion } from '@emotion/css';
|
|
2
|
-
import { StyleObject } from './types';
|
|
3
|
-
import { useTheme } from './useTheme';
|
|
4
|
-
|
|
5
|
-
// 🤫 https://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object
|
|
6
|
-
// lodash.isEmpty is tooo much KBs!
|
|
7
|
-
const isEmpty = (val: any) =>
|
|
8
|
-
val && Object.keys(val).length === 0 && val.constructor === Object;
|
|
9
|
-
|
|
10
|
-
export const useClassname = (...styles: StyleObject[]) => {
|
|
11
|
-
const { css } = useTheme();
|
|
12
|
-
return styles
|
|
13
|
-
.map(style => {
|
|
14
|
-
/**
|
|
15
|
-
* emotion will create a `css-0` class whenever an empty object is
|
|
16
|
-
* passed. Since this makes debugging harder we'll do not pass empty
|
|
17
|
-
* objects to emotion.
|
|
18
|
-
*/
|
|
19
|
-
const themedStyle = css(style);
|
|
20
|
-
return isEmpty(themedStyle) ? '' : emotion(themedStyle);
|
|
21
|
-
})
|
|
22
|
-
.join(' ');
|
|
23
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { Meta, Story, Canvas } from '@storybook/addon-docs';
|
|
2
|
-
import { useStyles } from './useStyles';
|
|
3
|
-
import { Text } from '@marigold/components';
|
|
4
|
-
|
|
5
|
-
<Meta title="Hooks/useStyles" />
|
|
6
|
-
|
|
7
|
-
# useStyles
|
|
8
|
-
|
|
9
|
-
<Canvas>
|
|
10
|
-
<Story name="usestyles">
|
|
11
|
-
<Text>
|
|
12
|
-
The useStyles hook generates classnames that style your component:
|
|
13
|
-
</Text>
|
|
14
|
-
</Story>
|
|
15
|
-
</Canvas>
|
|
16
|
-
|
|
17
|
-
```tsx
|
|
18
|
-
useStyles({
|
|
19
|
-
element: ElementType; // reset styles for the given HTML element
|
|
20
|
-
css: CSSObject; // custom styles
|
|
21
|
-
variant: string|string[]; // theme variant(s)
|
|
22
|
-
className: string; // additional classnames
|
|
23
|
-
})
|
|
24
|
-
```
|