@charcoal-ui/react 3.3.0 → 3.5.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 (68) hide show
  1. package/dist/_lib/useForwardedRef.d.ts +3 -0
  2. package/dist/_lib/useForwardedRef.d.ts.map +1 -0
  3. package/dist/components/Clickable/index.d.ts.map +1 -1
  4. package/dist/components/DropdownSelector/DropdownPopover.d.ts.map +1 -1
  5. package/dist/components/DropdownSelector/Popover/index.d.ts.map +1 -1
  6. package/dist/components/DropdownSelector/Popover/usePreventScroll.d.ts +2 -0
  7. package/dist/components/DropdownSelector/Popover/usePreventScroll.d.ts.map +1 -0
  8. package/dist/components/DropdownSelector/index.story.d.ts +1 -0
  9. package/dist/components/DropdownSelector/index.story.d.ts.map +1 -1
  10. package/dist/components/Icon/index.story.d.ts +1 -1
  11. package/dist/components/Modal/Dialog/index.d.ts +26 -0
  12. package/dist/components/Modal/Dialog/index.d.ts.map +1 -0
  13. package/dist/components/Modal/ModalBackgroundContext.d.ts +6 -0
  14. package/dist/components/Modal/ModalBackgroundContext.d.ts.map +1 -0
  15. package/dist/components/Modal/ModalPlumbing.d.ts.map +1 -1
  16. package/dist/components/Modal/__stories__/InternalScrollStory.d.ts +4 -0
  17. package/dist/components/Modal/__stories__/InternalScrollStory.d.ts.map +1 -0
  18. package/dist/components/Modal/index.d.ts +12 -2
  19. package/dist/components/Modal/index.d.ts.map +1 -1
  20. package/dist/components/Modal/index.story.d.ts +3 -2
  21. package/dist/components/Modal/index.story.d.ts.map +1 -1
  22. package/dist/components/TextArea/index.d.ts.map +1 -1
  23. package/dist/components/TextField/index.d.ts +4 -0
  24. package/dist/components/TextField/index.d.ts.map +1 -1
  25. package/dist/components/TextField/useFocusWithClick.d.ts +3 -0
  26. package/dist/components/TextField/useFocusWithClick.d.ts.map +1 -0
  27. package/dist/index.cjs.js +532 -426
  28. package/dist/index.cjs.js.map +1 -1
  29. package/dist/index.esm.js +482 -377
  30. package/dist/index.esm.js.map +1 -1
  31. package/package.json +6 -6
  32. package/src/_lib/useForwardedRef.tsx +16 -0
  33. package/src/components/Button/__snapshots__/index.story.storyshot +1971 -0
  34. package/src/components/Checkbox/__snapshots__/index.story.storyshot +390 -0
  35. package/src/components/Clickable/__snapshots__/index.story.storyshot +100 -0
  36. package/src/components/Clickable/index.tsx +17 -35
  37. package/src/components/DropdownSelector/DropdownPopover.tsx +0 -1
  38. package/src/components/DropdownSelector/ListItem/__snapshots__/index.story.storyshot +245 -0
  39. package/src/components/DropdownSelector/MenuList/__snapshots__/index.story.storyshot +555 -0
  40. package/src/components/DropdownSelector/Popover/__snapshots__/index.story.storyshot +174 -0
  41. package/src/components/DropdownSelector/Popover/index.tsx +17 -2
  42. package/src/components/DropdownSelector/Popover/usePreventScroll.tsx +18 -0
  43. package/src/components/DropdownSelector/__snapshots__/index.story.storyshot +1241 -0
  44. package/src/components/DropdownSelector/index.story.tsx +69 -13
  45. package/src/components/Icon/__snapshots__/index.story.storyshot +12 -0
  46. package/src/components/{Button/__snapshots__/index.test.tsx.snap → IconButton/__snapshots__/index.story.storyshot} +94 -91
  47. package/src/components/LoadingSpinner/__snapshots__/index.story.storyshot +81 -0
  48. package/src/components/Modal/Dialog/index.tsx +82 -0
  49. package/src/components/Modal/ModalBackgroundContext.tsx +8 -0
  50. package/src/components/Modal/ModalPlumbing.tsx +16 -4
  51. package/src/components/Modal/__snapshots__/index.story.storyshot +533 -0
  52. package/src/components/Modal/__stories__/InternalScrollStory.tsx +75 -0
  53. package/src/components/Modal/index.story.tsx +57 -38
  54. package/src/components/Modal/index.tsx +63 -94
  55. package/src/components/MultiSelect/__snapshots__/index.story.storyshot +511 -0
  56. package/src/components/Radio/__snapshots__/index.story.storyshot +319 -0
  57. package/src/components/SegmentedControl/__snapshots__/index.story.storyshot +483 -0
  58. package/src/components/Switch/__snapshots__/index.story.storyshot +454 -0
  59. package/src/components/TagItem/__snapshots__/index.story.storyshot +1181 -0
  60. package/src/components/TextArea/__snapshots__/TextArea.story.storyshot +1114 -0
  61. package/src/components/TextArea/index.tsx +38 -43
  62. package/src/components/TextField/TextField.story.tsx +2 -3
  63. package/src/components/TextField/__snapshots__/TextField.story.storyshot +1603 -0
  64. package/src/components/TextField/index.tsx +86 -84
  65. package/src/components/TextField/useFocusWithClick.tsx +22 -0
  66. package/dist/components/Button/index.test.d.ts +0 -4
  67. package/dist/components/Button/index.test.d.ts.map +0 -1
  68. package/src/components/Button/index.test.tsx +0 -24
@@ -2,11 +2,11 @@ import { useTextField } from '@react-aria/textfield'
2
2
  import { useVisuallyHidden } from '@react-aria/visually-hidden'
3
3
  import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
4
4
  import * as React from 'react'
5
- import styled from 'styled-components'
5
+ import styled, { css } from 'styled-components'
6
6
  import FieldLabel, { FieldLabelProps } from '../FieldLabel'
7
7
  import { countCodePointsInString, mergeRefs } from '../../_lib'
8
- import { theme } from '../../styled'
9
8
  import { ReactAreaUseTextFieldCompat } from '../../_lib/compat'
9
+ import { useFocusWithClick } from './useFocusWithClick'
10
10
 
11
11
  type DOMProps = Omit<
12
12
  React.InputHTMLAttributes<HTMLInputElement>,
@@ -64,13 +64,9 @@ const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
64
64
 
65
65
  const { visuallyHiddenProps } = useVisuallyHidden()
66
66
  const ariaRef = useRef<HTMLInputElement>(null)
67
- const prefixRef = useRef<HTMLSpanElement>(null)
68
- const suffixRef = useRef<HTMLSpanElement>(null)
69
67
  const [count, setCount] = useState(
70
68
  countCodePointsInString(props.value ?? '')
71
69
  )
72
- const [prefixWidth, setPrefixWidth] = useState(0)
73
- const [suffixWidth, setSuffixWidth] = useState(0)
74
70
 
75
71
  const nonControlled = props.value === undefined
76
72
  const handleChange = useCallback(
@@ -106,26 +102,9 @@ const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
106
102
  ariaRef
107
103
  )
108
104
 
109
- useEffect(() => {
110
- const prefixObserver = new ResizeObserver((entries) => {
111
- setPrefixWidth(entries[0].contentRect.width)
112
- })
113
- const suffixObserver = new ResizeObserver((entries) => {
114
- setSuffixWidth(entries[0].contentRect.width)
115
- })
116
-
117
- if (prefixRef.current !== null) {
118
- prefixObserver.observe(prefixRef.current)
119
- }
120
- if (suffixRef.current !== null) {
121
- suffixObserver.observe(suffixRef.current)
122
- }
123
-
124
- return () => {
125
- suffixObserver.disconnect()
126
- prefixObserver.disconnect()
127
- }
128
- }, [])
105
+ const containerRef = useRef(null)
106
+
107
+ useFocusWithClick(containerRef, ariaRef)
129
108
 
130
109
  return (
131
110
  <TextFieldRoot className={className} isDisabled={disabled}>
@@ -137,25 +116,29 @@ const TextField = React.forwardRef<HTMLInputElement, TextFieldProps>(
137
116
  {...labelProps}
138
117
  {...(!showLabel ? visuallyHiddenProps : {})}
139
118
  />
140
- <StyledInputContainer>
141
- <PrefixContainer ref={prefixRef}>
142
- <Affix>{prefix}</Affix>
143
- </PrefixContainer>
119
+ <StyledInputContainer
120
+ ref={containerRef}
121
+ invalid={invalid}
122
+ aria-disabled={disabled === true ? true : undefined}
123
+ hasPrefix={prefix != null}
124
+ hasSuffix={suffix != null || showCount}
125
+ >
126
+ {prefix && <PrefixContainer>{prefix}</PrefixContainer>}
144
127
  <StyledInput
145
128
  ref={mergeRefs(forwardRef, ariaRef)}
146
129
  invalid={invalid}
147
- extraLeftPadding={prefixWidth}
148
- extraRightPadding={suffixWidth}
149
130
  {...inputProps}
150
131
  />
151
- <SuffixContainer ref={suffixRef}>
152
- <Affix>{suffix}</Affix>
153
- {showCount && (
154
- <SingleLineCounter>
155
- {maxLength !== undefined ? `${count}/${maxLength}` : count}
156
- </SingleLineCounter>
157
- )}
158
- </SuffixContainer>
132
+ {(suffix || showCount) && (
133
+ <SuffixContainer>
134
+ {suffix}
135
+ {showCount && (
136
+ <SingleLineCounter>
137
+ {maxLength !== undefined ? `${count}/${maxLength}` : count}
138
+ </SingleLineCounter>
139
+ )}
140
+ </SuffixContainer>
141
+ )}
159
142
  </StyledInputContainer>
160
143
  {assistiveText != null && assistiveText.length !== 0 && (
161
144
  <AssistiveText
@@ -179,44 +162,69 @@ const TextFieldRoot = styled.div<{ isDisabled: boolean }>`
179
162
  ${(p) => p.isDisabled && { opacity: p.theme.elementEffect.disabled.opacity }}
180
163
  `
181
164
 
182
- const TextFieldLabel = styled(FieldLabel)`
183
- ${theme((o) => o.margin.bottom(8))}
165
+ export const TextFieldLabel = styled(FieldLabel)`
166
+ margin-bottom: 8px;
184
167
  `
185
168
 
186
- const StyledInputContainer = styled.div`
187
- height: 40px;
169
+ const StyledInputContainer = styled.div<{
170
+ invalid: boolean
171
+ hasPrefix: boolean
172
+ hasSuffix: boolean
173
+ }>`
188
174
  display: grid;
189
- position: relative;
175
+ grid-template-columns: ${(p) =>
176
+ [p.hasPrefix && 'auto', '1fr', p.hasSuffix && 'auto']
177
+ .filter(Boolean)
178
+ .join(' ')};
179
+ height: 40px;
180
+ transition: 0.2s background-color, 0.2s box-shadow;
181
+ color: var(--charcoal-text2);
182
+ background-color: var(--charcoal-surface3);
183
+ border-radius: 4px;
184
+ gap: 4px;
185
+ padding: 0 8px;
186
+ line-height: 22px;
187
+ font-size: 14px;
188
+
189
+ :not(:disabled):not([aria-disabled]):hover,
190
+ [aria-disabled='false']:hover {
191
+ background-color: var(--charcoal-surface3-hover);
192
+ }
193
+
194
+ :not(:disabled):not([aria-disabled]):active,
195
+ [aria-disabled='false']:active {
196
+ outline: none;
197
+ box-shadow: 0 0 0 4px
198
+ ${(p) => (p.invalid ? `rgba(255,43,0,0.32)` : `rgba(0, 150, 250, 0.32);`)};
199
+ }
200
+
201
+ :focus-within {
202
+ outline: none;
203
+ box-shadow: 0 0 0 4px
204
+ ${(p) => (p.invalid ? `rgba(255,43,0,0.32)` : `rgba(0, 150, 250, 0.32);`)};
205
+ }
206
+
207
+ ${(p) =>
208
+ p.invalid &&
209
+ css`
210
+ box-shadow: 0 0 0 4px rgba(255, 43, 0, 0.32);
211
+ `}
190
212
  `
191
213
 
192
- const PrefixContainer = styled.span`
193
- position: absolute;
194
- top: 50%;
195
- left: 8px;
196
- transform: translateY(-50%);
197
- z-index: 1;
214
+ const PrefixContainer = styled.div`
215
+ display: flex;
216
+ padding-left: 8px;
217
+ align-items: center;
198
218
  `
199
219
 
200
220
  const SuffixContainer = styled.span`
201
- position: absolute;
202
- top: 50%;
203
- right: 8px;
204
- transform: translateY(-50%);
205
-
206
221
  display: flex;
222
+ align-items: center;
207
223
  gap: 8px;
208
224
  `
209
225
 
210
- const Affix = styled.span`
211
- user-select: none;
212
-
213
- ${theme((o) => [o.typography(14).preserveHalfLeading, o.font.text2])}
214
- `
215
-
216
226
  const StyledInput = styled.input<{
217
227
  invalid: boolean
218
- extraLeftPadding: number
219
- extraRightPadding: number
220
228
  }>`
221
229
  border: none;
222
230
  box-sizing: border-box;
@@ -230,36 +238,30 @@ const StyledInput = styled.input<{
230
238
  height: calc(100% / 0.875);
231
239
  font-size: calc(14px / 0.875);
232
240
  line-height: calc(22px / 0.875);
233
- padding-left: calc((8px + ${(p) => p.extraLeftPadding}px) / 0.875);
234
- padding-right: calc((8px + ${(p) => p.extraRightPadding}px) / 0.875);
241
+ padding-left: 0;
242
+ padding-right: 0;
235
243
  border-radius: calc(4px / 0.875);
236
244
 
237
245
  /* Display box-shadow for iOS Safari */
238
246
  appearance: none;
247
+ background: transparent;
239
248
 
240
- ${(p) =>
241
- theme((o) => [
242
- o.bg.surface3.hover,
243
- o.outline.default.focus,
244
- p.invalid && o.outline.assertive,
245
- o.font.text2,
246
- ])}
247
-
249
+ color: var(--charcoal-text2);
248
250
  &::placeholder {
249
- ${theme((o) => o.font.text3)}
251
+ color: var(--charcoal-text3);
250
252
  }
251
253
  `
252
254
 
253
255
  const SingleLineCounter = styled.span`
254
- ${theme((o) => [o.typography(14).preserveHalfLeading, o.font.text3])}
256
+ line-height: 22px;
257
+ font-size: 14px;
258
+ color: var(--charcoal-text3);
255
259
  `
256
260
 
257
- const AssistiveText = styled.p<{ invalid: boolean }>`
258
- ${(p) =>
259
- theme((o) => [
260
- o.typography(14),
261
- o.margin.top(8),
262
- o.margin.bottom(0),
263
- o.font[p.invalid ? 'assertive' : 'text1'],
264
- ])}
261
+ export const AssistiveText = styled.p<{ invalid: boolean }>`
262
+ font-size: 14px;
263
+ line-height: 22px;
264
+ margin-top: 4px;
265
+ margin-bottom: -4px;
266
+ color: ${(p) => `var(--charcoal-${p.invalid ? `assertive` : `text2`})`};
265
267
  `
@@ -0,0 +1,22 @@
1
+ import { useEffect } from 'react'
2
+ import * as React from 'react'
3
+
4
+ export function useFocusWithClick(
5
+ containerRef: React.RefObject<HTMLDivElement>,
6
+ inputRef: React.RefObject<HTMLInputElement | HTMLTextAreaElement>
7
+ ) {
8
+ useEffect(() => {
9
+ const el = containerRef.current
10
+ if (el) {
11
+ const handleDown = (e: MouseEvent) => {
12
+ if (e.target !== inputRef.current) {
13
+ inputRef.current?.focus()
14
+ }
15
+ }
16
+ el.addEventListener('click', handleDown)
17
+ return () => {
18
+ el.removeEventListener('click', handleDown)
19
+ }
20
+ }
21
+ })
22
+ }
@@ -1,4 +0,0 @@
1
- import 'jest-styled-components';
2
- import renderer from 'react-test-renderer';
3
- export declare function render(children: JSX.Element): renderer.ReactTestRendererJSON | renderer.ReactTestRendererJSON[] | null;
4
- //# sourceMappingURL=index.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/components/Button/index.test.tsx"],"names":[],"mappings":"AAAA,OAAO,wBAAwB,CAAA;AAG/B,OAAO,QAAQ,MAAM,qBAAqB,CAAA;AAI1C,wBAAgB,MAAM,CAAC,QAAQ,EAAE,GAAG,CAAC,OAAO,4EAI3C"}
@@ -1,24 +0,0 @@
1
- import 'jest-styled-components'
2
-
3
- import Button from '.'
4
- import renderer from 'react-test-renderer'
5
- import { ThemeProvider } from 'styled-components'
6
- import { light } from '@charcoal-ui/theme'
7
-
8
- export function render(children: JSX.Element) {
9
- return renderer
10
- .create(<ThemeProvider theme={light}>{children}</ThemeProvider>)
11
- .toJSON()
12
- }
13
-
14
- describe('Basic', () => {
15
- test('<Button>Hello</Button>', () => {
16
- expect(render(<Button>Hello</Button>)).toMatchSnapshot()
17
- })
18
- })
19
-
20
- describe('Link', () => {
21
- test('<Button to="#">Hello</Button>', () => {
22
- expect(render(<Button to="#">Hello</Button>)).toMatchSnapshot()
23
- })
24
- })