@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
@@ -1,5 +1,4 @@
1
1
  import { action } from '@storybook/addon-actions'
2
- import React from 'react'
3
2
  import SegmentedControl, { SegmentedControlProps } from '.'
4
3
  import { Story } from '../../_lib/compat'
5
4
 
@@ -1,4 +1,5 @@
1
- import React, { forwardRef, memo, useMemo, useRef } from 'react'
1
+ import { ReactNode, forwardRef, memo, useMemo, useRef } from 'react'
2
+ import * as React from 'react'
2
3
  import { useRadioGroupState } from 'react-stately'
3
4
  import {
4
5
  AriaRadioGroupProps,
@@ -24,6 +25,7 @@ export type SegmentedControlProps = {
24
25
  readonly disabled?: boolean
25
26
  readonly readonly?: boolean
26
27
  readonly required?: boolean
28
+ readonly className?: string
27
29
 
28
30
  readonly value?: string
29
31
  readonly defaultValue?: string
@@ -54,7 +56,11 @@ const SegmentedControl = forwardRef<HTMLDivElement, SegmentedControlProps>(
54
56
  }, [props.data])
55
57
 
56
58
  return (
57
- <SegmentedControlRoot ref={ref} {...radioGroupProps}>
59
+ <SegmentedControlRoot
60
+ ref={ref}
61
+ {...radioGroupProps}
62
+ className={props.className}
63
+ >
58
64
  <RadioProvider value={state}>
59
65
  {segmentedControlItems.map((item) => (
60
66
  <Segmented
@@ -76,13 +82,18 @@ export default memo(SegmentedControl)
76
82
  type RadioProps = {
77
83
  value: string
78
84
  disabled?: boolean
85
+ children?: ReactNode
79
86
  }
80
87
 
81
- const Segmented: React.FC<RadioProps> = ({ children, ...props }) => {
88
+ const Segmented = (props: RadioProps) => {
82
89
  const state = useRadioContext()
83
90
  const ref = useRef<HTMLInputElement>(null)
84
91
  const ariaRadioProps = useMemo<AriaRadioProps>(
85
- () => ({ ...props, isDisabled: props.disabled }),
92
+ () => ({
93
+ value: props.value,
94
+ isDisabled: props.disabled,
95
+ children: props.children,
96
+ }),
86
97
  [props]
87
98
  )
88
99
 
@@ -99,7 +110,7 @@ const Segmented: React.FC<RadioProps> = ({ children, ...props }) => {
99
110
  >
100
111
  <SegmentedInput {...inputProps} ref={ref} />
101
112
  <RadioLabel>
102
- <SegmentedLabelInner>{children}</SegmentedLabelInner>
113
+ <SegmentedLabelInner>{props.children}</SegmentedLabelInner>
103
114
  </RadioLabel>
104
115
  </SegmentedRoot>
105
116
  )
@@ -1,5 +1,5 @@
1
1
  import { action } from '@storybook/addon-actions'
2
- import React, { useState } from 'react'
2
+ import { useState } from 'react'
3
3
  import { Story } from '../../_lib/compat'
4
4
  import Switch from '.'
5
5
 
@@ -1,10 +1,12 @@
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 { useMemo, memo, forwardRef } from 'react'
4
+ import * as React from 'react'
4
5
  import { useToggleState } from 'react-stately'
5
6
  import styled from 'styled-components'
6
7
  import { theme } from '../../styled'
7
8
  import { disabledSelector } from '@charcoal-ui/utils'
9
+ import { useObjectRef } from '@react-aria/utils'
8
10
 
9
11
  export type SwitchProps = {
10
12
  name: string
@@ -23,37 +25,41 @@ export type SwitchProps = {
23
25
  }
24
26
  )
25
27
 
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
- }
28
+ const SwitchCheckbox = forwardRef<HTMLInputElement, SwitchProps>(
29
+ function SwitchCheckboxInner(props, external) {
30
+ const { disabled, className } = props
31
+
32
+ const ariaSwitchProps: AriaSwitchProps = useMemo(
33
+ () => ({
34
+ ...props,
35
+
36
+ // children がいない場合は aria-label をつけないといけない
37
+ 'aria-label': 'children' in props ? undefined : props.label,
38
+ isDisabled: props.disabled,
39
+ isSelected: props.checked,
40
+ }),
41
+ [props]
42
+ )
43
+
44
+ const state = useToggleState(ariaSwitchProps)
45
+ const ref = useObjectRef<HTMLInputElement>(external)
46
+ const {
47
+ inputProps: { className: _className, type: _type, ...rest },
48
+ } = useSwitch(ariaSwitchProps, state, ref)
49
+
50
+ return (
51
+ <Label className={className} aria-disabled={disabled}>
52
+ <SwitchInput {...rest} ref={ref} />
53
+ {'children' in props ? (
54
+ // eslint-disable-next-line react/destructuring-assignment
55
+ <LabelInner>{props.children}</LabelInner>
56
+ ) : undefined}
57
+ </Label>
58
+ )
59
+ }
60
+ )
61
+
62
+ export default memo(SwitchCheckbox)
57
63
 
58
64
  const Label = styled.label`
59
65
  display: inline-grid;
@@ -1,5 +1,4 @@
1
1
  import { action } from '@storybook/addon-actions'
2
- import React from 'react'
3
2
  import styled from 'styled-components'
4
3
  import TagItem, { TagItemProps } from '.'
5
4
  import { Story } from '../../_lib/compat'
@@ -1,9 +1,4 @@
1
- import React, {
2
- forwardRef,
3
- memo,
4
- useMemo,
5
- ComponentPropsWithoutRef,
6
- } from 'react'
1
+ import { forwardRef, memo, useMemo, ComponentPropsWithoutRef } from 'react'
7
2
  import { useObjectRef } from '@react-aria/utils'
8
3
  import styled, { css } from 'styled-components'
9
4
  import { theme } from '../../styled'
@@ -0,0 +1,61 @@
1
+ import { action } from '@storybook/addon-actions'
2
+ import styled from 'styled-components'
3
+ import { Story } from '../../_lib/compat'
4
+ import Clickable from '../Clickable'
5
+ import TextArea, { TextAreaProps } from '.'
6
+ import { px } from '@charcoal-ui/utils'
7
+
8
+ export default {
9
+ title: 'TextArea',
10
+ component: TextArea,
11
+ argTypes: {},
12
+ args: {
13
+ showLabel: false,
14
+ label: 'Label',
15
+ assistiveText: '',
16
+ disabled: false,
17
+ required: false,
18
+ invalid: false,
19
+ },
20
+ }
21
+
22
+ const Container = styled.div`
23
+ display: grid;
24
+ gap: ${({ theme }) => px(theme.spacing[24])};
25
+ `
26
+
27
+ const Template: Story<Partial<TextAreaProps>> = (args) => (
28
+ <Container>
29
+ <TextArea
30
+ label="Label"
31
+ requiredText="*必須"
32
+ subLabel={
33
+ <Clickable onClick={action('label-click')}>Text Link</Clickable>
34
+ }
35
+ placeholder="Text Area"
36
+ {...args}
37
+ />
38
+ </Container>
39
+ )
40
+
41
+ export const Default = Template.bind({})
42
+
43
+ export const HasLabel = Template.bind({})
44
+ HasLabel.args = {
45
+ showLabel: true,
46
+ assistiveText: 'Assistive text',
47
+ required: true,
48
+ }
49
+
50
+ export const HasCount = Template.bind({})
51
+ HasCount.args = {
52
+ showCount: true,
53
+ maxLength: 100,
54
+ }
55
+
56
+ export const AutoHeight: Story<Partial<TextAreaProps>> = (args) => (
57
+ <TextArea label="Label" placeholder="TextArea" {...args} />
58
+ )
59
+ AutoHeight.args = {
60
+ autoHeight: true,
61
+ }
@@ -0,0 +1,246 @@
1
+ import { useTextField } from '@react-aria/textfield'
2
+ import { useVisuallyHidden } from '@react-aria/visually-hidden'
3
+ import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'
4
+ import styled, { css } from 'styled-components'
5
+ import FieldLabel, { FieldLabelProps } from '../FieldLabel'
6
+ import { countCodePointsInString, mergeRefs } from '../../_lib'
7
+ import { ReactAreaUseTextFieldCompat } from '../../_lib/compat'
8
+ import { theme } from '../../styled'
9
+
10
+ type DOMProps = Omit<
11
+ React.TextareaHTMLAttributes<HTMLTextAreaElement>,
12
+ // react-ariaのhookは、onChangeが`(v: string) => void`想定
13
+ | 'onChange'
14
+ // ReactAreaUseTextFieldCompatに書いてあるような事情で、ここにあるイベントハンドラの型をゆるめる
15
+ | keyof ReactAreaUseTextFieldCompat
16
+ >
17
+
18
+ export interface TextAreaProps
19
+ extends Pick<FieldLabelProps, 'label' | 'requiredText' | 'subLabel'>,
20
+ DOMProps,
21
+ ReactAreaUseTextFieldCompat {
22
+ readonly autoHeight?: boolean
23
+ readonly rows?: number
24
+
25
+ // <input> 要素は number とか string[] もありうるが、今はこれしか想定してない
26
+ readonly defaultValue?: string
27
+ readonly value?: string
28
+ readonly onChange?: (value: string) => void
29
+
30
+ // react-ariaの型定義のせいでHTMLTextAreaElementにできない
31
+ readonly onKeyDown?: (event: React.KeyboardEvent<Element>) => void
32
+ readonly onFocus?: (event: React.FocusEvent<Element>) => void
33
+ readonly onBlur?: (event: React.FocusEvent<Element>) => void
34
+
35
+ readonly showCount?: boolean
36
+ readonly showLabel?: boolean
37
+ readonly assistiveText?: string
38
+ readonly invalid?: boolean
39
+ }
40
+
41
+ const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
42
+ function TextAreaInner({ onChange, ...props }, forwardRef) {
43
+ const {
44
+ className,
45
+ showCount = false,
46
+ showLabel = false,
47
+ label,
48
+ requiredText,
49
+ subLabel,
50
+ disabled = false,
51
+ required,
52
+ invalid = false,
53
+ assistiveText,
54
+ maxLength,
55
+ autoHeight = false,
56
+ rows: initialRows = 4,
57
+ } = props
58
+
59
+ const { visuallyHiddenProps } = useVisuallyHidden()
60
+ const textareaRef = useRef<HTMLTextAreaElement>(null)
61
+ const ariaRef = useRef<HTMLTextAreaElement>(null)
62
+ const [count, setCount] = useState(
63
+ countCodePointsInString(props.value ?? '')
64
+ )
65
+ const [rows, setRows] = useState(initialRows)
66
+
67
+ const syncHeight = useCallback(
68
+ (textarea: HTMLTextAreaElement) => {
69
+ const rows = (`${textarea.value}\n`.match(/\n/gu)?.length ?? 0) || 1
70
+ setRows(initialRows <= rows ? rows : initialRows)
71
+ },
72
+ [initialRows]
73
+ )
74
+
75
+ const nonControlled = props.value === undefined
76
+ const handleChange = useCallback(
77
+ (value: string) => {
78
+ const count = countCodePointsInString(value)
79
+ if (maxLength !== undefined && count > maxLength) {
80
+ return
81
+ }
82
+ if (nonControlled) {
83
+ setCount(count)
84
+ }
85
+ if (autoHeight && textareaRef.current !== null) {
86
+ syncHeight(textareaRef.current)
87
+ }
88
+ onChange?.(value)
89
+ },
90
+ [autoHeight, maxLength, nonControlled, onChange, syncHeight]
91
+ )
92
+
93
+ useEffect(() => {
94
+ setCount(countCodePointsInString(props.value ?? ''))
95
+ }, [props.value])
96
+
97
+ const { inputProps, labelProps, descriptionProps, errorMessageProps } =
98
+ useTextField(
99
+ {
100
+ inputElementType: 'textarea',
101
+ isDisabled: disabled,
102
+ isRequired: required,
103
+ validationState: invalid ? 'invalid' : 'valid',
104
+ description: !invalid && assistiveText,
105
+ errorMessage: invalid && assistiveText,
106
+ onChange: handleChange,
107
+ ...props,
108
+ },
109
+ ariaRef
110
+ )
111
+
112
+ useEffect(() => {
113
+ if (autoHeight && textareaRef.current !== null) {
114
+ syncHeight(textareaRef.current)
115
+ }
116
+ }, [autoHeight, syncHeight])
117
+
118
+ return (
119
+ <TextFieldRoot className={className} isDisabled={disabled}>
120
+ <TextFieldLabel
121
+ label={label}
122
+ requiredText={requiredText}
123
+ required={required}
124
+ subLabel={subLabel}
125
+ {...labelProps}
126
+ {...(!showLabel ? visuallyHiddenProps : {})}
127
+ />
128
+ <StyledTextareaContainer
129
+ invalid={invalid}
130
+ rows={showCount ? rows + 1 : rows}
131
+ >
132
+ <StyledTextarea
133
+ ref={mergeRefs(textareaRef, forwardRef, ariaRef)}
134
+ rows={rows}
135
+ noBottomPadding={showCount}
136
+ {...inputProps}
137
+ />
138
+ {showCount && (
139
+ <MultiLineCounter>
140
+ {maxLength !== undefined ? `${count}/${maxLength}` : count}
141
+ </MultiLineCounter>
142
+ )}
143
+ </StyledTextareaContainer>
144
+ {assistiveText != null && assistiveText.length !== 0 && (
145
+ <AssistiveText
146
+ invalid={invalid}
147
+ {...(invalid ? errorMessageProps : descriptionProps)}
148
+ >
149
+ {assistiveText}
150
+ </AssistiveText>
151
+ )}
152
+ </TextFieldRoot>
153
+ )
154
+ }
155
+ )
156
+
157
+ export default TextArea
158
+
159
+ const TextFieldRoot = styled.div<{ isDisabled: boolean }>`
160
+ display: flex;
161
+ flex-direction: column;
162
+
163
+ ${(p) => p.isDisabled && { opacity: p.theme.elementEffect.disabled.opacity }}
164
+ `
165
+
166
+ const TextFieldLabel = styled(FieldLabel)`
167
+ ${theme((o) => o.margin.bottom(8))}
168
+ `
169
+
170
+ const StyledTextareaContainer = styled.div<{ rows: number; invalid: boolean }>`
171
+ position: relative;
172
+ overflow: hidden;
173
+ padding: 0 8px;
174
+
175
+ ${(p) =>
176
+ theme((o) => [
177
+ o.bg.surface3.hover,
178
+ p.invalid && o.outline.assertive,
179
+ o.font.text2,
180
+ o.borderRadius(4),
181
+ ])}
182
+
183
+ &:focus-within {
184
+ ${(p) =>
185
+ theme((o) => (p.invalid ? o.outline.assertive : o.outline.default))}
186
+ }
187
+
188
+ ${({ rows }) => css`
189
+ height: calc(22px * ${rows} + 18px);
190
+ `};
191
+ `
192
+
193
+ const StyledTextarea = styled.textarea<{ noBottomPadding: boolean }>`
194
+ border: none;
195
+ outline: none;
196
+ resize: none;
197
+ font-family: inherit;
198
+ color: inherit;
199
+
200
+ /* Prevent zooming for iOS Safari */
201
+ transform-origin: top left;
202
+ transform: scale(0.875);
203
+ width: calc(100% / 0.875);
204
+ font-size: calc(14px / 0.875);
205
+ line-height: calc(22px / 0.875);
206
+ padding: calc(9px / 0.875) 0 ${(p) => (p.noBottomPadding ? 0 : '')};
207
+
208
+ ${({ rows = 1 }) => css`
209
+ height: calc(22px / 0.875 * ${rows});
210
+ `};
211
+
212
+ /* Display box-shadow for iOS Safari */
213
+ appearance: none;
214
+
215
+ background: none;
216
+
217
+ &::placeholder {
218
+ ${theme((o) => o.font.text3)}
219
+ }
220
+
221
+ /* Hide scrollbar for Chrome, Safari and Opera */
222
+ &::-webkit-scrollbar {
223
+ display: none;
224
+ }
225
+ /* Hide scrollbar for IE, Edge and Firefox */
226
+ -ms-overflow-style: none; /* IE and Edge */
227
+ scrollbar-width: none; /* Firefox */
228
+ `
229
+
230
+ const MultiLineCounter = styled.span`
231
+ position: absolute;
232
+ bottom: 9px;
233
+ right: 8px;
234
+
235
+ ${theme((o) => [o.typography(14).preserveHalfLeading, o.font.text3])}
236
+ `
237
+
238
+ const AssistiveText = styled.p<{ invalid: boolean }>`
239
+ ${(p) =>
240
+ theme((o) => [
241
+ o.typography(14),
242
+ o.margin.top(8),
243
+ o.margin.bottom(0),
244
+ o.font[p.invalid ? 'assertive' : 'text1'],
245
+ ])}
246
+ `
@@ -1,13 +1,8 @@
1
1
  import { action } from '@storybook/addon-actions'
2
- import React from 'react'
3
2
  import styled from 'styled-components'
4
3
  import { Story } from '../../_lib/compat'
5
4
  import Clickable from '../Clickable'
6
- import TextField, {
7
- MultiLineTextFieldProps,
8
- SingleLineTextFieldProps,
9
- TextFieldProps,
10
- } from '.'
5
+ import TextField, { TextFieldProps } from '.'
11
6
  import { px } from '@charcoal-ui/utils'
12
7
  import IconButton from '../IconButton'
13
8
 
@@ -38,19 +33,8 @@ const Template: Story<Partial<TextFieldProps>> = (args) => (
38
33
  subLabel={
39
34
  <Clickable onClick={action('label-click')}>Text Link</Clickable>
40
35
  }
41
- placeholder="Single Line"
42
- {...(args as Partial<SingleLineTextFieldProps>)}
43
- multiline={false}
44
- />
45
- <TextField
46
- label="Label"
47
- requiredText="*必須"
48
- subLabel={
49
- <Clickable onClick={action('label-click')}>Text Link</Clickable>
50
- }
51
- placeholder="Multi Line"
52
- {...(args as Partial<MultiLineTextFieldProps>)}
53
- multiline
36
+ placeholder="TextField"
37
+ {...args}
54
38
  />
55
39
  </Container>
56
40
  )
@@ -70,7 +54,7 @@ HasCount.args = {
70
54
  maxLength: 100,
71
55
  }
72
56
 
73
- export const HasAffix: Story<Partial<SingleLineTextFieldProps>> = (args) => (
57
+ export const HasAffix: Story<Partial<TextFieldProps>> = (args) => (
74
58
  <TextField label="Label" placeholder="path/to/your/file" {...args} />
75
59
  )
76
60
  HasAffix.args = {
@@ -80,14 +64,7 @@ HasAffix.args = {
80
64
  suffix: '.png',
81
65
  }
82
66
 
83
- export const AutoHeight: Story<Partial<MultiLineTextFieldProps>> = (args) => (
84
- <TextField label="Label" placeholder="Multi Line" {...args} multiline />
85
- )
86
- AutoHeight.args = {
87
- autoHeight: true,
88
- }
89
-
90
- export const PrefixIcon: Story<Partial<SingleLineTextFieldProps>> = (args) => (
67
+ export const PrefixIcon: Story<Partial<TextFieldProps>> = (args) => (
91
68
  <TextField
92
69
  label="Label"
93
70
  placeholder="Icon prefix"
@@ -102,7 +79,7 @@ export const PrefixIcon: Story<Partial<SingleLineTextFieldProps>> = (args) => (
102
79
  )
103
80
 
104
81
  const PrefixIconWrap = styled.div`
105
- color: ${({ theme }) => theme.color.text4};
82
+ color: ${({ theme }) => theme.color.text3};
106
83
  margin-top: 2px;
107
84
  margin-right: 4px;
108
85
  `