@marigold/system 0.0.1 → 0.2.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 (50) hide show
  1. package/dist/Element.d.ts +8 -0
  2. package/dist/cache.d.ts +4 -0
  3. package/dist/index.d.ts +6 -4
  4. package/dist/normalize.d.ts +110 -0
  5. package/dist/reset.d.ts +24 -0
  6. package/dist/system.cjs.development.js +332 -95
  7. package/dist/system.cjs.development.js.map +1 -1
  8. package/dist/system.cjs.production.min.js +1 -1
  9. package/dist/system.cjs.production.min.js.map +1 -1
  10. package/dist/system.esm.js +323 -92
  11. package/dist/system.esm.js.map +1 -1
  12. package/dist/types.d.ts +9 -0
  13. package/dist/useClassname.d.ts +2 -0
  14. package/dist/useStyles.d.ts +15 -0
  15. package/dist/useTheme.d.ts +10 -0
  16. package/package.json +7 -10
  17. package/src/Colors.stories.mdx +616 -448
  18. package/src/Element.test.tsx +203 -0
  19. package/src/Element.tsx +59 -0
  20. package/src/cache.ts +4 -0
  21. package/src/concepts-principles.mdx +1 -1
  22. package/src/index.ts +6 -4
  23. package/src/normalize.test.tsx +42 -0
  24. package/src/normalize.ts +131 -0
  25. package/src/reset.ts +108 -0
  26. package/src/types.ts +16 -0
  27. package/src/useClassname.test.tsx +70 -0
  28. package/src/useClassname.ts +23 -0
  29. package/src/useStyles.stories.mdx +24 -0
  30. package/src/useStyles.test.tsx +286 -0
  31. package/src/useStyles.ts +63 -0
  32. package/src/useTheme.test.tsx +115 -0
  33. package/src/useTheme.tsx +22 -0
  34. package/dist/Box/Box.d.ts +0 -6
  35. package/dist/Box/index.d.ts +0 -1
  36. package/dist/MarigoldProvider.d.ts +0 -7
  37. package/dist/categories.d.ts +0 -169
  38. package/dist/emotion.d.ts +0 -7
  39. package/dist/system.d.ts +0 -37
  40. package/src/Box/Box.stories.mdx +0 -148
  41. package/src/Box/Box.test.tsx +0 -215
  42. package/src/Box/Box.tsx +0 -58
  43. package/src/Box/index.ts +0 -1
  44. package/src/MarigoldProvider.test.tsx +0 -80
  45. package/src/MarigoldProvider.tsx +0 -37
  46. package/src/categories.ts +0 -203
  47. package/src/emotion.ts +0 -39
  48. package/src/system.test.tsx +0 -84
  49. package/src/system.tsx +0 -55
  50. package/src/writeComponent.stories.mdx +0 -114
package/dist/system.d.ts DELETED
@@ -1,37 +0,0 @@
1
- /**
2
- * Typings are based on [Reach UI](https://github.com/reach/reach-ui/blob/4cb497f530b0f83f80c6f6f2da46ab55b1160cb6/packages/utils/src/types.tsx).
3
- */
4
- import { ComponentPropsWithRef, ElementType, ReactElement, ValidationMap, WeakValidationMap } from 'react';
5
- /**
6
- * SystemProps support the `as` and `variant` prop. The former
7
- * is used to changed the rendered root element of a component.
8
- *
9
- * These props also infer additional allowed props based on the
10
- * value of the `as` prop. For example, setting `as="button"` will
11
- * allow to use HTMLButtonAttributes on the component.
12
- */
13
- export declare type SystemProps<P, T extends ElementType> = P & Omit<ComponentPropsWithRef<T>, 'as' | keyof P> & {
14
- as?: T;
15
- variant?: string;
16
- };
17
- /**
18
- * Enhanced version of `React.FunctionComponent` that accepts `SystemProps`
19
- * and infers allowed properties based on the `as` prop.
20
- */
21
- export interface SystemComponent<P, T extends ElementType> {
22
- /**
23
- * These types are a bit of a hack, but cover us in cases where the `as` prop
24
- * is not a JSX string type. Makes the compiler happy so 🤷‍♂️
25
- */
26
- <TT extends ElementType>(props: SystemProps<P, TT>): ReactElement | null;
27
- (props: SystemProps<P, T>): ReactElement | null;
28
- displayName?: string;
29
- propTypes?: WeakValidationMap<SystemProps<P, T>>;
30
- contextTypes?: ValidationMap<any>;
31
- defaultProps?: Partial<SystemProps<P, T>>;
32
- }
33
- /**
34
- * Helper to write components that adhere to a common design system API,
35
- * which includes the `as` and `variant` prop.
36
- */
37
- export declare function system<P, T extends ElementType>(render: (props: SystemProps<P, T>) => ReactElement | null): SystemComponent<P, T>;
@@ -1,148 +0,0 @@
1
- import { Meta, Story, Preview } from '@storybook/addon-docs/blocks';
2
- import { Box } from './Box';
3
- import { Label, Button } from '@marigold/components';
4
-
5
- <Meta title="Components/Box Component" />
6
-
7
- # Box-Component
8
-
9
- ## Description
10
-
11
- `Box` is the most abstract component on top of which all other Marigold components are build.
12
- The `Box` allows us to apply styling via a dedicated prop (`css`) while respecting the rules and constraints of our design system.
13
- Instead of exposing the underlying tools that will create CSS, the `Box` component wraps them and exposes a more React-like API to style components.
14
-
15
- ### Style components
16
-
17
- The style of components is defined by three different layers that are structured hierarchically. A layer can overwrite the attributes of the previous layer.
18
-
19
- **Default styles, theme styles and style props**
20
-
21
- - **default styles**
22
-
23
- - add default styling to a component, even without a theme
24
- - should be used to achieve minimal level of consistency between browsers
25
-
26
- <Preview>
27
- <Story name="label">
28
- <Label variant="" htmlFor="labelId">
29
- A simple Label
30
- </Label>
31
- </Story>
32
- <Story name="button">
33
- <Button>Click me!</Button>
34
- </Story>
35
- </Preview>
36
-
37
- - **theme styles**
38
-
39
- - style values from a specific theme can be easily used with the box
40
- - the theme can overwrite the default styles
41
- - in this way you can stay inside the boundaries of the design system.
42
-
43
- <Preview>
44
- <Story name="label-theme-styled">
45
- <Label htmlFor="labelId">A simple Label</Label>
46
- </Story>
47
- <Story name="button-theme-styled">
48
- <Button variant="primary.large">Click me!</Button>
49
- </Story>
50
- </Preview>
51
-
52
- - **style props**
53
-
54
- - style props can be used to add and modify component styles beyond the boundaries of the design system
55
-
56
- <Preview>
57
- <Story name="label-css-styled">
58
- <Label
59
- htmlFor="labelId"
60
- css={{ px: 64, fontSize: '24px', letterSpacing: '8px' }}
61
- >
62
- A simple Label
63
- </Label>
64
- </Story>
65
- <Story name="button-css-styled">
66
- <Button variant="primary.large" css={{ letterSpacing: '8px' }}>
67
- Click me!
68
- </Button>
69
- </Story>
70
- </Preview>
71
-
72
- > Staying inside the boundaries of the design system and its contraints should be the norm. But limiting styling only to allowed values defined by the system can be very restrictive up to a point where the design system becomes an obstacle. This is why there is a an escape hatch for when it is absolutely necessary to apply styling from a third party or style it in a way that is non-compliant with the design system.
73
-
74
- ## Properties
75
-
76
- | Property | Type | Default |
77
- | :------------- | :--------------------------- | :------ |
78
- | `as` | `HTMLTag`, `ComponentType` | `'div'` |
79
- | `css` | [`css props`](#cssProps) | |
80
- | `themeSection` | `string` | |
81
- | `variant` | `string` | |
82
- | `SpacingProps` | [`space props`](#spaceProps) | |
83
-
84
- ### as-Property
85
-
86
- The HTML element used for the root node. Either a string to use a DOM element or a component. By default the `Box` component will render a `<div/>`. And while this might be fine most of the time, sometimes you would rather render a `<button/>` or an `<input/>`. To allow this, the `Box` component has a special prop called `as`, which accepts an HTML tag as input.
87
-
88
- ### css-Properties <a id="cssProps"></a>
89
-
90
- The `css` props let you style elements inline, using values from your theme. The css prop allows all CSS properties and the below shorthands.
91
-
92
- | Category | Prop |
93
- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
94
- | `css` | `fontFamily, fontSize, fontWeight, lineHeight, letterSpacing, color, backgroundColor, bg, margin, m, marginTop, mt, marginRight, mr, marginBottom, mb, marginLeft, ml, marginX, mx, marginY, my, padding, p, paddingTop, pt, paddingRight, pr, paddingBottom, pb, paddingLeft, pl, paddingX, px, paddingY, py, top, bottom, left, right, border, borderTop, borderRight, borderBottom, borderLeft, borderColor, borderWidth, borderStyle, borderRadius, boxShadow, textShadow, zIndex, width, minWidth, maxWidth, height, minHeight, maxHeight, size` |
95
-
96
- ### themeSection
97
-
98
- With the `themeSection` proberty, users can define as a `string` the section from a theme which should be used. E.g. you defined in your theme explicitly colors, text or buttons with seperate styles for these elements.
99
-
100
- ### variant
101
-
102
- The `variant` proberty is the next layer under themeSection. In a section like e.g. buttons there are two varaints: primary and secondary. With the variant prop you can define as a `string` a button with primary or secondary style from a theme.
103
-
104
- ### Spacing Props <a id="spaceProps"></a>
105
-
106
- The `SpacingProps` are part of the style props which can be used to give your component custom css style. The values are usable with shortcuts which you can see in the Prop column. For more information click the space Doc link to Theme-Ui in the following table.
107
-
108
- | Category | Prop |
109
- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
110
- | `SpacingProps` | `margin, m, marginTop, mt, marginRight, mr, marginBottom, mb, marginLeft, ml, marginX, mx, marginY, my, padding, p, paddingTop, pt, paddingRight, pr, paddingBottom, pb, paddingLeft, pl, paddingX, px, paddingY, py` |
111
-
112
- ## Import
113
-
114
- ```js
115
- import { Box } from '@marigold/system';
116
- ```
117
-
118
- ## Usage
119
-
120
- <Preview>
121
- <Story name="div-box">
122
- <Box>I am a div box!</Box>
123
- </Story>
124
- </Preview>
125
-
126
- <Preview>
127
- <Story name="text-box">
128
- <Box as="span" css={{ color: 'orange' }}>
129
- I am a text box!
130
- </Box>
131
- </Story>
132
- </Preview>
133
-
134
- <Preview>
135
- <Story name="button-box">
136
- <Box as="button" css={{ border: '1px solid black' }}>
137
- I am a simple button!
138
- </Box>
139
- </Story>
140
- </Preview>
141
-
142
- <Preview>
143
- <Story name="h1-box">
144
- <Box as="h1" css={{ color: 'blue' }}>
145
- I am a blue headline!
146
- </Box>
147
- </Story>
148
- </Preview>
@@ -1,215 +0,0 @@
1
- import React from 'react';
2
- import { render, screen } from '@testing-library/react';
3
- import { ThemeContext } from '../emotion';
4
- import { SpacingProps } from '../categories';
5
-
6
- import { Box } from './Box';
7
-
8
- // Setup
9
- // ---------------
10
- const theme = {
11
- colors: {
12
- primary: 'hotpink',
13
- black: '#000',
14
- white: '#FFF',
15
- blue: '#2980b9',
16
- },
17
- text: {
18
- body: {
19
- fontSize: 1,
20
- color: 'black',
21
- },
22
- heading: {
23
- fontSize: 3,
24
- color: 'primary',
25
- },
26
- },
27
- buttons: {
28
- primary: {
29
- color: 'white',
30
- bg: 'blue',
31
- },
32
- secondary: {
33
- color: 'black',
34
- bg: 'white',
35
- },
36
- },
37
- };
38
-
39
- // Tests
40
- // ---------------
41
- test('renders a <div> by default', () => {
42
- render(<Box>box</Box>);
43
- const box = screen.getByText(/box/);
44
-
45
- expect(box instanceof HTMLDivElement).toBeTruthy();
46
- });
47
-
48
- test('changes rendered element via as prop', () => {
49
- render(<Box as="span">box</Box>);
50
- const box = screen.getByText(/box/);
51
-
52
- expect(box instanceof HTMLSpanElement).toBeTruthy();
53
- });
54
-
55
- test('passes down all HTML attributes', () => {
56
- render(
57
- <Box id="box-id" disabled>
58
- box
59
- </Box>
60
- );
61
- const box = screen.getByText(/box/);
62
-
63
- expect(box.getAttribute('id')).toEqual('box-id');
64
- expect(box.getAttribute('disabled')).toMatch('');
65
- });
66
-
67
- test('apply some base css styling for normalization', () => {
68
- render(<Box>box</Box>);
69
- const box = screen.getByText(/box/);
70
-
71
- expect(box).toHaveStyle(`box-sizing: border-box`);
72
- });
73
-
74
- test('forward ref', () => {
75
- const ref = React.createRef<HTMLButtonElement>();
76
- render(
77
- <Box as="button" ref={ref}>
78
- button
79
- </Box>
80
- );
81
-
82
- expect(ref.current instanceof HTMLButtonElement).toBeTruthy();
83
- });
84
-
85
- test('apply default styling via css prop', () => {
86
- const Button: React.FC = ({ children }) => (
87
- <Box as="button" css={{ border: '1px solid black' }}>
88
- {children}
89
- </Box>
90
- );
91
- render(<Button>button</Button>);
92
- const button = screen.getByText(/button/);
93
-
94
- expect(button).toHaveStyle(`border: 1px solid black`);
95
- });
96
-
97
- test('use design tokens for scale values', () => {
98
- render(<Box css={{ px: 1 }}>box</Box>);
99
- const box = screen.getByText(/box/);
100
-
101
- expect(box).toHaveStyle(`padding-left: 4px`);
102
- expect(box).toHaveStyle(`padding-right: 4px`);
103
- });
104
-
105
- test('interpolate responsive values', () => {
106
- render(<Box css={{ px: [1, 2] }}>box</Box>);
107
- const box = screen.getByText(/box/);
108
-
109
- expect(box).toHaveStyle(`padding-left: 4px`);
110
- expect(box).toHaveStyle(`padding-right: 4px`);
111
- });
112
-
113
- test('support style props for spacing', () => {
114
- const Button: React.FC = ({ children }) => (
115
- <Box as="button" my="2">
116
- {children}
117
- </Box>
118
- );
119
- render(<Button>button</Button>);
120
- const button = screen.getByText(/button/);
121
-
122
- expect(button).toHaveStyle(`margin-top: 8px`);
123
- expect(button).toHaveStyle(`margin-bottom: 8px`);
124
- });
125
-
126
- test('supports variants from theme', () => {
127
- const Text: React.FC<{ variant?: keyof typeof theme.text }> = ({
128
- variant = 'body',
129
- children,
130
- }) => (
131
- <Box themeSection="text" variant={variant}>
132
- {children}
133
- </Box>
134
- );
135
-
136
- // Body Text
137
- render(
138
- <ThemeContext.Provider value={theme}>
139
- <Text>body</Text>
140
- </ThemeContext.Provider>
141
- );
142
- const body = screen.getByText(/body/);
143
-
144
- expect(body).toHaveStyle(`font-size: 14px`);
145
- expect(body).toHaveStyle(`color: ${theme.colors.black}`);
146
-
147
- // Heading Text
148
- render(
149
- <ThemeContext.Provider value={theme}>
150
- <Text variant="heading">heading</Text>
151
- </ThemeContext.Provider>
152
- );
153
- const heading = screen.getByText(/heading/);
154
-
155
- expect(heading).toHaveStyle(`font-size: 20px`);
156
- expect(heading).toHaveStyle(`color: ${theme.colors.primary}`);
157
- });
158
-
159
- test('order of application: base < theme < style props', () => {
160
- const Button: React.FC<
161
- {
162
- variant?: keyof typeof theme.buttons;
163
- } & SpacingProps
164
- > = ({ children, variant = 'secondary', ...props }) => (
165
- <Box
166
- {...props}
167
- as="button"
168
- themeSection="buttons"
169
- variant={variant}
170
- css={{
171
- display: 'inline-block',
172
- color: 'hotpink',
173
- border: 0,
174
- px: 2,
175
- py: 1,
176
- }}
177
- >
178
- {children}
179
- </Box>
180
- );
181
-
182
- render(
183
- <ThemeContext.Provider value={theme}>
184
- <Button>button</Button>
185
- </ThemeContext.Provider>
186
- );
187
- const button = screen.getByText(/button/);
188
-
189
- // Added via css prop
190
- expect(button).toHaveStyle(`display: inline-block`);
191
- expect(button).toHaveStyle(`border: 0`);
192
- expect(button).toHaveStyle(`padding-left: 8px`);
193
- expect(button).toHaveStyle(`padding-right: 8px`);
194
- expect(button).toHaveStyle(`padding-top: 4px`);
195
- expect(button).toHaveStyle(`padding-bottom: 4px`);
196
-
197
- // Added via variant
198
- expect(button).toHaveStyle(`color: ${theme.colors.black}`); // overrides "hotpink"
199
- expect(button).toHaveStyle(`background-color: ${theme.colors.white}`);
200
-
201
- render(
202
- <ThemeContext.Provider value={theme}>
203
- <Button px="3" py="4">
204
- variantbutton
205
- </Button>
206
- </ThemeContext.Provider>
207
- );
208
- const variantbutton = screen.getByText(/variantbutton/);
209
-
210
- expect(variantbutton).toHaveStyle(`padding: 32px 16px 32px 16px`);
211
- expect(variantbutton).not.toHaveStyle(`padding-left: 8px`);
212
- expect(variantbutton).not.toHaveStyle(`padding-right: 8px`);
213
- expect(variantbutton).not.toHaveStyle(`padding-top: 4px`);
214
- expect(variantbutton).not.toHaveStyle(`padding-bottom: 4px`);
215
- });
package/src/Box/Box.tsx DELETED
@@ -1,58 +0,0 @@
1
- // @ts-ignore
2
- import { css } from '@theme-ui/css';
3
- import pick from 'lodash.pick';
4
- import { SPACE_PROPS, SpacingProps } from '../categories';
5
- import { jsx } from '../emotion';
6
- import { system } from '../system';
7
-
8
- export type BoxProps = {
9
- css?: Object;
10
- themeSection?: string;
11
- } & SpacingProps;
12
-
13
- /**
14
- * Props that we have to remove (because they are not valid HTML attributes)
15
- * and want to process (for styling the component).
16
- */
17
- const SKIP_PROPS = ['css', 'variant', 'themeSection', ...SPACE_PROPS];
18
-
19
- /**
20
- * Gather styling related props (css, variant, space props, ...) and put them in a
21
- * single `css` prop for emotion. All gathered props will be passed to `@theme-ui/css`
22
- * before emotion will process them. This way CSS properties will interpolated based on
23
- * the given theme.
24
- */
25
- const parseProps = (props: { [key: string]: any }) => {
26
- const next: any = {};
27
-
28
- // TODO: optimize loop such that the style props are picked
29
- // within the loop (and remove lodash.pick!)
30
- for (let key in props) {
31
- if (SKIP_PROPS.includes(key)) continue;
32
- next[key] = props[key];
33
- }
34
-
35
- const styles = {
36
- ...props.css,
37
- ...pick(props, SPACE_PROPS),
38
- };
39
-
40
- const variant =
41
- props.themeSection &&
42
- props.variant &&
43
- `${props.themeSection}.${props.variant}`;
44
-
45
- next.css = (theme: any) => {
46
- return [
47
- { boxSizing: 'border-box', margin: 0, minWidth: 0 },
48
- css(styles)(theme),
49
- css({ variant })(theme),
50
- ];
51
- };
52
-
53
- return next;
54
- };
55
-
56
- export const Box = system<BoxProps, 'div'>(
57
- ({ as = 'div', children, ...props }) => jsx(as, parseProps(props), children)
58
- );
package/src/Box/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './Box';
@@ -1,80 +0,0 @@
1
- import React from 'react';
2
- import { render } from '@testing-library/react';
3
-
4
- import { Global } from './emotion';
5
- import { Box } from './Box';
6
- import { MarigoldProvider } from './MarigoldProvider';
7
-
8
- // Mock
9
- // ---------------
10
- /**
11
- * We're mocking emotion's `<Global/>` here even though this will make us test
12
- * implementation details. This is currently the only way to test CSS and
13
- * media queries.
14
- */
15
- jest.mock('@emotion/core', () => {
16
- const original = jest.requireActual('@emotion/core');
17
- return {
18
- ...original,
19
- Global: jest.fn(() => null),
20
- };
21
- });
22
-
23
- // Setup
24
- // ---------------
25
- const theme = {
26
- colors: {
27
- black: '#111',
28
- },
29
- text: {
30
- body: {
31
- fontSize: 1,
32
- color: 'black',
33
- },
34
- },
35
- };
36
-
37
- // Tests
38
- // ---------------
39
- test('set theme context', () => {
40
- const Text: React.FC<{
41
- variant?: keyof typeof theme.text;
42
- }> = ({ children }) => (
43
- <Box themeSection="text" variant="body">
44
- {children}
45
- </Box>
46
- );
47
-
48
- const { getByText } = render(
49
- <MarigoldProvider theme={theme}>
50
- <Text>I am a body text!</Text>
51
- </MarigoldProvider>
52
- );
53
- const element = getByText('I am a body text!');
54
-
55
- expect(element).toHaveStyle(`color: ${theme.colors.black}`);
56
- expect(element).toHaveStyle(`font-size: 14px`);
57
- });
58
-
59
- test('removes animation when "reduce-motion" media query is set', () => {
60
- const spy = Global as jest.Mock;
61
- spy.mockClear();
62
-
63
- render(<MarigoldProvider theme={theme} />);
64
- expect((Global as jest.Mock).mock.calls[0]).toMatchInlineSnapshot(`
65
- Array [
66
- Object {
67
- "styles": Object {
68
- "@media screen and (prefers-reduced-motion: reduce), (update: slow)": Object {
69
- "*": Object {
70
- "animationDuration": "0.001ms !important",
71
- "animationIterationCount": "1 !important",
72
- "transitionDuration": "0.001ms !important",
73
- },
74
- },
75
- },
76
- },
77
- Object {},
78
- ]
79
- `);
80
- });
@@ -1,37 +0,0 @@
1
- import React from 'react';
2
- import { Global, ThemeContext } from './emotion';
3
-
4
- /**
5
- * CSS snippet and idea from:
6
- * https://css-tricks.com/revisiting-prefers-reduced-motion-the-reduced-motion-media-query/
7
- */
8
- const ReduceMotion = () => (
9
- <Global
10
- styles={{
11
- '@media screen and (prefers-reduced-motion: reduce), (update: slow)': {
12
- '*': {
13
- animationDuration: '0.001ms !important',
14
- animationIterationCount: '1 !important',
15
- transitionDuration: '0.001ms !important',
16
- },
17
- },
18
- }}
19
- />
20
- );
21
-
22
- // TODO: change any to theme when theme component exists
23
- export type MarigoldProviderProps<T extends any> = React.PropsWithChildren<{
24
- theme: T;
25
- }>;
26
-
27
- export const MarigoldProvider = <T extends any>({
28
- theme,
29
- children,
30
- }: MarigoldProviderProps<T>) => (
31
- <ThemeContext.Provider value={theme}>
32
- <>
33
- <ReduceMotion />
34
- {children}
35
- </>
36
- </ThemeContext.Provider>
37
- );