@marigold/components 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.
Files changed (201) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/dist/ActionGroup/ActionGroup.d.ts +1 -2
  3. package/dist/ActionGroup/ActionGroup.stories.d.ts +5 -0
  4. package/dist/Alert/Alert.d.ts +7 -1
  5. package/dist/Alert/Alert.stories.d.ts +5 -0
  6. package/dist/Badge/Badge.d.ts +5 -0
  7. package/dist/Badge/Badge.stories.d.ts +5 -0
  8. package/dist/Box.d.ts +2 -0
  9. package/dist/Button/Button.d.ts +5 -0
  10. package/dist/Button/Button.stories.d.ts +5 -0
  11. package/dist/Card/Card.d.ts +5 -0
  12. package/dist/Card/Card.stories.d.ts +5 -0
  13. package/dist/Checkbox/Checkbox.d.ts +10 -5
  14. package/dist/Checkbox/Checkbox.stories.d.ts +5 -0
  15. package/dist/Checkbox/CheckboxIcons.d.ts +9 -9
  16. package/dist/Column/Column.stories.d.ts +5 -0
  17. package/dist/Columns/Columns.d.ts +2 -2
  18. package/dist/Columns/Columns.stories.d.ts +5 -0
  19. package/dist/Container/Container.stories.d.ts +5 -0
  20. package/dist/Dialog/Dialog.d.ts +5 -2
  21. package/dist/Dialog/Dialog.stories.d.ts +5 -0
  22. package/dist/Dialog/ModalDialog.d.ts +4 -1
  23. package/dist/Divider/Divider.d.ts +7 -3
  24. package/dist/Divider/Divider.stories.d.ts +5 -0
  25. package/dist/Field/Field.d.ts +2 -0
  26. package/dist/Field/Field.stories.d.ts +5 -0
  27. package/dist/Image/Image.d.ts +5 -0
  28. package/dist/Image/Image.stories.d.ts +5 -0
  29. package/dist/Inline/Inline.d.ts +7 -0
  30. package/dist/Inline/Inline.stories.d.ts +5 -0
  31. package/dist/Inline/index.d.ts +1 -0
  32. package/dist/Input/Input.d.ts +5 -0
  33. package/dist/Input/Input.stories.d.ts +5 -0
  34. package/dist/Label/Label.d.ts +12 -1
  35. package/dist/Label/Label.stories.d.ts +5 -0
  36. package/dist/Link/Link.d.ts +3 -0
  37. package/dist/Link/Link.stories.d.ts +5 -0
  38. package/dist/Menu/Menu.d.ts +3 -0
  39. package/dist/Menu/Menu.stories.d.ts +5 -0
  40. package/dist/MenuItem/MenuItem.d.ts +5 -0
  41. package/dist/MenuItem/MenuItem.stories.d.ts +5 -0
  42. package/dist/Message/Message.d.ts +5 -0
  43. package/dist/Message/Message.stories.d.ts +5 -0
  44. package/dist/Provider/MarigoldProvider.d.ts +11 -3
  45. package/dist/Provider/index.d.ts +1 -1
  46. package/dist/Radio/Radio.d.ts +9 -5
  47. package/dist/Radio/Radio.stories.d.ts +5 -0
  48. package/dist/Radio/RadioIcon.d.ts +9 -0
  49. package/dist/Select/ListBox.d.ts +1 -0
  50. package/dist/Select/ListBoxSection.d.ts +1 -0
  51. package/dist/Select/Option.d.ts +1 -0
  52. package/dist/Select/Select.d.ts +15 -1
  53. package/dist/Select/Select.stories.d.ts +5 -0
  54. package/dist/Slider/Slider.d.ts +5 -0
  55. package/dist/Slider/Slider.stories.d.ts +5 -0
  56. package/dist/Stack/Stack.stories.d.ts +5 -0
  57. package/dist/Text/Text.d.ts +5 -0
  58. package/dist/Text/Text.stories.d.ts +5 -0
  59. package/dist/Textarea/Textarea.d.ts +5 -0
  60. package/dist/Textarea/Textarea.stories.d.ts +5 -0
  61. package/dist/ValidationMessage/ValidationMessage.d.ts +5 -0
  62. package/dist/ValidationMessage/ValidationMessage.stories.d.ts +5 -0
  63. package/dist/VisuallyHidden/VisuallyHidden.d.ts +1 -0
  64. package/dist/VisuallyHidden/VisuallyHidden.stories.d.ts +5 -0
  65. package/dist/VisuallyHidden/index.d.ts +1 -0
  66. package/dist/components.cjs.development.js +536 -580
  67. package/dist/components.cjs.development.js.map +1 -1
  68. package/dist/components.cjs.production.min.js +1 -1
  69. package/dist/components.cjs.production.min.js.map +1 -1
  70. package/dist/components.esm.js +481 -535
  71. package/dist/components.esm.js.map +1 -1
  72. package/dist/index.d.ts +2 -2
  73. package/dist/theme.d.ts +23 -48
  74. package/package.json +8 -4
  75. package/src/ActionGroup/ActionGroup.stories.tsx +47 -0
  76. package/src/ActionGroup/ActionGroup.test.tsx +36 -36
  77. package/src/ActionGroup/ActionGroup.tsx +17 -28
  78. package/src/Alert/Alert.stories.tsx +32 -0
  79. package/src/Alert/Alert.test.tsx +4 -1
  80. package/src/Alert/Alert.tsx +18 -3
  81. package/src/Badge/Badge.stories.tsx +38 -0
  82. package/src/Badge/Badge.test.tsx +5 -1
  83. package/src/Badge/Badge.tsx +13 -1
  84. package/src/Box.ts +2 -0
  85. package/src/Button/{Button.stories.mdx → Button.stories.tsx} +10 -17
  86. package/src/Button/Button.test.tsx +34 -11
  87. package/src/Button/Button.tsx +17 -3
  88. package/src/Card/{Card.stories.mdx → Card.stories.tsx} +9 -17
  89. package/src/Card/Card.test.tsx +8 -3
  90. package/src/Card/Card.tsx +13 -1
  91. package/src/Checkbox/{Checkbox.stories.mdx → Checkbox.stories.tsx} +31 -39
  92. package/src/Checkbox/Checkbox.test.tsx +77 -8
  93. package/src/Checkbox/Checkbox.tsx +70 -90
  94. package/src/Checkbox/CheckboxIcons.tsx +51 -41
  95. package/src/Column/Column.stories.tsx +33 -0
  96. package/src/Column/Column.tsx +0 -0
  97. package/src/Columns/Columns.stories.tsx +75 -0
  98. package/src/Columns/Columns.test.tsx +34 -23
  99. package/src/Columns/Columns.tsx +30 -30
  100. package/src/Container/Container.stories.tsx +14 -0
  101. package/src/Container/Container.tsx +0 -0
  102. package/src/Dialog/{Dialog.stories.mdx → Dialog.stories.tsx} +33 -18
  103. package/src/Dialog/Dialog.test.tsx +91 -20
  104. package/src/Dialog/Dialog.tsx +63 -17
  105. package/src/Dialog/ModalDialog.tsx +33 -4
  106. package/src/Divider/Divider.stories.tsx +30 -0
  107. package/src/Divider/Divider.test.tsx +32 -23
  108. package/src/Divider/Divider.tsx +27 -7
  109. package/src/Field/{Field.stories.mdx → Field.stories.tsx} +33 -20
  110. package/src/Field/Field.test.tsx +55 -5
  111. package/src/Field/Field.tsx +10 -8
  112. package/src/Image/Image.stories.tsx +34 -0
  113. package/src/Image/Image.test.tsx +4 -1
  114. package/src/Image/Image.tsx +13 -1
  115. package/src/Inline/Inline.stories.tsx +39 -0
  116. package/src/Inline/Inline.test.tsx +99 -0
  117. package/src/Inline/Inline.tsx +38 -0
  118. package/src/Inline/index.ts +1 -0
  119. package/src/Input/{Input.stories.mdx → Input.stories.tsx} +10 -17
  120. package/src/Input/Input.test.tsx +7 -3
  121. package/src/Input/Input.tsx +13 -1
  122. package/src/Label/{Label.stories.mdx → Label.stories.tsx} +10 -21
  123. package/src/Label/Label.test.tsx +25 -4
  124. package/src/Label/Label.tsx +42 -9
  125. package/src/Link/Link.stories.tsx +35 -0
  126. package/src/Link/Link.test.tsx +6 -2
  127. package/src/Link/Link.tsx +12 -1
  128. package/src/Menu/{Menu.stories.mdx → Menu.stories.tsx} +13 -32
  129. package/src/Menu/Menu.test.tsx +7 -2
  130. package/src/Menu/Menu.tsx +10 -0
  131. package/src/MenuItem/MenuItem.stories.tsx +30 -0
  132. package/src/MenuItem/MenuItem.test.tsx +7 -2
  133. package/src/MenuItem/MenuItem.tsx +12 -0
  134. package/src/Message/Message.stories.tsx +30 -0
  135. package/src/Message/Message.test.tsx +4 -1
  136. package/src/Message/Message.tsx +17 -5
  137. package/src/Provider/MarigoldProvider.test.tsx +65 -55
  138. package/src/Provider/MarigoldProvider.tsx +37 -19
  139. package/src/Provider/index.ts +2 -1
  140. package/src/Radio/{Radio.stories.mdx → Radio.stories.tsx} +31 -39
  141. package/src/Radio/Radio.test.tsx +78 -9
  142. package/src/Radio/Radio.tsx +58 -87
  143. package/src/Radio/RadioIcon.tsx +49 -0
  144. package/src/Select/ListBox.tsx +1 -0
  145. package/src/Select/{Select.stories.mdx → Select.stories.tsx} +23 -20
  146. package/src/Select/Select.test.tsx +39 -1
  147. package/src/Select/Select.tsx +24 -13
  148. package/src/Slider/Slider.stories.tsx +24 -0
  149. package/src/Slider/Slider.test.tsx +10 -6
  150. package/src/Slider/Slider.tsx +25 -13
  151. package/src/Stack/Stack.stories.tsx +57 -0
  152. package/src/Stack/Stack.test.tsx +16 -7
  153. package/src/Stack/Stack.tsx +0 -0
  154. package/src/Text/{Text.stories.mdx → Text.stories.tsx} +20 -19
  155. package/src/Text/Text.test.tsx +2 -2
  156. package/src/Text/Text.tsx +12 -0
  157. package/src/Textarea/{Textarea.stories.mdx → Textarea.stories.tsx} +14 -24
  158. package/src/Textarea/Textarea.test.tsx +7 -3
  159. package/src/Textarea/Textarea.tsx +13 -1
  160. package/src/ValidationMessage/{ValidationMessage.stories.mdx → ValidationMessage.stories.tsx} +8 -17
  161. package/src/ValidationMessage/ValidationMessage.test.tsx +7 -2
  162. package/src/ValidationMessage/ValidationMessage.tsx +12 -0
  163. package/src/VisuallyHidden/VisuallyHidden.stories.tsx +19 -0
  164. package/src/VisuallyHidden/VisuallyHidden.test.tsx +10 -0
  165. package/src/VisuallyHidden/VisuallyHidden.tsx +1 -0
  166. package/src/VisuallyHidden/index.ts +1 -0
  167. package/src/index.ts +2 -2
  168. package/src/theme.ts +49 -48
  169. package/dist/Box/Box.d.ts +0 -47
  170. package/dist/Box/index.d.ts +0 -1
  171. package/dist/Heading/Heading.d.ts +0 -7
  172. package/dist/Heading/index.d.ts +0 -1
  173. package/dist/Hidden/Hidden.d.ts +0 -5
  174. package/dist/Hidden/index.d.ts +0 -1
  175. package/dist/Radio/RadioIcons.d.ts +0 -9
  176. package/src/ActionGroup/ActionGroup.stories.mdx +0 -62
  177. package/src/Alert/Alert.stories.mdx +0 -35
  178. package/src/Badge/Badge.stories.mdx +0 -57
  179. package/src/Box/Box.stories.mdx +0 -334
  180. package/src/Box/Box.test.tsx +0 -133
  181. package/src/Box/Box.tsx +0 -165
  182. package/src/Box/index.ts +0 -1
  183. package/src/Column/Column.stories.mdx +0 -49
  184. package/src/Columns/Columns.stories.mdx +0 -65
  185. package/src/Container/Container.stories.mdx +0 -19
  186. package/src/Divider/Divider.stories.mdx +0 -37
  187. package/src/Heading/Heading.stories.mdx +0 -39
  188. package/src/Heading/Heading.test.tsx +0 -77
  189. package/src/Heading/Heading.tsx +0 -19
  190. package/src/Heading/index.ts +0 -1
  191. package/src/Hidden/Hidden.stories.mdx +0 -39
  192. package/src/Hidden/Hidden.test.tsx +0 -24
  193. package/src/Hidden/Hidden.tsx +0 -16
  194. package/src/Hidden/index.ts +0 -1
  195. package/src/Image/Image.stories.mdx +0 -36
  196. package/src/Link/Link.stories.mdx +0 -45
  197. package/src/MenuItem/MenuItem.stories.mdx +0 -37
  198. package/src/Message/Message.stories.mdx +0 -44
  199. package/src/Radio/RadioIcons.tsx +0 -39
  200. package/src/Slider/Slider.stories.mdx +0 -31
  201. package/src/Stack/Stack.stories.mdx +0 -51
@@ -1,16 +1,22 @@
1
1
  import React from 'react';
2
2
  import { useTheme } from '@marigold/system';
3
3
  import { render, screen } from '@testing-library/react';
4
- import { useModal } from '@react-aria/overlays';
5
4
 
6
5
  import { MarigoldProvider } from './MarigoldProvider';
7
6
 
8
7
  // Setup
9
8
  // ---------------
10
9
  const theme = {
10
+ colors: {
11
+ black: '#000',
12
+ },
13
+ fontSizes: {
14
+ body: 16,
15
+ heading: 32,
16
+ },
11
17
  text: {
12
18
  body: {
13
- fontSize: 1,
19
+ fontSize: 'body',
14
20
  color: 'black',
15
21
  },
16
22
  },
@@ -64,63 +70,67 @@ test('themes can be cascaded', () => {
64
70
  `);
65
71
  });
66
72
 
67
- // if theres no OverlayProvider you got an error with text: Modal is not contained within a provider
68
- test('OverlayProvider is present and supports useModal hook', () => {
69
- const ChildComponent: React.FC = ({ children }) => {
70
- const { modalProps } = useModal();
71
- return <div {...modalProps}>{children}</div>;
72
- };
73
+ test('OverlayProvider is added', () => {
74
+ const { container } = render(
75
+ <MarigoldProvider theme={theme}>Test</MarigoldProvider>
76
+ );
73
77
 
74
- render(
78
+ expect(
79
+ container.querySelector(`div[data-overlay-container="true"]`)
80
+ ).toBeDefined();
81
+ });
82
+
83
+ test('OverlayProvider is added only once', () => {
84
+ const innerTheme = { colors: { primary: 'red' } };
85
+ const { container } = render(
75
86
  <MarigoldProvider theme={theme}>
76
- <ChildComponent>Test</ChildComponent>
87
+ <MarigoldProvider theme={innerTheme}>Test</MarigoldProvider>
77
88
  </MarigoldProvider>
78
89
  );
79
90
 
80
- const childComp = screen.getByText('Test');
81
- expect(childComp).toBeDefined();
91
+ expect(
92
+ container.querySelectorAll(`div[data-overlay-container="true"]`).length
93
+ ).toEqual(1);
82
94
  });
83
95
 
84
- // test('renders global styles for body and html based on root in theme', () => {
85
- // const root = render(
86
- // <MarigoldProvider
87
- // theme={{
88
- // fonts: {
89
- // body: 'Inter',
90
- // html: 'Roboto',
91
- // },
92
- // lineHeights: {
93
- // body: 1.5,
94
- // html: 1,
95
- // },
96
- // fontWeights: {
97
- // body: 500,
98
- // html: 700,
99
- // },
100
- // root: {
101
- // body: {
102
- // fontFamily: 'body',
103
- // lineHeight: 'body',
104
- // fontWeight: 'body',
105
- // },
106
- // html: {
107
- // fontFamily: 'html',
108
- // lineHeight: 'html',
109
- // fontWeight: 'html',
110
- // }
111
- // },
112
- // }}
113
- // >
114
- // <h1 title="h1">Hello</h1>
115
- // </MarigoldProvider>
116
- // );
117
-
118
- // const html = window.getComputedStyle(root.baseElement.parentElement!);
119
- // expect(html.fontFamily).toBe('Roboto');
120
- // expect(html.fontWeight).toBe('700');
121
- // expect(html.lineHeight).toBe('1');
122
- // const body = window.getComputedStyle(root.baseElement);
123
- // expect(body.fontFamily).toBe('Inter');
124
- // expect(body.fontWeight).toBe('500');
125
- // expect(body.lineHeight).toBe('1.5');
126
- // });
96
+ test('applies global styles for body and html based on `theme.root`', () => {
97
+ const theme = {
98
+ fonts: {
99
+ body: 'Inter',
100
+ html: 'Roboto',
101
+ },
102
+ lineHeights: {
103
+ body: 2.5,
104
+ },
105
+ fontWeights: {
106
+ body: 500,
107
+ html: 700,
108
+ },
109
+ root: {
110
+ body: {
111
+ fontFamily: 'body',
112
+ lineHeight: 'body',
113
+ fontWeight: 'body',
114
+ },
115
+ html: {
116
+ fontFamily: 'html',
117
+ fontWeight: 'html',
118
+ },
119
+ },
120
+ };
121
+
122
+ const root = render(
123
+ <MarigoldProvider theme={theme}>
124
+ <h1>Hello</h1>
125
+ </MarigoldProvider>
126
+ );
127
+
128
+ const html = root.baseElement.parentElement;
129
+ expect(html).toHaveStyle(`font-family: ${theme.fonts.html}`);
130
+ expect(html).toHaveStyle(`font-weight: ${theme.fontWeights.html}`);
131
+
132
+ const body = root.baseElement;
133
+ expect(body).toHaveStyle(`font-family: ${theme.fonts.body}`);
134
+ expect(body).toHaveStyle(`font-weight: ${theme.fontWeights.body}`);
135
+ expect(body).toHaveStyle(`line-height: ${theme.lineHeights.body}`);
136
+ });
@@ -1,29 +1,47 @@
1
1
  import React from 'react';
2
2
  import { OverlayProvider } from '@react-aria/overlays';
3
- import { ThemeProvider, ThemeProviderProps } from '@marigold/system';
4
- // import { Global } from '@emotion/react';
5
- // import { css } from '@theme-ui/css';
3
+ import {
4
+ Theme,
5
+ Global,
6
+ ThemeProvider,
7
+ ThemeProviderProps,
8
+ useTheme,
9
+ __defaultTheme,
10
+ } from '@marigold/system';
6
11
 
7
- // const GlobalStyles = () => {
8
- // const theme = useTheme();
9
- // const styles = css({
10
- // body: { variant: 'root.body' },
11
- // html: { variant: 'root.html' },
12
- // })(theme);
12
+ // Theme Extension
13
+ // ---------------
14
+ export interface RootThemeExtension<Value> {
15
+ root?: {
16
+ body?: Value;
17
+ html?: Value;
18
+ };
19
+ }
13
20
 
14
- // return <Global styles={styles} />;
15
- // };
16
- // a merge of the ThemeProvider and the react-aria OverlayProvider
17
- export const MarigoldProvider: React.FC<ThemeProviderProps> = ({
21
+ // Props
22
+ // ---------------
23
+ export interface MarigoldProviderProps<T extends Theme>
24
+ extends ThemeProviderProps<T> {}
25
+
26
+ // Provider
27
+ // ---------------
28
+ export function MarigoldProvider<T extends Theme>({
18
29
  theme,
19
30
  children,
20
- }) => {
31
+ }: MarigoldProviderProps<T>) {
32
+ const outer = useTheme();
33
+ const isTopLevel = outer.theme === __defaultTheme;
34
+
21
35
  return (
22
36
  <ThemeProvider theme={theme}>
23
- <OverlayProvider>
24
- {/* <GlobalStyles /> */}
25
- {children}
26
- </OverlayProvider>
37
+ {isTopLevel ? (
38
+ <>
39
+ <Global />
40
+ <OverlayProvider>{children}</OverlayProvider>
41
+ </>
42
+ ) : (
43
+ children
44
+ )}
27
45
  </ThemeProvider>
28
46
  );
29
- };
47
+ }
@@ -1,3 +1,4 @@
1
1
  export { useTheme, ThemeProvider } from '@marigold/system';
2
- export * from './MarigoldProvider';
3
2
  export { SSRProvider } from '@react-aria/ssr';
3
+
4
+ export * from './MarigoldProvider';
@@ -1,60 +1,57 @@
1
- import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
1
+ import React, { useState } from 'react';
2
+ import type { Meta, ComponentStory } from '@storybook/react';
3
+
2
4
  import { Radio } from './Radio';
3
- import { useState } from 'react';
4
5
 
5
- <Meta
6
- title="Components/Radio"
7
- parameters={{
6
+ export default {
7
+ title: 'Components/Radio',
8
+ parameters: {
8
9
  actions: {
9
10
  handles: ['click'],
10
11
  },
11
- }}
12
- argTypes={{
13
- id: {
12
+ },
13
+ argTypes: {
14
+ variant: {
14
15
  control: {
15
16
  type: 'text',
16
17
  },
17
- type: { required: true },
18
- description: 'Unique ID',
18
+ description: 'Radio variant',
19
+ defaultValue: '__default',
19
20
  },
20
- variant: {
21
+ labelVariant: {
21
22
  control: {
22
23
  type: 'text',
23
24
  },
24
- description: 'Radio variant',
25
- table: {
26
- defaultValue: {
27
- summary: 'default',
28
- },
29
- },
25
+ description: 'Radio label variant',
26
+ defaultValue: 'inline',
30
27
  },
31
28
  label: {
32
29
  control: {
33
30
  type: 'text',
34
31
  },
35
32
  description: 'Label',
33
+ defaultValue: 'Radio Label',
36
34
  },
37
35
  required: {
38
36
  control: {
39
37
  type: 'boolean',
40
38
  },
41
- description: 'Require',
42
- table: {
43
- defaultValue: {
44
- summary: false,
45
- },
39
+ description: 'Required',
40
+ defaultValue: false,
41
+ },
42
+ disabled: {
43
+ control: {
44
+ type: 'boolean',
46
45
  },
46
+ description: 'Disabled',
47
+ defaultValue: false,
47
48
  },
48
49
  error: {
49
50
  control: {
50
51
  type: 'boolean',
51
52
  },
52
53
  description: 'Error',
53
- table: {
54
- defaultValue: {
55
- summary: false,
56
- },
57
- },
54
+ defaultValue: false,
58
55
  },
59
56
  errorMessage: {
60
57
  control: {
@@ -62,25 +59,20 @@ import { useState } from 'react';
62
59
  },
63
60
  description: 'Error Message',
64
61
  },
65
- }}
66
- />
62
+ },
63
+ } as Meta;
67
64
 
68
- # Radio
69
-
70
- export const Template = ({ onChange, checked, ...args }) => {
65
+ export const Basic: ComponentStory<typeof Radio> = ({
66
+ onChange,
67
+ checked,
68
+ ...args
69
+ }) => {
71
70
  const [isChecked, setChecked] = useState(false);
72
71
  return (
73
72
  <Radio
74
73
  onChange={() => setChecked(!isChecked)}
75
74
  checked={isChecked}
76
- label="Radio Label"
77
75
  {...args}
78
76
  />
79
77
  );
80
78
  };
81
-
82
- <Canvas>
83
- <Story name="Default">{Template.bind({})}</Story>
84
- </Canvas>
85
-
86
- <ArgsTable story="Default" />
@@ -1,16 +1,53 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
3
  import { Radio } from './Radio';
4
4
  import { ThemeProvider } from '@marigold/system';
5
5
 
6
6
  const theme = {
7
+ space: {
8
+ none: 0,
9
+ small: 2,
10
+ },
11
+ colors: {
12
+ disabled: 'gray',
13
+ },
7
14
  radio: {
8
- default: {
9
- m: '2px',
15
+ __default: {
16
+ m: 'small',
17
+ },
18
+ },
19
+ label: {
20
+ above: {
21
+ fontSize: '8px',
22
+ },
23
+ inline: {
24
+ fontSize: '14px',
10
25
  },
11
26
  },
12
27
  };
13
28
 
29
+ test('supports default labelVariant', () => {
30
+ render(
31
+ <ThemeProvider theme={theme}>
32
+ <Radio id="test" title="checkbox" label="label" />
33
+ </ThemeProvider>
34
+ );
35
+
36
+ const label = screen.getByText(/label/);
37
+ expect(label).toHaveStyle(`font-size: 14px`);
38
+ });
39
+
40
+ test('supports other labelVariant than default', () => {
41
+ render(
42
+ <ThemeProvider theme={theme}>
43
+ <Radio id="test" title="checkbox" label="label" labelVariant="above" />
44
+ </ThemeProvider>
45
+ );
46
+
47
+ const label = screen.getByText(/label/);
48
+ expect(label).toHaveStyle(`font-size: 8px`);
49
+ });
50
+
14
51
  test('supports label prop', () => {
15
52
  render(<Radio label="Test" id="test" title="radio" />);
16
53
 
@@ -26,14 +63,14 @@ test('supports required prop and renders required icon', () => {
26
63
  });
27
64
 
28
65
  test('supports default type', () => {
29
- render(<Radio id="radio" title="radio" />);
66
+ render(<Radio id="radio" title="radio" label="test" />);
30
67
 
31
68
  const radio = screen.getByTitle(/radio/);
32
69
  expect(radio.getAttribute('type')).toEqual('radio');
33
70
  });
34
71
 
35
72
  test('renders <input> element', () => {
36
- render(<Radio id="radio" title="radio" />);
73
+ render(<Radio id="radio" title="radio" label="test" />);
37
74
 
38
75
  const radio = screen.getByTitle(/radio/);
39
76
  expect(radio instanceof HTMLInputElement).toBeTruthy();
@@ -48,6 +85,8 @@ test('supports disabled prop', () => {
48
85
 
49
86
  const radio = screen.getByTitle(/radio/);
50
87
  expect(radio).toHaveAttribute('disabled');
88
+ const label = screen.getByText(/label/);
89
+ expect(label).toHaveStyle(`color: gray`);
51
90
  });
52
91
 
53
92
  test('supports error and errorMessage prop', () => {
@@ -64,22 +103,52 @@ test('supports error and errorMessage prop', () => {
64
103
  test('supports checked radio', () => {
65
104
  render(
66
105
  <ThemeProvider theme={theme}>
67
- <Radio id="test" title="radio" onChange={() => {}} checked />
106
+ <Radio id="test" title="radio" label="test" onChange={() => {}} checked />
68
107
  </ThemeProvider>
69
108
  );
70
109
 
71
110
  const radio = screen.getByTitle(/radio/);
72
- expect(radio).toBeDefined();
111
+ expect(radio).toHaveAttribute('checked');
73
112
  });
74
113
 
75
114
  test('supports checked and disabled radio', () => {
76
115
  render(
77
116
  <ThemeProvider theme={theme}>
78
- <Radio id="test" title="radio" onChange={() => {}} checked disabled />
117
+ <Radio
118
+ id="test"
119
+ title="radio"
120
+ label="test"
121
+ onChange={() => {}}
122
+ checked
123
+ disabled
124
+ />
79
125
  </ThemeProvider>
80
126
  );
81
127
 
82
128
  const radio = screen.getByTitle(/radio/);
83
- expect(radio).toBeDefined();
129
+ expect(radio).toHaveAttribute('checked');
84
130
  expect(radio).toHaveAttribute('disabled');
85
131
  });
132
+
133
+ test('correctly handles interaction', () => {
134
+ const click = jest.fn();
135
+ const change = jest.fn();
136
+
137
+ render(
138
+ <ThemeProvider theme={theme}>
139
+ <Radio
140
+ id="test"
141
+ title="radio"
142
+ label="Test"
143
+ onClick={click}
144
+ onChange={change}
145
+ />
146
+ </ThemeProvider>
147
+ );
148
+
149
+ const radio = screen.getByTitle(/radio/);
150
+ fireEvent.click(radio);
151
+
152
+ expect(click).toHaveBeenCalledTimes(1);
153
+ expect(change).toHaveBeenCalledTimes(1);
154
+ });
@@ -1,86 +1,56 @@
1
1
  import React from 'react';
2
2
  import { ComponentProps } from '@marigold/types';
3
3
  import { Exclamation } from '@marigold/icons';
4
+ import { useFocusRing } from '@react-aria/focus';
5
+ import { VisuallyHidden } from '@react-aria/visually-hidden';
4
6
 
7
+ import { RadioIcon, RadioIconProps } from './RadioIcon';
5
8
  import { Box } from '../Box';
6
9
  import { Label } from '../Label';
7
10
  import { ValidationMessage } from '../ValidationMessage';
8
11
 
9
- import { RadioChecked, RadioUnchecked } from './RadioIcons';
12
+ // Theme Extension
13
+ // ---------------
14
+ export interface RadioThemeExtension<Value> {
15
+ radio?: {
16
+ [key: string]: Value;
17
+ };
18
+ }
10
19
 
11
- // Radio Icon
20
+ // Radio Input
12
21
  // ---------------
13
- type RadioIconProps = {
14
- variant?: string;
15
- checked?: boolean;
16
- disabled?: boolean;
17
- error?: boolean;
18
- children?: never;
19
- };
22
+ type RadioInputProps = RadioIconProps & ComponentProps<'input'>;
23
+
24
+ const RadioInput: React.FC<RadioInputProps> = ({ error, ...props }) => {
25
+ const { focusProps } = useFocusRing();
20
26
 
21
- const RadioIcon: React.FC<RadioIconProps> = ({
22
- variant,
23
- checked,
24
- disabled,
25
- error,
26
- }) => {
27
- if (checked) {
28
- return (
29
- <Box as={RadioChecked} variant={`radio.${variant}`} disabled={disabled} />
30
- );
31
- }
32
27
  return (
33
- <Box
34
- as={RadioUnchecked}
35
- variant={`radio.${variant}`}
36
- disabled={disabled}
37
- error={error}
38
- />
28
+ <Box pr="xsmall">
29
+ <VisuallyHidden>
30
+ <input
31
+ type="radio"
32
+ disabled={props.disabled}
33
+ {...focusProps}
34
+ {...props}
35
+ />
36
+ </VisuallyHidden>
37
+ <RadioIcon
38
+ variant={props.variant}
39
+ disabled={props.disabled}
40
+ checked={props.checked}
41
+ error={error}
42
+ />
43
+ </Box>
39
44
  );
40
45
  };
41
46
 
42
- // Radio Input
43
- // ---------------
44
- type RadioInputProps = {
45
- variant?: string;
46
- error?: boolean;
47
- } & ComponentProps<'input'>;
48
-
49
- const RadioInput: React.FC<RadioInputProps> = ({
50
- className,
51
- variant = 'default',
52
- error,
53
- ...props
54
- }) => (
55
- <Box display="inline-block" className={className}>
56
- <Box
57
- as="input"
58
- type="radio"
59
- css={{
60
- position: 'absolute',
61
- opacity: 0,
62
- zIndex: -1,
63
- width: 1,
64
- height: 1,
65
- overflow: 'hidden',
66
- }}
67
- {...props}
68
- />
69
- <RadioIcon
70
- checked={props.checked}
71
- variant={variant}
72
- disabled={props.disabled}
73
- error={error}
74
- />
75
- </Box>
76
- );
77
-
78
47
  // Radio
79
48
  // ---------------
80
49
  export type RadioProps = {
81
50
  id: string;
82
- label?: string;
51
+ label: string;
83
52
  required?: boolean;
53
+ labelVariant?: string;
84
54
  error?: boolean;
85
55
  errorMessage?: string;
86
56
  } & RadioInputProps;
@@ -88,30 +58,31 @@ export type RadioProps = {
88
58
  export const Radio: React.FC<RadioProps> = ({
89
59
  label,
90
60
  required,
61
+ labelVariant = 'inline',
91
62
  error,
92
63
  errorMessage,
93
64
  ...props
94
- }) => {
95
- if (label) {
96
- return (
97
- <>
98
- <Label
99
- htmlFor={props.id}
100
- required={required}
101
- variant={props.disabled ? 'disabled' : 'inline'}
102
- >
103
- <Box as={RadioInput} pr="8px" error={error} {...props} />
104
- {label}
105
- </Label>
106
- {error && errorMessage && (
107
- <ValidationMessage>
108
- <Exclamation size={16} />
109
- {errorMessage}
110
- </ValidationMessage>
111
- )}
112
- </>
113
- );
114
- }
115
-
116
- return <RadioInput {...props} />;
117
- };
65
+ }) => (
66
+ <>
67
+ <Box
68
+ as={Label}
69
+ htmlFor={props.id}
70
+ required={required}
71
+ variant={`label.${labelVariant}`}
72
+ css={
73
+ props.disabled
74
+ ? { color: 'disabled', ':hover': { cursor: 'not-allowed' } }
75
+ : { color: 'text', ':hover': { cursor: 'pointer' } }
76
+ }
77
+ >
78
+ <Box as={RadioInput} error={error} {...props} />
79
+ {label}
80
+ </Box>
81
+ {error && errorMessage && (
82
+ <ValidationMessage>
83
+ <Exclamation size={16} />
84
+ {errorMessage}
85
+ </ValidationMessage>
86
+ )}
87
+ </>
88
+ );