@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.
Files changed (33) hide show
  1. package/dist/components/DropdownSelector/Divider.d.ts +4 -0
  2. package/dist/components/DropdownSelector/Divider.d.ts.map +1 -0
  3. package/dist/components/DropdownSelector/DropdownPopover.d.ts +13 -0
  4. package/dist/components/DropdownSelector/DropdownPopover.d.ts.map +1 -0
  5. package/dist/components/DropdownSelector/ListBoxSection.d.ts +7 -0
  6. package/dist/components/DropdownSelector/ListBoxSection.d.ts.map +1 -0
  7. package/dist/components/DropdownSelector/Listbox.d.ts +1 -1
  8. package/dist/components/DropdownSelector/Listbox.d.ts.map +1 -1
  9. package/dist/components/DropdownSelector/Option.d.ts +11 -0
  10. package/dist/components/DropdownSelector/Option.d.ts.map +1 -0
  11. package/dist/components/DropdownSelector/index.d.ts.map +1 -1
  12. package/dist/components/DropdownSelector/index.story.d.ts +3 -0
  13. package/dist/components/DropdownSelector/index.story.d.ts.map +1 -1
  14. package/dist/{index.cjs → index.cjs.js} +250 -184
  15. package/dist/index.cjs.js.map +1 -0
  16. package/dist/{index.js → index.esm.js} +203 -137
  17. package/dist/index.esm.js.map +1 -0
  18. package/dist/styled.d.ts +75 -73
  19. package/dist/styled.d.ts.map +1 -1
  20. package/package.json +24 -24
  21. package/src/components/DropdownSelector/Divider.tsx +13 -0
  22. package/src/components/DropdownSelector/DropdownPopover.tsx +72 -0
  23. package/src/components/DropdownSelector/ListBoxSection.tsx +60 -0
  24. package/src/components/DropdownSelector/Listbox.tsx +14 -75
  25. package/src/components/DropdownSelector/Option.tsx +66 -0
  26. package/src/components/DropdownSelector/index.story.tsx +118 -45
  27. package/src/components/DropdownSelector/index.tsx +8 -11
  28. package/src/components/TagItem/index.tsx +5 -3
  29. package/dist/components/DropdownSelector/Popover.d.ts +0 -10
  30. package/dist/components/DropdownSelector/Popover.d.ts.map +0 -1
  31. package/dist/index.cjs.map +0 -1
  32. package/dist/index.js.map +0 -1
  33. 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.0",
3
+ "version": "2.5.0",
4
4
  "license": "Apache-2.0",
5
- "type": "module",
6
- "main": "./dist/index.cjs",
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.3.0",
54
- "@charcoal-ui/styled": "^2.3.0",
55
- "@charcoal-ui/theme": "^2.3.0",
56
- "@charcoal-ui/utils": "^2.3.0",
57
- "@react-aria/button": "^3.6.3",
58
- "@react-aria/checkbox": "^3.2.3",
59
- "@react-aria/dialog": "^3.2.1",
60
- "@react-aria/focus": "^3.6.1",
61
- "@react-aria/listbox": "^3.7.1",
62
- "@react-aria/overlays": "^3.12.1",
63
- "@react-aria/radio": "^3.4.0",
64
- "@react-aria/select": "^3.8.2",
65
- "@react-aria/ssr": "^3.3.0",
66
- "@react-aria/switch": "^3.1.3",
67
- "@react-aria/textfield": "^3.5.0",
68
- "@react-aria/utils": "^3.14.2",
69
- "@react-aria/visually-hidden": "^3.2.3",
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": "b6b0cd6117cf288735bdcf167098ace9b28e0072"
91
+ "gitHead": "f2d6f530bbc4467e9205a4e70ce65a57372860a8"
92
92
  }
@@ -0,0 +1,13 @@
1
+ import styled from 'styled-components'
2
+
3
+ export const Divider = styled.div.attrs({ role: 'separator' })`
4
+ display: flex;
5
+
6
+ &:before {
7
+ content: '';
8
+ display: block;
9
+ width: 100%;
10
+ height: 1px;
11
+ background: #00000014;
12
+ }
13
+ `
@@ -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, { css } from 'styled-components'
2
+ import styled from 'styled-components'
3
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'
4
+ import { useListBox } from '@react-aria/listbox'
9
5
  import { theme } from '../../styled'
10
6
 
11
- import type { Node } from '@react-types/shared'
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
- <Option item={node} state={state} mode={mode} />
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
- <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>
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
- <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>
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
- <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>
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
- <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>
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 open={state.isOpen} onClose={() => state.close()}>
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: 288px;
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
- `