@charcoal-ui/react 3.0.0-beta.2 → 3.0.0-beta.4

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 (186) hide show
  1. package/dist/_lib/compat.d.ts +19 -1
  2. package/dist/_lib/compat.d.ts.map +1 -1
  3. package/dist/_lib/index.d.ts +7 -0
  4. package/dist/_lib/index.d.ts.map +1 -1
  5. package/dist/components/Button/index.d.ts +1 -2
  6. package/dist/components/Button/index.d.ts.map +1 -1
  7. package/dist/components/Button/index.story.d.ts +1 -2
  8. package/dist/components/Button/index.story.d.ts.map +1 -1
  9. package/dist/components/Button/index.test.d.ts +4 -0
  10. package/dist/components/Button/index.test.d.ts.map +1 -0
  11. package/dist/components/Checkbox/index.d.ts +2 -1
  12. package/dist/components/Checkbox/index.d.ts.map +1 -1
  13. package/dist/components/Checkbox/index.story.d.ts +2 -2
  14. package/dist/components/Checkbox/index.story.d.ts.map +1 -1
  15. package/dist/components/Clickable/index.d.ts +1 -1
  16. package/dist/components/Clickable/index.d.ts.map +1 -1
  17. package/dist/components/Clickable/index.story.d.ts +1 -2
  18. package/dist/components/Clickable/index.story.d.ts.map +1 -1
  19. package/dist/components/DropdownSelector/Divider.d.ts +3 -0
  20. package/dist/components/DropdownSelector/Divider.d.ts.map +1 -1
  21. package/dist/components/DropdownSelector/DropdownMenuItem.d.ts +7 -0
  22. package/dist/components/DropdownSelector/DropdownMenuItem.d.ts.map +1 -0
  23. package/dist/components/DropdownSelector/DropdownPopover.d.ts +8 -8
  24. package/dist/components/DropdownSelector/DropdownPopover.d.ts.map +1 -1
  25. package/dist/components/DropdownSelector/ListItem/index.d.ts +18 -0
  26. package/dist/components/DropdownSelector/ListItem/index.d.ts.map +1 -0
  27. package/dist/components/DropdownSelector/ListItem/index.story.d.ts +9 -0
  28. package/dist/components/DropdownSelector/ListItem/index.story.d.ts.map +1 -0
  29. package/dist/components/DropdownSelector/MenuItem/index.d.ts +11 -0
  30. package/dist/components/DropdownSelector/MenuItem/index.d.ts.map +1 -0
  31. package/dist/components/DropdownSelector/MenuItem/internals/handleFocusByKeyBoard.d.ts +9 -0
  32. package/dist/components/DropdownSelector/MenuItem/internals/handleFocusByKeyBoard.d.ts.map +1 -0
  33. package/dist/components/DropdownSelector/MenuItem/internals/useMenuItemHandleKeyDown.d.ts +10 -0
  34. package/dist/components/DropdownSelector/MenuItem/internals/useMenuItemHandleKeyDown.d.ts.map +1 -0
  35. package/dist/components/DropdownSelector/MenuItemGroup/index.d.ts +14 -0
  36. package/dist/components/DropdownSelector/MenuItemGroup/index.d.ts.map +1 -0
  37. package/dist/components/DropdownSelector/MenuList/MenuListContext.d.ts +10 -0
  38. package/dist/components/DropdownSelector/MenuList/MenuListContext.d.ts.map +1 -0
  39. package/dist/components/DropdownSelector/MenuList/index.d.ts +18 -0
  40. package/dist/components/DropdownSelector/MenuList/index.d.ts.map +1 -0
  41. package/dist/components/DropdownSelector/MenuList/index.story.d.ts +11 -0
  42. package/dist/components/DropdownSelector/MenuList/index.story.d.ts.map +1 -0
  43. package/dist/components/DropdownSelector/MenuList/internals/getValuesRecursive.d.ts +11 -0
  44. package/dist/components/DropdownSelector/MenuList/internals/getValuesRecursive.d.ts.map +1 -0
  45. package/dist/components/DropdownSelector/Popover/index.d.ts +17 -0
  46. package/dist/components/DropdownSelector/Popover/index.d.ts.map +1 -0
  47. package/dist/components/DropdownSelector/Popover/index.story.d.ts +9 -0
  48. package/dist/components/DropdownSelector/Popover/index.story.d.ts.map +1 -0
  49. package/dist/components/DropdownSelector/index.d.ts +3 -10
  50. package/dist/components/DropdownSelector/index.d.ts.map +1 -1
  51. package/dist/components/DropdownSelector/index.story.d.ts +4 -4
  52. package/dist/components/DropdownSelector/index.story.d.ts.map +1 -1
  53. package/dist/components/DropdownSelector/utils/findPreviewRecursive.d.ts +12 -0
  54. package/dist/components/DropdownSelector/utils/findPreviewRecursive.d.ts.map +1 -0
  55. package/dist/components/FieldLabel/index.d.ts +1 -1
  56. package/dist/components/FieldLabel/index.d.ts.map +1 -1
  57. package/dist/components/Icon/index.d.ts +1 -1
  58. package/dist/components/Icon/index.d.ts.map +1 -1
  59. package/dist/components/Icon/index.story.d.ts +2 -3
  60. package/dist/components/Icon/index.story.d.ts.map +1 -1
  61. package/dist/components/IconButton/index.d.ts +1 -2
  62. package/dist/components/IconButton/index.d.ts.map +1 -1
  63. package/dist/components/IconButton/index.story.d.ts +1 -2
  64. package/dist/components/IconButton/index.story.d.ts.map +1 -1
  65. package/dist/components/LoadingSpinner/index.d.ts +9 -8
  66. package/dist/components/LoadingSpinner/index.d.ts.map +1 -1
  67. package/dist/components/LoadingSpinner/index.story.d.ts +1 -2
  68. package/dist/components/LoadingSpinner/index.story.d.ts.map +1 -1
  69. package/dist/components/Modal/ModalPlumbing.d.ts.map +1 -1
  70. package/dist/components/Modal/index.d.ts +18 -27
  71. package/dist/components/Modal/index.d.ts.map +1 -1
  72. package/dist/components/Modal/index.story.d.ts +12 -2
  73. package/dist/components/Modal/index.story.d.ts.map +1 -1
  74. package/dist/components/MultiSelect/context.d.ts +1 -1
  75. package/dist/components/MultiSelect/context.d.ts.map +1 -1
  76. package/dist/components/MultiSelect/index.d.ts +18 -6
  77. package/dist/components/MultiSelect/index.d.ts.map +1 -1
  78. package/dist/components/MultiSelect/index.story.d.ts +21 -16
  79. package/dist/components/MultiSelect/index.story.d.ts.map +1 -1
  80. package/dist/components/Radio/index.d.ts +13 -6
  81. package/dist/components/Radio/index.d.ts.map +1 -1
  82. package/dist/components/Radio/index.story.d.ts +11 -8
  83. package/dist/components/Radio/index.story.d.ts.map +1 -1
  84. package/dist/components/SegmentedControl/RadioGroupContext.d.ts +1 -1
  85. package/dist/components/SegmentedControl/RadioGroupContext.d.ts.map +1 -1
  86. package/dist/components/SegmentedControl/index.d.ts +2 -1
  87. package/dist/components/SegmentedControl/index.d.ts.map +1 -1
  88. package/dist/components/SegmentedControl/index.story.d.ts +1 -2
  89. package/dist/components/SegmentedControl/index.story.d.ts.map +1 -1
  90. package/dist/components/Switch/index.d.ts +3 -2
  91. package/dist/components/Switch/index.d.ts.map +1 -1
  92. package/dist/components/Switch/index.story.d.ts +1 -2
  93. package/dist/components/Switch/index.story.d.ts.map +1 -1
  94. package/dist/components/TagItem/index.d.ts +3 -3
  95. package/dist/components/TagItem/index.d.ts.map +1 -1
  96. package/dist/components/TagItem/index.story.d.ts +2 -3
  97. package/dist/components/TagItem/index.story.d.ts.map +1 -1
  98. package/dist/components/TextArea/TextArea.story.d.ts +28 -0
  99. package/dist/components/TextArea/TextArea.story.d.ts.map +1 -0
  100. package/dist/components/TextArea/index.d.ts +21 -0
  101. package/dist/components/TextArea/index.d.ts.map +1 -0
  102. package/dist/components/TextField/TextField.story.d.ts +28 -0
  103. package/dist/components/TextField/TextField.story.d.ts.map +1 -0
  104. package/dist/components/TextField/index.d.ts +8 -30
  105. package/dist/components/TextField/index.d.ts.map +1 -1
  106. package/dist/core/CharcoalProvider.d.ts +1 -1
  107. package/dist/core/CharcoalProvider.d.ts.map +1 -1
  108. package/dist/core/ComponentAbstraction.d.ts +1 -1
  109. package/dist/core/ComponentAbstraction.d.ts.map +1 -1
  110. package/dist/index.cjs.js +1064 -771
  111. package/dist/index.cjs.js.map +1 -1
  112. package/dist/index.d.ts +5 -3
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.esm.js +1028 -750
  115. package/dist/index.esm.js.map +1 -1
  116. package/dist/styled.d.ts +13 -13
  117. package/package.json +7 -7
  118. package/src/_lib/compat.ts +20 -1
  119. package/src/_lib/index.ts +23 -0
  120. package/src/components/Button/__snapshots__/index.test.tsx.snap +385 -0
  121. package/src/components/Button/index.story.tsx +1 -1
  122. package/src/components/Button/index.test.tsx +24 -0
  123. package/src/components/Button/index.tsx +2 -2
  124. package/src/components/Checkbox/index.story.tsx +1 -1
  125. package/src/components/Checkbox/index.tsx +4 -2
  126. package/src/components/Clickable/index.story.tsx +0 -1
  127. package/src/components/Clickable/index.tsx +1 -1
  128. package/src/components/DropdownSelector/Divider.tsx +3 -0
  129. package/src/components/DropdownSelector/DropdownMenuItem.tsx +40 -0
  130. package/src/components/DropdownSelector/DropdownPopover.tsx +21 -42
  131. package/src/components/DropdownSelector/ListItem/index.story.tsx +51 -0
  132. package/src/components/DropdownSelector/ListItem/index.tsx +58 -0
  133. package/src/components/DropdownSelector/MenuItem/index.tsx +31 -0
  134. package/src/components/DropdownSelector/MenuItem/internals/handleFocusByKeyBoard.tsx +43 -0
  135. package/src/components/DropdownSelector/MenuItem/internals/useMenuItemHandleKeyDown.tsx +55 -0
  136. package/src/components/DropdownSelector/MenuItemGroup/index.tsx +42 -0
  137. package/src/components/DropdownSelector/MenuList/MenuListContext.ts +17 -0
  138. package/src/components/DropdownSelector/MenuList/index.story.tsx +51 -0
  139. package/src/components/DropdownSelector/MenuList/index.tsx +51 -0
  140. package/src/components/DropdownSelector/MenuList/internals/getValuesRecursive.tsx +35 -0
  141. package/src/components/DropdownSelector/Popover/index.story.tsx +65 -0
  142. package/src/components/DropdownSelector/Popover/index.tsx +69 -0
  143. package/src/components/DropdownSelector/index.story.tsx +56 -21
  144. package/src/components/DropdownSelector/index.tsx +19 -60
  145. package/src/components/DropdownSelector/utils/findPreviewRecursive.tsx +39 -0
  146. package/src/components/FieldLabel/index.tsx +1 -1
  147. package/src/components/Icon/index.story.tsx +0 -1
  148. package/src/components/Icon/index.tsx +1 -1
  149. package/src/components/IconButton/index.story.tsx +0 -1
  150. package/src/components/IconButton/index.tsx +2 -2
  151. package/src/components/LoadingSpinner/index.story.tsx +8 -2
  152. package/src/components/LoadingSpinner/index.tsx +44 -29
  153. package/src/components/Modal/ModalPlumbing.tsx +0 -1
  154. package/src/components/Modal/index.story.tsx +0 -1
  155. package/src/components/Modal/index.tsx +19 -12
  156. package/src/components/MultiSelect/context.ts +2 -2
  157. package/src/components/MultiSelect/index.story.tsx +26 -27
  158. package/src/components/MultiSelect/index.test.tsx +5 -23
  159. package/src/components/MultiSelect/index.tsx +83 -78
  160. package/src/components/Radio/index.story.tsx +7 -9
  161. package/src/components/Radio/index.test.tsx +3 -4
  162. package/src/components/Radio/index.tsx +24 -23
  163. package/src/components/SegmentedControl/RadioGroupContext.tsx +2 -1
  164. package/src/components/SegmentedControl/index.story.tsx +0 -1
  165. package/src/components/SegmentedControl/index.tsx +16 -5
  166. package/src/components/Switch/index.story.tsx +1 -1
  167. package/src/components/Switch/index.tsx +38 -32
  168. package/src/components/TagItem/index.story.tsx +0 -1
  169. package/src/components/TagItem/index.tsx +1 -6
  170. package/src/components/TextArea/TextArea.story.tsx +61 -0
  171. package/src/components/TextArea/index.tsx +246 -0
  172. package/src/components/TextField/{index.story.tsx → TextField.story.tsx} +6 -29
  173. package/src/components/TextField/index.tsx +148 -378
  174. package/src/components/a11y.test.tsx +0 -1
  175. package/src/core/CharcoalProvider.tsx +1 -1
  176. package/src/core/ComponentAbstraction.tsx +2 -1
  177. package/src/index.ts +8 -6
  178. package/dist/components/DropdownSelector/OptionItem.d.ts +0 -7
  179. package/dist/components/DropdownSelector/OptionItem.d.ts.map +0 -1
  180. package/dist/components/DropdownSelector/utils/focusIfHTMLLIElement.d.ts +0 -6
  181. package/dist/components/DropdownSelector/utils/focusIfHTMLLIElement.d.ts.map +0 -1
  182. package/dist/components/DropdownSelector/utils/handleFocusByKeyBoard.d.ts +0 -6
  183. package/dist/components/DropdownSelector/utils/handleFocusByKeyBoard.d.ts.map +0 -1
  184. package/src/components/DropdownSelector/OptionItem.tsx +0 -85
  185. package/src/components/DropdownSelector/utils/focusIfHTMLLIElement.tsx +0 -12
  186. package/src/components/DropdownSelector/utils/handleFocusByKeyBoard.tsx +0 -20
@@ -1,4 +1,4 @@
1
- import React, { useState } from 'react'
1
+ import { useState } from 'react'
2
2
  import { Story } from '../../_lib/compat'
3
3
  import styled from 'styled-components'
4
4
  import { MultiSelectGroup, default as MultiSelect } from '.'
@@ -12,7 +12,7 @@ export default {
12
12
  type: 'text',
13
13
  },
14
14
  },
15
- ariaLabel: {
15
+ label: {
16
16
  control: {
17
17
  type: 'text',
18
18
  },
@@ -22,11 +22,6 @@ export default {
22
22
  type: 'boolean',
23
23
  },
24
24
  },
25
- firstOptionForceChecked: {
26
- control: {
27
- type: 'boolean',
28
- },
29
- },
30
25
  disabled: {
31
26
  control: {
32
27
  type: 'boolean',
@@ -37,7 +32,7 @@ export default {
37
32
  type: 'boolean',
38
33
  },
39
34
  },
40
- hasError: {
35
+ invalid: {
41
36
  control: {
42
37
  type: 'boolean',
43
38
  },
@@ -53,14 +48,14 @@ export default {
53
48
 
54
49
  type Props = {
55
50
  name: string
56
- ariaLabel: string
51
+ label: string
57
52
  selected: boolean
58
- firstOptionForceChecked: boolean
59
53
  onChange: (selected: string[]) => void
60
54
  disabled?: boolean
61
55
  readonly?: boolean
62
- hasError?: boolean
56
+ invalid?: boolean
63
57
  variant?: 'default' | 'overlay'
58
+ className?: string
64
59
  }
65
60
 
66
61
  const StyledMultiSelectGroup = styled(MultiSelectGroup)`
@@ -71,34 +66,33 @@ const StyledMultiSelectGroup = styled(MultiSelectGroup)`
71
66
 
72
67
  const Template: Story<Props> = ({
73
68
  name,
74
- ariaLabel,
69
+ label,
75
70
  selected,
76
- firstOptionForceChecked,
77
71
  onChange,
78
72
  disabled,
79
73
  readonly,
80
- hasError,
74
+ invalid,
81
75
  variant,
76
+ className,
82
77
  }) => {
83
78
  return (
84
79
  <StyledMultiSelectGroup
85
80
  {...{
86
81
  name,
87
- ariaLabel,
82
+ label,
88
83
  onChange,
89
84
  disabled,
90
85
  readonly,
91
- hasError,
86
+ invalid,
92
87
  }}
93
- className={''}
94
88
  selected={selected ? ['選択肢1', '選択肢3'] : []}
95
89
  >
96
90
  {[1, 2, 3, 4].map((idx) => (
97
91
  <MultiSelect
98
92
  value={`選択肢${idx}`}
99
- forceChecked={firstOptionForceChecked && idx === 1}
100
93
  variant={variant}
101
94
  key={idx}
95
+ className={className}
102
96
  >
103
97
  選択肢{idx}
104
98
  </MultiSelect>
@@ -110,12 +104,11 @@ const Template: Story<Props> = ({
110
104
  export const Default = Template.bind({})
111
105
  Default.args = {
112
106
  name: '',
113
- ariaLabel: '',
107
+ label: '',
114
108
  selected: true,
115
- firstOptionForceChecked: false,
116
109
  disabled: false,
117
110
  readonly: false,
118
- hasError: false,
111
+ invalid: false,
119
112
  variant: 'default',
120
113
  // eslint-disable-next-line no-console
121
114
  onChange: (selected) => console.log(selected),
@@ -123,14 +116,15 @@ Default.args = {
123
116
 
124
117
  type PlaygroundProps = {
125
118
  name: string
126
- ariaLabel: string
119
+ label: string
127
120
  disabled?: boolean
128
121
  readonly?: boolean
129
- hasError?: boolean
122
+ invalid?: boolean
123
+ className?: string
130
124
  variant?: 'default' | 'overlay'
131
125
  }
132
126
 
133
- export const Playground: Story<PlaygroundProps> = (props) => {
127
+ export const Playground: Story<PlaygroundProps> = ({ className, ...props }) => {
134
128
  const [selected, setSelected] = useState<string[]>([])
135
129
 
136
130
  return (
@@ -140,7 +134,12 @@ export const Playground: Story<PlaygroundProps> = (props) => {
140
134
  onChange={setSelected}
141
135
  >
142
136
  {[1, 2, 3, 4].map((idx) => (
143
- <MultiSelect value={`選択肢${idx}`} variant={props.variant} key={idx}>
137
+ <MultiSelect
138
+ value={`選択肢${idx}`}
139
+ variant={props.variant}
140
+ key={idx}
141
+ className={className}
142
+ >
144
143
  選択肢{idx}
145
144
  </MultiSelect>
146
145
  ))}
@@ -149,9 +148,9 @@ export const Playground: Story<PlaygroundProps> = (props) => {
149
148
  }
150
149
  Playground.args = {
151
150
  name: 'defaultName',
152
- ariaLabel: '',
151
+ label: '',
153
152
  disabled: false,
154
153
  readonly: false,
155
- hasError: false,
154
+ invalid: false,
156
155
  variant: 'default',
157
156
  }
@@ -1,5 +1,4 @@
1
1
  import { fireEvent, render, screen } from '@testing-library/react'
2
- import React from 'react'
3
2
  import { ThemeProvider } from 'styled-components'
4
3
  import { default as MultiSelect, MultiSelectGroup } from '.'
5
4
  import { light } from '@charcoal-ui/theme'
@@ -184,7 +183,7 @@ describe('MultiSelect', () => {
184
183
  let allOptions: HTMLInputElement[]
185
184
 
186
185
  beforeEach(() => {
187
- render(<TestComponent selected={['option1']} hasError={true} />)
186
+ render(<TestComponent selected={['option1']} invalid={true} />)
188
187
 
189
188
  option1 = screen.getByDisplayValue('option1')
190
189
  option2 = screen.getByDisplayValue('option2')
@@ -199,20 +198,6 @@ describe('MultiSelect', () => {
199
198
  })
200
199
  })
201
200
 
202
- describe('option1 is force checked', () => {
203
- let option1: HTMLInputElement
204
-
205
- beforeEach(() => {
206
- render(<TestComponent selected={[]} firstOptionForceChecked={true} />)
207
-
208
- option1 = screen.getByDisplayValue('option1')
209
- })
210
-
211
- it('option1 is force checked', () => {
212
- expect(option1.checked).toBeTruthy()
213
- })
214
- })
215
-
216
201
  describe('option1 is disabled', () => {
217
202
  let option1: HTMLInputElement
218
203
  let option2: HTMLInputElement
@@ -239,8 +224,7 @@ const TestComponent = ({
239
224
  childOnChange,
240
225
  parentDisabled = false,
241
226
  readonly = false,
242
- hasError = false,
243
- firstOptionForceChecked = false,
227
+ invalid = false,
244
228
  firstOptionDisabled = false,
245
229
  }: {
246
230
  selected: string[]
@@ -248,23 +232,21 @@ const TestComponent = ({
248
232
  childOnChange?: (payload: { value: string; selected: boolean }) => void
249
233
  parentDisabled?: boolean
250
234
  readonly?: boolean
251
- hasError?: boolean
252
- firstOptionForceChecked?: boolean
235
+ invalid?: boolean
253
236
  firstOptionDisabled?: boolean
254
237
  }) => {
255
238
  return (
256
239
  <ThemeProvider theme={light}>
257
240
  <MultiSelectGroup
258
241
  name="defaultName"
259
- ariaLabel="defaultAriaLabel"
242
+ label="defaultAriaLabel"
260
243
  disabled={parentDisabled}
261
244
  onChange={parentOnChange}
262
- {...{ selected, readonly, hasError }}
245
+ {...{ selected, readonly, invalid }}
263
246
  >
264
247
  <MultiSelect
265
248
  value="option1"
266
249
  disabled={firstOptionDisabled}
267
- forceChecked={firstOptionForceChecked}
268
250
  onChange={childOnChange}
269
251
  >
270
252
  Option 1
@@ -1,4 +1,5 @@
1
- import React, { ChangeEvent, useCallback, useContext } from 'react'
1
+ import { ChangeEvent, useCallback, useContext, forwardRef, memo } from 'react'
2
+ import * as React from 'react'
2
3
  import styled, { css } from 'styled-components'
3
4
  import warning from 'warning'
4
5
  import { theme } from '../../styled'
@@ -8,74 +9,82 @@ import { MultiSelectGroupContext } from './context'
8
9
 
9
10
  export type MultiSelectProps = React.PropsWithChildren<{
10
11
  value: string
11
- forceChecked?: boolean
12
12
  disabled?: boolean
13
13
  variant?: 'default' | 'overlay'
14
+ className?: string
14
15
  onChange?: (payload: { value: string; selected: boolean }) => void
15
16
  }>
16
17
 
17
- export default function MultiSelect({
18
- value,
19
- forceChecked = false,
20
- disabled = false,
21
- onChange,
22
- variant = 'default',
23
- children,
24
- }: MultiSelectProps) {
25
- const {
26
- name,
27
- selected,
28
- disabled: parentDisabled,
29
- readonly,
30
- hasError,
31
- onChange: parentOnChange,
32
- } = useContext(MultiSelectGroupContext)
33
-
34
- warning(
35
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
36
- name !== undefined,
37
- `"name" is not Provided for <MultiSelect>. Perhaps you forgot to wrap with <MultiSelectGroup> ?`
38
- )
39
-
40
- const isSelected = selected.includes(value) || forceChecked
41
- const isDisabled = disabled || parentDisabled || readonly
42
-
43
- const handleChange = useCallback(
44
- (event: ChangeEvent<HTMLInputElement>) => {
45
- if (!(event.currentTarget instanceof HTMLInputElement)) {
46
- return
47
- }
48
- if (onChange) onChange({ value, selected: event.currentTarget.checked })
49
- parentOnChange({ value, selected: event.currentTarget.checked })
18
+ const MultiSelect = forwardRef<HTMLInputElement, MultiSelectProps>(
19
+ function MultiSelectInner(
20
+ {
21
+ value,
22
+ disabled = false,
23
+ onChange,
24
+ variant = 'default',
25
+ className,
26
+ children,
50
27
  },
51
- [onChange, parentOnChange, value]
52
- )
28
+ ref
29
+ ) {
30
+ const {
31
+ name,
32
+ selected,
33
+ disabled: parentDisabled,
34
+ readonly,
35
+ invalid,
36
+ onChange: parentOnChange,
37
+ } = useContext(MultiSelectGroupContext)
38
+
39
+ warning(
40
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
41
+ name !== undefined,
42
+ `"name" is not Provided for <MultiSelect>. Perhaps you forgot to wrap with <MultiSelectGroup> ?`
43
+ )
44
+
45
+ const isSelected = selected.includes(value)
46
+ const isDisabled = disabled || parentDisabled || readonly
47
+
48
+ const handleChange = useCallback(
49
+ (event: ChangeEvent<HTMLInputElement>) => {
50
+ if (!(event.currentTarget instanceof HTMLInputElement)) {
51
+ return
52
+ }
53
+ if (onChange) onChange({ value, selected: event.currentTarget.checked })
54
+ parentOnChange({ value, selected: event.currentTarget.checked })
55
+ },
56
+ [onChange, parentOnChange, value]
57
+ )
58
+
59
+ return (
60
+ <MultiSelectRoot aria-disabled={isDisabled} className={className}>
61
+ <MultiSelectInput
62
+ {...{
63
+ name,
64
+ value,
65
+ invalid,
66
+ }}
67
+ checked={isSelected}
68
+ disabled={isDisabled}
69
+ onChange={handleChange}
70
+ overlay={variant === 'overlay'}
71
+ aria-invalid={invalid}
72
+ ref={ref}
73
+ />
74
+ <MultiSelectInputOverlay
75
+ overlay={variant === 'overlay'}
76
+ invalid={invalid}
77
+ aria-hidden={true}
78
+ >
79
+ <pixiv-icon name="24/Check" unsafe-non-guideline-scale={16 / 24} />
80
+ </MultiSelectInputOverlay>
81
+ {Boolean(children) && <MultiSelectLabel>{children}</MultiSelectLabel>}
82
+ </MultiSelectRoot>
83
+ )
84
+ }
85
+ )
53
86
 
54
- return (
55
- <MultiSelectRoot aria-disabled={isDisabled}>
56
- <MultiSelectInput
57
- {...{
58
- name,
59
- value,
60
- hasError,
61
- }}
62
- checked={isSelected}
63
- disabled={isDisabled}
64
- onChange={handleChange}
65
- overlay={variant === 'overlay'}
66
- aria-invalid={hasError}
67
- />
68
- <MultiSelectInputOverlay
69
- overlay={variant === 'overlay'}
70
- hasError={hasError}
71
- aria-hidden={true}
72
- >
73
- <pixiv-icon name="24/Check" unsafe-non-guideline-scale={16 / 24} />
74
- </MultiSelectInputOverlay>
75
- {Boolean(children) && <MultiSelectLabel>{children}</MultiSelectLabel>}
76
- </MultiSelectRoot>
77
- )
78
- }
87
+ export default memo(MultiSelect)
79
88
 
80
89
  const MultiSelectRoot = styled.label`
81
90
  display: grid;
@@ -97,7 +106,7 @@ const MultiSelectLabel = styled.div`
97
106
  `
98
107
 
99
108
  const MultiSelectInput = styled.input.attrs({ type: 'checkbox' })<{
100
- hasError: boolean
109
+ invalid: boolean
101
110
  overlay: boolean
102
111
  }>`
103
112
  &[type='checkbox'] {
@@ -111,11 +120,11 @@ const MultiSelectInput = styled.input.attrs({ type: 'checkbox' })<{
111
120
  ${theme((o) => o.bg.brand.hover.press)}
112
121
  }
113
122
 
114
- ${({ hasError, overlay }) =>
123
+ ${({ invalid, overlay }) =>
115
124
  theme((o) => [
116
125
  o.bg.text3.hover.press,
117
126
  o.borderRadius('oval'),
118
- hasError && !overlay && o.outline.assertive,
127
+ invalid && !overlay && o.outline.assertive,
119
128
  overlay && o.bg.surface4,
120
129
  ])};
121
130
  }
@@ -123,7 +132,7 @@ const MultiSelectInput = styled.input.attrs({ type: 'checkbox' })<{
123
132
 
124
133
  const MultiSelectInputOverlay = styled.div<{
125
134
  overlay: boolean
126
- hasError: boolean
135
+ invalid: boolean
127
136
  }>`
128
137
  position: absolute;
129
138
  top: -2px;
@@ -133,13 +142,13 @@ const MultiSelectInputOverlay = styled.div<{
133
142
  align-items: center;
134
143
  justify-content: center;
135
144
 
136
- ${({ hasError, overlay }) =>
145
+ ${({ invalid, overlay }) =>
137
146
  theme((o) => [
138
147
  o.width.px(24),
139
148
  o.height.px(24),
140
149
  o.borderRadius('oval'),
141
150
  o.font.text5,
142
- hasError && overlay && o.outline.assertive,
151
+ invalid && overlay && o.outline.assertive,
143
152
  ])}
144
153
 
145
154
  ${({ overlay }) =>
@@ -154,23 +163,23 @@ const MultiSelectInputOverlay = styled.div<{
154
163
  export type MultiSelectGroupProps = React.PropsWithChildren<{
155
164
  className?: string
156
165
  name: string
157
- ariaLabel: string
166
+ label: string
158
167
  selected: string[]
159
168
  onChange: (selected: string[]) => void
160
169
  disabled?: boolean
161
170
  readonly?: boolean
162
- hasError?: boolean
171
+ invalid?: boolean
163
172
  }>
164
173
 
165
174
  export function MultiSelectGroup({
166
175
  className,
167
176
  name,
168
- ariaLabel,
177
+ label,
169
178
  selected,
170
179
  onChange,
171
180
  disabled = false,
172
181
  readonly = false,
173
- hasError = false,
182
+ invalid = false,
174
183
  children,
175
184
  }: MultiSelectGroupProps) {
176
185
  const handleChange = useCallback(
@@ -197,15 +206,11 @@ export function MultiSelectGroup({
197
206
  selected: Array.from(new Set(selected)),
198
207
  disabled,
199
208
  readonly,
200
- hasError,
209
+ invalid,
201
210
  onChange: handleChange,
202
211
  }}
203
212
  >
204
- <div
205
- className={className}
206
- aria-label={ariaLabel}
207
- data-testid="SelectGroup"
208
- >
213
+ <div className={className} aria-label={label} data-testid="SelectGroup">
209
214
  {children}
210
215
  </div>
211
216
  </MultiSelectGroupContext.Provider>
@@ -1,5 +1,4 @@
1
1
  import { action } from '@storybook/addon-actions'
2
- import React from 'react'
3
2
  import { css } from 'styled-components'
4
3
  import { Story } from '../../_lib/compat'
5
4
  import Radio, { RadioGroup } from '.'
@@ -17,30 +16,29 @@ export default {
17
16
  },
18
17
  },
19
18
  args: {
20
- hasError: false,
19
+ invalid: false,
21
20
  parentDisabled: false,
22
21
  childDisabled: false,
23
- forceChecked: false,
24
22
  readonly: false,
25
23
  },
26
24
  }
27
25
 
28
26
  interface Props {
29
27
  value?: string
30
- hasError: boolean
28
+ invalid: boolean
31
29
  parentDisabled: boolean
32
30
  childDisabled: boolean
33
- forceChecked: boolean
34
31
  readonly: boolean
32
+ className?: string
35
33
  }
36
34
 
37
35
  const Template: Story<Partial<Props>> = ({
38
36
  value,
39
- forceChecked,
40
- hasError,
37
+ invalid,
41
38
  parentDisabled,
42
39
  childDisabled,
43
40
  readonly,
41
+ className,
44
42
  }) => (
45
43
  <div
46
44
  css={css`
@@ -58,14 +56,14 @@ const Template: Story<Partial<Props>> = ({
58
56
  onChange={action('onChange')}
59
57
  disabled={parentDisabled}
60
58
  readonly={readonly}
61
- hasError={hasError}
59
+ invalid={invalid}
62
60
  >
63
61
  {options.map((option) => (
64
62
  <Radio
65
63
  key={option}
66
64
  value={option}
67
65
  disabled={childDisabled}
68
- forceChecked={forceChecked}
66
+ className={className}
69
67
  >
70
68
  {name}({option})を選ぶ
71
69
  </Radio>
@@ -1,5 +1,4 @@
1
1
  import { fireEvent, render, screen } from '@testing-library/react'
2
- import React from 'react'
3
2
  import { ThemeProvider } from 'styled-components'
4
3
  import Radio, { RadioGroup } from '.'
5
4
  import { light } from '@charcoal-ui/theme'
@@ -112,7 +111,7 @@ function TestComponent({
112
111
  onChange = jest.fn(),
113
112
  radioGroupDisabled = false,
114
113
  readonly = false,
115
- hasError = false,
114
+ invalid = false,
116
115
  option1Disabled = false,
117
116
  option2Disabled = false,
118
117
  }: {
@@ -120,7 +119,7 @@ function TestComponent({
120
119
  onChange?: () => void
121
120
  radioGroupDisabled?: boolean
122
121
  readonly?: boolean
123
- hasError?: boolean
122
+ invalid?: boolean
124
123
  option1Disabled?: boolean
125
124
  option2Disabled?: boolean
126
125
  }) {
@@ -133,7 +132,7 @@ function TestComponent({
133
132
  onChange={onChange}
134
133
  disabled={radioGroupDisabled}
135
134
  readonly={readonly}
136
- hasError={hasError}
135
+ invalid={invalid}
137
136
  >
138
137
  <Radio value="option1" disabled={option1Disabled}>
139
138
  option1を選ぶ
@@ -1,4 +1,5 @@
1
- import React, { useCallback, useContext } from 'react'
1
+ import { memo, forwardRef, useCallback, useContext } from 'react'
2
+ import * as React from 'react'
2
3
  import styled from 'styled-components'
3
4
  import warning from 'warning'
4
5
  import { theme } from '../../styled'
@@ -6,22 +7,20 @@ import { px } from '@charcoal-ui/utils'
6
7
 
7
8
  export type RadioProps = React.PropsWithChildren<{
8
9
  value: string
9
- forceChecked?: boolean
10
10
  disabled?: boolean
11
+ className?: string
11
12
  }>
12
13
 
13
- export default function Radio({
14
- value,
15
- forceChecked = false,
16
- disabled = false,
17
- children,
18
- }: RadioProps) {
14
+ const Radio = forwardRef<HTMLInputElement, RadioProps>(function RadioInner(
15
+ { value, disabled = false, children, className },
16
+ ref
17
+ ) {
19
18
  const {
20
19
  name,
21
20
  selected,
22
21
  disabled: isParentDisabled,
23
22
  readonly,
24
- hasError,
23
+ invalid,
25
24
  onChange,
26
25
  } = useContext(RadioGroupContext)
27
26
 
@@ -43,19 +42,22 @@ export default function Radio({
43
42
  )
44
43
 
45
44
  return (
46
- <RadioRoot aria-disabled={isDisabled || isReadonly}>
45
+ <RadioRoot aria-disabled={isDisabled || isReadonly} className={className}>
47
46
  <RadioInput
48
47
  name={name}
49
48
  value={value}
50
- checked={forceChecked || isSelected}
51
- hasError={hasError}
49
+ checked={isSelected}
50
+ invalid={invalid}
52
51
  onChange={handleChange}
53
52
  disabled={isDisabled || isReadonly}
53
+ ref={ref}
54
54
  />
55
55
  {children != null && <RadioLabel>{children}</RadioLabel>}
56
56
  </RadioRoot>
57
57
  )
58
- }
58
+ })
59
+
60
+ export default memo(Radio)
59
61
 
60
62
  const RadioRoot = styled.label`
61
63
  display: grid;
@@ -68,7 +70,7 @@ const RadioRoot = styled.label`
68
70
  `
69
71
 
70
72
  export const RadioInput = styled.input.attrs({ type: 'radio' })<{
71
- hasError?: boolean
73
+ invalid?: boolean
72
74
  }>`
73
75
  /** Make prior to browser default style */
74
76
  &[type='radio'] {
@@ -81,14 +83,13 @@ export const RadioInput = styled.input.attrs({ type: 'radio' })<{
81
83
 
82
84
  width: 20px;
83
85
  height: 20px;
84
-
85
86
  cursor: pointer;
86
87
 
87
- ${({ hasError = false }) =>
88
+ ${({ invalid = false }) =>
88
89
  theme((o) => [
89
90
  o.borderRadius('oval'),
90
91
  o.bg.surface1.hover.press,
91
- hasError && o.outline.assertive,
92
+ invalid && o.outline.assertive,
92
93
  ])};
93
94
 
94
95
  &:not(:checked) {
@@ -130,7 +131,7 @@ export type RadioGroupProps = React.PropsWithChildren<{
130
131
  onChange(next: string): void
131
132
  disabled?: boolean
132
133
  readonly?: boolean
133
- hasError?: boolean
134
+ invalid?: boolean
134
135
  }>
135
136
 
136
137
  // TODO: use (or polyfill) flex gap
@@ -145,7 +146,7 @@ interface RadioGroupContext {
145
146
  selected?: string
146
147
  disabled: boolean
147
148
  readonly: boolean
148
- hasError: boolean
149
+ invalid: boolean
149
150
  onChange: (next: string) => void
150
151
  }
151
152
 
@@ -154,7 +155,7 @@ const RadioGroupContext = React.createContext<RadioGroupContext>({
154
155
  selected: undefined,
155
156
  disabled: false,
156
157
  readonly: false,
157
- hasError: false,
158
+ invalid: false,
158
159
  onChange() {
159
160
  throw new Error(
160
161
  'Cannot find onChange() handler. Perhaps you forgot to wrap with <RadioGroup> ?'
@@ -170,7 +171,7 @@ export function RadioGroup({
170
171
  onChange,
171
172
  disabled,
172
173
  readonly,
173
- hasError,
174
+ invalid,
174
175
  children,
175
176
  }: RadioGroupProps) {
176
177
  const handleChange = useCallback(
@@ -187,7 +188,7 @@ export function RadioGroup({
187
188
  selected: value,
188
189
  disabled: disabled ?? false,
189
190
  readonly: readonly ?? false,
190
- hasError: hasError ?? false,
191
+ invalid: invalid ?? false,
191
192
  onChange: handleChange,
192
193
  }}
193
194
  >
@@ -195,7 +196,7 @@ export function RadioGroup({
195
196
  role="radiogroup"
196
197
  aria-orientation="vertical"
197
198
  aria-label={label}
198
- aria-invalid={hasError}
199
+ aria-invalid={invalid}
199
200
  className={className}
200
201
  >
201
202
  {children}
@@ -1,4 +1,5 @@
1
- import React, { createContext, useContext } from 'react'
1
+ import { createContext, useContext } from 'react'
2
+ import * as React from 'react'
2
3
  import { RadioGroupState } from 'react-stately'
3
4
 
4
5
  const RadioContext = createContext<RadioGroupState | null>(null)