@charcoal-ui/react 3.0.0-beta.2 → 3.0.0-beta.4

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 (186) hide show
  1. package/dist/_lib/compat.d.ts +19 -1
  2. package/dist/_lib/compat.d.ts.map +1 -1
  3. package/dist/_lib/index.d.ts +7 -0
  4. package/dist/_lib/index.d.ts.map +1 -1
  5. package/dist/components/Button/index.d.ts +1 -2
  6. package/dist/components/Button/index.d.ts.map +1 -1
  7. package/dist/components/Button/index.story.d.ts +1 -2
  8. package/dist/components/Button/index.story.d.ts.map +1 -1
  9. package/dist/components/Button/index.test.d.ts +4 -0
  10. package/dist/components/Button/index.test.d.ts.map +1 -0
  11. package/dist/components/Checkbox/index.d.ts +2 -1
  12. package/dist/components/Checkbox/index.d.ts.map +1 -1
  13. package/dist/components/Checkbox/index.story.d.ts +2 -2
  14. package/dist/components/Checkbox/index.story.d.ts.map +1 -1
  15. package/dist/components/Clickable/index.d.ts +1 -1
  16. package/dist/components/Clickable/index.d.ts.map +1 -1
  17. package/dist/components/Clickable/index.story.d.ts +1 -2
  18. package/dist/components/Clickable/index.story.d.ts.map +1 -1
  19. package/dist/components/DropdownSelector/Divider.d.ts +3 -0
  20. package/dist/components/DropdownSelector/Divider.d.ts.map +1 -1
  21. package/dist/components/DropdownSelector/DropdownMenuItem.d.ts +7 -0
  22. package/dist/components/DropdownSelector/DropdownMenuItem.d.ts.map +1 -0
  23. package/dist/components/DropdownSelector/DropdownPopover.d.ts +8 -8
  24. package/dist/components/DropdownSelector/DropdownPopover.d.ts.map +1 -1
  25. package/dist/components/DropdownSelector/ListItem/index.d.ts +18 -0
  26. package/dist/components/DropdownSelector/ListItem/index.d.ts.map +1 -0
  27. package/dist/components/DropdownSelector/ListItem/index.story.d.ts +9 -0
  28. package/dist/components/DropdownSelector/ListItem/index.story.d.ts.map +1 -0
  29. package/dist/components/DropdownSelector/MenuItem/index.d.ts +11 -0
  30. package/dist/components/DropdownSelector/MenuItem/index.d.ts.map +1 -0
  31. package/dist/components/DropdownSelector/MenuItem/internals/handleFocusByKeyBoard.d.ts +9 -0
  32. package/dist/components/DropdownSelector/MenuItem/internals/handleFocusByKeyBoard.d.ts.map +1 -0
  33. package/dist/components/DropdownSelector/MenuItem/internals/useMenuItemHandleKeyDown.d.ts +10 -0
  34. package/dist/components/DropdownSelector/MenuItem/internals/useMenuItemHandleKeyDown.d.ts.map +1 -0
  35. package/dist/components/DropdownSelector/MenuItemGroup/index.d.ts +14 -0
  36. package/dist/components/DropdownSelector/MenuItemGroup/index.d.ts.map +1 -0
  37. package/dist/components/DropdownSelector/MenuList/MenuListContext.d.ts +10 -0
  38. package/dist/components/DropdownSelector/MenuList/MenuListContext.d.ts.map +1 -0
  39. package/dist/components/DropdownSelector/MenuList/index.d.ts +18 -0
  40. package/dist/components/DropdownSelector/MenuList/index.d.ts.map +1 -0
  41. package/dist/components/DropdownSelector/MenuList/index.story.d.ts +11 -0
  42. package/dist/components/DropdownSelector/MenuList/index.story.d.ts.map +1 -0
  43. package/dist/components/DropdownSelector/MenuList/internals/getValuesRecursive.d.ts +11 -0
  44. package/dist/components/DropdownSelector/MenuList/internals/getValuesRecursive.d.ts.map +1 -0
  45. package/dist/components/DropdownSelector/Popover/index.d.ts +17 -0
  46. package/dist/components/DropdownSelector/Popover/index.d.ts.map +1 -0
  47. package/dist/components/DropdownSelector/Popover/index.story.d.ts +9 -0
  48. package/dist/components/DropdownSelector/Popover/index.story.d.ts.map +1 -0
  49. package/dist/components/DropdownSelector/index.d.ts +3 -10
  50. package/dist/components/DropdownSelector/index.d.ts.map +1 -1
  51. package/dist/components/DropdownSelector/index.story.d.ts +4 -4
  52. package/dist/components/DropdownSelector/index.story.d.ts.map +1 -1
  53. package/dist/components/DropdownSelector/utils/findPreviewRecursive.d.ts +12 -0
  54. package/dist/components/DropdownSelector/utils/findPreviewRecursive.d.ts.map +1 -0
  55. package/dist/components/FieldLabel/index.d.ts +1 -1
  56. package/dist/components/FieldLabel/index.d.ts.map +1 -1
  57. package/dist/components/Icon/index.d.ts +1 -1
  58. package/dist/components/Icon/index.d.ts.map +1 -1
  59. package/dist/components/Icon/index.story.d.ts +2 -3
  60. package/dist/components/Icon/index.story.d.ts.map +1 -1
  61. package/dist/components/IconButton/index.d.ts +1 -2
  62. package/dist/components/IconButton/index.d.ts.map +1 -1
  63. package/dist/components/IconButton/index.story.d.ts +1 -2
  64. package/dist/components/IconButton/index.story.d.ts.map +1 -1
  65. package/dist/components/LoadingSpinner/index.d.ts +9 -8
  66. package/dist/components/LoadingSpinner/index.d.ts.map +1 -1
  67. package/dist/components/LoadingSpinner/index.story.d.ts +1 -2
  68. package/dist/components/LoadingSpinner/index.story.d.ts.map +1 -1
  69. package/dist/components/Modal/ModalPlumbing.d.ts.map +1 -1
  70. package/dist/components/Modal/index.d.ts +18 -27
  71. package/dist/components/Modal/index.d.ts.map +1 -1
  72. package/dist/components/Modal/index.story.d.ts +12 -2
  73. package/dist/components/Modal/index.story.d.ts.map +1 -1
  74. package/dist/components/MultiSelect/context.d.ts +1 -1
  75. package/dist/components/MultiSelect/context.d.ts.map +1 -1
  76. package/dist/components/MultiSelect/index.d.ts +18 -6
  77. package/dist/components/MultiSelect/index.d.ts.map +1 -1
  78. package/dist/components/MultiSelect/index.story.d.ts +21 -16
  79. package/dist/components/MultiSelect/index.story.d.ts.map +1 -1
  80. package/dist/components/Radio/index.d.ts +13 -6
  81. package/dist/components/Radio/index.d.ts.map +1 -1
  82. package/dist/components/Radio/index.story.d.ts +11 -8
  83. package/dist/components/Radio/index.story.d.ts.map +1 -1
  84. package/dist/components/SegmentedControl/RadioGroupContext.d.ts +1 -1
  85. package/dist/components/SegmentedControl/RadioGroupContext.d.ts.map +1 -1
  86. package/dist/components/SegmentedControl/index.d.ts +2 -1
  87. package/dist/components/SegmentedControl/index.d.ts.map +1 -1
  88. package/dist/components/SegmentedControl/index.story.d.ts +1 -2
  89. package/dist/components/SegmentedControl/index.story.d.ts.map +1 -1
  90. package/dist/components/Switch/index.d.ts +3 -2
  91. package/dist/components/Switch/index.d.ts.map +1 -1
  92. package/dist/components/Switch/index.story.d.ts +1 -2
  93. package/dist/components/Switch/index.story.d.ts.map +1 -1
  94. package/dist/components/TagItem/index.d.ts +3 -3
  95. package/dist/components/TagItem/index.d.ts.map +1 -1
  96. package/dist/components/TagItem/index.story.d.ts +2 -3
  97. package/dist/components/TagItem/index.story.d.ts.map +1 -1
  98. package/dist/components/TextArea/TextArea.story.d.ts +28 -0
  99. package/dist/components/TextArea/TextArea.story.d.ts.map +1 -0
  100. package/dist/components/TextArea/index.d.ts +21 -0
  101. package/dist/components/TextArea/index.d.ts.map +1 -0
  102. package/dist/components/TextField/TextField.story.d.ts +28 -0
  103. package/dist/components/TextField/TextField.story.d.ts.map +1 -0
  104. package/dist/components/TextField/index.d.ts +8 -30
  105. package/dist/components/TextField/index.d.ts.map +1 -1
  106. package/dist/core/CharcoalProvider.d.ts +1 -1
  107. package/dist/core/CharcoalProvider.d.ts.map +1 -1
  108. package/dist/core/ComponentAbstraction.d.ts +1 -1
  109. package/dist/core/ComponentAbstraction.d.ts.map +1 -1
  110. package/dist/index.cjs.js +1064 -771
  111. package/dist/index.cjs.js.map +1 -1
  112. package/dist/index.d.ts +5 -3
  113. package/dist/index.d.ts.map +1 -1
  114. package/dist/index.esm.js +1028 -750
  115. package/dist/index.esm.js.map +1 -1
  116. package/dist/styled.d.ts +13 -13
  117. package/package.json +7 -7
  118. package/src/_lib/compat.ts +20 -1
  119. package/src/_lib/index.ts +23 -0
  120. package/src/components/Button/__snapshots__/index.test.tsx.snap +385 -0
  121. package/src/components/Button/index.story.tsx +1 -1
  122. package/src/components/Button/index.test.tsx +24 -0
  123. package/src/components/Button/index.tsx +2 -2
  124. package/src/components/Checkbox/index.story.tsx +1 -1
  125. package/src/components/Checkbox/index.tsx +4 -2
  126. package/src/components/Clickable/index.story.tsx +0 -1
  127. package/src/components/Clickable/index.tsx +1 -1
  128. package/src/components/DropdownSelector/Divider.tsx +3 -0
  129. package/src/components/DropdownSelector/DropdownMenuItem.tsx +40 -0
  130. package/src/components/DropdownSelector/DropdownPopover.tsx +21 -42
  131. package/src/components/DropdownSelector/ListItem/index.story.tsx +51 -0
  132. package/src/components/DropdownSelector/ListItem/index.tsx +58 -0
  133. package/src/components/DropdownSelector/MenuItem/index.tsx +31 -0
  134. package/src/components/DropdownSelector/MenuItem/internals/handleFocusByKeyBoard.tsx +43 -0
  135. package/src/components/DropdownSelector/MenuItem/internals/useMenuItemHandleKeyDown.tsx +55 -0
  136. package/src/components/DropdownSelector/MenuItemGroup/index.tsx +42 -0
  137. package/src/components/DropdownSelector/MenuList/MenuListContext.ts +17 -0
  138. package/src/components/DropdownSelector/MenuList/index.story.tsx +51 -0
  139. package/src/components/DropdownSelector/MenuList/index.tsx +51 -0
  140. package/src/components/DropdownSelector/MenuList/internals/getValuesRecursive.tsx +35 -0
  141. package/src/components/DropdownSelector/Popover/index.story.tsx +65 -0
  142. package/src/components/DropdownSelector/Popover/index.tsx +69 -0
  143. package/src/components/DropdownSelector/index.story.tsx +56 -21
  144. package/src/components/DropdownSelector/index.tsx +19 -60
  145. package/src/components/DropdownSelector/utils/findPreviewRecursive.tsx +39 -0
  146. package/src/components/FieldLabel/index.tsx +1 -1
  147. package/src/components/Icon/index.story.tsx +0 -1
  148. package/src/components/Icon/index.tsx +1 -1
  149. package/src/components/IconButton/index.story.tsx +0 -1
  150. package/src/components/IconButton/index.tsx +2 -2
  151. package/src/components/LoadingSpinner/index.story.tsx +8 -2
  152. package/src/components/LoadingSpinner/index.tsx +44 -29
  153. package/src/components/Modal/ModalPlumbing.tsx +0 -1
  154. package/src/components/Modal/index.story.tsx +0 -1
  155. package/src/components/Modal/index.tsx +19 -12
  156. package/src/components/MultiSelect/context.ts +2 -2
  157. package/src/components/MultiSelect/index.story.tsx +26 -27
  158. package/src/components/MultiSelect/index.test.tsx +5 -23
  159. package/src/components/MultiSelect/index.tsx +83 -78
  160. package/src/components/Radio/index.story.tsx +7 -9
  161. package/src/components/Radio/index.test.tsx +3 -4
  162. package/src/components/Radio/index.tsx +24 -23
  163. package/src/components/SegmentedControl/RadioGroupContext.tsx +2 -1
  164. package/src/components/SegmentedControl/index.story.tsx +0 -1
  165. package/src/components/SegmentedControl/index.tsx +16 -5
  166. package/src/components/Switch/index.story.tsx +1 -1
  167. package/src/components/Switch/index.tsx +38 -32
  168. package/src/components/TagItem/index.story.tsx +0 -1
  169. package/src/components/TagItem/index.tsx +1 -6
  170. package/src/components/TextArea/TextArea.story.tsx +61 -0
  171. package/src/components/TextArea/index.tsx +246 -0
  172. package/src/components/TextField/{index.story.tsx → TextField.story.tsx} +6 -29
  173. package/src/components/TextField/index.tsx +148 -378
  174. package/src/components/a11y.test.tsx +0 -1
  175. package/src/core/CharcoalProvider.tsx +1 -1
  176. package/src/core/ComponentAbstraction.tsx +2 -1
  177. package/src/index.ts +8 -6
  178. package/dist/components/DropdownSelector/OptionItem.d.ts +0 -7
  179. package/dist/components/DropdownSelector/OptionItem.d.ts.map +0 -1
  180. package/dist/components/DropdownSelector/utils/focusIfHTMLLIElement.d.ts +0 -6
  181. package/dist/components/DropdownSelector/utils/focusIfHTMLLIElement.d.ts.map +0 -1
  182. package/dist/components/DropdownSelector/utils/handleFocusByKeyBoard.d.ts +0 -6
  183. package/dist/components/DropdownSelector/utils/handleFocusByKeyBoard.d.ts.map +0 -1
  184. package/src/components/DropdownSelector/OptionItem.tsx +0 -85
  185. package/src/components/DropdownSelector/utils/focusIfHTMLLIElement.tsx +0 -12
  186. package/src/components/DropdownSelector/utils/handleFocusByKeyBoard.tsx +0 -20
@@ -0,0 +1,69 @@
1
+ import { RefObject, useRef } from 'react'
2
+ import { ReactNode } from 'react'
3
+ import { DismissButton, Overlay, usePopover } from '@react-aria/overlays'
4
+ import styled from 'styled-components'
5
+ import { theme } from '../../../styled'
6
+
7
+ export type PopoverProps = {
8
+ isOpen: boolean
9
+ onClose: () => void
10
+ children: ReactNode
11
+ triggerRef: RefObject<Element>
12
+ popoverRef?: RefObject<HTMLDivElement>
13
+ }
14
+
15
+ const _empty = () => null
16
+
17
+ /**
18
+ * 画面の全面に動的に開くことができるコンテナ要素
19
+ * 外の要素をクリックしたり、内部からフォーカスを移動した場合に自動的に閉じる
20
+ *
21
+ * triggerRefの付近に画面内に収まるように表示される
22
+ */
23
+ export default function Popover(props: PopoverProps) {
24
+ const defaultPopoverRef = useRef<HTMLDivElement>(null)
25
+ const finalPopoverRef =
26
+ props.popoverRef === undefined ? defaultPopoverRef : props.popoverRef
27
+ const { popoverProps, underlayProps } = usePopover(
28
+ {
29
+ triggerRef: props.triggerRef,
30
+ popoverRef: finalPopoverRef,
31
+ containerPadding: 16,
32
+ },
33
+ {
34
+ close: props.onClose,
35
+ isOpen: props.isOpen,
36
+ // never used
37
+ open: _empty,
38
+ setOpen: _empty,
39
+ toggle: _empty,
40
+ }
41
+ )
42
+
43
+ if (!props.isOpen) return null
44
+
45
+ return (
46
+ <Overlay portalContainer={document.body}>
47
+ <div {...underlayProps} style={{ position: 'fixed', inset: 0 }} />
48
+ <DropdownPopoverDiv {...popoverProps} ref={finalPopoverRef}>
49
+ <DismissButton onDismiss={() => props.onClose()} />
50
+ {props.children}
51
+ <DismissButton onDismiss={() => props.onClose()} />
52
+ </DropdownPopoverDiv>
53
+ </Overlay>
54
+ )
55
+ }
56
+
57
+ const DropdownPopoverDiv = styled.div`
58
+ margin: 4px 0;
59
+ list-style: none;
60
+ overflow: auto;
61
+ max-height: inherit;
62
+
63
+ ${theme((o) => [
64
+ o.bg.background1,
65
+ o.border.default,
66
+ o.borderRadius(8),
67
+ o.padding.vertical(8),
68
+ ])}
69
+ `
@@ -1,18 +1,15 @@
1
- import React, { useState } from 'react'
1
+ import { useState } from 'react'
2
2
  import DropdownSelector, { DropdownSelectorProps } from '.'
3
3
  import { Story } from '../../_lib/compat'
4
- import { OptionItem } from './OptionItem'
4
+ import { Divider } from './Divider'
5
+ import MenuItemGroup from './MenuItemGroup'
6
+ import DropdownMenuItem from './DropdownMenuItem'
5
7
 
6
8
  export default {
7
9
  title: 'DropdownSelector',
8
10
  component: DropdownSelector,
9
11
  }
10
12
 
11
- type Props = Omit<
12
- DropdownSelectorProps,
13
- 'subLabel' | 'children' | 'onOpenChange'
14
- >
15
-
16
13
  const baseProps: DropdownSelectorProps = {
17
14
  label: 'Label',
18
15
  value: '',
@@ -20,9 +17,12 @@ const baseProps: DropdownSelectorProps = {
20
17
  onChange: () => {
21
18
  //
22
19
  },
20
+ children: <DropdownMenuItem value="item" />,
23
21
  }
24
22
 
25
- export const Playground: Story<Props> = (props: DropdownSelectorProps) => {
23
+ export const Playground: Story<DropdownSelectorProps> = (
24
+ props: DropdownSelectorProps
25
+ ) => {
26
26
  const [selected, setSelected] = useState('50')
27
27
  return (
28
28
  <div style={{ width: 288 }}>
@@ -36,9 +36,9 @@ export const Playground: Story<Props> = (props: DropdownSelectorProps) => {
36
36
  >
37
37
  {[...(Array(100) as undefined[])].map((_, i) => {
38
38
  return (
39
- <OptionItem key={i} value={i.toString()}>
39
+ <DropdownMenuItem key={i} value={i.toString()}>
40
40
  {i}
41
- </OptionItem>
41
+ </DropdownMenuItem>
42
42
  )
43
43
  })}
44
44
  </DropdownSelector>
@@ -48,7 +48,9 @@ export const Playground: Story<Props> = (props: DropdownSelectorProps) => {
48
48
 
49
49
  Playground.args = { ...baseProps }
50
50
 
51
- export const Basic: Story<Props> = (props: DropdownSelectorProps) => {
51
+ export const Basic: Story<DropdownSelectorProps> = (
52
+ props: DropdownSelectorProps
53
+ ) => {
52
54
  const [selected, setSelected] = useState('')
53
55
  return (
54
56
  <div style={{ width: 288 }}>
@@ -60,9 +62,9 @@ export const Basic: Story<Props> = (props: DropdownSelectorProps) => {
60
62
  value={selected}
61
63
  label="label"
62
64
  >
63
- <OptionItem value={'10'}>Apple</OptionItem>
64
- <OptionItem value={'20'}>Banana</OptionItem>
65
- <OptionItem value={'30'}>Orange</OptionItem>
65
+ <DropdownMenuItem value={'10'}>Apple</DropdownMenuItem>
66
+ <DropdownMenuItem value={'20'}>Banana</DropdownMenuItem>
67
+ <DropdownMenuItem value={'30'}>Orange</DropdownMenuItem>
66
68
  </DropdownSelector>
67
69
  </div>
68
70
  )
@@ -70,7 +72,9 @@ export const Basic: Story<Props> = (props: DropdownSelectorProps) => {
70
72
 
71
73
  Basic.args = { ...baseProps }
72
74
 
73
- export const CustomChildren: Story<Props> = (props: DropdownSelectorProps) => {
75
+ export const CustomChildren: Story<DropdownSelectorProps> = (
76
+ props: DropdownSelectorProps
77
+ ) => {
74
78
  const [selected, setSelected] = useState('10')
75
79
  return (
76
80
  <div style={{ width: 288 }}>
@@ -82,7 +86,7 @@ export const CustomChildren: Story<Props> = (props: DropdownSelectorProps) => {
82
86
  value={selected}
83
87
  label="label"
84
88
  >
85
- <OptionItem value={'10'}>
89
+ <DropdownMenuItem value={'10'}>
86
90
  <div
87
91
  style={{
88
92
  color: 'pink',
@@ -91,8 +95,8 @@ export const CustomChildren: Story<Props> = (props: DropdownSelectorProps) => {
91
95
  >
92
96
  Apple
93
97
  </div>
94
- </OptionItem>
95
- <OptionItem value={'20'}>
98
+ </DropdownMenuItem>
99
+ <DropdownMenuItem value={'20'}>
96
100
  <div
97
101
  style={{
98
102
  color: 'yellowgreen',
@@ -101,8 +105,8 @@ export const CustomChildren: Story<Props> = (props: DropdownSelectorProps) => {
101
105
  >
102
106
  Banana
103
107
  </div>
104
- </OptionItem>
105
- <OptionItem value={'30'}>
108
+ </DropdownMenuItem>
109
+ <DropdownMenuItem value={'30'}>
106
110
  <div
107
111
  style={{
108
112
  color: 'orange',
@@ -111,10 +115,41 @@ export const CustomChildren: Story<Props> = (props: DropdownSelectorProps) => {
111
115
  >
112
116
  Orange
113
117
  </div>
114
- </OptionItem>
118
+ </DropdownMenuItem>
115
119
  </DropdownSelector>
116
120
  </div>
117
121
  )
118
122
  }
119
123
 
120
124
  CustomChildren.args = { ...baseProps }
125
+
126
+ export const SectionList: Story<DropdownSelectorProps> = (
127
+ props: DropdownSelectorProps
128
+ ) => {
129
+ const [selected, setSelected] = useState('10')
130
+ return (
131
+ <div style={{ width: 288 }}>
132
+ <DropdownSelector
133
+ {...props}
134
+ onChange={(value) => {
135
+ setSelected(value)
136
+ }}
137
+ value={selected}
138
+ label="label"
139
+ >
140
+ <MenuItemGroup text="fruits">
141
+ <DropdownMenuItem value={'10'}>Apple</DropdownMenuItem>
142
+ <DropdownMenuItem value={'20'}>Banana</DropdownMenuItem>
143
+ <DropdownMenuItem value={'30'}>Orange</DropdownMenuItem>
144
+ </MenuItemGroup>
145
+ <Divider />
146
+ <MenuItemGroup text="vehicle">
147
+ <DropdownMenuItem value={'40'}>bicycle</DropdownMenuItem>
148
+ <DropdownMenuItem value={'50'}>car</DropdownMenuItem>
149
+ <DropdownMenuItem value={'60'}>train</DropdownMenuItem>
150
+ </MenuItemGroup>
151
+ </DropdownSelector>
152
+ </div>
153
+ )
154
+ }
155
+ SectionList.args = { ...baseProps }
@@ -1,18 +1,12 @@
1
- import React, { ReactNode, createContext, useRef } from 'react'
1
+ import { ReactNode, useState, useRef } from 'react'
2
2
  import styled from 'styled-components'
3
- import { useOverlayTriggerState } from 'react-stately'
4
3
  import { disabledSelector } from '@charcoal-ui/utils'
5
4
  import Icon from '../Icon'
6
5
  import FieldLabel from '../FieldLabel'
7
6
  import { theme } from '../../styled'
8
7
  import { DropdownPopover } from './DropdownPopover'
9
-
10
- export const DropdownSelectorContext = createContext({
11
- value: '',
12
- setValue: (_v: string) => {
13
- // empty
14
- },
15
- })
8
+ import { findPreviewRecursive } from './utils/findPreviewRecursive'
9
+ import MenuList, { MenuListChildren } from './MenuList'
16
10
 
17
11
  export type DropdownSelectorProps = {
18
12
  label: string
@@ -25,33 +19,16 @@ export type DropdownSelectorProps = {
25
19
  required?: boolean
26
20
  requiredText?: string
27
21
  subLabel?: ReactNode
28
- children?: ReactNode
22
+ children: MenuListChildren
29
23
  onChange: (value: string) => void
30
24
  }
31
25
 
32
- export type DropdownSelectorOption = {
33
- label: string
34
- id: string
35
- }
36
-
37
26
  const defaultRequiredText = '*必須'
38
27
 
39
28
  export default function DropdownSelector(props: DropdownSelectorProps) {
40
29
  const triggerRef = useRef<HTMLButtonElement>(null)
41
- const state = useOverlayTriggerState({})
42
-
43
- let preview: ReactNode | undefined
44
- const childArray = React.Children.toArray(props.children)
45
- for (let i = 0; i < childArray.length; i++) {
46
- const child = childArray[i]
47
- if (React.isValidElement(child) && 'value' in child.props) {
48
- const find = (child.props as { value: string }).value === props.value
49
- if (find && 'children' in child.props) {
50
- preview = (child.props as { children: ReactNode }).children
51
- break
52
- }
53
- }
54
- }
30
+ const [isOpen, setIsOpen] = useState(false)
31
+ const preview = findPreviewRecursive(props.children, props.value)
55
32
 
56
33
  return (
57
34
  <DropdownSelectorRoot aria-disabled={props.disabled}>
@@ -68,7 +45,7 @@ export default function DropdownSelector(props: DropdownSelectorProps) {
68
45
  disabled={props.disabled}
69
46
  onClick={() => {
70
47
  if (props.disabled === true) return
71
- state.open()
48
+ setIsOpen(true)
72
49
  }}
73
50
  ref={triggerRef}
74
51
  >
@@ -79,25 +56,22 @@ export default function DropdownSelector(props: DropdownSelectorProps) {
79
56
  </DropdownButtonText>
80
57
  <DropdownButtonIcon name="16/Menu" />
81
58
  </DropdownButton>
82
- {state.isOpen && (
59
+ {isOpen && (
83
60
  <DropdownPopover
84
- state={state}
61
+ isOpen={isOpen}
62
+ onClose={() => setIsOpen(false)}
85
63
  triggerRef={triggerRef}
86
64
  value={props.value}
87
65
  >
88
- <ListboxRoot>
89
- <DropdownSelectorContext.Provider
90
- value={{
91
- value: props.value,
92
- setValue: (v) => {
93
- props.onChange(v)
94
- state.close()
95
- },
96
- }}
97
- >
98
- {props.children}
99
- </DropdownSelectorContext.Provider>
100
- </ListboxRoot>
66
+ <MenuList
67
+ value={props.value}
68
+ onChange={(v) => {
69
+ props.onChange(v)
70
+ setIsOpen(false)
71
+ }}
72
+ >
73
+ {props.children}
74
+ </MenuList>
101
75
  </DropdownPopover>
102
76
  )}
103
77
  {props.assistiveText !== undefined && (
@@ -109,21 +83,6 @@ export default function DropdownSelector(props: DropdownSelectorProps) {
109
83
  )
110
84
  }
111
85
 
112
- const ListboxRoot = styled.ul`
113
- padding-left: 0;
114
- margin: 0;
115
- box-sizing: border-box;
116
- list-style: none;
117
- overflow: auto;
118
- max-height: inherit;
119
-
120
- ${theme((o) => [
121
- o.bg.background1,
122
- o.border.default,
123
- o.borderRadius(8),
124
- o.padding.vertical(8),
125
- ])}
126
- `
127
86
  const DropdownSelectorRoot = styled.div`
128
87
  display: inline-block;
129
88
  width: 100%;
@@ -0,0 +1,39 @@
1
+ import { ReactNode } from 'react'
2
+ import * as React from 'react'
3
+
4
+ /**
5
+ * DropdownSelectorの選択中の要素をレンダリングするため、
6
+ * 選択中のMenuItemを再帰的に探索しReactNodeを返す。
7
+ *
8
+ * @param children
9
+ * @param value
10
+ * @param values
11
+ * @returns
12
+ */
13
+ export function findPreviewRecursive(
14
+ children: ReactNode,
15
+ value: string
16
+ ): ReactNode | undefined {
17
+ const childArray = React.Children.toArray(children)
18
+ for (let i = 0; i < childArray.length; i++) {
19
+ const child = childArray[i]
20
+ if (React.isValidElement(child)) {
21
+ if ('value' in child.props) {
22
+ const childValue = (child.props as { value: string }).value
23
+ if (childValue === value && 'children' in child.props) {
24
+ const children = (child.props as { children: ReactNode }).children
25
+ return children
26
+ }
27
+ }
28
+ if ('children' in child.props) {
29
+ const children = findPreviewRecursive(
30
+ (child.props as { children: ReactNode }).children,
31
+ value
32
+ )
33
+ if (children !== undefined) {
34
+ return children
35
+ }
36
+ }
37
+ }
38
+ }
39
+ }
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import * as React from 'react'
2
2
  import styled from 'styled-components'
3
3
  import { createTheme } from '@charcoal-ui/styled'
4
4
 
@@ -1,4 +1,3 @@
1
- import React from 'react'
2
1
  import Icon, { IconProps } from '.'
3
2
  import { KNOWN_ICON_FILES } from '@charcoal-ui/icons'
4
3
  import { Story } from '../../_lib/compat'
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import * as React from 'react'
2
2
 
3
3
  import '@charcoal-ui/icons'
4
4
  import type { PixivIcon, Props } from '@charcoal-ui/icons'
@@ -1,4 +1,3 @@
1
- import React from 'react'
2
1
  import type { Story } from '../../_lib/compat'
3
2
  import '@charcoal-ui/icons'
4
3
  import IconButton from '.'
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import { forwardRef } from 'react'
2
2
  import styled from 'styled-components'
3
3
  import { theme } from '../../styled'
4
4
  import Clickable, { ClickableElement, ClickableProps } from '../Clickable'
@@ -15,7 +15,7 @@ interface StyledProps {
15
15
 
16
16
  export type IconButtonProps = StyledProps & ClickableProps
17
17
 
18
- const IconButton = React.forwardRef<ClickableElement, IconButtonProps>(
18
+ const IconButton = forwardRef<ClickableElement, IconButtonProps>(
19
19
  function IconButtonInner(
20
20
  { variant = 'Default', size = 'M', icon, ...rest }: IconButtonProps,
21
21
  ref
@@ -5,7 +5,7 @@ import {
5
5
  text,
6
6
  withKnobs,
7
7
  } from '@storybook/addon-knobs'
8
- import React, { useRef } from 'react'
8
+ import { useRef } from 'react'
9
9
  import LoadingSpinner, {
10
10
  LoadingSpinnerIcon,
11
11
  LoadingSpinnerIconHandler,
@@ -21,9 +21,15 @@ export function Basic() {
21
21
  const size = number('size', 48)
22
22
  const padding = number('padding', 16)
23
23
  const transparent = boolean('transparent', false)
24
+ const className = text('className', 'basic')
24
25
 
25
26
  return (
26
- <LoadingSpinner size={size} padding={padding} transparent={transparent} />
27
+ <LoadingSpinner
28
+ size={size}
29
+ padding={padding}
30
+ transparent={transparent}
31
+ className={className}
32
+ />
27
33
  )
28
34
  }
29
35
 
@@ -1,19 +1,35 @@
1
- import React, { useImperativeHandle, useRef } from 'react'
1
+ import { forwardRef, useImperativeHandle, useRef, memo } from 'react'
2
2
  import styled, { keyframes } from 'styled-components'
3
3
  import { theme } from '../../styled'
4
4
 
5
- export default function LoadingSpinner({
6
- size = 48,
7
- padding = 16,
8
- transparent = false,
9
- }) {
10
- return (
11
- <LoadingSpinnerRoot size={size} padding={padding} transparent={transparent}>
12
- <LoadingSpinnerIcon />
13
- </LoadingSpinnerRoot>
14
- )
5
+ export type LoadingSpinnerProps = {
6
+ readonly size?: number
7
+ readonly padding?: number
8
+ readonly transparent?: boolean
9
+ readonly className?: string
15
10
  }
16
11
 
12
+ const LoadingSpinner = forwardRef<HTMLDivElement, LoadingSpinnerProps>(
13
+ function LoadingSpinnerInner(
14
+ { size = 48, padding = 16, transparent = false, className },
15
+ ref
16
+ ) {
17
+ return (
18
+ <LoadingSpinnerRoot
19
+ size={size}
20
+ padding={padding}
21
+ transparent={transparent}
22
+ className={className}
23
+ ref={ref}
24
+ >
25
+ <LoadingSpinnerIcon />
26
+ </LoadingSpinnerRoot>
27
+ )
28
+ }
29
+ )
30
+
31
+ export default memo(LoadingSpinner)
32
+
17
33
  const LoadingSpinnerRoot = styled.div.attrs({ role: 'progressbar' })<{
18
34
  size: number
19
35
  padding: number
@@ -67,23 +83,22 @@ export interface LoadingSpinnerIconHandler {
67
83
  restart(): void
68
84
  }
69
85
 
70
- export const LoadingSpinnerIcon = React.forwardRef<
71
- LoadingSpinnerIconHandler,
72
- Props
73
- >(function LoadingSpinnerIcon({ once = false }, ref) {
74
- const iconRef = useRef<HTMLDivElement>(null)
86
+ export const LoadingSpinnerIcon = forwardRef<LoadingSpinnerIconHandler, Props>(
87
+ function LoadingSpinnerIcon({ once = false }, ref) {
88
+ const iconRef = useRef<HTMLDivElement>(null)
75
89
 
76
- useImperativeHandle(ref, () => ({
77
- restart: () => {
78
- if (!iconRef.current) {
79
- return
80
- }
81
- iconRef.current.dataset.resetAnimation = 'true'
82
- // Force reflow hack!
83
- void iconRef.current.offsetWidth
84
- delete iconRef.current.dataset.resetAnimation
85
- },
86
- }))
90
+ useImperativeHandle(ref, () => ({
91
+ restart: () => {
92
+ if (!iconRef.current) {
93
+ return
94
+ }
95
+ iconRef.current.dataset.resetAnimation = 'true'
96
+ // Force reflow hack!
97
+ void iconRef.current.offsetWidth
98
+ delete iconRef.current.dataset.resetAnimation
99
+ },
100
+ }))
87
101
 
88
- return <Icon ref={iconRef} once={once} />
89
- })
102
+ return <Icon ref={iconRef} once={once} />
103
+ }
104
+ )
@@ -1,4 +1,3 @@
1
- import React from 'react'
2
1
  import { ModalTitle } from '.'
3
2
  import styled from 'styled-components'
4
3
  import { theme } from '../../styled'
@@ -1,4 +1,3 @@
1
- import React from 'react'
2
1
  import { Story } from '../../_lib/compat'
3
2
  import Modal, { ModalDismissButton, ModalProps } from '.'
4
3
  import { OverlayProvider } from '@react-aria/overlays'
@@ -1,4 +1,5 @@
1
- import React, { useContext, useRef } from 'react'
1
+ import { useContext, forwardRef, memo } from 'react'
2
+ import * as React from 'react'
2
3
  import {
3
4
  AriaModalOverlayProps,
4
5
  Overlay,
@@ -17,6 +18,7 @@ import { useMedia } from '@charcoal-ui/styled'
17
18
  import { animated, useTransition, easings } from 'react-spring'
18
19
  import Button, { ButtonProps } from '../Button'
19
20
  import IconButton from '../IconButton'
21
+ import { useObjectRef } from '@react-aria/utils'
20
22
 
21
23
  type BottomSheet = boolean | 'full'
22
24
  type Size = 'S' | 'M' | 'L'
@@ -30,6 +32,7 @@ export type ModalProps = AriaModalOverlayProps &
30
32
  bottomSheet?: BottomSheet
31
33
  isOpen: boolean
32
34
  onClose: () => void
35
+ className?: string
33
36
 
34
37
  /**
35
38
  * https://github.com/adobe/react-spectrum/issues/3787
@@ -55,31 +58,32 @@ const DEFAULT_Z_INDEX = 10
55
58
  *
56
59
  * <OverlayProvider>
57
60
  * <App>
58
- * <Modal isOpen={state.isOpen} onClose={() => state.close()} isDismissable>
61
+ * <Modal title="Title" isOpen={state.isOpen} onClose={() => state.close()} isDismissable>
59
62
  * <ModalHeader />
60
- * <ModalBody>...</ModalBody>
61
- * <ModalButtons>...</ModalButtons>
63
+ * <ModalBody>
64
+ * ...
65
+ * <ModalButtons>...</ModalButtons>
66
+ * </ModalBody>
62
67
  * </Modal>
63
68
  * </App>
64
69
  * </OverlayProvider>
65
70
  * ```
66
71
  */
67
- export default function Modal({
68
- children,
69
- zIndex = DEFAULT_Z_INDEX,
70
- portalContainer,
71
- ...props
72
- }: ModalProps) {
72
+ const Modal = forwardRef<HTMLDivElement, ModalProps>(function ModalInner(
73
+ { children, zIndex = DEFAULT_Z_INDEX, portalContainer, ...props },
74
+ external
75
+ ) {
73
76
  const {
74
77
  title,
75
78
  size = 'M',
76
79
  bottomSheet = false,
77
80
  isDismissable,
78
81
  onClose,
82
+ className,
79
83
  isOpen = false,
80
84
  } = props
81
85
 
82
- const ref = useRef<HTMLDivElement>(null)
86
+ const ref = useObjectRef<HTMLDivElement>(external)
83
87
  const { overlayProps, underlayProps } = useOverlay(props, ref)
84
88
 
85
89
  const { modalProps } = useModalOverlay(
@@ -145,6 +149,7 @@ export default function Modal({
145
149
  style={transitionEnabled ? { transform } : {}}
146
150
  size={size}
147
151
  bottomSheet={bottomSheet}
152
+ className={className}
148
153
  >
149
154
  <ModalContext.Provider
150
155
  value={{ titleProps, title, close: onClose, showDismiss }}
@@ -165,7 +170,9 @@ export default function Modal({
165
170
  </Overlay>
166
171
  )
167
172
  )
168
- }
173
+ })
174
+
175
+ export default memo(Modal)
169
176
 
170
177
  const ModalContext = React.createContext<{
171
178
  titleProps: React.HTMLAttributes<HTMLElement>
@@ -5,7 +5,7 @@ type MultiSelectGroupContext = {
5
5
  selected: string[]
6
6
  disabled: boolean
7
7
  readonly: boolean
8
- hasError: boolean
8
+ invalid: boolean
9
9
  onChange: ({ value, selected }: { value: string; selected: boolean }) => void
10
10
  }
11
11
 
@@ -14,7 +14,7 @@ export const MultiSelectGroupContext = createContext<MultiSelectGroupContext>({
14
14
  selected: [],
15
15
  disabled: false,
16
16
  readonly: false,
17
- hasError: false,
17
+ invalid: false,
18
18
  onChange() {
19
19
  throw new Error(
20
20
  'Cannot find `onChange()` handler. Perhaps you forgot to wrap it with `<MultiSelectGroup />` ?'