@charcoal-ui/react 2.0.0-alpha.9 → 2.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/components/Checkbox/index.d.ts +21 -0
- package/dist/components/Checkbox/index.d.ts.map +1 -0
- package/dist/components/Checkbox/index.story.d.ts +16 -0
- package/dist/components/Checkbox/index.story.d.ts.map +1 -0
- package/dist/components/DropdownSelector/Listbox.d.ts +10 -0
- package/dist/components/DropdownSelector/Listbox.d.ts.map +1 -0
- package/dist/components/DropdownSelector/Popover.d.ts +10 -0
- package/dist/components/DropdownSelector/Popover.d.ts.map +1 -0
- package/dist/components/DropdownSelector/index.d.ts +32 -0
- package/dist/components/DropdownSelector/index.d.ts.map +1 -0
- package/dist/components/DropdownSelector/index.story.d.ts +22 -0
- package/dist/components/DropdownSelector/index.story.d.ts.map +1 -0
- package/dist/components/Icon/index.story.d.ts +1 -1
- package/dist/components/LoadingSpinner/index.d.ts +15 -0
- package/dist/components/LoadingSpinner/index.d.ts.map +1 -0
- package/dist/components/LoadingSpinner/index.story.d.ts +10 -0
- package/dist/components/LoadingSpinner/index.story.d.ts.map +1 -0
- package/dist/components/Modal/ModalPlumbing.d.ts +5 -0
- package/dist/components/Modal/ModalPlumbing.d.ts.map +1 -0
- package/dist/components/Modal/index.d.ts +16 -0
- package/dist/components/Modal/index.d.ts.map +1 -0
- package/dist/components/Modal/index.story.d.ts +33 -0
- package/dist/components/Modal/index.story.d.ts.map +1 -0
- package/dist/components/{Select → MultiSelect}/context.d.ts +2 -2
- package/dist/components/MultiSelect/context.d.ts.map +1 -0
- package/dist/components/MultiSelect/index.d.ts +24 -0
- package/dist/components/MultiSelect/index.d.ts.map +1 -0
- package/dist/components/{Select → MultiSelect}/index.story.d.ts +2 -2
- package/dist/components/MultiSelect/index.story.d.ts.map +1 -0
- package/dist/components/{Select → MultiSelect}/index.test.d.ts +0 -0
- package/dist/components/MultiSelect/index.test.d.ts.map +1 -0
- package/dist/components/SegmentedControl/RadioGroupContext.d.ts +9 -0
- package/dist/components/SegmentedControl/RadioGroupContext.d.ts.map +1 -0
- package/dist/components/SegmentedControl/index.d.ts +20 -0
- package/dist/components/SegmentedControl/index.d.ts.map +1 -0
- package/dist/components/SegmentedControl/index.story.d.ts +11 -0
- package/dist/components/SegmentedControl/index.story.d.ts.map +1 -0
- package/dist/components/TextField/index.d.ts +6 -3
- package/dist/components/TextField/index.d.ts.map +1 -1
- package/dist/components/TextField/index.story.d.ts +1 -0
- package/dist/components/TextField/index.story.d.ts.map +1 -1
- package/dist/core/SSRProvider.d.ts +2 -0
- package/dist/core/SSRProvider.d.ts.map +1 -0
- package/dist/index.cjs +22686 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.modern.js +21612 -45
- package/dist/index.modern.js.map +1 -1
- package/dist/index.module.js +22641 -1
- package/dist/index.module.js.map +1 -1
- package/package.json +15 -8
- package/src/components/Checkbox/index.story.tsx +64 -0
- package/src/components/Checkbox/index.tsx +122 -0
- package/src/components/DropdownSelector/Listbox.tsx +127 -0
- package/src/components/DropdownSelector/Popover.tsx +46 -0
- package/src/components/DropdownSelector/index.story.tsx +134 -0
- package/src/components/DropdownSelector/index.tsx +214 -0
- package/src/components/FieldLabel/index.tsx +1 -1
- package/src/components/LoadingSpinner/index.story.tsx +52 -0
- package/src/components/LoadingSpinner/index.tsx +87 -0
- package/src/components/Modal/ModalPlumbing.tsx +47 -0
- package/src/components/Modal/index.story.tsx +195 -0
- package/src/components/Modal/index.tsx +226 -0
- package/src/components/{Select → MultiSelect}/context.ts +3 -3
- package/src/components/{Select → MultiSelect}/index.story.tsx +16 -12
- package/src/components/{Select → MultiSelect}/index.test.tsx +13 -13
- package/src/components/{Select → MultiSelect}/index.tsx +24 -21
- package/src/components/SegmentedControl/RadioGroupContext.tsx +22 -0
- package/src/components/SegmentedControl/index.story.tsx +36 -0
- package/src/components/SegmentedControl/index.tsx +157 -0
- package/src/components/TextField/index.story.tsx +31 -16
- package/src/components/TextField/index.tsx +53 -34
- package/src/components/a11y.test.tsx +11 -0
- package/src/core/SSRProvider.tsx +1 -0
- package/src/index.ts +22 -5
- package/src/styled.ts +1 -1
- package/dist/components/Select/context.d.ts.map +0 -1
- package/dist/components/Select/index.d.ts +0 -24
- package/dist/components/Select/index.d.ts.map +0 -1
- package/dist/components/Select/index.story.d.ts.map +0 -1
- package/dist/components/Select/index.test.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charcoal-ui/react",
|
|
3
|
-
"version": "2.0.0-
|
|
3
|
+
"version": "2.0.0-rc.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"source": "./src/index.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"sideEffects": false,
|
|
15
15
|
"scripts": {
|
|
16
|
-
"build": "microbundle -f modern,esm,cjs --tsconfig tsconfig.build.json --jsx React.createElement --jsxFragment React.Fragment",
|
|
16
|
+
"build": "microbundle --compress=false -f modern,esm,cjs --tsconfig tsconfig.build.json --jsx React.createElement --jsxFragment React.Fragment",
|
|
17
17
|
"typecheck": "tsc --noEmit --project tsconfig.build.json",
|
|
18
18
|
"clean": "rimraf dist"
|
|
19
19
|
},
|
|
@@ -48,16 +48,23 @@
|
|
|
48
48
|
"typescript": "^4.5.5"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@charcoal-ui/icons": "^
|
|
52
|
-
"@charcoal-ui/styled": "^
|
|
53
|
-
"@charcoal-ui/theme": "^2.0.0-
|
|
54
|
-
"@charcoal-ui/utils": "^
|
|
51
|
+
"@charcoal-ui/icons": "^2.0.0-rc.0",
|
|
52
|
+
"@charcoal-ui/styled": "^2.0.0-rc.0",
|
|
53
|
+
"@charcoal-ui/theme": "^2.0.0-rc.0",
|
|
54
|
+
"@charcoal-ui/utils": "^2.0.0-rc.0",
|
|
55
|
+
"@react-aria/button": "^3.6.2",
|
|
55
56
|
"@react-aria/checkbox": "^3.2.3",
|
|
57
|
+
"@react-aria/dialog": "^3.2.1",
|
|
58
|
+
"@react-aria/focus": "^3.6.1",
|
|
59
|
+
"@react-aria/overlays": "^3.9.1",
|
|
60
|
+
"@react-aria/radio": "^3.4.0",
|
|
61
|
+
"@react-aria/select": "^3.8.2",
|
|
62
|
+
"@react-aria/ssr": "^3.3.0",
|
|
56
63
|
"@react-aria/switch": "^3.1.3",
|
|
57
64
|
"@react-aria/textfield": "^3.5.0",
|
|
58
65
|
"@react-aria/visually-hidden": "^3.2.3",
|
|
59
66
|
"polished": "^4.1.4",
|
|
60
|
-
"react-stately": "^3.
|
|
67
|
+
"react-stately": "^3.19.0",
|
|
61
68
|
"warning": "^4.0.3"
|
|
62
69
|
},
|
|
63
70
|
"peerDependencies": {
|
|
@@ -76,5 +83,5 @@
|
|
|
76
83
|
"url": "https://github.com/pixiv/charcoal.git",
|
|
77
84
|
"directory": "packages/react"
|
|
78
85
|
},
|
|
79
|
-
"gitHead": "
|
|
86
|
+
"gitHead": "01429610d6100445690d4ca8ce4b1d0fd57c00b2"
|
|
80
87
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import Checkbox from '.'
|
|
4
|
+
import { Story } from '../../_lib/compat'
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: 'Checkbox',
|
|
8
|
+
component: Checkbox,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
type Props = {
|
|
12
|
+
checked: boolean
|
|
13
|
+
defaultChecked: boolean
|
|
14
|
+
disabled: boolean
|
|
15
|
+
readonly: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Labelled: Story<Props> = (props) => {
|
|
19
|
+
return (
|
|
20
|
+
<div>
|
|
21
|
+
<Checkbox
|
|
22
|
+
{...props}
|
|
23
|
+
name="labelled"
|
|
24
|
+
label="label"
|
|
25
|
+
onBlur={action('blur')}
|
|
26
|
+
onClick={action('click')}
|
|
27
|
+
onChange={action('change')}
|
|
28
|
+
onFocus={action('focus')}
|
|
29
|
+
>
|
|
30
|
+
同意する
|
|
31
|
+
</Checkbox>
|
|
32
|
+
</div>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
Labelled.args = {
|
|
37
|
+
checked: false,
|
|
38
|
+
defaultChecked: false,
|
|
39
|
+
disabled: false,
|
|
40
|
+
readonly: false,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const Unlabelled: Story<Props> = (props) => {
|
|
44
|
+
return (
|
|
45
|
+
<div>
|
|
46
|
+
<Checkbox
|
|
47
|
+
{...props}
|
|
48
|
+
name="unlabelled"
|
|
49
|
+
label="label"
|
|
50
|
+
onBlur={action('blur')}
|
|
51
|
+
onClick={action('click')}
|
|
52
|
+
onChange={action('change')}
|
|
53
|
+
onFocus={action('focus')}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
Unlabelled.args = {
|
|
60
|
+
checked: false,
|
|
61
|
+
defaultChecked: false,
|
|
62
|
+
disabled: false,
|
|
63
|
+
readonly: false,
|
|
64
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React, { forwardRef, memo, useMemo } from 'react'
|
|
2
|
+
import styled, { css } from 'styled-components'
|
|
3
|
+
import { useCheckbox } from '@react-aria/checkbox'
|
|
4
|
+
import { useObjectRef } from '@react-aria/utils'
|
|
5
|
+
import { useToggleState } from 'react-stately'
|
|
6
|
+
import { disabledSelector, px } from '@charcoal-ui/utils'
|
|
7
|
+
import { theme } from '../../styled'
|
|
8
|
+
|
|
9
|
+
import type { AriaCheckboxProps } from '@react-types/checkbox'
|
|
10
|
+
import Icon from '../Icon'
|
|
11
|
+
|
|
12
|
+
type CheckboxLabelProps =
|
|
13
|
+
| {
|
|
14
|
+
children: React.ReactNode
|
|
15
|
+
}
|
|
16
|
+
| {
|
|
17
|
+
label: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type CheckboxProps = CheckboxLabelProps & {
|
|
21
|
+
readonly id?: string
|
|
22
|
+
readonly name?: string
|
|
23
|
+
|
|
24
|
+
readonly checked?: boolean
|
|
25
|
+
readonly defaultChecked?: boolean
|
|
26
|
+
readonly disabled?: boolean
|
|
27
|
+
readonly readonly?: boolean
|
|
28
|
+
|
|
29
|
+
readonly onClick?: () => void
|
|
30
|
+
readonly onChange?: (isSelected: boolean) => void
|
|
31
|
+
readonly onBlur?: () => void
|
|
32
|
+
readonly onFocus?: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
|
|
36
|
+
function CheckboxInner(props, ref) {
|
|
37
|
+
const ariaCheckboxProps = useMemo<AriaCheckboxProps>(
|
|
38
|
+
() => ({
|
|
39
|
+
...props,
|
|
40
|
+
isSelected: props.checked,
|
|
41
|
+
defaultSelected: props.defaultChecked,
|
|
42
|
+
// children がいない場合は aria-label をつけないといけない
|
|
43
|
+
'aria-label': 'children' in props ? undefined : props.label,
|
|
44
|
+
isDisabled: props.disabled,
|
|
45
|
+
}),
|
|
46
|
+
[props]
|
|
47
|
+
)
|
|
48
|
+
const state = useToggleState(ariaCheckboxProps)
|
|
49
|
+
const objectRef = useObjectRef(ref)
|
|
50
|
+
|
|
51
|
+
const { inputProps } = useCheckbox(ariaCheckboxProps, state, objectRef)
|
|
52
|
+
const isDisabled = (props.disabled ?? false) || (props.readonly ?? false)
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<InputRoot aria-disabled={isDisabled}>
|
|
56
|
+
<CheckboxInput type="checkbox" {...inputProps} />
|
|
57
|
+
<CheckboxInputOverlay aria-hidden={true} checked={inputProps.checked}>
|
|
58
|
+
<Icon name="24/Check" unsafeNonGuidelineScale={2 / 3} />
|
|
59
|
+
</CheckboxInputOverlay>
|
|
60
|
+
|
|
61
|
+
{'children' in props && <InputLabel>{props.children}</InputLabel>}
|
|
62
|
+
</InputRoot>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
export default memo(Checkbox)
|
|
68
|
+
|
|
69
|
+
const hiddenCss = css`
|
|
70
|
+
visibility: hidden;
|
|
71
|
+
`
|
|
72
|
+
|
|
73
|
+
const InputRoot = styled.label`
|
|
74
|
+
position: relative;
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
${disabledSelector} {
|
|
79
|
+
cursor: default;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
gap: ${({ theme }) => px(theme.spacing[4])};
|
|
83
|
+
${theme((o) => [o.disabled])}
|
|
84
|
+
`
|
|
85
|
+
|
|
86
|
+
const CheckboxInput = styled.input`
|
|
87
|
+
&[type='checkbox'] {
|
|
88
|
+
appearance: none;
|
|
89
|
+
display: block;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
margin: 0;
|
|
92
|
+
width: 20px;
|
|
93
|
+
height: 20px;
|
|
94
|
+
|
|
95
|
+
&:checked {
|
|
96
|
+
${theme((o) => o.bg.brand.hover.press)}
|
|
97
|
+
}
|
|
98
|
+
&:not(:checked) {
|
|
99
|
+
border-width: 2px;
|
|
100
|
+
border-style: solid;
|
|
101
|
+
border-color: ${({ theme }) => theme.color.text4};
|
|
102
|
+
}
|
|
103
|
+
${theme((o) => [o.outline.default.focus, o.borderRadius(4)])}
|
|
104
|
+
}
|
|
105
|
+
`
|
|
106
|
+
const CheckboxInputOverlay = styled.div<{ checked?: boolean }>`
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: -2px;
|
|
109
|
+
left: -2px;
|
|
110
|
+
box-sizing: border-box;
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: center;
|
|
114
|
+
|
|
115
|
+
${theme((o) => [o.width.px(24), o.height.px(24), o.font.text5])}
|
|
116
|
+
|
|
117
|
+
${({ checked }) => checked !== true && hiddenCss};
|
|
118
|
+
`
|
|
119
|
+
|
|
120
|
+
const InputLabel = styled.div`
|
|
121
|
+
${theme((o) => [o.typography(14)])}
|
|
122
|
+
`
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React, { memo, useRef, Fragment, useMemo } from 'react'
|
|
2
|
+
import styled, { css } from 'styled-components'
|
|
3
|
+
import { ListProps, ListState } from 'react-stately'
|
|
4
|
+
import { useListBox, useOption } from '@react-aria/listbox'
|
|
5
|
+
import { mergeProps } from '@react-aria/utils'
|
|
6
|
+
import { useFocusRing } from '@react-aria/focus'
|
|
7
|
+
import { px } from '@charcoal-ui/utils'
|
|
8
|
+
import Icon from '../Icon'
|
|
9
|
+
import { theme } from '../../styled'
|
|
10
|
+
|
|
11
|
+
import type { Node } from '@react-types/shared'
|
|
12
|
+
|
|
13
|
+
type ListMode = 'default' | 'separator'
|
|
14
|
+
export type ListboxProps<T> = Omit<ListProps<T>, 'children'> & {
|
|
15
|
+
state: ListState<T>
|
|
16
|
+
mode?: ListMode
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Listbox = <T,>({
|
|
20
|
+
state,
|
|
21
|
+
mode = 'default',
|
|
22
|
+
...props
|
|
23
|
+
}: ListboxProps<T>) => {
|
|
24
|
+
const ref = useRef<HTMLUListElement>(null)
|
|
25
|
+
|
|
26
|
+
const { listBoxProps } = useListBox(props, state, ref)
|
|
27
|
+
const collection = useMemo(
|
|
28
|
+
() =>
|
|
29
|
+
[...state.collection].map((node, index, self) => ({
|
|
30
|
+
node,
|
|
31
|
+
first: index === 0,
|
|
32
|
+
last: index === self.length - 1,
|
|
33
|
+
})),
|
|
34
|
+
[state.collection]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<ListboxRoot ref={ref} {...listBoxProps}>
|
|
39
|
+
{collection.map(({ node, last }) => (
|
|
40
|
+
<Fragment key={node.key}>
|
|
41
|
+
<Option item={node} state={state} mode={mode} />
|
|
42
|
+
{!last && mode === 'separator' && <Divider />}
|
|
43
|
+
</Fragment>
|
|
44
|
+
))}
|
|
45
|
+
</ListboxRoot>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
export default memo(Listbox)
|
|
49
|
+
|
|
50
|
+
const ListboxRoot = styled.ul`
|
|
51
|
+
padding-left: 0;
|
|
52
|
+
margin: 0;
|
|
53
|
+
box-sizing: border-box;
|
|
54
|
+
list-style: none;
|
|
55
|
+
|
|
56
|
+
${theme((o) => [
|
|
57
|
+
o.bg.background1,
|
|
58
|
+
o.border.default,
|
|
59
|
+
o.borderRadius(8),
|
|
60
|
+
o.outline.default.focus,
|
|
61
|
+
])}
|
|
62
|
+
`
|
|
63
|
+
|
|
64
|
+
const Divider = styled.div.attrs({ role: 'separator' })`
|
|
65
|
+
display: flex;
|
|
66
|
+
${theme((o) => [o.padding.horizontal(8)])}
|
|
67
|
+
|
|
68
|
+
&:before {
|
|
69
|
+
content: '';
|
|
70
|
+
display: block;
|
|
71
|
+
width: 100%;
|
|
72
|
+
height: 1px;
|
|
73
|
+
background: #00000014;
|
|
74
|
+
}
|
|
75
|
+
`
|
|
76
|
+
|
|
77
|
+
type OptionProps<T> = {
|
|
78
|
+
item: Node<T>
|
|
79
|
+
state: ListState<T>
|
|
80
|
+
mode?: ListMode
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const Option = <T,>({ item, state, mode }: OptionProps<T>) => {
|
|
84
|
+
const ref = useRef<HTMLLIElement>(null)
|
|
85
|
+
|
|
86
|
+
const { optionProps, isSelected } = useOption(item, state, ref)
|
|
87
|
+
const { focusProps } = useFocusRing()
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<OptionRoot {...mergeProps(optionProps, focusProps)} ref={ref} mode={mode}>
|
|
91
|
+
<OptionCheckIcon name="16/Check" isSelected={isSelected} />
|
|
92
|
+
<OptionText>{item.rendered}</OptionText>
|
|
93
|
+
</OptionRoot>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const OptionRoot = styled.li<{ mode?: ListMode }>`
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
gap: ${({ theme }) => px(theme.spacing[4])};
|
|
101
|
+
height: 40px;
|
|
102
|
+
cursor: pointer;
|
|
103
|
+
outline: none;
|
|
104
|
+
|
|
105
|
+
${({ mode }) =>
|
|
106
|
+
theme((o) => [
|
|
107
|
+
o.padding.horizontal(8),
|
|
108
|
+
mode === 'separator' && o.padding.vertical(4),
|
|
109
|
+
])}
|
|
110
|
+
|
|
111
|
+
&:focus {
|
|
112
|
+
${theme((o) => [o.bg.surface3])}
|
|
113
|
+
}
|
|
114
|
+
`
|
|
115
|
+
const OptionCheckIcon = styled(Icon)<{ isSelected: boolean }>`
|
|
116
|
+
visibility: hidden;
|
|
117
|
+
|
|
118
|
+
${({ isSelected }) =>
|
|
119
|
+
isSelected &&
|
|
120
|
+
css`
|
|
121
|
+
visibility: visible;
|
|
122
|
+
`}
|
|
123
|
+
`
|
|
124
|
+
const OptionText = styled.span`
|
|
125
|
+
display: block;
|
|
126
|
+
${theme((o) => [o.typography(14)])}
|
|
127
|
+
`
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { FocusScope } from '@react-aria/focus'
|
|
2
|
+
import { DismissButton, useOverlay } from '@react-aria/overlays'
|
|
3
|
+
import React, {
|
|
4
|
+
FC,
|
|
5
|
+
useRef,
|
|
6
|
+
useMemo,
|
|
7
|
+
PropsWithChildren,
|
|
8
|
+
memo,
|
|
9
|
+
CSSProperties,
|
|
10
|
+
} from 'react'
|
|
11
|
+
import { mergeProps } from '@react-aria/utils'
|
|
12
|
+
|
|
13
|
+
type Props = PropsWithChildren<{
|
|
14
|
+
open?: boolean
|
|
15
|
+
onClose?: () => void
|
|
16
|
+
style?: CSSProperties
|
|
17
|
+
className?: string
|
|
18
|
+
}>
|
|
19
|
+
|
|
20
|
+
const Popover: FC<Props> = ({ open, onClose, children, ...props }) => {
|
|
21
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
22
|
+
|
|
23
|
+
const { overlayProps } = useOverlay(
|
|
24
|
+
useMemo(
|
|
25
|
+
() => ({
|
|
26
|
+
isOpen: open,
|
|
27
|
+
onClose,
|
|
28
|
+
shouldCloseOnBlur: true,
|
|
29
|
+
isDismissable: true,
|
|
30
|
+
}),
|
|
31
|
+
[onClose, open]
|
|
32
|
+
),
|
|
33
|
+
ref
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<FocusScope restoreFocus>
|
|
38
|
+
<div {...mergeProps(overlayProps, props)} ref={ref}>
|
|
39
|
+
{children}
|
|
40
|
+
<DismissButton onDismiss={onClose} />
|
|
41
|
+
</div>
|
|
42
|
+
</FocusScope>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default memo(Popover)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { action } from '@storybook/addon-actions'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import DropdownSelector, {
|
|
4
|
+
DropdownSelectorItem,
|
|
5
|
+
DropdownSelectorProps,
|
|
6
|
+
} from '.'
|
|
7
|
+
import { Story } from '../../_lib/compat'
|
|
8
|
+
import Clickable from '../Clickable'
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
title: 'DropdownSelector',
|
|
12
|
+
component: DropdownSelector,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Props = Omit<
|
|
16
|
+
DropdownSelectorProps,
|
|
17
|
+
'subLabel' | 'children' | 'onOpenChange'
|
|
18
|
+
>
|
|
19
|
+
export const Default: Story<Props> = (props) => {
|
|
20
|
+
return (
|
|
21
|
+
<DropdownSelector
|
|
22
|
+
{...props}
|
|
23
|
+
placeholder={props.placeholder ?? 'Drop Down menu'}
|
|
24
|
+
onChange={action('change')}
|
|
25
|
+
onOpenChange={action('open')}
|
|
26
|
+
>
|
|
27
|
+
<DropdownSelectorItem key="k:1">選択肢1</DropdownSelectorItem>
|
|
28
|
+
<DropdownSelectorItem key="k:2">選択肢2</DropdownSelectorItem>
|
|
29
|
+
<DropdownSelectorItem key="k:3">選択肢3</DropdownSelectorItem>
|
|
30
|
+
</DropdownSelector>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
Default.args = {
|
|
34
|
+
label: 'Label',
|
|
35
|
+
requiredText: '*必須',
|
|
36
|
+
required: false,
|
|
37
|
+
showLabel: false,
|
|
38
|
+
invalid: false,
|
|
39
|
+
disabled: false,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type HasLabelProps = {
|
|
43
|
+
disabled?: boolean
|
|
44
|
+
}
|
|
45
|
+
export const HasLabel: Story<HasLabelProps> = ({ disabled }) => {
|
|
46
|
+
const defaultProps: Omit<DropdownSelectorProps, 'children'> = {
|
|
47
|
+
required: true,
|
|
48
|
+
showLabel: true,
|
|
49
|
+
label: 'Label',
|
|
50
|
+
requiredText: '*必須',
|
|
51
|
+
subLabel: <Clickable onClick={action('label-click')}>Text Link</Clickable>,
|
|
52
|
+
assertiveText: 'Hint',
|
|
53
|
+
}
|
|
54
|
+
return (
|
|
55
|
+
<DropdownSelector
|
|
56
|
+
{...defaultProps}
|
|
57
|
+
disabled={disabled}
|
|
58
|
+
placeholder={'Drop Down menu'}
|
|
59
|
+
onChange={action('change')}
|
|
60
|
+
onOpenChange={action('open')}
|
|
61
|
+
>
|
|
62
|
+
<DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
|
|
63
|
+
<DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
|
|
64
|
+
<DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
|
|
65
|
+
</DropdownSelector>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
HasLabel.args = {
|
|
70
|
+
disabled: false,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
type WithSeparatorProps = {
|
|
74
|
+
mode: 'default' | 'separator'
|
|
75
|
+
}
|
|
76
|
+
export const WithSeparator: Story<WithSeparatorProps> = ({
|
|
77
|
+
mode,
|
|
78
|
+
...props
|
|
79
|
+
}) => {
|
|
80
|
+
const defaultProps: Omit<DropdownSelectorProps, 'children'> = {
|
|
81
|
+
required: true,
|
|
82
|
+
showLabel: true,
|
|
83
|
+
label: 'Label',
|
|
84
|
+
requiredText: '*必須',
|
|
85
|
+
subLabel: <Clickable onClick={action('label-click')}>Text Link</Clickable>,
|
|
86
|
+
assertiveText: 'Hint',
|
|
87
|
+
}
|
|
88
|
+
return (
|
|
89
|
+
<DropdownSelector
|
|
90
|
+
{...defaultProps}
|
|
91
|
+
mode={mode}
|
|
92
|
+
placeholder={'Drop Down menu'}
|
|
93
|
+
onChange={action('change')}
|
|
94
|
+
onOpenChange={action('open')}
|
|
95
|
+
{...props}
|
|
96
|
+
>
|
|
97
|
+
<DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
|
|
98
|
+
<DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
|
|
99
|
+
<DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
|
|
100
|
+
</DropdownSelector>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
WithSeparator.args = {
|
|
105
|
+
mode: 'separator',
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type InvalidProps = {
|
|
109
|
+
disabled?: boolean
|
|
110
|
+
}
|
|
111
|
+
export const Invalid: Story<InvalidProps> = ({ disabled }) => {
|
|
112
|
+
const props: Omit<DropdownSelectorProps, 'children'> = {
|
|
113
|
+
label: '',
|
|
114
|
+
assertiveText: 'error message',
|
|
115
|
+
invalid: true,
|
|
116
|
+
}
|
|
117
|
+
return (
|
|
118
|
+
<DropdownSelector
|
|
119
|
+
{...props}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
placeholder={'Drop Down menu'}
|
|
122
|
+
onChange={action('change')}
|
|
123
|
+
onOpenChange={action('open')}
|
|
124
|
+
>
|
|
125
|
+
<DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
|
|
126
|
+
<DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
|
|
127
|
+
<DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
|
|
128
|
+
</DropdownSelector>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Invalid.args = {
|
|
133
|
+
disabled: false,
|
|
134
|
+
}
|