@marigold/components 0.3.0 → 0.3.1

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.
@@ -1,5 +1,5 @@
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
 
@@ -8,6 +8,9 @@ const theme = {
8
8
  none: 0,
9
9
  small: 2,
10
10
  },
11
+ colors: {
12
+ disabled: 'gray',
13
+ },
11
14
  radio: {
12
15
  __default: {
13
16
  m: 'small',
@@ -60,14 +63,14 @@ test('supports required prop and renders required icon', () => {
60
63
  });
61
64
 
62
65
  test('supports default type', () => {
63
- render(<Radio id="radio" title="radio" />);
66
+ render(<Radio id="radio" title="radio" label="test" />);
64
67
 
65
68
  const radio = screen.getByTitle(/radio/);
66
69
  expect(radio.getAttribute('type')).toEqual('radio');
67
70
  });
68
71
 
69
72
  test('renders <input> element', () => {
70
- render(<Radio id="radio" title="radio" />);
73
+ render(<Radio id="radio" title="radio" label="test" />);
71
74
 
72
75
  const radio = screen.getByTitle(/radio/);
73
76
  expect(radio instanceof HTMLInputElement).toBeTruthy();
@@ -82,6 +85,8 @@ test('supports disabled prop', () => {
82
85
 
83
86
  const radio = screen.getByTitle(/radio/);
84
87
  expect(radio).toHaveAttribute('disabled');
88
+ const label = screen.getByText(/label/);
89
+ expect(label).toHaveStyle(`color: gray`);
85
90
  });
86
91
 
87
92
  test('supports error and errorMessage prop', () => {
@@ -98,22 +103,52 @@ test('supports error and errorMessage prop', () => {
98
103
  test('supports checked radio', () => {
99
104
  render(
100
105
  <ThemeProvider theme={theme}>
101
- <Radio id="test" title="radio" onChange={() => {}} checked />
106
+ <Radio id="test" title="radio" label="test" onChange={() => {}} checked />
102
107
  </ThemeProvider>
103
108
  );
104
109
 
105
110
  const radio = screen.getByTitle(/radio/);
106
- expect(radio).toBeDefined();
111
+ expect(radio).toHaveAttribute('checked');
107
112
  });
108
113
 
109
114
  test('supports checked and disabled radio', () => {
110
115
  render(
111
116
  <ThemeProvider theme={theme}>
112
- <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
+ />
113
125
  </ThemeProvider>
114
126
  );
115
127
 
116
128
  const radio = screen.getByTitle(/radio/);
117
- expect(radio).toBeDefined();
129
+ expect(radio).toHaveAttribute('checked');
118
130
  expect(radio).toHaveAttribute('disabled');
119
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,13 +1,14 @@
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';
10
-
11
12
  // Theme Extension
12
13
  // ---------------
13
14
  export interface RadioThemeExtension<Value> {
@@ -16,78 +17,38 @@ export interface RadioThemeExtension<Value> {
16
17
  };
17
18
  }
18
19
 
19
- // Radio Icon
20
+ // Radio Input
20
21
  // ---------------
21
- type RadioIconProps = {
22
- variant?: string;
23
- checked?: boolean;
24
- disabled?: boolean;
25
- error?: boolean;
26
- children?: never;
27
- };
22
+ type RadioInputProps = RadioIconProps & ComponentProps<'input'>;
23
+
24
+ const RadioInput: React.FC<RadioInputProps> = ({ error, ...props }) => {
25
+ const { focusProps } = useFocusRing();
28
26
 
29
- const RadioIcon: React.FC<RadioIconProps> = ({
30
- variant,
31
- checked,
32
- disabled,
33
- error,
34
- }) => {
35
- if (checked) {
36
- return (
37
- <Box as={RadioChecked} variant={`radio.${variant}`} disabled={disabled} />
38
- );
39
- }
40
27
  return (
41
- <Box
42
- as={RadioUnchecked}
43
- variant={`radio.${variant}`}
44
- disabled={disabled}
45
- error={error}
46
- />
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>
47
44
  );
48
45
  };
49
46
 
50
- // Radio Input
51
- // ---------------
52
- type RadioInputProps = {
53
- variant?: string;
54
- error?: boolean;
55
- } & ComponentProps<'input'>;
56
-
57
- const RadioInput: React.FC<RadioInputProps> = ({
58
- className,
59
- variant = '',
60
- error,
61
- ...props
62
- }) => (
63
- <Box display="inline-block" className={className}>
64
- <Box
65
- as="input"
66
- type="radio"
67
- css={{
68
- position: 'absolute',
69
- opacity: 0,
70
- zIndex: -1,
71
- width: 1,
72
- height: 1,
73
- overflow: 'hidden',
74
- }}
75
- {...props}
76
- />
77
- <RadioIcon
78
- checked={props.checked}
79
- variant={variant}
80
- disabled={props.disabled}
81
- error={error}
82
- />
83
- </Box>
84
- );
85
-
86
47
  // Radio
87
48
  // ---------------
88
49
  export type RadioProps = {
89
50
  id: string;
90
- label?: string;
51
+ label: string;
91
52
  required?: boolean;
92
53
  labelVariant?: string;
93
54
  error?: boolean;
@@ -101,28 +62,27 @@ export const Radio: React.FC<RadioProps> = ({
101
62
  error,
102
63
  errorMessage,
103
64
  ...props
104
- }) => {
105
- if (label) {
106
- return (
107
- <>
108
- <Label
109
- htmlFor={props.id}
110
- required={required}
111
- variant={labelVariant}
112
- color={props.disabled ? 'disabled' : 'text'}
113
- >
114
- <Box as={RadioInput} pr="8px" error={error} {...props} />
115
- {label}
116
- </Label>
117
- {error && errorMessage && (
118
- <ValidationMessage>
119
- <Exclamation size={16} />
120
- {errorMessage}
121
- </ValidationMessage>
122
- )}
123
- </>
124
- );
125
- }
126
-
127
- return <RadioInput {...props} />;
128
- };
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
+ );
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import { conditional, State, SVG } from '@marigold/system';
3
+
4
+ import { Box } from '../Box';
5
+
6
+ // Radio Icon
7
+ // ---------------
8
+ export type RadioIconProps = {
9
+ variant?: string;
10
+ checked?: boolean;
11
+ disabled?: boolean;
12
+ error?: boolean;
13
+ children?: never;
14
+ };
15
+
16
+ export const RadioIcon: React.FC<RadioIconProps> = ({
17
+ variant = '',
18
+ checked = false,
19
+ disabled = false,
20
+ error = false,
21
+ }) => {
22
+ const conditionalStates: State = disabled
23
+ ? {
24
+ disabled: disabled,
25
+ }
26
+ : {
27
+ checked: checked,
28
+ error: error,
29
+ };
30
+
31
+ return (
32
+ <SVG
33
+ width="16"
34
+ height="32"
35
+ viewBox="0 0 16 32"
36
+ fill="none"
37
+ aria-hidden="true"
38
+ >
39
+ <Box
40
+ variant={conditional(`radio.${variant}`, conditionalStates)}
41
+ as="circle"
42
+ cx="8"
43
+ cy="16"
44
+ r="7.5"
45
+ />
46
+ {checked && <circle fill="white" cx="8" cy="16" r="3" />}
47
+ </SVG>
48
+ );
49
+ };
@@ -1,10 +0,0 @@
1
- /// <reference types="react" />
2
- export declare const RadioChecked: ({ disabled, ...props }: {
3
- [x: string]: any;
4
- disabled?: boolean | undefined;
5
- }) => JSX.Element;
6
- export declare const RadioUnchecked: ({ disabled, error, ...props }: {
7
- [x: string]: any;
8
- disabled?: boolean | undefined;
9
- error?: boolean | undefined;
10
- }) => JSX.Element;
@@ -1,97 +0,0 @@
1
- import { ArgsTable, Canvas, Meta, Story } from '@storybook/addon-docs';
2
- import { Checkbox } from './Checkbox';
3
- import { useState } from 'react';
4
-
5
- <Meta
6
- title="Components/Checkbox"
7
- parameters={{
8
- actions: {
9
- handles: ['click'],
10
- },
11
- }}
12
- argTypes={{
13
- id: {
14
- control: {
15
- type: 'text',
16
- },
17
- type: { required: true },
18
- description: 'Unique ID',
19
- },
20
- variant: {
21
- control: {
22
- type: 'text',
23
- },
24
- description: 'Checkbox variant',
25
- table: {
26
- defaultValue: {
27
- summary: 'default',
28
- },
29
- },
30
- },
31
- labelVariant: {
32
- control: {
33
- type: 'text',
34
- },
35
- description: 'Checkbox label variant',
36
- table: {
37
- defaultValue: {
38
- summary: 'inline',
39
- },
40
- },
41
- },
42
- label: {
43
- control: {
44
- type: 'text',
45
- },
46
- description: 'Label',
47
- },
48
- required: {
49
- control: {
50
- type: 'boolean',
51
- },
52
- description: 'Require',
53
- table: {
54
- defaultValue: {
55
- summary: false,
56
- },
57
- },
58
- },
59
- error: {
60
- control: {
61
- type: 'boolean',
62
- },
63
- description: 'Error',
64
- table: {
65
- defaultValue: {
66
- summary: false,
67
- },
68
- },
69
- },
70
- errorMessage: {
71
- control: {
72
- type: 'text',
73
- },
74
- description: 'Error Message',
75
- },
76
- }}
77
- />
78
-
79
- # Checkbox
80
-
81
- export const Template = ({ onChange, checked, ...args }) => {
82
- const [isChecked, setChecked] = useState(false);
83
- return (
84
- <Checkbox
85
- onChange={() => setChecked(!isChecked)}
86
- checked={isChecked}
87
- label="Checkbox Label"
88
- {...args}
89
- />
90
- );
91
- };
92
-
93
- <Canvas>
94
- <Story name="Default">{Template.bind({})}</Story>
95
- </Canvas>
96
-
97
- <ArgsTable story="Default" />
@@ -1,39 +0,0 @@
1
- import React from 'react';
2
- import { SVG } from '@marigold/system';
3
-
4
- import { Box } from '../Box';
5
-
6
- export const RadioChecked = ({ disabled = false, ...props }) => (
7
- <SVG width="16" height="32" viewBox="0 0 16 32" fill="none" {...props}>
8
- <Box
9
- as="circle"
10
- cx="8"
11
- cy="16"
12
- r="7.5"
13
- variant={disabled ? 'radio.checked.disabled' : 'radio.checked'}
14
- />
15
- <Box as="circle" cx="8" cy="16" r="3" variant="radio.checked.circle" />
16
- </SVG>
17
- );
18
-
19
- export const RadioUnchecked = ({
20
- disabled = false,
21
- error = false,
22
- ...props
23
- }) => (
24
- <SVG width="16" height="32" viewBox="0 0 16 32" fill="none" {...props}>
25
- <Box
26
- as="circle"
27
- cx="8"
28
- cy="16"
29
- r="7.5"
30
- variant={
31
- disabled
32
- ? 'radio.unchecked.disabled'
33
- : error
34
- ? 'radio.unchecked.error'
35
- : 'radio.unchecked'
36
- }
37
- />
38
- </SVG>
39
- );