@charcoal-ui/react 2.8.0 → 2.10.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 (48) hide show
  1. package/dist/components/Button/index.test.d.ts +4 -0
  2. package/dist/components/Button/index.test.d.ts.map +1 -0
  3. package/dist/components/Checkbox/index.d.ts +1 -0
  4. package/dist/components/Checkbox/index.d.ts.map +1 -1
  5. package/dist/components/Checkbox/index.story.d.ts +1 -0
  6. package/dist/components/Checkbox/index.story.d.ts.map +1 -1
  7. package/dist/components/LoadingSpinner/index.d.ts +8 -6
  8. package/dist/components/LoadingSpinner/index.d.ts.map +1 -1
  9. package/dist/components/LoadingSpinner/index.story.d.ts +2 -2
  10. package/dist/components/LoadingSpinner/index.story.d.ts.map +1 -1
  11. package/dist/components/Modal/index.d.ts +17 -26
  12. package/dist/components/Modal/index.d.ts.map +1 -1
  13. package/dist/components/Modal/index.story.d.ts +13 -2
  14. package/dist/components/Modal/index.story.d.ts.map +1 -1
  15. package/dist/components/MultiSelect/index.d.ts +15 -1
  16. package/dist/components/MultiSelect/index.d.ts.map +1 -1
  17. package/dist/components/MultiSelect/index.story.d.ts +15 -2
  18. package/dist/components/MultiSelect/index.story.d.ts.map +1 -1
  19. package/dist/components/Radio/index.d.ts +10 -1
  20. package/dist/components/Radio/index.d.ts.map +1 -1
  21. package/dist/components/Radio/index.story.d.ts +9 -2
  22. package/dist/components/Radio/index.story.d.ts.map +1 -1
  23. package/dist/components/SegmentedControl/index.d.ts +1 -0
  24. package/dist/components/SegmentedControl/index.d.ts.map +1 -1
  25. package/dist/components/Switch/index.d.ts +2 -1
  26. package/dist/components/Switch/index.d.ts.map +1 -1
  27. package/dist/components/Switch/index.story.d.ts +2 -2
  28. package/dist/components/Switch/index.story.d.ts.map +1 -1
  29. package/dist/index.cjs.js +170 -141
  30. package/dist/index.cjs.js.map +1 -1
  31. package/dist/index.esm.js +188 -154
  32. package/dist/index.esm.js.map +1 -1
  33. package/package.json +6 -6
  34. package/src/components/Button/__snapshots__/index.test.tsx.snap +385 -0
  35. package/src/components/Button/index.test.tsx +25 -0
  36. package/src/components/Checkbox/index.story.tsx +1 -0
  37. package/src/components/Checkbox/index.tsx +2 -1
  38. package/src/components/LoadingSpinner/index.story.tsx +7 -1
  39. package/src/components/LoadingSpinner/index.tsx +27 -11
  40. package/src/components/Modal/index.tsx +18 -12
  41. package/src/components/MultiSelect/index.story.tsx +11 -3
  42. package/src/components/MultiSelect/index.tsx +77 -61
  43. package/src/components/Radio/index.story.tsx +3 -0
  44. package/src/components/Radio/index.tsx +13 -9
  45. package/src/components/SegmentedControl/index.tsx +15 -5
  46. package/src/components/Switch/index.tsx +37 -32
  47. package/src/components/TextField/index.story.tsx +1 -1
  48. package/src/components/TextField/index.tsx +1 -0
@@ -1,4 +1,10 @@
1
- import React, { ChangeEvent, useCallback, useContext } from 'react'
1
+ import React, {
2
+ ChangeEvent,
3
+ forwardRef,
4
+ memo,
5
+ useCallback,
6
+ useContext,
7
+ } from 'react'
2
8
  import styled, { css } from 'styled-components'
3
9
  import warning from 'warning'
4
10
  import { theme } from '../../styled'
@@ -11,71 +17,81 @@ export type MultiSelectProps = React.PropsWithChildren<{
11
17
  forceChecked?: boolean
12
18
  disabled?: boolean
13
19
  variant?: 'default' | 'overlay'
20
+ className?: string
14
21
  onChange?: (payload: { value: string; selected: boolean }) => void
15
22
  }>
16
23
 
17
- export default function MultiSelect({
18
- value,
19
- forceChecked = false,
20
- disabled = false,
21
- onChange,
22
- variant = 'default',
23
- children,
24
- }: MultiSelectProps) {
25
- const {
26
- name,
27
- selected,
28
- disabled: parentDisabled,
29
- readonly,
30
- hasError,
31
- onChange: parentOnChange,
32
- } = useContext(MultiSelectGroupContext)
33
-
34
- warning(
35
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
36
- name !== undefined,
37
- `"name" is not Provided for <MultiSelect>. Perhaps you forgot to wrap with <MultiSelectGroup> ?`
38
- )
39
-
40
- const isSelected = selected.includes(value) || forceChecked
41
- const isDisabled = disabled || parentDisabled || readonly
42
-
43
- const handleChange = useCallback(
44
- (event: ChangeEvent<HTMLInputElement>) => {
45
- if (!(event.currentTarget instanceof HTMLInputElement)) {
46
- return
47
- }
48
- if (onChange) onChange({ value, selected: event.currentTarget.checked })
49
- parentOnChange({ value, selected: event.currentTarget.checked })
24
+ const MultiSelect = forwardRef<HTMLInputElement, MultiSelectProps>(
25
+ function MultiSelectInner(
26
+ {
27
+ value,
28
+ forceChecked = false,
29
+ disabled = false,
30
+ onChange,
31
+ variant = 'default',
32
+ className,
33
+ children,
50
34
  },
51
- [onChange, parentOnChange, value]
52
- )
35
+ ref
36
+ ) {
37
+ const {
38
+ name,
39
+ selected,
40
+ disabled: parentDisabled,
41
+ readonly,
42
+ hasError,
43
+ onChange: parentOnChange,
44
+ } = useContext(MultiSelectGroupContext)
45
+
46
+ warning(
47
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
48
+ name !== undefined,
49
+ `"name" is not Provided for <MultiSelect>. Perhaps you forgot to wrap with <MultiSelectGroup> ?`
50
+ )
51
+
52
+ const isSelected = selected.includes(value) || forceChecked
53
+ const isDisabled = disabled || parentDisabled || readonly
54
+
55
+ const handleChange = useCallback(
56
+ (event: ChangeEvent<HTMLInputElement>) => {
57
+ if (!(event.currentTarget instanceof HTMLInputElement)) {
58
+ return
59
+ }
60
+ if (onChange) onChange({ value, selected: event.currentTarget.checked })
61
+ parentOnChange({ value, selected: event.currentTarget.checked })
62
+ },
63
+ [onChange, parentOnChange, value]
64
+ )
65
+
66
+ return (
67
+ <MultiSelectRoot aria-disabled={isDisabled} className={className}>
68
+ <MultiSelectInput
69
+ {...{
70
+ name,
71
+ value,
72
+ hasError,
73
+ }}
74
+ checked={isSelected}
75
+ disabled={isDisabled}
76
+ onChange={handleChange}
77
+ overlay={variant === 'overlay'}
78
+ aria-invalid={hasError}
79
+ ref={ref}
80
+ />
81
+ <MultiSelectInputOverlay
82
+ overlay={variant === 'overlay'}
83
+ hasError={hasError}
84
+ aria-hidden={true}
85
+ >
86
+ <pixiv-icon name="24/Check" unsafe-non-guideline-scale={16 / 24} />
87
+ </MultiSelectInputOverlay>
88
+ {Boolean(children) && <MultiSelectLabel>{children}</MultiSelectLabel>}
89
+ </MultiSelectRoot>
90
+ )
91
+ }
92
+ )
53
93
 
54
- return (
55
- <MultiSelectRoot aria-disabled={isDisabled}>
56
- <MultiSelectInput
57
- {...{
58
- name,
59
- value,
60
- hasError,
61
- }}
62
- checked={isSelected}
63
- disabled={isDisabled}
64
- onChange={handleChange}
65
- overlay={variant === 'overlay'}
66
- aria-invalid={hasError}
67
- />
68
- <MultiSelectInputOverlay
69
- overlay={variant === 'overlay'}
70
- hasError={hasError}
71
- aria-hidden={true}
72
- >
73
- <pixiv-icon name="24/Check" unsafe-non-guideline-scale={16 / 24} />
74
- </MultiSelectInputOverlay>
75
- {Boolean(children) && <MultiSelectLabel>{children}</MultiSelectLabel>}
76
- </MultiSelectRoot>
77
- )
78
- }
94
+ export default memo(MultiSelect)
79
95
 
80
96
  const MultiSelectRoot = styled.label`
81
97
  display: grid;
@@ -32,6 +32,7 @@ interface Props {
32
32
  childDisabled: boolean
33
33
  forceChecked: boolean
34
34
  readonly: boolean
35
+ className?: string
35
36
  }
36
37
 
37
38
  const Template: Story<Partial<Props>> = ({
@@ -41,6 +42,7 @@ const Template: Story<Partial<Props>> = ({
41
42
  parentDisabled,
42
43
  childDisabled,
43
44
  readonly,
45
+ className,
44
46
  }) => (
45
47
  <div
46
48
  css={css`
@@ -66,6 +68,7 @@ const Template: Story<Partial<Props>> = ({
66
68
  value={option}
67
69
  disabled={childDisabled}
68
70
  forceChecked={forceChecked}
71
+ className={className}
69
72
  >
70
73
  {name}({option})を選ぶ
71
74
  </Radio>
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useContext } from 'react'
1
+ import React, { forwardRef, memo, useCallback, useContext } from 'react'
2
2
  import styled from 'styled-components'
3
3
  import warning from 'warning'
4
4
  import { theme } from '../../styled'
@@ -8,14 +8,13 @@ export type RadioProps = React.PropsWithChildren<{
8
8
  value: string
9
9
  forceChecked?: boolean
10
10
  disabled?: boolean
11
+ className?: string
11
12
  }>
12
13
 
13
- export default function Radio({
14
- value,
15
- forceChecked = false,
16
- disabled = false,
17
- children,
18
- }: RadioProps) {
14
+ const Radio = forwardRef<HTMLInputElement, RadioProps>(function RadioInner(
15
+ { value, forceChecked = false, disabled = false, children, className },
16
+ ref
17
+ ) {
19
18
  const {
20
19
  name,
21
20
  selected,
@@ -43,7 +42,7 @@ export default function Radio({
43
42
  )
44
43
 
45
44
  return (
46
- <RadioRoot aria-disabled={isDisabled || isReadonly}>
45
+ <RadioRoot aria-disabled={isDisabled || isReadonly} className={className}>
47
46
  <RadioInput
48
47
  name={name}
49
48
  value={value}
@@ -51,11 +50,14 @@ export default function Radio({
51
50
  hasError={hasError}
52
51
  onChange={handleChange}
53
52
  disabled={isDisabled || isReadonly}
53
+ ref={ref}
54
54
  />
55
55
  {children != null && <RadioLabel>{children}</RadioLabel>}
56
56
  </RadioRoot>
57
57
  )
58
- }
58
+ })
59
+
60
+ export default memo(Radio)
59
61
 
60
62
  const RadioRoot = styled.label`
61
63
  display: grid;
@@ -82,6 +84,8 @@ export const RadioInput = styled.input.attrs({ type: 'radio' })<{
82
84
  width: 20px;
83
85
  height: 20px;
84
86
 
87
+ cursor: pointer;
88
+
85
89
  ${({ hasError = false }) =>
86
90
  theme((o) => [
87
91
  o.borderRadius('oval'),
@@ -1,4 +1,4 @@
1
- import React, { forwardRef, memo, useMemo, useRef } from 'react'
1
+ import React, { ReactNode, forwardRef, memo, useMemo, useRef } from 'react'
2
2
  import { useRadioGroupState } from 'react-stately'
3
3
  import {
4
4
  AriaRadioGroupProps,
@@ -24,6 +24,7 @@ export type SegmentedControlProps = {
24
24
  readonly disabled?: boolean
25
25
  readonly readonly?: boolean
26
26
  readonly required?: boolean
27
+ readonly className?: string
27
28
 
28
29
  readonly value?: string
29
30
  readonly defaultValue?: string
@@ -54,7 +55,11 @@ const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
54
55
  }, [props.data])
55
56
 
56
57
  return (
57
- <SegmentedControlRoot ref={ref} {...radioGroupProps}>
58
+ <SegmentedControlRoot
59
+ ref={ref}
60
+ {...radioGroupProps}
61
+ className={props.className}
62
+ >
58
63
  <RadioProvider value={state}>
59
64
  {segmentedControlItems.map((item) => (
60
65
  <Segmented
@@ -76,13 +81,18 @@ export default memo(SegmentedControl)
76
81
  type RadioProps = {
77
82
  value: string
78
83
  disabled?: boolean
84
+ children?: ReactNode
79
85
  }
80
86
 
81
- const Segmented: React.FC<RadioProps> = ({ children, ...props }) => {
87
+ const Segmented = (props: RadioProps) => {
82
88
  const state = useRadioContext()
83
89
  const ref = useRef<HTMLInputElement>(null)
84
90
  const ariaRadioProps = useMemo<AriaRadioProps>(
85
- () => ({ ...props, isDisabled: props.disabled }),
91
+ () => ({
92
+ value: props.value,
93
+ isDisabled: props.disabled,
94
+ children: props.children,
95
+ }),
86
96
  [props]
87
97
  )
88
98
 
@@ -99,7 +109,7 @@ const Segmented: React.FC<RadioProps> = ({ children, ...props }) => {
99
109
  >
100
110
  <SegmentedInput {...inputProps} ref={ref} />
101
111
  <RadioLabel>
102
- <SegmentedLabelInner>{children}</SegmentedLabelInner>
112
+ <SegmentedLabelInner>{props.children}</SegmentedLabelInner>
103
113
  </RadioLabel>
104
114
  </SegmentedRoot>
105
115
  )
@@ -1,10 +1,11 @@
1
1
  import { useSwitch } from '@react-aria/switch'
2
2
  import type { AriaSwitchProps } from '@react-types/switch'
3
- import React, { useRef, useMemo } from 'react'
3
+ import React, { useMemo, memo, forwardRef } from 'react'
4
4
  import { useToggleState } from 'react-stately'
5
5
  import styled from 'styled-components'
6
6
  import { theme } from '../../styled'
7
7
  import { disabledSelector } from '@charcoal-ui/utils'
8
+ import { useObjectRef } from '@react-aria/utils'
8
9
 
9
10
  export type SwitchProps = {
10
11
  name: string
@@ -23,37 +24,41 @@ export type SwitchProps = {
23
24
  }
24
25
  )
25
26
 
26
- export default function SwitchCheckbox(props: SwitchProps) {
27
- const { disabled, className } = props
28
-
29
- const ariaSwitchProps: AriaSwitchProps = useMemo(
30
- () => ({
31
- ...props,
32
-
33
- // children がいない場合は aria-label をつけないといけない
34
- 'aria-label': 'children' in props ? undefined : props.label,
35
- isDisabled: props.disabled,
36
- isSelected: props.checked,
37
- }),
38
- [props]
39
- )
40
-
41
- const state = useToggleState(ariaSwitchProps)
42
- const ref = useRef<HTMLInputElement>(null)
43
- const {
44
- inputProps: { className: _className, type: _type, ...rest },
45
- } = useSwitch(ariaSwitchProps, state, ref)
46
-
47
- return (
48
- <Label className={className} aria-disabled={disabled}>
49
- <SwitchInput {...rest} ref={ref} />
50
- {'children' in props ? (
51
- // eslint-disable-next-line react/destructuring-assignment
52
- <LabelInner>{props.children}</LabelInner>
53
- ) : undefined}
54
- </Label>
55
- )
56
- }
27
+ const SwitchCheckbox = forwardRef<HTMLInputElement, SwitchProps>(
28
+ function SwitchCheckboxInner(props, external) {
29
+ const { disabled, className } = props
30
+
31
+ const ariaSwitchProps: AriaSwitchProps = useMemo(
32
+ () => ({
33
+ ...props,
34
+
35
+ // children がいない場合は aria-label をつけないといけない
36
+ 'aria-label': 'children' in props ? undefined : props.label,
37
+ isDisabled: props.disabled,
38
+ isSelected: props.checked,
39
+ }),
40
+ [props]
41
+ )
42
+
43
+ const state = useToggleState(ariaSwitchProps)
44
+ const ref = useObjectRef<HTMLInputElement>(external)
45
+ const {
46
+ inputProps: { className: _className, type: _type, ...rest },
47
+ } = useSwitch(ariaSwitchProps, state, ref)
48
+
49
+ return (
50
+ <Label className={className} aria-disabled={disabled}>
51
+ <SwitchInput {...rest} ref={ref} />
52
+ {'children' in props ? (
53
+ // eslint-disable-next-line react/destructuring-assignment
54
+ <LabelInner>{props.children}</LabelInner>
55
+ ) : undefined}
56
+ </Label>
57
+ )
58
+ }
59
+ )
60
+
61
+ export default memo(SwitchCheckbox)
57
62
 
58
63
  const Label = styled.label`
59
64
  display: inline-grid;
@@ -102,7 +102,7 @@ export const PrefixIcon: Story<Partial<SingleLineTextFieldProps>> = (args) => (
102
102
  )
103
103
 
104
104
  const PrefixIconWrap = styled.div`
105
- color: ${({ theme }) => theme.color.text4};
105
+ color: ${({ theme }) => theme.color.text3};
106
106
  margin-top: 2px;
107
107
  margin-right: 4px;
108
108
  `
@@ -349,6 +349,7 @@ const PrefixContainer = styled.span`
349
349
  top: 50%;
350
350
  left: 8px;
351
351
  transform: translateY(-50%);
352
+ z-index: 1;
352
353
  `
353
354
 
354
355
  const SuffixContainer = styled.span`