@charcoal-ui/react 2.3.0 → 2.5.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/dist/components/DropdownSelector/Divider.d.ts +4 -0
- package/dist/components/DropdownSelector/Divider.d.ts.map +1 -0
- package/dist/components/DropdownSelector/DropdownPopover.d.ts +13 -0
- package/dist/components/DropdownSelector/DropdownPopover.d.ts.map +1 -0
- package/dist/components/DropdownSelector/ListBoxSection.d.ts +7 -0
- package/dist/components/DropdownSelector/ListBoxSection.d.ts.map +1 -0
- package/dist/components/DropdownSelector/Listbox.d.ts +1 -1
- package/dist/components/DropdownSelector/Listbox.d.ts.map +1 -1
- package/dist/components/DropdownSelector/Option.d.ts +11 -0
- package/dist/components/DropdownSelector/Option.d.ts.map +1 -0
- package/dist/components/DropdownSelector/index.d.ts.map +1 -1
- package/dist/components/DropdownSelector/index.story.d.ts +3 -0
- package/dist/components/DropdownSelector/index.story.d.ts.map +1 -1
- package/dist/{index.cjs → index.cjs.js} +250 -184
- package/dist/index.cjs.js.map +1 -0
- package/dist/{index.js → index.esm.js} +203 -137
- package/dist/index.esm.js.map +1 -0
- package/dist/styled.d.ts +75 -73
- package/dist/styled.d.ts.map +1 -1
- package/package.json +24 -24
- package/src/components/DropdownSelector/Divider.tsx +13 -0
- package/src/components/DropdownSelector/DropdownPopover.tsx +72 -0
- package/src/components/DropdownSelector/ListBoxSection.tsx +60 -0
- package/src/components/DropdownSelector/Listbox.tsx +14 -75
- package/src/components/DropdownSelector/Option.tsx +66 -0
- package/src/components/DropdownSelector/index.story.tsx +118 -45
- package/src/components/DropdownSelector/index.tsx +8 -11
- package/src/components/TagItem/index.tsx +5 -3
- package/dist/components/DropdownSelector/Popover.d.ts +0 -10
- package/dist/components/DropdownSelector/Popover.d.ts.map +0 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/src/components/DropdownSelector/Popover.tsx +0 -46
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@charcoal-ui/react",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"module": "./dist/index.js",
|
|
5
|
+
"main": "./dist/index.cjs.js",
|
|
6
|
+
"module": "./dist/index.esm.js",
|
|
8
7
|
"exports": {
|
|
9
|
-
"require": "./dist/index.cjs",
|
|
10
|
-
"default": "./dist/index.js"
|
|
8
|
+
"require": "./dist/index.cjs.js",
|
|
9
|
+
"default": "./dist/index.esm.js"
|
|
11
10
|
},
|
|
12
11
|
"types": "./dist/index.d.ts",
|
|
13
12
|
"sideEffects": false,
|
|
@@ -50,23 +49,24 @@
|
|
|
50
49
|
"typescript": "^4.5.5"
|
|
51
50
|
},
|
|
52
51
|
"dependencies": {
|
|
53
|
-
"@charcoal-ui/icons": "^2.
|
|
54
|
-
"@charcoal-ui/styled": "^2.
|
|
55
|
-
"@charcoal-ui/theme": "^2.
|
|
56
|
-
"@charcoal-ui/utils": "^2.
|
|
57
|
-
"@react-aria/button": "^3.
|
|
58
|
-
"@react-aria/checkbox": "^3.
|
|
59
|
-
"@react-aria/dialog": "^3.
|
|
60
|
-
"@react-aria/focus": "^3.
|
|
61
|
-
"@react-aria/listbox": "^3.
|
|
62
|
-
"@react-aria/overlays": "^3.
|
|
63
|
-
"@react-aria/radio": "^3.
|
|
64
|
-
"@react-aria/select": "^3.
|
|
65
|
-
"@react-aria/
|
|
66
|
-
"@react-aria/
|
|
67
|
-
"@react-aria/
|
|
68
|
-
"@react-aria/
|
|
69
|
-
"@react-aria/
|
|
52
|
+
"@charcoal-ui/icons": "^2.5.0",
|
|
53
|
+
"@charcoal-ui/styled": "^2.5.0",
|
|
54
|
+
"@charcoal-ui/theme": "^2.5.0",
|
|
55
|
+
"@charcoal-ui/utils": "^2.5.0",
|
|
56
|
+
"@react-aria/button": "^3.7.0",
|
|
57
|
+
"@react-aria/checkbox": "^3.8.0",
|
|
58
|
+
"@react-aria/dialog": "^3.5.0",
|
|
59
|
+
"@react-aria/focus": "^3.11.0",
|
|
60
|
+
"@react-aria/listbox": "^3.8.0",
|
|
61
|
+
"@react-aria/overlays": "^3.13.0",
|
|
62
|
+
"@react-aria/radio": "^3.5.0",
|
|
63
|
+
"@react-aria/select": "^3.9.0",
|
|
64
|
+
"@react-aria/separator": "^3.3.0",
|
|
65
|
+
"@react-aria/ssr": "^3.5.0",
|
|
66
|
+
"@react-aria/switch": "^3.4.0",
|
|
67
|
+
"@react-aria/textfield": "^3.9.0",
|
|
68
|
+
"@react-aria/utils": "^3.15.0",
|
|
69
|
+
"@react-aria/visually-hidden": "^3.7.0",
|
|
70
70
|
"polished": "^4.1.4",
|
|
71
71
|
"react-spring": "^9.0.0",
|
|
72
72
|
"react-stately": "^3.19.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": "f2d6f530bbc4467e9205a4e70ce65a57372860a8"
|
|
92
92
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React, { Key, useEffect, useRef } from 'react'
|
|
2
|
+
import { OverlayTriggerState } from 'react-stately'
|
|
3
|
+
import { ReactNode } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
AriaPopoverProps,
|
|
6
|
+
DismissButton,
|
|
7
|
+
Overlay,
|
|
8
|
+
usePopover,
|
|
9
|
+
} from '@react-aria/overlays'
|
|
10
|
+
import styled from 'styled-components'
|
|
11
|
+
import { theme } from '../../styled'
|
|
12
|
+
|
|
13
|
+
const DropdownPopoverDiv = styled.div`
|
|
14
|
+
width: 100%;
|
|
15
|
+
${theme((o) => o.margin.top(4).bottom(4))}
|
|
16
|
+
`
|
|
17
|
+
|
|
18
|
+
type Props = Omit<AriaPopoverProps, 'popoverRef'> & {
|
|
19
|
+
state: OverlayTriggerState
|
|
20
|
+
} & {
|
|
21
|
+
children: ReactNode
|
|
22
|
+
value?: Key
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function DropdownPopover({ children, state, ...props }: Props) {
|
|
26
|
+
const ref = useRef<HTMLDivElement>(null)
|
|
27
|
+
const { popoverProps, underlayProps } = usePopover(
|
|
28
|
+
{
|
|
29
|
+
...props,
|
|
30
|
+
popoverRef: ref,
|
|
31
|
+
containerPadding: 0,
|
|
32
|
+
},
|
|
33
|
+
state
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (ref.current && props.triggerRef.current) {
|
|
38
|
+
ref.current.style.width = `${props.triggerRef.current.clientWidth}px`
|
|
39
|
+
}
|
|
40
|
+
}, [props.triggerRef])
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (state.isOpen && props.value !== undefined) {
|
|
44
|
+
// useListbox内部で現在の選択肢までスクロールする処理がある
|
|
45
|
+
// react-ariaより遅らせるためrequestAnimationFrameで呼び出す
|
|
46
|
+
window.requestAnimationFrame(() => {
|
|
47
|
+
if (props.value === undefined) return
|
|
48
|
+
// react-aria の scrollIntoViewport は 'nearest' なので
|
|
49
|
+
// listboxの中心に来るように上書きする
|
|
50
|
+
// 'center'は windowのスクロールも変更されるため戻す処理を入れている。
|
|
51
|
+
const windowScrollY = window.scrollY
|
|
52
|
+
const windowScrollX = window.scrollX
|
|
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
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}, [props.value, state.isOpen])
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Overlay portalContainer={document.body}>
|
|
64
|
+
<div {...underlayProps} style={{ position: 'fixed', inset: 0 }} />
|
|
65
|
+
<DropdownPopoverDiv {...popoverProps} ref={ref}>
|
|
66
|
+
<DismissButton onDismiss={() => state.close()} />
|
|
67
|
+
{children}
|
|
68
|
+
<DismissButton onDismiss={() => state.close()} />
|
|
69
|
+
</DropdownPopoverDiv>
|
|
70
|
+
</Overlay>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { useListBoxSection } from '@react-aria/listbox'
|
|
2
|
+
import { useSeparator } from '@react-aria/separator'
|
|
3
|
+
import { Node } from '@react-types/shared'
|
|
4
|
+
import React from 'react'
|
|
5
|
+
import { ListState } from 'react-stately'
|
|
6
|
+
import styled from 'styled-components'
|
|
7
|
+
import { Option } from './Option'
|
|
8
|
+
import { Divider } from './Divider'
|
|
9
|
+
import { theme } from '../../styled'
|
|
10
|
+
|
|
11
|
+
export function ListBoxSection<T>(props: {
|
|
12
|
+
section: Node<T>
|
|
13
|
+
state: ListState<T>
|
|
14
|
+
}) {
|
|
15
|
+
const { state } = props
|
|
16
|
+
const { itemProps, headingProps, groupProps } = useListBoxSection({
|
|
17
|
+
heading: props.section.rendered,
|
|
18
|
+
'aria-label': props.section['aria-label'],
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const { separatorProps } = useSeparator({
|
|
22
|
+
elementType: 'li',
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<>
|
|
27
|
+
{props.section.key !== props.state.collection.getFirstKey() && (
|
|
28
|
+
<Divider {...separatorProps} role="separator" />
|
|
29
|
+
)}
|
|
30
|
+
<StyledLi {...itemProps}>
|
|
31
|
+
{props.section.rendered != null && (
|
|
32
|
+
<SectionSpan {...headingProps}>{props.section.rendered}</SectionSpan>
|
|
33
|
+
)}
|
|
34
|
+
<StyledUl {...groupProps}>
|
|
35
|
+
{[...props.section.childNodes].map((node) => (
|
|
36
|
+
<Option key={node.key} item={node} state={state} />
|
|
37
|
+
))}
|
|
38
|
+
</StyledUl>
|
|
39
|
+
</StyledLi>
|
|
40
|
+
</>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const SectionSpan = styled.span`
|
|
45
|
+
${theme((o) => [
|
|
46
|
+
o.font.text3,
|
|
47
|
+
o.typography(12).bold,
|
|
48
|
+
o.margin.bottom(8).left(16).top(16),
|
|
49
|
+
])}
|
|
50
|
+
`
|
|
51
|
+
|
|
52
|
+
const StyledUl = styled.ul`
|
|
53
|
+
padding-left: 0;
|
|
54
|
+
margin: 0;
|
|
55
|
+
box-sizing: border-box;
|
|
56
|
+
list-style: none;
|
|
57
|
+
overflow: hidden;
|
|
58
|
+
`
|
|
59
|
+
|
|
60
|
+
const StyledLi = styled.li``
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import React, { memo, useRef, Fragment, useMemo } from 'react'
|
|
2
|
-
import styled
|
|
2
|
+
import styled from 'styled-components'
|
|
3
3
|
import { ListProps, ListState } from 'react-stately'
|
|
4
|
-
import { useListBox
|
|
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'
|
|
4
|
+
import { useListBox } from '@react-aria/listbox'
|
|
9
5
|
import { theme } from '../../styled'
|
|
10
6
|
|
|
11
|
-
import
|
|
7
|
+
import { ListBoxSection } from './ListBoxSection'
|
|
8
|
+
import { Divider } from './Divider'
|
|
9
|
+
import { Option } from './Option'
|
|
12
10
|
|
|
13
|
-
type ListMode = 'default' | 'separator'
|
|
11
|
+
export type ListMode = 'default' | 'separator'
|
|
14
12
|
export type ListboxProps<T> = Omit<ListProps<T>, 'children'> & {
|
|
15
13
|
state: ListState<T>
|
|
16
14
|
mode?: ListMode
|
|
@@ -38,7 +36,11 @@ const Listbox = <T,>({
|
|
|
38
36
|
<ListboxRoot ref={ref} {...listBoxProps}>
|
|
39
37
|
{collection.map(({ node, last }) => (
|
|
40
38
|
<Fragment key={node.key}>
|
|
41
|
-
|
|
39
|
+
{node.type === 'section' ? (
|
|
40
|
+
<ListBoxSection section={node} state={state} />
|
|
41
|
+
) : (
|
|
42
|
+
<Option item={node} state={state} mode={mode} />
|
|
43
|
+
)}
|
|
42
44
|
{!last && mode === 'separator' && <Divider />}
|
|
43
45
|
</Fragment>
|
|
44
46
|
))}
|
|
@@ -52,77 +54,14 @@ const ListboxRoot = styled.ul`
|
|
|
52
54
|
margin: 0;
|
|
53
55
|
box-sizing: border-box;
|
|
54
56
|
list-style: none;
|
|
57
|
+
overflow: auto;
|
|
58
|
+
max-height: inherit;
|
|
55
59
|
|
|
56
60
|
${theme((o) => [
|
|
57
61
|
o.bg.background1,
|
|
58
62
|
o.border.default,
|
|
59
63
|
o.borderRadius(8),
|
|
64
|
+
o.padding.vertical(8),
|
|
60
65
|
o.outline.default.focus,
|
|
61
66
|
])}
|
|
62
67
|
`
|
|
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
|
-
${theme((o) => [o.font.text2])}
|
|
118
|
-
|
|
119
|
-
${({ isSelected }) =>
|
|
120
|
-
isSelected &&
|
|
121
|
-
css`
|
|
122
|
-
visibility: visible;
|
|
123
|
-
`}
|
|
124
|
-
`
|
|
125
|
-
const OptionText = styled.span`
|
|
126
|
-
display: block;
|
|
127
|
-
${theme((o) => [o.typography(14), o.font.text2])}
|
|
128
|
-
`
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useRef } from 'react'
|
|
2
|
+
import styled, { css } from 'styled-components'
|
|
3
|
+
import { ListState } from 'react-stately'
|
|
4
|
+
import { 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
|
+
import { Node } from '@react-types/shared'
|
|
11
|
+
import { ListMode } from './Listbox'
|
|
12
|
+
|
|
13
|
+
type OptionProps<T> = {
|
|
14
|
+
item: Node<T>
|
|
15
|
+
state: ListState<T>
|
|
16
|
+
mode?: ListMode
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Option<T>({ item, state, mode }: OptionProps<T>) {
|
|
20
|
+
const ref = useRef<HTMLLIElement>(null)
|
|
21
|
+
|
|
22
|
+
const { optionProps, isSelected } = useOption(item, state, ref)
|
|
23
|
+
const { focusProps } = useFocusRing()
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<OptionRoot {...mergeProps(optionProps, focusProps)} ref={ref} mode={mode}>
|
|
27
|
+
<OptionCheckIcon name="16/Check" isSelected={isSelected} />
|
|
28
|
+
<OptionText>{item.rendered}</OptionText>
|
|
29
|
+
</OptionRoot>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const OptionRoot = styled.li<{ mode?: ListMode }>`
|
|
34
|
+
display: flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
gap: ${({ theme }) => px(theme.spacing[4])};
|
|
37
|
+
height: 40px;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
outline: none;
|
|
40
|
+
|
|
41
|
+
${({ mode }) =>
|
|
42
|
+
theme((o) => [
|
|
43
|
+
o.padding.horizontal(8),
|
|
44
|
+
mode === 'separator' && o.padding.vertical(4),
|
|
45
|
+
])}
|
|
46
|
+
|
|
47
|
+
&:focus {
|
|
48
|
+
${theme((o) => [o.bg.surface3])}
|
|
49
|
+
}
|
|
50
|
+
`
|
|
51
|
+
|
|
52
|
+
const OptionCheckIcon = styled(Icon)<{ isSelected: boolean }>`
|
|
53
|
+
visibility: hidden;
|
|
54
|
+
${theme((o) => [o.font.text2])}
|
|
55
|
+
|
|
56
|
+
${({ isSelected }) =>
|
|
57
|
+
isSelected &&
|
|
58
|
+
css`
|
|
59
|
+
visibility: visible;
|
|
60
|
+
`}
|
|
61
|
+
`
|
|
62
|
+
|
|
63
|
+
const OptionText = styled.span`
|
|
64
|
+
display: block;
|
|
65
|
+
${theme((o) => [o.typography(14), o.font.text2])}
|
|
66
|
+
`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { action } from '@storybook/addon-actions'
|
|
2
|
-
import React from 'react'
|
|
2
|
+
import React, { useState } from 'react'
|
|
3
|
+
import { Section } from 'react-stately'
|
|
3
4
|
import DropdownSelector, {
|
|
4
5
|
DropdownSelectorItem,
|
|
5
6
|
DropdownSelectorProps,
|
|
@@ -18,16 +19,18 @@ type Props = Omit<
|
|
|
18
19
|
>
|
|
19
20
|
export const Default: Story<Props> = (props) => {
|
|
20
21
|
return (
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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>
|
|
31
34
|
)
|
|
32
35
|
}
|
|
33
36
|
Default.args = {
|
|
@@ -39,6 +42,70 @@ Default.args = {
|
|
|
39
42
|
disabled: false,
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
export const Sections: Story<DropdownSelectorProps> = (props) => {
|
|
46
|
+
return (
|
|
47
|
+
<div>
|
|
48
|
+
<DropdownSelector
|
|
49
|
+
{...props}
|
|
50
|
+
placeholder={'Drop Down menu'}
|
|
51
|
+
onChange={action('change')}
|
|
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
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const Many: Story<DropdownSelectorProps> = (props) => {
|
|
84
|
+
const [value, setValue] = useState('50')
|
|
85
|
+
return (
|
|
86
|
+
<div style={{ padding: '300px 100px' }}>
|
|
87
|
+
<DropdownSelector
|
|
88
|
+
{...props}
|
|
89
|
+
placeholder={'Drop Down menu'}
|
|
90
|
+
onChange={(v) => {
|
|
91
|
+
setValue(v.toString())
|
|
92
|
+
action('change')
|
|
93
|
+
}}
|
|
94
|
+
onOpenChange={action('open')}
|
|
95
|
+
value={value}
|
|
96
|
+
>
|
|
97
|
+
{[...(Array(100) as undefined[])].map((_, i) => {
|
|
98
|
+
return (
|
|
99
|
+
<DropdownSelectorItem textValue={i.toString()} key={i}>
|
|
100
|
+
選択肢{i}
|
|
101
|
+
</DropdownSelectorItem>
|
|
102
|
+
)
|
|
103
|
+
})}
|
|
104
|
+
</DropdownSelector>
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
42
109
|
type HasLabelProps = {
|
|
43
110
|
disabled?: boolean
|
|
44
111
|
}
|
|
@@ -52,17 +119,19 @@ export const HasLabel: Story<HasLabelProps> = ({ disabled }) => {
|
|
|
52
119
|
assertiveText: 'Hint',
|
|
53
120
|
}
|
|
54
121
|
return (
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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>
|
|
66
135
|
)
|
|
67
136
|
}
|
|
68
137
|
|
|
@@ -86,18 +155,20 @@ export const WithSeparator: Story<WithSeparatorProps> = ({
|
|
|
86
155
|
assertiveText: 'Hint',
|
|
87
156
|
}
|
|
88
157
|
return (
|
|
89
|
-
<
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
158
|
+
<div style={{ width: 288 }}>
|
|
159
|
+
<DropdownSelector
|
|
160
|
+
{...defaultProps}
|
|
161
|
+
mode={mode}
|
|
162
|
+
placeholder={'Drop Down menu'}
|
|
163
|
+
onChange={action('change')}
|
|
164
|
+
onOpenChange={action('open')}
|
|
165
|
+
{...props}
|
|
166
|
+
>
|
|
167
|
+
<DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
|
|
168
|
+
<DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
|
|
169
|
+
<DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
|
|
170
|
+
</DropdownSelector>
|
|
171
|
+
</div>
|
|
101
172
|
)
|
|
102
173
|
}
|
|
103
174
|
|
|
@@ -115,17 +186,19 @@ export const Invalid: Story<InvalidProps> = ({ disabled }) => {
|
|
|
115
186
|
invalid: true,
|
|
116
187
|
}
|
|
117
188
|
return (
|
|
118
|
-
<
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
189
|
+
<div style={{ width: 288 }}>
|
|
190
|
+
<DropdownSelector
|
|
191
|
+
{...props}
|
|
192
|
+
disabled={disabled}
|
|
193
|
+
placeholder={'Drop Down menu'}
|
|
194
|
+
onChange={action('change')}
|
|
195
|
+
onOpenChange={action('open')}
|
|
196
|
+
>
|
|
197
|
+
<DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
|
|
198
|
+
<DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
|
|
199
|
+
<DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
|
|
200
|
+
</DropdownSelector>
|
|
201
|
+
</div>
|
|
129
202
|
)
|
|
130
203
|
}
|
|
131
204
|
|
|
@@ -7,13 +7,13 @@ import { useSelect, HiddenSelect } from '@react-aria/select'
|
|
|
7
7
|
import { useButton } from '@react-aria/button'
|
|
8
8
|
import { SelectProps } from '@react-types/select'
|
|
9
9
|
import Listbox, { ListboxProps } from './Listbox'
|
|
10
|
-
import Popover from './Popover'
|
|
11
10
|
import Icon from '../Icon'
|
|
12
11
|
import FieldLabel from '../FieldLabel'
|
|
13
12
|
import { theme } from '../../styled'
|
|
14
13
|
|
|
15
14
|
import type { CollectionBase } from '@react-types/shared'
|
|
16
15
|
import type { ReactNode } from 'react'
|
|
16
|
+
import { DropdownPopover } from './DropdownPopover'
|
|
17
17
|
|
|
18
18
|
type LabelProps = {
|
|
19
19
|
readonly showLabel?: boolean
|
|
@@ -124,7 +124,11 @@ const DropdownSelector = <T extends Record<string, unknown>>({
|
|
|
124
124
|
<DropdownButtonIcon name="16/Menu" />
|
|
125
125
|
</DropdownButton>
|
|
126
126
|
{state.isOpen && (
|
|
127
|
-
<DropdownPopover
|
|
127
|
+
<DropdownPopover
|
|
128
|
+
state={state}
|
|
129
|
+
triggerRef={triggerRef}
|
|
130
|
+
value={props.value ?? props.defaultValue}
|
|
131
|
+
>
|
|
128
132
|
<Listbox {...menuProps} state={state} mode={mode} />
|
|
129
133
|
</DropdownPopover>
|
|
130
134
|
)}
|
|
@@ -148,6 +152,7 @@ export const DropdownSelectorItem = Item
|
|
|
148
152
|
const DropdownSelectorRoot = styled.div`
|
|
149
153
|
position: relative;
|
|
150
154
|
display: inline-block;
|
|
155
|
+
width: 100%;
|
|
151
156
|
|
|
152
157
|
${disabledSelector} {
|
|
153
158
|
cursor: default;
|
|
@@ -171,7 +176,7 @@ const DropdownButton = styled.button<{ invalid: boolean }>`
|
|
|
171
176
|
align-items: center;
|
|
172
177
|
|
|
173
178
|
height: 40px;
|
|
174
|
-
width:
|
|
179
|
+
width: 100%;
|
|
175
180
|
box-sizing: border-box;
|
|
176
181
|
cursor: pointer;
|
|
177
182
|
|
|
@@ -208,11 +213,3 @@ const AssertiveText = styled.div<{ invalid: boolean }>`
|
|
|
208
213
|
invalid ? o.font.assertive : o.font.text2,
|
|
209
214
|
])}
|
|
210
215
|
`
|
|
211
|
-
|
|
212
|
-
const DropdownPopover = styled(Popover)`
|
|
213
|
-
position: absolute;
|
|
214
|
-
width: 100%;
|
|
215
|
-
|
|
216
|
-
top: 100%;
|
|
217
|
-
margin-top: 2px;
|
|
218
|
-
`
|