@charcoal-ui/react 2.0.0-alpha.9 → 2.0.0-rc.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.
Files changed (83) hide show
  1. package/README.md +16 -0
  2. package/dist/components/Checkbox/index.d.ts +21 -0
  3. package/dist/components/Checkbox/index.d.ts.map +1 -0
  4. package/dist/components/Checkbox/index.story.d.ts +16 -0
  5. package/dist/components/Checkbox/index.story.d.ts.map +1 -0
  6. package/dist/components/DropdownSelector/Listbox.d.ts +10 -0
  7. package/dist/components/DropdownSelector/Listbox.d.ts.map +1 -0
  8. package/dist/components/DropdownSelector/Popover.d.ts +10 -0
  9. package/dist/components/DropdownSelector/Popover.d.ts.map +1 -0
  10. package/dist/components/DropdownSelector/index.d.ts +32 -0
  11. package/dist/components/DropdownSelector/index.d.ts.map +1 -0
  12. package/dist/components/DropdownSelector/index.story.d.ts +22 -0
  13. package/dist/components/DropdownSelector/index.story.d.ts.map +1 -0
  14. package/dist/components/Icon/index.story.d.ts +1 -1
  15. package/dist/components/LoadingSpinner/index.d.ts +15 -0
  16. package/dist/components/LoadingSpinner/index.d.ts.map +1 -0
  17. package/dist/components/LoadingSpinner/index.story.d.ts +10 -0
  18. package/dist/components/LoadingSpinner/index.story.d.ts.map +1 -0
  19. package/dist/components/Modal/ModalPlumbing.d.ts +5 -0
  20. package/dist/components/Modal/ModalPlumbing.d.ts.map +1 -0
  21. package/dist/components/Modal/index.d.ts +16 -0
  22. package/dist/components/Modal/index.d.ts.map +1 -0
  23. package/dist/components/Modal/index.story.d.ts +33 -0
  24. package/dist/components/Modal/index.story.d.ts.map +1 -0
  25. package/dist/components/{Select → MultiSelect}/context.d.ts +2 -2
  26. package/dist/components/MultiSelect/context.d.ts.map +1 -0
  27. package/dist/components/MultiSelect/index.d.ts +24 -0
  28. package/dist/components/MultiSelect/index.d.ts.map +1 -0
  29. package/dist/components/{Select → MultiSelect}/index.story.d.ts +2 -2
  30. package/dist/components/MultiSelect/index.story.d.ts.map +1 -0
  31. package/dist/components/{Select → MultiSelect}/index.test.d.ts +0 -0
  32. package/dist/components/MultiSelect/index.test.d.ts.map +1 -0
  33. package/dist/components/SegmentedControl/RadioGroupContext.d.ts +9 -0
  34. package/dist/components/SegmentedControl/RadioGroupContext.d.ts.map +1 -0
  35. package/dist/components/SegmentedControl/index.d.ts +20 -0
  36. package/dist/components/SegmentedControl/index.d.ts.map +1 -0
  37. package/dist/components/SegmentedControl/index.story.d.ts +11 -0
  38. package/dist/components/SegmentedControl/index.story.d.ts.map +1 -0
  39. package/dist/components/TextField/index.d.ts +6 -3
  40. package/dist/components/TextField/index.d.ts.map +1 -1
  41. package/dist/components/TextField/index.story.d.ts +1 -0
  42. package/dist/components/TextField/index.story.d.ts.map +1 -1
  43. package/dist/core/SSRProvider.d.ts +2 -0
  44. package/dist/core/SSRProvider.d.ts.map +1 -0
  45. package/dist/index.cjs +22686 -1
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.d.ts +8 -2
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.modern.js +21612 -45
  50. package/dist/index.modern.js.map +1 -1
  51. package/dist/index.module.js +22641 -1
  52. package/dist/index.module.js.map +1 -1
  53. package/package.json +15 -8
  54. package/src/components/Checkbox/index.story.tsx +64 -0
  55. package/src/components/Checkbox/index.tsx +122 -0
  56. package/src/components/DropdownSelector/Listbox.tsx +127 -0
  57. package/src/components/DropdownSelector/Popover.tsx +46 -0
  58. package/src/components/DropdownSelector/index.story.tsx +134 -0
  59. package/src/components/DropdownSelector/index.tsx +214 -0
  60. package/src/components/FieldLabel/index.tsx +1 -1
  61. package/src/components/LoadingSpinner/index.story.tsx +52 -0
  62. package/src/components/LoadingSpinner/index.tsx +87 -0
  63. package/src/components/Modal/ModalPlumbing.tsx +47 -0
  64. package/src/components/Modal/index.story.tsx +195 -0
  65. package/src/components/Modal/index.tsx +226 -0
  66. package/src/components/{Select → MultiSelect}/context.ts +3 -3
  67. package/src/components/{Select → MultiSelect}/index.story.tsx +16 -12
  68. package/src/components/{Select → MultiSelect}/index.test.tsx +13 -13
  69. package/src/components/{Select → MultiSelect}/index.tsx +24 -21
  70. package/src/components/SegmentedControl/RadioGroupContext.tsx +22 -0
  71. package/src/components/SegmentedControl/index.story.tsx +36 -0
  72. package/src/components/SegmentedControl/index.tsx +157 -0
  73. package/src/components/TextField/index.story.tsx +31 -16
  74. package/src/components/TextField/index.tsx +53 -34
  75. package/src/components/a11y.test.tsx +11 -0
  76. package/src/core/SSRProvider.tsx +1 -0
  77. package/src/index.ts +22 -5
  78. package/src/styled.ts +1 -1
  79. package/dist/components/Select/context.d.ts.map +0 -1
  80. package/dist/components/Select/index.d.ts +0 -24
  81. package/dist/components/Select/index.d.ts.map +0 -1
  82. package/dist/components/Select/index.story.d.ts.map +0 -1
  83. package/dist/components/Select/index.test.d.ts.map +0 -1
@@ -0,0 +1,226 @@
1
+ import React, { useContext, useRef } from 'react'
2
+ import {
3
+ OverlayContainer,
4
+ OverlayProps,
5
+ useModal,
6
+ useOverlay,
7
+ usePreventScroll,
8
+ } from '@react-aria/overlays'
9
+ import styled, { css, useTheme } from 'styled-components'
10
+ import { theme } from '../../styled'
11
+ import { FocusScope } from '@react-aria/focus'
12
+ import { useDialog } from '@react-aria/dialog'
13
+ import { AriaDialogProps } from '@react-types/dialog'
14
+ import { columnSystem, COLUMN_UNIT, GUTTER_UNIT } from '@charcoal-ui/foundation'
15
+ import { unreachable } from '../../_lib'
16
+ import { maxWidth } from '@charcoal-ui/utils'
17
+ import { useMedia } from '@charcoal-ui/styled'
18
+ import { animated, useTransition, easings } from 'react-spring'
19
+ import Button, { ButtonProps } from '../Button'
20
+ import IconButton from '../IconButton'
21
+
22
+ export type ModalProps = OverlayProps &
23
+ AriaDialogProps & {
24
+ children: React.ReactNode
25
+ zIndex?: number
26
+ title: string
27
+ size?: 'S' | 'M' | 'L'
28
+ bottomSheet?: boolean | 'full'
29
+
30
+ // NOTICE: デフォルト値を与えてはならない
31
+ // (たとえば document.body をデフォルト値にすると SSR できなくなる)
32
+ portalContainer?: HTMLElement
33
+ }
34
+
35
+ const DEFAULT_Z_INDEX = 10
36
+
37
+ export default function Modal({
38
+ children,
39
+ zIndex = DEFAULT_Z_INDEX,
40
+ portalContainer,
41
+ ...props
42
+ }: ModalProps) {
43
+ const {
44
+ title,
45
+ size = 'M',
46
+ bottomSheet = false,
47
+ isDismissable,
48
+ onClose,
49
+ isOpen = false,
50
+ } = props
51
+
52
+ const ref = useRef<HTMLDivElement>(null)
53
+ const { overlayProps, underlayProps } = useOverlay(props, ref)
54
+
55
+ usePreventScroll()
56
+ const { modalProps } = useModal()
57
+
58
+ const { dialogProps, titleProps } = useDialog(props, ref)
59
+
60
+ const theme = useTheme()
61
+ const isMobile = useMedia(maxWidth(theme.breakpoint.screen1)) ?? false
62
+ const transitionEnabled = isMobile && bottomSheet !== false
63
+ const transition = useTransition(isOpen, {
64
+ from: {
65
+ transform: 'translateY(100%)',
66
+ backgroundColor: 'rgba(0, 0, 0, 0)',
67
+ },
68
+ enter: {
69
+ transform: 'translateY(0%)',
70
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
71
+ },
72
+ leave: {
73
+ transform: 'translateY(100%)',
74
+ backgroundColor: 'rgba(0, 0, 0, 0)',
75
+ },
76
+ config: transitionEnabled
77
+ ? { duration: 400, easing: easings.easeOutQuart }
78
+ : { duration: 0 },
79
+ })
80
+ const showDismiss = !isMobile || bottomSheet !== true
81
+
82
+ return transition(
83
+ ({ backgroundColor, transform }, item) =>
84
+ item && (
85
+ <OverlayContainer portalContainer={portalContainer}>
86
+ <ModalBackground
87
+ zIndex={zIndex}
88
+ {...underlayProps}
89
+ style={transitionEnabled ? { backgroundColor } : {}}
90
+ >
91
+ <FocusScope contain restoreFocus autoFocus>
92
+ <ModalDialog
93
+ ref={ref}
94
+ {...overlayProps}
95
+ {...modalProps}
96
+ {...dialogProps}
97
+ style={transitionEnabled ? { transform } : {}}
98
+ size={size}
99
+ bottomSheet={bottomSheet}
100
+ >
101
+ <ModalContext.Provider
102
+ value={{ titleProps, title, close: onClose, showDismiss }}
103
+ >
104
+ {children}
105
+ {isDismissable === true && (
106
+ <ModalCrossButton
107
+ size="S"
108
+ icon="24/Close"
109
+ onClick={onClose}
110
+ />
111
+ )}
112
+ </ModalContext.Provider>
113
+ </ModalDialog>
114
+ </FocusScope>
115
+ </ModalBackground>
116
+ </OverlayContainer>
117
+ )
118
+ )
119
+ }
120
+
121
+ const ModalContext = React.createContext<{
122
+ titleProps: React.HTMLAttributes<HTMLElement>
123
+ title: string
124
+ close?: () => void
125
+ showDismiss: boolean
126
+ }>({
127
+ titleProps: {},
128
+ title: '',
129
+ close: undefined,
130
+ showDismiss: true,
131
+ })
132
+
133
+ const ModalBackground = animated(styled.div<{ zIndex: number }>`
134
+ z-index: ${({ zIndex }) => zIndex};
135
+ position: fixed;
136
+ top: 0;
137
+ left: 0;
138
+ width: 100%;
139
+ height: 100%;
140
+
141
+ ${theme((o) => [o.bg.surface4])}
142
+ `)
143
+
144
+ const ModalDialog = animated(styled.div<{
145
+ size: 'S' | 'M' | 'L'
146
+ bottomSheet: boolean | 'full'
147
+ }>`
148
+ position: absolute;
149
+ top: 50%;
150
+ left: 50%;
151
+ transform: translate(-50%, -50%);
152
+ width: ${(p) =>
153
+ p.size === 'S'
154
+ ? columnSystem(3, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
155
+ : p.size === 'M'
156
+ ? columnSystem(4, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
157
+ : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
158
+ p.size === 'L'
159
+ ? columnSystem(6, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
160
+ : unreachable(p.size)}px;
161
+
162
+ ${theme((o) => [o.bg.background1, o.borderRadius(24)])}
163
+
164
+ @media ${({ theme }) => maxWidth(theme.breakpoint.screen1)} {
165
+ ${(p) =>
166
+ p.bottomSheet === 'full'
167
+ ? css`
168
+ top: auto;
169
+ bottom: 0;
170
+ left: 0;
171
+ transform: none;
172
+ border-radius: 0;
173
+ width: 100%;
174
+ height: 100%;
175
+ `
176
+ : p.bottomSheet
177
+ ? css`
178
+ top: auto;
179
+ bottom: 0;
180
+ left: 0;
181
+ transform: none;
182
+ border-radius: 0;
183
+ width: 100%;
184
+ `
185
+ : css`
186
+ width: calc(100% - 48px);
187
+ `}
188
+ }
189
+ `)
190
+
191
+ const ModalCrossButton = styled(IconButton)`
192
+ position: absolute;
193
+ top: 8px;
194
+ right: 8px;
195
+
196
+ ${theme((o) => [o.font.text3.hover.press])}
197
+ `
198
+
199
+ export function ModalTitle(props: React.HTMLAttributes<HTMLHeadingElement>) {
200
+ const { titleProps, title } = useContext(ModalContext)
201
+ return (
202
+ <ModalHeading {...titleProps} {...props}>
203
+ {title}
204
+ </ModalHeading>
205
+ )
206
+ }
207
+
208
+ const ModalHeading = styled.h3`
209
+ margin: 0;
210
+ font-weight: inherit;
211
+ font-size: inherit;
212
+ `
213
+
214
+ export function ModalDismissButton({ children, ...props }: ButtonProps) {
215
+ const { close, showDismiss } = useContext(ModalContext)
216
+
217
+ if (!showDismiss) {
218
+ return null
219
+ }
220
+
221
+ return (
222
+ <Button {...props} onClick={close} fixed>
223
+ {children}
224
+ </Button>
225
+ )
226
+ }
@@ -1,6 +1,6 @@
1
1
  import { createContext } from 'react'
2
2
 
3
- type SelectGroupContext = {
3
+ type MultiSelectGroupContext = {
4
4
  name: string
5
5
  selected: string[]
6
6
  disabled: boolean
@@ -9,7 +9,7 @@ type SelectGroupContext = {
9
9
  onChange: ({ value, selected }: { value: string; selected: boolean }) => void
10
10
  }
11
11
 
12
- export const SelectGroupContext = createContext<SelectGroupContext>({
12
+ export const MultiSelectGroupContext = createContext<MultiSelectGroupContext>({
13
13
  name: undefined as never,
14
14
  selected: [],
15
15
  disabled: false,
@@ -17,7 +17,7 @@ export const SelectGroupContext = createContext<SelectGroupContext>({
17
17
  hasError: false,
18
18
  onChange() {
19
19
  throw new Error(
20
- 'Cannot find `onChange()` handler. Perhaps you forgot to wrap it with `<SelectGroup />` ?'
20
+ 'Cannot find `onChange()` handler. Perhaps you forgot to wrap it with `<MultiSelectGroup />` ?'
21
21
  )
22
22
  },
23
23
  })
@@ -1,11 +1,11 @@
1
1
  import React, { useState } from 'react'
2
2
  import { Story } from '../../_lib/compat'
3
3
  import styled from 'styled-components'
4
- import { SelectGroup, default as Select } from '.'
4
+ import { MultiSelectGroup, default as MultiSelect } from '.'
5
5
 
6
6
  export default {
7
- title: 'Select',
8
- component: Select,
7
+ title: 'MultiSelect',
8
+ component: MultiSelect,
9
9
  argTypes: {
10
10
  name: {
11
11
  control: {
@@ -63,7 +63,7 @@ type Props = {
63
63
  variant?: 'default' | 'overlay'
64
64
  }
65
65
 
66
- const StyledSelectGroup = styled(SelectGroup)`
66
+ const StyledMultiSelectGroup = styled(MultiSelectGroup)`
67
67
  display: grid;
68
68
  grid-template-columns: 1fr;
69
69
  gap: 8px;
@@ -81,7 +81,7 @@ const Template: Story<Props> = ({
81
81
  variant,
82
82
  }) => {
83
83
  return (
84
- <StyledSelectGroup
84
+ <StyledMultiSelectGroup
85
85
  {...{
86
86
  name,
87
87
  ariaLabel,
@@ -94,16 +94,16 @@ const Template: Story<Props> = ({
94
94
  selected={selected ? ['選択肢1', '選択肢3'] : []}
95
95
  >
96
96
  {[1, 2, 3, 4].map((idx) => (
97
- <Select
97
+ <MultiSelect
98
98
  value={`選択肢${idx}`}
99
99
  forceChecked={firstOptionForceChecked && idx === 1}
100
100
  variant={variant}
101
101
  key={idx}
102
102
  >
103
103
  選択肢{idx}
104
- </Select>
104
+ </MultiSelect>
105
105
  ))}
106
- </StyledSelectGroup>
106
+ </StyledMultiSelectGroup>
107
107
  )
108
108
  }
109
109
 
@@ -134,13 +134,17 @@ export const Playground: Story<PlaygroundProps> = (props) => {
134
134
  const [selected, setSelected] = useState<string[]>([])
135
135
 
136
136
  return (
137
- <StyledSelectGroup {...props} selected={selected} onChange={setSelected}>
137
+ <StyledMultiSelectGroup
138
+ {...props}
139
+ selected={selected}
140
+ onChange={setSelected}
141
+ >
138
142
  {[1, 2, 3, 4].map((idx) => (
139
- <Select value={`選択肢${idx}`} variant={props.variant} key={idx}>
143
+ <MultiSelect value={`選択肢${idx}`} variant={props.variant} key={idx}>
140
144
  選択肢{idx}
141
- </Select>
145
+ </MultiSelect>
142
146
  ))}
143
- </StyledSelectGroup>
147
+ </StyledMultiSelectGroup>
144
148
  )
145
149
  }
146
150
  Playground.args = {
@@ -1,23 +1,23 @@
1
1
  import { fireEvent, render, screen } from '@testing-library/react'
2
2
  import React from 'react'
3
3
  import { ThemeProvider } from 'styled-components'
4
- import { default as Select, SelectGroup } from '.'
4
+ import { default as MultiSelect, MultiSelectGroup } from '.'
5
5
  import { light } from '@charcoal-ui/theme'
6
6
 
7
- describe('Select', () => {
7
+ describe('MultiSelect', () => {
8
8
  describe('in development mode', () => {
9
9
  beforeEach(() => {
10
10
  process.env.NODE_ENV = 'development'
11
11
  })
12
12
 
13
- describe('when `<Select />` is used without `<SelectGroup />`', () => {
13
+ describe('when `<MultiSelect />` is used without `<MultiSelectGroup />`', () => {
14
14
  beforeEach(() => {
15
15
  // eslint-disable-next-line no-console
16
16
  console.error = jest.fn()
17
17
 
18
18
  render(
19
19
  <ThemeProvider theme={light}>
20
- <Select value="a" />
20
+ <MultiSelect value="a" />
21
21
  </ThemeProvider>
22
22
  )
23
23
  })
@@ -26,7 +26,7 @@ describe('Select', () => {
26
26
  // eslint-disable-next-line no-console
27
27
  expect(console.error).toHaveBeenCalledWith(
28
28
  expect.stringMatching(
29
- /Perhaps you forgot to wrap with <SelectGroup>/u
29
+ /Perhaps you forgot to wrap with <MultiSelectGroup>/u
30
30
  )
31
31
  )
32
32
  })
@@ -254,28 +254,28 @@ const TestComponent = ({
254
254
  }) => {
255
255
  return (
256
256
  <ThemeProvider theme={light}>
257
- <SelectGroup
257
+ <MultiSelectGroup
258
258
  name="defaultName"
259
259
  ariaLabel="defaultAriaLabel"
260
260
  disabled={parentDisabled}
261
261
  onChange={parentOnChange}
262
262
  {...{ selected, readonly, hasError }}
263
263
  >
264
- <Select
264
+ <MultiSelect
265
265
  value="option1"
266
266
  disabled={firstOptionDisabled}
267
267
  forceChecked={firstOptionForceChecked}
268
268
  onChange={childOnChange}
269
269
  >
270
270
  Option 1
271
- </Select>
272
- <Select value="option2" onChange={childOnChange}>
271
+ </MultiSelect>
272
+ <MultiSelect value="option2" onChange={childOnChange}>
273
273
  Option 2
274
- </Select>
275
- <Select value="option3" onChange={childOnChange}>
274
+ </MultiSelect>
275
+ <MultiSelect value="option3" onChange={childOnChange}>
276
276
  Option 3
277
- </Select>
278
- </SelectGroup>
277
+ </MultiSelect>
278
+ </MultiSelectGroup>
279
279
  </ThemeProvider>
280
280
  )
281
281
  }
@@ -4,9 +4,9 @@ import warning from 'warning'
4
4
  import { theme } from '../../styled'
5
5
  import { disabledSelector, px } from '@charcoal-ui/utils'
6
6
 
7
- import { SelectGroupContext } from './context'
7
+ import { MultiSelectGroupContext } from './context'
8
8
 
9
- export type SelectProps = React.PropsWithChildren<{
9
+ export type MultiSelectProps = React.PropsWithChildren<{
10
10
  value: string
11
11
  forceChecked?: boolean
12
12
  disabled?: boolean
@@ -14,14 +14,14 @@ export type SelectProps = React.PropsWithChildren<{
14
14
  onChange?: (payload: { value: string; selected: boolean }) => void
15
15
  }>
16
16
 
17
- export default function Select({
17
+ export default function MultiSelect({
18
18
  value,
19
19
  forceChecked = false,
20
20
  disabled = false,
21
21
  onChange,
22
22
  variant = 'default',
23
23
  children,
24
- }: SelectProps) {
24
+ }: MultiSelectProps) {
25
25
  const {
26
26
  name,
27
27
  selected,
@@ -29,12 +29,12 @@ export default function Select({
29
29
  readonly,
30
30
  hasError,
31
31
  onChange: parentOnChange,
32
- } = useContext(SelectGroupContext)
32
+ } = useContext(MultiSelectGroupContext)
33
33
 
34
34
  warning(
35
35
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
36
36
  name !== undefined,
37
- `"name" is not Provided for <Select>. Perhaps you forgot to wrap with <SelectGroup> ?`
37
+ `"name" is not Provided for <MultiSelect>. Perhaps you forgot to wrap with <MultiSelectGroup> ?`
38
38
  )
39
39
 
40
40
  const isSelected = selected.includes(value) || forceChecked
@@ -52,8 +52,8 @@ export default function Select({
52
52
  )
53
53
 
54
54
  return (
55
- <SelectRoot aria-disabled={isDisabled}>
56
- <SelectInput
55
+ <MultiSelectRoot aria-disabled={isDisabled}>
56
+ <MultiSelectInput
57
57
  {...{
58
58
  name,
59
59
  value,
@@ -65,19 +65,19 @@ export default function Select({
65
65
  overlay={variant === 'overlay'}
66
66
  aria-invalid={hasError}
67
67
  />
68
- <SelectInputOverlay
68
+ <MultiSelectInputOverlay
69
69
  overlay={variant === 'overlay'}
70
70
  hasError={hasError}
71
71
  aria-hidden={true}
72
72
  >
73
73
  <pixiv-icon name="24/Check" unsafe-non-guideline-scale={16 / 24} />
74
- </SelectInputOverlay>
75
- {Boolean(children) && <SelectLabel>{children}</SelectLabel>}
76
- </SelectRoot>
74
+ </MultiSelectInputOverlay>
75
+ {Boolean(children) && <MultiSelectLabel>{children}</MultiSelectLabel>}
76
+ </MultiSelectRoot>
77
77
  )
78
78
  }
79
79
 
80
- const SelectRoot = styled.label`
80
+ const MultiSelectRoot = styled.label`
81
81
  display: grid;
82
82
  grid-template-columns: auto 1fr;
83
83
  align-items: center;
@@ -90,13 +90,13 @@ const SelectRoot = styled.label`
90
90
  ${theme((o) => o.disabled)}
91
91
  `
92
92
 
93
- const SelectLabel = styled.div`
93
+ const MultiSelectLabel = styled.div`
94
94
  display: flex;
95
95
  align-items: center;
96
96
  ${theme((o) => [o.typography(14), o.font.text1])}
97
97
  `
98
98
 
99
- const SelectInput = styled.input.attrs({ type: 'checkbox' })<{
99
+ const MultiSelectInput = styled.input.attrs({ type: 'checkbox' })<{
100
100
  hasError: boolean
101
101
  overlay: boolean
102
102
  }>`
@@ -121,7 +121,10 @@ const SelectInput = styled.input.attrs({ type: 'checkbox' })<{
121
121
  }
122
122
  `
123
123
 
124
- const SelectInputOverlay = styled.div<{ overlay: boolean; hasError: boolean }>`
124
+ const MultiSelectInputOverlay = styled.div<{
125
+ overlay: boolean
126
+ hasError: boolean
127
+ }>`
125
128
  position: absolute;
126
129
  top: -2px;
127
130
  left: -2px;
@@ -148,7 +151,7 @@ const SelectInputOverlay = styled.div<{ overlay: boolean; hasError: boolean }>`
148
151
  `}
149
152
  `
150
153
 
151
- export type SelectGroupProps = React.PropsWithChildren<{
154
+ export type MultiSelectGroupProps = React.PropsWithChildren<{
152
155
  className?: string
153
156
  name: string
154
157
  ariaLabel: string
@@ -159,7 +162,7 @@ export type SelectGroupProps = React.PropsWithChildren<{
159
162
  hasError?: boolean
160
163
  }>
161
164
 
162
- export function SelectGroup({
165
+ export function MultiSelectGroup({
163
166
  className,
164
167
  name,
165
168
  ariaLabel,
@@ -169,7 +172,7 @@ export function SelectGroup({
169
172
  readonly = false,
170
173
  hasError = false,
171
174
  children,
172
- }: SelectGroupProps) {
175
+ }: MultiSelectGroupProps) {
173
176
  const handleChange = useCallback(
174
177
  (payload: { value: string; selected: boolean }) => {
175
178
  const index = selected.indexOf(payload.value)
@@ -188,7 +191,7 @@ export function SelectGroup({
188
191
  )
189
192
 
190
193
  return (
191
- <SelectGroupContext.Provider
194
+ <MultiSelectGroupContext.Provider
192
195
  value={{
193
196
  name,
194
197
  selected: Array.from(new Set(selected)),
@@ -205,6 +208,6 @@ export function SelectGroup({
205
208
  >
206
209
  {children}
207
210
  </div>
208
- </SelectGroupContext.Provider>
211
+ </MultiSelectGroupContext.Provider>
209
212
  )
210
213
  }
@@ -0,0 +1,22 @@
1
+ import React, { createContext, useContext } from 'react'
2
+ import { RadioGroupState } from 'react-stately'
3
+
4
+ const RadioContext = createContext<RadioGroupState | null>(null)
5
+
6
+ type RadioProviderProps = React.PropsWithChildren<{
7
+ value: RadioGroupState
8
+ }>
9
+ export const RadioProvider: React.FC<RadioProviderProps> = ({
10
+ value,
11
+ children,
12
+ }) => {
13
+ return <RadioContext.Provider value={value}>{children}</RadioContext.Provider>
14
+ }
15
+ export const useRadioContext = () => {
16
+ const state = useContext(RadioContext)
17
+
18
+ if (state === null)
19
+ throw new Error('`<RadioProvider>` is not likely mounted.')
20
+
21
+ return state
22
+ }
@@ -0,0 +1,36 @@
1
+ import { action } from '@storybook/addon-actions'
2
+ import React from 'react'
3
+ import SegmentedControl, { SegmentedControlProps } from '.'
4
+ import { Story } from '../../_lib/compat'
5
+
6
+ export default {
7
+ title: 'SegmentedControl',
8
+ component: SegmentedControl,
9
+ }
10
+
11
+ export const StringSegments: Story<SegmentedControlProps> = (props) => {
12
+ return <SegmentedControl {...props} onChange={action('change')} />
13
+ }
14
+
15
+ StringSegments.args = {
16
+ data: ['option1', 'option2', 'option3'],
17
+ disabled: false,
18
+ readonly: false,
19
+ required: false,
20
+ }
21
+
22
+ export const ObjectSegments: Story<SegmentedControlProps> = (props) => {
23
+ return <SegmentedControl {...props} onChange={action('change')} />
24
+ }
25
+
26
+ ObjectSegments.args = {
27
+ data: [
28
+ { label: '選択肢1', value: 'option1' },
29
+ { label: '選択肢2', value: 'option2' },
30
+ { label: '選択肢3', value: 'option3' },
31
+ { label: '選択肢4', value: 'option4', disabled: true },
32
+ ],
33
+ disabled: false,
34
+ readonly: false,
35
+ required: false,
36
+ }