@charcoal-ui/react 2.7.0 → 3.0.0-beta.1

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 (36) hide show
  1. package/dist/components/Button/index.d.ts +1 -1
  2. package/dist/components/Button/index.d.ts.map +1 -1
  3. package/dist/components/DropdownSelector/DropdownPopover.d.ts.map +1 -1
  4. package/dist/components/DropdownSelector/OptionItem.d.ts +7 -0
  5. package/dist/components/DropdownSelector/OptionItem.d.ts.map +1 -0
  6. package/dist/components/DropdownSelector/OptionLi.d.ts +11 -0
  7. package/dist/components/DropdownSelector/OptionLi.d.ts.map +1 -0
  8. package/dist/components/DropdownSelector/index.d.ts +22 -29
  9. package/dist/components/DropdownSelector/index.d.ts.map +1 -1
  10. package/dist/components/DropdownSelector/index.story.d.ts +5 -18
  11. package/dist/components/DropdownSelector/index.story.d.ts.map +1 -1
  12. package/dist/components/DropdownSelector/utils/focusIfHTMLLIElement.d.ts +6 -0
  13. package/dist/components/DropdownSelector/utils/focusIfHTMLLIElement.d.ts.map +1 -0
  14. package/dist/components/DropdownSelector/utils/handleFocusByKeyBoard.d.ts +6 -0
  15. package/dist/components/DropdownSelector/utils/handleFocusByKeyBoard.d.ts.map +1 -0
  16. package/dist/index.cjs.js +265 -313
  17. package/dist/index.cjs.js.map +1 -1
  18. package/dist/index.d.ts +2 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.esm.js +248 -296
  21. package/dist/index.esm.js.map +1 -1
  22. package/package.json +6 -6
  23. package/src/components/Button/index.story.tsx +6 -6
  24. package/src/components/Button/index.tsx +5 -5
  25. package/src/components/DropdownSelector/DropdownPopover.tsx +9 -15
  26. package/src/components/DropdownSelector/OptionItem.tsx +85 -0
  27. package/src/components/DropdownSelector/index.story.tsx +69 -156
  28. package/src/components/DropdownSelector/index.tsx +110 -140
  29. package/src/components/DropdownSelector/utils/focusIfHTMLLIElement.tsx +12 -0
  30. package/src/components/DropdownSelector/utils/handleFocusByKeyBoard.tsx +20 -0
  31. package/src/components/Modal/index.story.tsx +5 -5
  32. package/src/components/Modal/index.tsx +1 -1
  33. package/src/index.ts +6 -1
  34. package/src/components/DropdownSelector/ListBoxSection.tsx +0 -60
  35. package/src/components/DropdownSelector/Listbox.tsx +0 -67
  36. 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": "2.7.0",
3
+ "version": "3.0.0-beta.1",
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": "^2.7.0",
53
- "@charcoal-ui/styled": "^2.7.0",
54
- "@charcoal-ui/theme": "^2.7.0",
55
- "@charcoal-ui/utils": "^2.7.0",
52
+ "@charcoal-ui/icons": "^3.0.0-beta.1",
53
+ "@charcoal-ui/styled": "^3.0.0-beta.1",
54
+ "@charcoal-ui/theme": "^3.0.0-beta.1",
55
+ "@charcoal-ui/utils": "^3.0.0-beta.1",
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": "77e32148d5a981f988e8bbc0c88aec82b0936a1a"
91
+ "gitHead": "b1c0225c8ad4d3a5492c0e406e8604eea8284ed8"
92
92
  }
@@ -58,7 +58,7 @@ Small.args = {
58
58
 
59
59
  export const Fixed: Story<ButtonProps> = DefaultStory.bind({})
60
60
  Fixed.args = {
61
- fixed: true,
61
+ fullWidth: true,
62
62
  }
63
63
 
64
64
  export const Disabled: Story<ButtonProps> = DefaultStory.bind({})
@@ -121,10 +121,10 @@ const LayoutExampleStory = (args: ButtonProps) => (
121
121
  gap: 8px;
122
122
  `}
123
123
  >
124
- <Button {...args} variant="Primary" fixed>
124
+ <Button {...args} variant="Primary" fullWidth>
125
125
  Submit
126
126
  </Button>
127
- <Button {...args} variant="Default" fixed>
127
+ <Button {...args} variant="Default" fullWidth>
128
128
  Cancel
129
129
  </Button>
130
130
  </div>
@@ -156,10 +156,10 @@ const LayoutExampleStory = (args: ButtonProps) => (
156
156
  }
157
157
  `}
158
158
  >
159
- <Button {...args} variant="Primary" fixed>
159
+ <Button {...args} variant="Primary" fullWidth>
160
160
  Submit
161
161
  </Button>
162
- <Button {...args} variant="Default" fixed>
162
+ <Button {...args} variant="Default" fullWidth>
163
163
  Cancel
164
164
  </Button>
165
165
  </div>
@@ -174,7 +174,7 @@ const LayoutExampleStory = (args: ButtonProps) => (
174
174
  }
175
175
  `}
176
176
  >
177
- <Button {...args} variant="Primary" fixed>
177
+ <Button {...args} variant="Primary" fullWidth>
178
178
  すべて見る
179
179
  </Button>
180
180
  <Button {...args} variant="Default">
@@ -19,7 +19,7 @@ interface StyledProps {
19
19
  /**
20
20
  * 幅を最大まで広げて描画
21
21
  */
22
- fixed: boolean
22
+ fullWidth: boolean
23
23
  }
24
24
 
25
25
  export type ButtonProps = Partial<StyledProps> & ClickableProps
@@ -29,7 +29,7 @@ const Button = React.forwardRef<ClickableElement, ButtonProps>(function Button(
29
29
  children,
30
30
  variant = 'Default',
31
31
  size = 'M',
32
- fixed = false,
32
+ fullWidth: fixed = false,
33
33
  disabled = false,
34
34
  ...rest
35
35
  },
@@ -41,7 +41,7 @@ const Button = React.forwardRef<ClickableElement, ButtonProps>(function Button(
41
41
  disabled={disabled}
42
42
  variant={variant}
43
43
  size={size}
44
- fixed={fixed}
44
+ fullWidth={fixed}
45
45
  ref={ref}
46
46
  >
47
47
  {children}
@@ -54,11 +54,11 @@ const StyledButton = styled(Clickable)
54
54
  .withConfig<StyledProps>({
55
55
  shouldForwardProp(prop) {
56
56
  // fixed は <button> 要素に渡ってはいけない
57
- return prop !== 'fixed'
57
+ return prop !== 'fullWidth'
58
58
  },
59
59
  })
60
60
  .attrs<StyledProps, ReturnType<typeof styledProps>>(styledProps)`
61
- width: ${(p) => (p.fixed ? 'stretch' : 'min-content')};
61
+ width: ${(p) => (p.fullWidth ? 'stretch' : 'min-content')};
62
62
  display: inline-grid;
63
63
  align-items: center;
64
64
  justify-content: center;
@@ -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
- // 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
- })
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 { Section } from 'react-stately'
4
- import DropdownSelector, {
5
- DropdownSelectorItem,
6
- DropdownSelectorProps,
7
- } from '.'
2
+ import DropdownSelector, { DropdownSelectorProps } from '.'
8
3
  import { Story } from '../../_lib/compat'
9
- import Clickable from '../Clickable'
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
- 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
- )
16
+ const baseProps: DropdownSelectorProps = {
17
+ label: 'Label',
18
+ value: '',
19
+ placeholder: 'placeholder',
20
+ onChange: () => {
21
+ //
22
+ },
81
23
  }
82
24
 
83
- export const Many: Story<DropdownSelectorProps> = (props) => {
84
- const [value, setValue] = useState('50')
25
+ export const Playground: Story<Props> = (props: DropdownSelectorProps) => {
26
+ const [selected, setSelected] = useState('50')
85
27
  return (
86
- <div style={{ padding: '300px 100px' }}>
28
+ <div style={{ width: 288 }}>
87
29
  <DropdownSelector
88
30
  {...props}
89
- placeholder={'Drop Down menu'}
90
- onChange={(v) => {
91
- setValue(v.toString())
92
- action('change')
31
+ onChange={(value) => {
32
+ setSelected(value)
93
33
  }}
94
- onOpenChange={action('open')}
95
- value={value}
34
+ value={selected}
35
+ label="label"
96
36
  >
97
37
  {[...(Array(100) as undefined[])].map((_, i) => {
98
38
  return (
99
- <DropdownSelectorItem textValue={i.toString()} key={i}>
100
- 選択肢{i}
101
- </DropdownSelectorItem>
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
- type HasLabelProps = {
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
- HasLabel.args = {
139
- disabled: false,
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
- <DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
168
- <DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
169
- <DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
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
- WithSeparator.args = {
176
- mode: 'separator',
177
- }
71
+ Basic.args = { ...baseProps }
178
72
 
179
- type InvalidProps = {
180
- disabled?: boolean
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
- disabled={disabled}
193
- placeholder={'Drop Down menu'}
194
- onChange={action('change')}
195
- onOpenChange={action('open')}
79
+ onChange={(value) => {
80
+ setSelected(value)
81
+ }}
82
+ value={selected}
83
+ label="label"
196
84
  >
197
- <DropdownSelectorItem key="1">選択肢1</DropdownSelectorItem>
198
- <DropdownSelectorItem key="2">選択肢2</DropdownSelectorItem>
199
- <DropdownSelectorItem key="3">選択肢3</DropdownSelectorItem>
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
- Invalid.args = {
206
- disabled: false,
207
- }
120
+ CustomChildren.args = { ...baseProps }