@charcoal-ui/react 3.0.0-beta.0 → 3.0.0-beta.2
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/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/DropdownSelector/DropdownPopover.d.ts.map +1 -1
- package/dist/components/DropdownSelector/OptionItem.d.ts +7 -0
- package/dist/components/DropdownSelector/OptionItem.d.ts.map +1 -0
- package/dist/components/DropdownSelector/index.d.ts +22 -29
- package/dist/components/DropdownSelector/index.d.ts.map +1 -1
- package/dist/components/DropdownSelector/index.story.d.ts +5 -18
- package/dist/components/DropdownSelector/index.story.d.ts.map +1 -1
- package/dist/components/DropdownSelector/utils/focusIfHTMLLIElement.d.ts +6 -0
- package/dist/components/DropdownSelector/utils/focusIfHTMLLIElement.d.ts.map +1 -0
- package/dist/components/DropdownSelector/utils/handleFocusByKeyBoard.d.ts +6 -0
- package/dist/components/DropdownSelector/utils/handleFocusByKeyBoard.d.ts.map +1 -0
- package/dist/components/LoadingSpinner/index.d.ts.map +1 -1
- package/dist/components/Modal/index.d.ts +5 -2
- package/dist/components/Modal/index.d.ts.map +1 -1
- package/dist/components/Radio/index.d.ts.map +1 -1
- package/dist/components/SegmentedControl/index.d.ts.map +1 -1
- package/dist/components/SegmentedControl/index.story.d.ts.map +1 -1
- package/dist/index.cjs.js +313 -336
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +296 -319
- package/dist/index.esm.js.map +1 -1
- package/package.json +6 -6
- package/src/components/DropdownSelector/DropdownPopover.tsx +9 -15
- package/src/components/DropdownSelector/OptionItem.tsx +85 -0
- package/src/components/DropdownSelector/index.story.tsx +69 -156
- package/src/components/DropdownSelector/index.tsx +110 -140
- package/src/components/DropdownSelector/utils/focusIfHTMLLIElement.tsx +12 -0
- package/src/components/DropdownSelector/utils/handleFocusByKeyBoard.tsx +20 -0
- package/src/components/LoadingSpinner/index.tsx +1 -0
- package/src/components/Modal/index.tsx +79 -61
- package/src/components/Radio/index.tsx +2 -0
- package/src/components/SegmentedControl/index.story.tsx +2 -0
- package/src/components/SegmentedControl/index.tsx +1 -0
- package/src/components/TextField/index.tsx +1 -0
- package/src/index.ts +7 -2
- package/src/components/DropdownSelector/ListBoxSection.tsx +0 -60
- package/src/components/DropdownSelector/Listbox.tsx +0 -67
- package/src/components/DropdownSelector/Option.tsx +0 -66
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charcoal-ui/react",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "./dist/index.cjs.js",
|
|
6
6
|
"module": "./dist/index.esm.js",
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
"typescript": "^4.9.5"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@charcoal-ui/icons": "^3.0.0-beta.
|
|
53
|
-
"@charcoal-ui/styled": "^3.0.0-beta.
|
|
54
|
-
"@charcoal-ui/theme": "^3.0.0-beta.
|
|
55
|
-
"@charcoal-ui/utils": "^3.0.0-beta.
|
|
52
|
+
"@charcoal-ui/icons": "^3.0.0-beta.2",
|
|
53
|
+
"@charcoal-ui/styled": "^3.0.0-beta.2",
|
|
54
|
+
"@charcoal-ui/theme": "^3.0.0-beta.2",
|
|
55
|
+
"@charcoal-ui/utils": "^3.0.0-beta.2",
|
|
56
56
|
"@react-aria/button": "^3.7.0",
|
|
57
57
|
"@react-aria/checkbox": "^3.8.0",
|
|
58
58
|
"@react-aria/dialog": "^3.5.0",
|
|
@@ -88,5 +88,5 @@
|
|
|
88
88
|
"url": "https://github.com/pixiv/charcoal.git",
|
|
89
89
|
"directory": "packages/react"
|
|
90
90
|
},
|
|
91
|
-
"gitHead": "
|
|
91
|
+
"gitHead": "dbf33f97e67fc6eabc4283fa38ac9af0b89292f4"
|
|
92
92
|
}
|
|
@@ -41,21 +41,15 @@ export function DropdownPopover({ children, state, ...props }: Props) {
|
|
|
41
41
|
|
|
42
42
|
useEffect(() => {
|
|
43
43
|
if (state.isOpen && props.value !== undefined) {
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
window.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const selectedElement = document.querySelector(
|
|
54
|
-
`[data-key="${props.value.toString()}"]`
|
|
55
|
-
) as HTMLElement | undefined
|
|
56
|
-
selectedElement?.scrollIntoView({ block: 'center' })
|
|
57
|
-
window.scrollTo(windowScrollX, windowScrollY)
|
|
58
|
-
})
|
|
44
|
+
// windowのスクロールを維持したまま選択肢をPopoverの中心に表示する
|
|
45
|
+
const windowScrollY = window.scrollY
|
|
46
|
+
const windowScrollX = window.scrollX
|
|
47
|
+
const selectedElement = document.querySelector(
|
|
48
|
+
`[data-key="${props.value.toString()}"]`
|
|
49
|
+
) as HTMLElement | undefined
|
|
50
|
+
selectedElement?.scrollIntoView({ block: 'center' })
|
|
51
|
+
selectedElement?.focus()
|
|
52
|
+
window.scrollTo(windowScrollX, windowScrollY)
|
|
59
53
|
}
|
|
60
54
|
}, [props.value, state.isOpen])
|
|
61
55
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { ReactNode, useContext } from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import { px } from '@charcoal-ui/utils'
|
|
4
|
+
import Icon from '../Icon'
|
|
5
|
+
import { theme } from '../../styled'
|
|
6
|
+
import { DropdownSelectorContext } from '.'
|
|
7
|
+
import { focusIfHTMLLIElement } from './utils/focusIfHTMLLIElement'
|
|
8
|
+
|
|
9
|
+
export type OptionItemProps = {
|
|
10
|
+
children?: ReactNode
|
|
11
|
+
value: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function OptionItem(props: OptionItemProps) {
|
|
15
|
+
const { value, setValue } = useContext(DropdownSelectorContext)
|
|
16
|
+
const isSelected = props.value === value
|
|
17
|
+
const onSelect = () => {
|
|
18
|
+
setValue(props.value)
|
|
19
|
+
}
|
|
20
|
+
return (
|
|
21
|
+
<OptionRoot
|
|
22
|
+
data-key={props.value}
|
|
23
|
+
onMouseMove={(e) => {
|
|
24
|
+
e.currentTarget.focus({ preventScroll: true })
|
|
25
|
+
}}
|
|
26
|
+
onKeyDown={(e) => {
|
|
27
|
+
if (e.key === 'Enter') {
|
|
28
|
+
onSelect()
|
|
29
|
+
} else if (e.key === 'ArrowUp') {
|
|
30
|
+
// prevent scroll
|
|
31
|
+
e.preventDefault()
|
|
32
|
+
const prev = e.currentTarget.previousElementSibling
|
|
33
|
+
if (prev === null) {
|
|
34
|
+
focusIfHTMLLIElement(e.currentTarget.parentElement?.lastChild)
|
|
35
|
+
}
|
|
36
|
+
focusIfHTMLLIElement(prev)
|
|
37
|
+
} else if (e.key === 'ArrowDown') {
|
|
38
|
+
// prevent scroll
|
|
39
|
+
e.preventDefault()
|
|
40
|
+
const next = e.currentTarget.nextElementSibling
|
|
41
|
+
if (next === null) {
|
|
42
|
+
focusIfHTMLLIElement(e.currentTarget.parentElement?.firstChild)
|
|
43
|
+
}
|
|
44
|
+
focusIfHTMLLIElement(next)
|
|
45
|
+
} else if (e.key === ' ') {
|
|
46
|
+
// prevent scroll
|
|
47
|
+
e.preventDefault()
|
|
48
|
+
}
|
|
49
|
+
}}
|
|
50
|
+
onClick={onSelect}
|
|
51
|
+
tabIndex={-1}
|
|
52
|
+
>
|
|
53
|
+
{isSelected && <OptionCheckIcon name="16/Check" />}
|
|
54
|
+
<OptionText isSelected={isSelected}>{props.children}</OptionText>
|
|
55
|
+
</OptionRoot>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const OptionRoot = styled.li`
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: ${({ theme }) => px(theme.spacing[4])};
|
|
63
|
+
height: 40px;
|
|
64
|
+
cursor: pointer;
|
|
65
|
+
outline: none;
|
|
66
|
+
|
|
67
|
+
${theme((o) => [o.padding.horizontal(8)])}
|
|
68
|
+
|
|
69
|
+
:focus {
|
|
70
|
+
${theme((o) => [o.bg.surface3])}
|
|
71
|
+
}
|
|
72
|
+
`
|
|
73
|
+
|
|
74
|
+
const OptionCheckIcon = styled(Icon)`
|
|
75
|
+
${theme((o) => [o.font.text2])}
|
|
76
|
+
`
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* アイコンがない時を考慮して20px(16pxのwidthと4pxのgap)の余白をとる
|
|
80
|
+
*/
|
|
81
|
+
const OptionText = styled.span<{ isSelected?: boolean }>`
|
|
82
|
+
display: block;
|
|
83
|
+
${theme((o) => [o.typography(14), o.font.text2])}
|
|
84
|
+
margin-left: ${({ isSelected }) => (isSelected === true ? 0 : 20)}px;
|
|
85
|
+
`
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import { action } from '@storybook/addon-actions'
|
|
2
1
|
import React, { useState } from 'react'
|
|
3
|
-
import {
|
|
4
|
-
import DropdownSelector, {
|
|
5
|
-
DropdownSelectorItem,
|
|
6
|
-
DropdownSelectorProps,
|
|
7
|
-
} from '.'
|
|
2
|
+
import DropdownSelector, { DropdownSelectorProps } from '.'
|
|
8
3
|
import { Story } from '../../_lib/compat'
|
|
9
|
-
import
|
|
4
|
+
import { OptionItem } from './OptionItem'
|
|
10
5
|
|
|
11
6
|
export default {
|
|
12
7
|
title: 'DropdownSelector',
|
|
@@ -17,88 +12,33 @@ type Props = Omit<
|
|
|
17
12
|
DropdownSelectorProps,
|
|
18
13
|
'subLabel' | 'children' | 'onOpenChange'
|
|
19
14
|
>
|
|
20
|
-
export const Default: Story<Props> = (props) => {
|
|
21
|
-
return (
|
|
22
|
-
<div style={{ width: 288 }}>
|
|
23
|
-
<DropdownSelector
|
|
24
|
-
{...props}
|
|
25
|
-
placeholder={props.placeholder ?? 'Drop Down menu'}
|
|
26
|
-
onChange={action('change')}
|
|
27
|
-
onOpenChange={action('open')}
|
|
28
|
-
>
|
|
29
|
-
<DropdownSelectorItem key="k:1">選択肢1</DropdownSelectorItem>
|
|
30
|
-
<DropdownSelectorItem key="k:2">選択肢2</DropdownSelectorItem>
|
|
31
|
-
<DropdownSelectorItem key="k:3">選択肢3</DropdownSelectorItem>
|
|
32
|
-
</DropdownSelector>
|
|
33
|
-
</div>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
Default.args = {
|
|
37
|
-
label: 'Label',
|
|
38
|
-
requiredText: '*必須',
|
|
39
|
-
required: false,
|
|
40
|
-
showLabel: false,
|
|
41
|
-
invalid: false,
|
|
42
|
-
disabled: false,
|
|
43
|
-
}
|
|
44
15
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
onOpenChange={action('open')}
|
|
53
|
-
>
|
|
54
|
-
<Section title="Section1">
|
|
55
|
-
<DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
|
|
56
|
-
</Section>
|
|
57
|
-
<Section title="Section2">
|
|
58
|
-
<DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
|
|
59
|
-
<DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
|
|
60
|
-
</Section>
|
|
61
|
-
</DropdownSelector>
|
|
62
|
-
</div>
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export const Bottom: Story<DropdownSelectorProps> = (props) => {
|
|
67
|
-
return (
|
|
68
|
-
<div style={{ marginTop: '1000px' }}>
|
|
69
|
-
<DropdownSelector
|
|
70
|
-
{...props}
|
|
71
|
-
placeholder={'Drop Down menu'}
|
|
72
|
-
onChange={action('change')}
|
|
73
|
-
onOpenChange={action('open')}
|
|
74
|
-
>
|
|
75
|
-
<DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
|
|
76
|
-
<DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
|
|
77
|
-
<DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
|
|
78
|
-
</DropdownSelector>
|
|
79
|
-
</div>
|
|
80
|
-
)
|
|
16
|
+
const baseProps: DropdownSelectorProps = {
|
|
17
|
+
label: 'Label',
|
|
18
|
+
value: '',
|
|
19
|
+
placeholder: 'placeholder',
|
|
20
|
+
onChange: () => {
|
|
21
|
+
//
|
|
22
|
+
},
|
|
81
23
|
}
|
|
82
24
|
|
|
83
|
-
export const
|
|
84
|
-
const [
|
|
25
|
+
export const Playground: Story<Props> = (props: DropdownSelectorProps) => {
|
|
26
|
+
const [selected, setSelected] = useState('50')
|
|
85
27
|
return (
|
|
86
|
-
<div style={{
|
|
28
|
+
<div style={{ width: 288 }}>
|
|
87
29
|
<DropdownSelector
|
|
88
30
|
{...props}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
setValue(v.toString())
|
|
92
|
-
action('change')
|
|
31
|
+
onChange={(value) => {
|
|
32
|
+
setSelected(value)
|
|
93
33
|
}}
|
|
94
|
-
|
|
95
|
-
|
|
34
|
+
value={selected}
|
|
35
|
+
label="label"
|
|
96
36
|
>
|
|
97
37
|
{[...(Array(100) as undefined[])].map((_, i) => {
|
|
98
38
|
return (
|
|
99
|
-
<
|
|
100
|
-
|
|
101
|
-
</
|
|
39
|
+
<OptionItem key={i} value={i.toString()}>
|
|
40
|
+
{i}
|
|
41
|
+
</OptionItem>
|
|
102
42
|
)
|
|
103
43
|
})}
|
|
104
44
|
</DropdownSelector>
|
|
@@ -106,102 +46,75 @@ export const Many: Story<DropdownSelectorProps> = (props) => {
|
|
|
106
46
|
)
|
|
107
47
|
}
|
|
108
48
|
|
|
109
|
-
|
|
110
|
-
disabled?: boolean
|
|
111
|
-
}
|
|
112
|
-
export const HasLabel: Story<HasLabelProps> = ({ disabled }) => {
|
|
113
|
-
const defaultProps: Omit<DropdownSelectorProps, 'children'> = {
|
|
114
|
-
required: true,
|
|
115
|
-
showLabel: true,
|
|
116
|
-
label: 'Label',
|
|
117
|
-
requiredText: '*必須',
|
|
118
|
-
subLabel: <Clickable onClick={action('label-click')}>Text Link</Clickable>,
|
|
119
|
-
assertiveText: 'Hint',
|
|
120
|
-
}
|
|
121
|
-
return (
|
|
122
|
-
<div style={{ width: 288 }}>
|
|
123
|
-
<DropdownSelector
|
|
124
|
-
{...defaultProps}
|
|
125
|
-
disabled={disabled}
|
|
126
|
-
placeholder={'Drop Down menu'}
|
|
127
|
-
onChange={action('change')}
|
|
128
|
-
onOpenChange={action('open')}
|
|
129
|
-
>
|
|
130
|
-
<DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
|
|
131
|
-
<DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
|
|
132
|
-
<DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
|
|
133
|
-
</DropdownSelector>
|
|
134
|
-
</div>
|
|
135
|
-
)
|
|
136
|
-
}
|
|
49
|
+
Playground.args = { ...baseProps }
|
|
137
50
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
type WithSeparatorProps = {
|
|
143
|
-
mode: 'default' | 'separator'
|
|
144
|
-
}
|
|
145
|
-
export const WithSeparator: Story<WithSeparatorProps> = ({
|
|
146
|
-
mode,
|
|
147
|
-
...props
|
|
148
|
-
}) => {
|
|
149
|
-
const defaultProps: Omit<DropdownSelectorProps, 'children'> = {
|
|
150
|
-
required: true,
|
|
151
|
-
showLabel: true,
|
|
152
|
-
label: 'Label',
|
|
153
|
-
requiredText: '*必須',
|
|
154
|
-
subLabel: <Clickable onClick={action('label-click')}>Text Link</Clickable>,
|
|
155
|
-
assertiveText: 'Hint',
|
|
156
|
-
}
|
|
51
|
+
export const Basic: Story<Props> = (props: DropdownSelectorProps) => {
|
|
52
|
+
const [selected, setSelected] = useState('')
|
|
157
53
|
return (
|
|
158
54
|
<div style={{ width: 288 }}>
|
|
159
55
|
<DropdownSelector
|
|
160
|
-
{...defaultProps}
|
|
161
|
-
mode={mode}
|
|
162
|
-
placeholder={'Drop Down menu'}
|
|
163
|
-
onChange={action('change')}
|
|
164
|
-
onOpenChange={action('open')}
|
|
165
56
|
{...props}
|
|
57
|
+
onChange={(value) => {
|
|
58
|
+
setSelected(value)
|
|
59
|
+
}}
|
|
60
|
+
value={selected}
|
|
61
|
+
label="label"
|
|
166
62
|
>
|
|
167
|
-
<
|
|
168
|
-
<
|
|
169
|
-
<
|
|
63
|
+
<OptionItem value={'10'}>Apple</OptionItem>
|
|
64
|
+
<OptionItem value={'20'}>Banana</OptionItem>
|
|
65
|
+
<OptionItem value={'30'}>Orange</OptionItem>
|
|
170
66
|
</DropdownSelector>
|
|
171
67
|
</div>
|
|
172
68
|
)
|
|
173
69
|
}
|
|
174
70
|
|
|
175
|
-
|
|
176
|
-
mode: 'separator',
|
|
177
|
-
}
|
|
71
|
+
Basic.args = { ...baseProps }
|
|
178
72
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
export const Invalid: Story<InvalidProps> = ({ disabled }) => {
|
|
183
|
-
const props: Omit<DropdownSelectorProps, 'children'> = {
|
|
184
|
-
label: '',
|
|
185
|
-
assertiveText: 'error message',
|
|
186
|
-
invalid: true,
|
|
187
|
-
}
|
|
73
|
+
export const CustomChildren: Story<Props> = (props: DropdownSelectorProps) => {
|
|
74
|
+
const [selected, setSelected] = useState('10')
|
|
188
75
|
return (
|
|
189
76
|
<div style={{ width: 288 }}>
|
|
190
77
|
<DropdownSelector
|
|
191
78
|
{...props}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
79
|
+
onChange={(value) => {
|
|
80
|
+
setSelected(value)
|
|
81
|
+
}}
|
|
82
|
+
value={selected}
|
|
83
|
+
label="label"
|
|
196
84
|
>
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
85
|
+
<OptionItem value={'10'}>
|
|
86
|
+
<div
|
|
87
|
+
style={{
|
|
88
|
+
color: 'pink',
|
|
89
|
+
fontWeight: 'bold',
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
Apple
|
|
93
|
+
</div>
|
|
94
|
+
</OptionItem>
|
|
95
|
+
<OptionItem value={'20'}>
|
|
96
|
+
<div
|
|
97
|
+
style={{
|
|
98
|
+
color: 'yellowgreen',
|
|
99
|
+
fontStyle: 'italic',
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
Banana
|
|
103
|
+
</div>
|
|
104
|
+
</OptionItem>
|
|
105
|
+
<OptionItem value={'30'}>
|
|
106
|
+
<div
|
|
107
|
+
style={{
|
|
108
|
+
color: 'orange',
|
|
109
|
+
fontSize: '24px',
|
|
110
|
+
}}
|
|
111
|
+
>
|
|
112
|
+
Orange
|
|
113
|
+
</div>
|
|
114
|
+
</OptionItem>
|
|
200
115
|
</DropdownSelector>
|
|
201
116
|
</div>
|
|
202
117
|
)
|
|
203
118
|
}
|
|
204
119
|
|
|
205
|
-
|
|
206
|
-
disabled: false,
|
|
207
|
-
}
|
|
120
|
+
CustomChildren.args = { ...baseProps }
|