@charcoal-ui/react 2.6.0 → 2.8.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 (35) hide show
  1. package/dist/components/DropdownSelector/index.story.d.ts.map +1 -1
  2. package/dist/components/LoadingSpinner/index.d.ts.map +1 -1
  3. package/dist/components/Modal/ModalPlumbing.d.ts.map +1 -1
  4. package/dist/components/Modal/index.d.ts +5 -2
  5. package/dist/components/Modal/index.d.ts.map +1 -1
  6. package/dist/components/Radio/index.d.ts.map +1 -1
  7. package/dist/components/SegmentedControl/index.d.ts.map +1 -1
  8. package/dist/components/SegmentedControl/index.story.d.ts.map +1 -1
  9. package/dist/components/Switch/index.story.d.ts +1 -0
  10. package/dist/components/Switch/index.story.d.ts.map +1 -1
  11. package/dist/components/TagItem/index.d.ts +2 -0
  12. package/dist/components/TagItem/index.d.ts.map +1 -1
  13. package/dist/components/TagItem/index.story.d.ts +1 -0
  14. package/dist/components/TagItem/index.story.d.ts.map +1 -1
  15. package/dist/index.cjs.js +144 -90
  16. package/dist/index.cjs.js.map +1 -1
  17. package/dist/index.d.ts +2 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.esm.js +125 -71
  20. package/dist/index.esm.js.map +1 -1
  21. package/package.json +6 -6
  22. package/src/components/Checkbox/index.tsx +4 -0
  23. package/src/components/DropdownSelector/index.story.tsx +4 -1
  24. package/src/components/LoadingSpinner/index.tsx +1 -0
  25. package/src/components/Modal/ModalPlumbing.tsx +2 -10
  26. package/src/components/Modal/index.tsx +79 -61
  27. package/src/components/Radio/index.tsx +3 -0
  28. package/src/components/SegmentedControl/index.story.tsx +2 -0
  29. package/src/components/SegmentedControl/index.tsx +1 -0
  30. package/src/components/Switch/index.story.tsx +20 -1
  31. package/src/components/Switch/index.tsx +58 -33
  32. package/src/components/TagItem/index.story.tsx +1 -0
  33. package/src/components/TagItem/index.tsx +3 -0
  34. package/src/components/TextField/index.tsx +7 -0
  35. package/src/index.ts +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@charcoal-ui/react",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
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.6.0",
53
- "@charcoal-ui/styled": "^2.6.0",
54
- "@charcoal-ui/theme": "^2.6.0",
55
- "@charcoal-ui/utils": "^2.6.0",
52
+ "@charcoal-ui/icons": "^2.8.0",
53
+ "@charcoal-ui/styled": "^2.8.0",
54
+ "@charcoal-ui/theme": "^2.8.0",
55
+ "@charcoal-ui/utils": "^2.8.0",
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": "8579b406b316285a35858512030d2143524ae154"
91
+ "gitHead": "f45afd2e7fd92a3bed12babb8e23e4524057a96d"
92
92
  }
@@ -107,8 +107,12 @@ const CheckboxInput = styled.input`
107
107
  border-color: ${({ theme }) => theme.color.text4};
108
108
  }
109
109
  ${theme((o) => [o.outline.default.focus, o.borderRadius(4)])}
110
+
111
+ /* FIXME: o.outline.default.focus の transition に o.bg.brand の transition が打ち消されてしまう */
112
+ transition: all 0.2s !important;
110
113
  }
111
114
  `
115
+
112
116
  const CheckboxInputOverlay = styled.div<{ checked?: boolean }>`
113
117
  position: absolute;
114
118
  top: -2px;
@@ -47,6 +47,7 @@ export const Sections: Story<DropdownSelectorProps> = (props) => {
47
47
  <div>
48
48
  <DropdownSelector
49
49
  {...props}
50
+ label="sections"
50
51
  placeholder={'Drop Down menu'}
51
52
  onChange={action('change')}
52
53
  onOpenChange={action('open')}
@@ -68,6 +69,7 @@ export const Bottom: Story<DropdownSelectorProps> = (props) => {
68
69
  <div style={{ marginTop: '1000px' }}>
69
70
  <DropdownSelector
70
71
  {...props}
72
+ label="bottom"
71
73
  placeholder={'Drop Down menu'}
72
74
  onChange={action('change')}
73
75
  onOpenChange={action('open')}
@@ -86,6 +88,7 @@ export const Many: Story<DropdownSelectorProps> = (props) => {
86
88
  <div style={{ padding: '300px 100px' }}>
87
89
  <DropdownSelector
88
90
  {...props}
91
+ label="many"
89
92
  placeholder={'Drop Down menu'}
90
93
  onChange={(v) => {
91
94
  setValue(v.toString())
@@ -181,7 +184,7 @@ type InvalidProps = {
181
184
  }
182
185
  export const Invalid: Story<InvalidProps> = ({ disabled }) => {
183
186
  const props: Omit<DropdownSelectorProps, 'children'> = {
184
- label: '',
187
+ label: 'invalid',
185
188
  assertiveText: 'error message',
186
189
  invalid: true,
187
190
  }
@@ -19,6 +19,7 @@ const LoadingSpinnerRoot = styled.div.attrs({ role: 'progressbar' })<{
19
19
  padding: number
20
20
  transparent: boolean
21
21
  }>`
22
+ box-sizing: content-box;
22
23
  margin: auto;
23
24
  padding: ${(props) => props.padding}px;
24
25
  border-radius: 8px;
@@ -2,7 +2,6 @@ import React from 'react'
2
2
  import { ModalTitle } from '.'
3
3
  import styled from 'styled-components'
4
4
  import { theme } from '../../styled'
5
- import { maxWidth } from '@charcoal-ui/utils'
6
5
 
7
6
  export function ModalHeader() {
8
7
  return (
@@ -24,11 +23,7 @@ const StyledModalTitle = styled(ModalTitle)`
24
23
  `
25
24
 
26
25
  export const ModalAlign = styled.div`
27
- ${theme((o) => [o.padding.horizontal(24)])}
28
-
29
- @media ${({ theme }) => maxWidth(theme.breakpoint.screen1)} {
30
- ${theme((o) => [o.padding.horizontal(16)])}
31
- }
26
+ ${theme((o) => [o.padding.horizontal(16)])}
32
27
  `
33
28
 
34
29
  export const ModalBody = styled.div`
@@ -39,9 +34,6 @@ export const ModalButtons = styled.div`
39
34
  display: grid;
40
35
  grid-auto-flow: row;
41
36
  grid-row-gap: 8px;
42
- ${theme((o) => [o.padding.horizontal(24).top(16)])}
43
37
 
44
- @media ${({ theme }) => maxWidth(theme.breakpoint.screen1)} {
45
- ${theme((o) => [o.padding.horizontal(16)])}
46
- }
38
+ ${theme((o) => [o.padding.horizontal(16).top(16)])}
47
39
  `
@@ -18,13 +18,16 @@ import { animated, useTransition, easings } from 'react-spring'
18
18
  import Button, { ButtonProps } from '../Button'
19
19
  import IconButton from '../IconButton'
20
20
 
21
+ type BottomSheet = boolean | 'full'
22
+ type Size = 'S' | 'M' | 'L'
23
+
21
24
  export type ModalProps = AriaModalOverlayProps &
22
25
  AriaDialogProps & {
23
26
  children: React.ReactNode
24
27
  zIndex?: number
25
28
  title: string
26
- size?: 'S' | 'M' | 'L'
27
- bottomSheet?: boolean | 'full'
29
+ size?: Size
30
+ bottomSheet?: BottomSheet
28
31
  isOpen: boolean
29
32
  onClose: () => void
30
33
 
@@ -133,28 +136,30 @@ export default function Modal({
133
136
  style={transitionEnabled ? { backgroundColor } : {}}
134
137
  >
135
138
  <FocusScope contain restoreFocus autoFocus>
136
- <ModalDialog
137
- ref={ref}
138
- {...overlayProps}
139
- {...modalProps}
140
- {...dialogProps}
141
- style={transitionEnabled ? { transform } : {}}
142
- size={size}
143
- bottomSheet={bottomSheet}
144
- >
145
- <ModalContext.Provider
146
- value={{ titleProps, title, close: onClose, showDismiss }}
139
+ <DialogContainer bottomSheet={bottomSheet} size={size}>
140
+ <ModalDialog
141
+ ref={ref}
142
+ {...overlayProps}
143
+ {...modalProps}
144
+ {...dialogProps}
145
+ style={transitionEnabled ? { transform } : {}}
146
+ size={size}
147
+ bottomSheet={bottomSheet}
147
148
  >
148
- {children}
149
- {isDismissable === true && (
150
- <ModalCrossButton
151
- size="S"
152
- icon="24/Close"
153
- onClick={onClose}
154
- />
155
- )}
156
- </ModalContext.Provider>
157
- </ModalDialog>
149
+ <ModalContext.Provider
150
+ value={{ titleProps, title, close: onClose, showDismiss }}
151
+ >
152
+ {children}
153
+ {isDismissable === true && (
154
+ <ModalCrossButton
155
+ size="S"
156
+ icon="24/Close"
157
+ onClick={onClose}
158
+ />
159
+ )}
160
+ </ModalContext.Provider>
161
+ </ModalDialog>
162
+ </DialogContainer>
158
163
  </FocusScope>
159
164
  </ModalBackground>
160
165
  </Overlay>
@@ -176,6 +181,8 @@ const ModalContext = React.createContext<{
176
181
 
177
182
  const ModalBackground = animated(styled.div<{ zIndex: number }>`
178
183
  z-index: ${({ zIndex }) => zIndex};
184
+ overflow: scroll;
185
+ display: flex;
179
186
  position: fixed;
180
187
  top: 0;
181
188
  left: 0;
@@ -185,50 +192,61 @@ const ModalBackground = animated(styled.div<{ zIndex: number }>`
185
192
  ${theme((o) => [o.bg.surface4])}
186
193
  `)
187
194
 
195
+ const DialogContainer = styled.div<{ bottomSheet: BottomSheet; size: Size }>`
196
+ position: relative;
197
+ margin: auto;
198
+ padding: 24px 0;
199
+ width: ${(p) => {
200
+ switch (p.size) {
201
+ case 'S': {
202
+ return columnSystem(3, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
203
+ }
204
+ case 'M': {
205
+ return columnSystem(4, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
206
+ }
207
+ case 'L': {
208
+ return columnSystem(6, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
209
+ }
210
+ default: {
211
+ return unreachable(p.size)
212
+ }
213
+ }
214
+ }}px;
215
+
216
+ @media ${({ theme }) => maxWidth(theme.breakpoint.screen1)} {
217
+ width: calc(100% - 48px);
218
+ ${(p) =>
219
+ p.bottomSheet !== false &&
220
+ css`
221
+ margin: 0;
222
+ padding: 0;
223
+ bottom: 0;
224
+ position: absolute;
225
+ width: 100%;
226
+ ${p.bottomSheet === 'full' ? 'height: 100%' : ''};
227
+ `}
228
+ }
229
+ `
230
+
188
231
  const ModalDialog = animated(styled.div<{
189
- size: 'S' | 'M' | 'L'
190
- bottomSheet: boolean | 'full'
232
+ size: Size
233
+ bottomSheet: BottomSheet
191
234
  }>`
192
- position: absolute;
193
- top: 50%;
194
- left: 50%;
195
- transform: translate(-50%, -50%);
196
- width: ${(p) =>
197
- p.size === 'S'
198
- ? columnSystem(3, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
199
- : p.size === 'M'
200
- ? columnSystem(4, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
201
- : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
202
- p.size === 'L'
203
- ? columnSystem(6, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
204
- : unreachable(p.size)}px;
235
+ position: relative;
236
+ margin: auto;
237
+ padding: 24px 0;
205
238
 
206
239
  ${theme((o) => [o.bg.background1, o.borderRadius(24)])}
207
-
208
240
  @media ${({ theme }) => maxWidth(theme.breakpoint.screen1)} {
209
241
  ${(p) =>
210
- p.bottomSheet === 'full'
211
- ? css`
212
- top: auto;
213
- bottom: 0;
214
- left: 0;
215
- transform: none;
216
- border-radius: 0;
217
- width: 100%;
218
- height: 100%;
219
- `
220
- : p.bottomSheet
221
- ? css`
222
- top: auto;
223
- bottom: 0;
224
- left: 0;
225
- transform: none;
226
- border-radius: 0;
227
- width: 100%;
228
- `
229
- : css`
230
- width: calc(100% - 48px);
231
- `}
242
+ p.bottomSheet !== false &&
243
+ css`
244
+ border-radius: 0;
245
+ ${p.bottomSheet === 'full' &&
246
+ css`
247
+ height: 100%;
248
+ `}
249
+ `}
232
250
  }
233
251
  `)
234
252
 
@@ -110,6 +110,9 @@ export const RadioInput = styled.input.attrs({ type: 'radio' })<{
110
110
  }
111
111
 
112
112
  ${theme((o) => o.outline.default.focus)}
113
+
114
+ /* FIXME: o.outline.default.focus の transition に o.bg.brand の transition が打ち消されてしまう */
115
+ transition: all 0.2s !important;
113
116
  }
114
117
  `
115
118
 
@@ -13,6 +13,7 @@ export const StringSegments: Story<SegmentedControlProps> = (props) => {
13
13
  }
14
14
 
15
15
  StringSegments.args = {
16
+ name: 'test',
16
17
  data: ['option1', 'option2', 'option3'],
17
18
  disabled: false,
18
19
  readonly: false,
@@ -24,6 +25,7 @@ export const ObjectSegments: Story<SegmentedControlProps> = (props) => {
24
25
  }
25
26
 
26
27
  ObjectSegments.args = {
28
+ name: 'test',
27
29
  data: [
28
30
  { label: '選択肢1', value: 'option1' },
29
31
  { label: '選択肢2', value: 'option2' },
@@ -41,6 +41,7 @@ const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
41
41
  isDisabled: props.disabled,
42
42
  isReadOnly: props.readonly,
43
43
  isRequired: props.required,
44
+ 'aria-label': props.name,
44
45
  }),
45
46
  [props]
46
47
  )
@@ -1,5 +1,5 @@
1
1
  import { action } from '@storybook/addon-actions'
2
- import React from 'react'
2
+ import React, { useState } from 'react'
3
3
  import { Story } from '../../_lib/compat'
4
4
  import Switch from '.'
5
5
 
@@ -13,6 +13,25 @@ interface Props {
13
13
  disabled: boolean
14
14
  }
15
15
 
16
+ export const Playground: Story<Props> = (props: Props) => {
17
+ const [checked, setChecked] = useState(false)
18
+ return (
19
+ <div>
20
+ <Switch
21
+ {...props}
22
+ name="name"
23
+ onChange={(v) => {
24
+ setChecked(v)
25
+ action('onChange')
26
+ }}
27
+ checked={checked}
28
+ >
29
+ 選択する
30
+ </Switch>
31
+ </div>
32
+ )
33
+ }
34
+
16
35
  export const Labelled: Story<Props> = (props: Props) => (
17
36
  <div>
18
37
  <Switch {...props} name="name" onChange={action('onChange')}>
@@ -64,6 +64,10 @@ const Label = styled.label`
64
64
 
65
65
  ${theme((o) => o.disabled)}
66
66
 
67
+ :active > input {
68
+ box-shadow: 0 0 0 4px rgba(0, 150, 250, 0.32);
69
+ }
70
+
67
71
  ${disabledSelector} {
68
72
  cursor: default;
69
73
  }
@@ -80,42 +84,63 @@ const LabelInner = styled.div`
80
84
  const SwitchInput = styled.input.attrs({
81
85
  type: 'checkbox',
82
86
  })`
83
- &[type='checkbox'] {
84
- appearance: none;
85
- display: inline-flex;
86
- position: relative;
87
- box-sizing: border-box;
88
- width: 28px;
89
- border: 2px solid transparent;
90
- transition: box-shadow 0.2s, background-color 0.2s;
91
- cursor: inherit;
92
- ${theme((o) => [
93
- o.borderRadius(16),
94
- o.height.px(16),
95
- o.bg.text4.hover.press,
96
- o.outline.default.focus,
97
- o.margin.all(0),
98
- ])}
87
+ appearance: none;
88
+ display: inline-flex;
89
+ position: relative;
90
+ box-sizing: border-box;
91
+ width: 28px;
92
+ border: 2px solid transparent;
99
93
 
100
- &::after {
101
- content: '';
102
- position: absolute;
103
- display: block;
104
- top: 0;
105
- left: 0;
106
- width: 12px;
107
- height: 12px;
108
- transform: translateX(0);
109
- transition: transform 0.2s;
110
- ${theme((o) => [o.bg.text5.hover.press, o.borderRadius('oval')])}
111
- }
94
+ transition-property: background-color, box-shadow;
95
+ transition-duration: 0.2s;
96
+ cursor: inherit;
97
+
98
+ outline: none;
99
+ border-radius: 16px;
100
+ height: 16px;
101
+ margin: 0;
102
+ background-color: var(--charcoal-text4);
103
+ :hover {
104
+ background-color: var(--charcoal-text4-hover);
105
+ }
106
+ :active {
107
+ background-color: var(--charcoal-text4-press);
108
+ }
109
+ :focus {
110
+ box-shadow: 0 0 0 4px rgba(0, 150, 250, 0.32);
111
+ }
112
112
 
113
- &:checked {
114
- ${theme((o) => o.bg.brand.hover.press)}
113
+ &::after {
114
+ content: '';
115
+ position: absolute;
116
+ display: block;
117
+ top: 0;
118
+ left: 0;
119
+ width: 12px;
120
+ height: 12px;
121
+ transform: translateX(0);
122
+ transition: transform 0.2s;
123
+ border-radius: 1024px;
124
+ background-color: var(--charcoal-text5);
125
+ :hover {
126
+ background-color: var(--charcoal-text5-hover);
127
+ }
128
+ :active {
129
+ background-color: var(--charcoal-text5-press);
130
+ }
131
+ }
115
132
 
116
- &::after {
117
- transform: translateX(12px);
118
- }
133
+ &:checked {
134
+ background-color: var(--charcoal-brand);
135
+ :hover {
136
+ background-color: var(--charcoal-brand-hover);
137
+ }
138
+ :active {
139
+ background-color: var(--charcoal-brand-press);
140
+ }
141
+ &::after {
142
+ transform: translateX(12px);
143
+ transition: transform 0.2s;
119
144
  }
120
145
  }
121
146
  `
@@ -25,6 +25,7 @@ Default.args = {
25
25
  href: '',
26
26
  rel: '',
27
27
  target: '',
28
+ className: '',
28
29
  }
29
30
 
30
31
  export const Playground: Story<TagItemProps> = ({
@@ -24,6 +24,7 @@ export type TagItemProps = {
24
24
  status?: 'default' | 'active' | 'inactive'
25
25
  size?: keyof typeof sizeMap
26
26
  disabled?: boolean
27
+ className?: string
27
28
  } & Pick<ComponentPropsWithoutRef<'a'>, 'href' | 'target' | 'rel' | 'onClick'>
28
29
 
29
30
  const TagItem = forwardRef<HTMLAnchorElement, TagItemProps>(
@@ -36,6 +37,7 @@ const TagItem = forwardRef<HTMLAnchorElement, TagItemProps>(
36
37
  size = 'M',
37
38
  disabled,
38
39
  status = 'default',
40
+ className,
39
41
  ...props
40
42
  },
41
43
  _ref
@@ -60,6 +62,7 @@ const TagItem = forwardRef<HTMLAnchorElement, TagItemProps>(
60
62
  size={hasTranslatedLabel ? 'M' : size}
61
63
  status={status}
62
64
  {...buttonProps}
65
+ className={className}
63
66
  >
64
67
  <Background bgColor={bgColor} bgImage={bgImage} status={status} />
65
68
 
@@ -417,6 +417,13 @@ const StyledTextareaContainer = styled.div<{ rows: number; invalid: boolean }>`
417
417
  o.borderRadius(4),
418
418
  ])}
419
419
 
420
+ /**
421
+ * FIXME: o.outline.default を &:focus-within 内に書いてると、外れるときに transition が効かない
422
+ * 本来 o.outline.default.focus と書けば足してくれるような transition の内容を一旦明示している
423
+ * o.outline.default.focusWithin のようなものがあればこの行は不要になるはず
424
+ */
425
+ transition: box-shadow 0.2s;
426
+
420
427
  &:focus-within {
421
428
  ${(p) =>
422
429
  theme((o) => (p.invalid ? o.outline.assertive : o.outline.default))}
package/src/index.ts CHANGED
@@ -52,11 +52,12 @@ export {
52
52
  } from './components/LoadingSpinner'
53
53
  export {
54
54
  default as DropdownSelector,
55
+ type DropdownSelectorProps,
55
56
  DropdownSelectorItem,
56
57
  } from './components/DropdownSelector'
57
58
  export {
58
59
  default as SegmentedControl,
59
60
  type SegmentedControlProps,
60
61
  } from './components/SegmentedControl'
61
- export { default as Checkbox } from './components/Checkbox'
62
+ export { default as Checkbox, type CheckboxProps } from './components/Checkbox'
62
63
  export { default as TagItem, type TagItemProps } from './components/TagItem'