@charcoal-ui/react 1.0.0 → 2.0.0-alpha.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.
- package/dist/components/Radio/index.d.ts +2 -2
- package/dist/components/Radio/index.d.ts.map +1 -1
- package/dist/components/Radio/index.story.d.ts +11 -4
- package/dist/components/Radio/index.story.d.ts.map +1 -1
- package/dist/components/TextField/index.d.ts +4 -0
- package/dist/components/TextField/index.d.ts.map +1 -1
- package/dist/components/TextField/index.story.d.ts +2 -1
- package/dist/components/TextField/index.story.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.modern.js +49 -32
- package/dist/index.modern.js.map +1 -1
- package/dist/index.module.js +1 -1
- package/dist/index.module.js.map +1 -1
- package/package.json +4 -4
- package/src/components/IconButton/index.tsx +1 -0
- package/src/components/Radio/index.story.tsx +16 -17
- package/src/components/Radio/index.test.tsx +15 -16
- package/src/components/Radio/index.tsx +4 -7
- package/src/components/TextField/index.story.tsx +10 -0
- package/src/components/TextField/index.tsx +79 -13
- package/src/components/a11y.test.tsx +32 -24
- package/src/type.d.ts +0 -4
|
@@ -32,6 +32,8 @@ export interface SingleLineTextFieldProps extends TextFieldBaseProps {
|
|
|
32
32
|
readonly multiline?: false
|
|
33
33
|
readonly rows?: never
|
|
34
34
|
readonly type?: string
|
|
35
|
+
readonly prefix?: string
|
|
36
|
+
readonly suffix?: string
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export interface MultiLineTextFieldProps extends TextFieldBaseProps {
|
|
@@ -39,6 +41,8 @@ export interface MultiLineTextFieldProps extends TextFieldBaseProps {
|
|
|
39
41
|
readonly multiline: true
|
|
40
42
|
readonly rows?: number
|
|
41
43
|
readonly type?: never
|
|
44
|
+
readonly prefix?: never
|
|
45
|
+
readonly suffix?: never
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export type TextFieldProps = SingleLineTextFieldProps | MultiLineTextFieldProps
|
|
@@ -88,11 +92,17 @@ const SingleLineTextField = React.forwardRef<
|
|
|
88
92
|
invalid = false,
|
|
89
93
|
assistiveText,
|
|
90
94
|
maxLength,
|
|
95
|
+
prefix = '',
|
|
96
|
+
suffix = '',
|
|
91
97
|
} = props
|
|
92
98
|
|
|
93
99
|
const { visuallyHiddenProps } = useVisuallyHidden()
|
|
94
100
|
const ariaRef = useRef<HTMLInputElement>(null)
|
|
101
|
+
const prefixRef = useRef<HTMLSpanElement>(null)
|
|
102
|
+
const suffixRef = useRef<HTMLSpanElement>(null)
|
|
95
103
|
const [count, setCount] = useState(countStringInCodePoints(props.value ?? ''))
|
|
104
|
+
const [prefixWidth, setPrefixWidth] = useState(0)
|
|
105
|
+
const [suffixWidth, setSuffixWidth] = useState(0)
|
|
96
106
|
|
|
97
107
|
const handleChange = useCallback(
|
|
98
108
|
(value: string) => {
|
|
@@ -121,6 +131,27 @@ const SingleLineTextField = React.forwardRef<
|
|
|
121
131
|
ariaRef
|
|
122
132
|
)
|
|
123
133
|
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
const prefixObserver = new ResizeObserver((entries) => {
|
|
136
|
+
setPrefixWidth(entries[0].contentRect.width)
|
|
137
|
+
})
|
|
138
|
+
const suffixObserver = new ResizeObserver((entries) => {
|
|
139
|
+
setSuffixWidth(entries[0].contentRect.width)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
if (prefixRef.current !== null) {
|
|
143
|
+
prefixObserver.observe(prefixRef.current)
|
|
144
|
+
}
|
|
145
|
+
if (suffixRef.current !== null) {
|
|
146
|
+
suffixObserver.observe(suffixRef.current)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return () => {
|
|
150
|
+
suffixObserver.disconnect()
|
|
151
|
+
prefixObserver.disconnect()
|
|
152
|
+
}
|
|
153
|
+
}, [])
|
|
154
|
+
|
|
124
155
|
return (
|
|
125
156
|
<TextFieldRoot className={className} isDisabled={disabled}>
|
|
126
157
|
<TextFieldLabel
|
|
@@ -132,16 +163,24 @@ const SingleLineTextField = React.forwardRef<
|
|
|
132
163
|
{...(!showLabel ? visuallyHiddenProps : {})}
|
|
133
164
|
/>
|
|
134
165
|
<StyledInputContainer>
|
|
166
|
+
<PrefixContainer ref={prefixRef}>
|
|
167
|
+
<Affix>{prefix}</Affix>
|
|
168
|
+
</PrefixContainer>
|
|
135
169
|
<StyledInput
|
|
136
170
|
ref={mergeRefs(forwardRef, ariaRef)}
|
|
137
171
|
invalid={invalid}
|
|
172
|
+
extraLeftPadding={prefixWidth}
|
|
173
|
+
extraRightPadding={suffixWidth}
|
|
138
174
|
{...inputProps}
|
|
139
175
|
/>
|
|
140
|
-
{
|
|
141
|
-
<
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
176
|
+
<SuffixContainer ref={suffixRef}>
|
|
177
|
+
<Affix>{suffix}</Affix>
|
|
178
|
+
{showCount && maxLength && (
|
|
179
|
+
<SingleLineCounter>
|
|
180
|
+
{count}/{maxLength}
|
|
181
|
+
</SingleLineCounter>
|
|
182
|
+
)}
|
|
183
|
+
</SuffixContainer>
|
|
145
184
|
</StyledInputContainer>
|
|
146
185
|
{assistiveText != null && assistiveText.length !== 0 && (
|
|
147
186
|
<AssistiveText
|
|
@@ -275,19 +314,50 @@ const StyledInputContainer = styled.div`
|
|
|
275
314
|
position: relative;
|
|
276
315
|
`
|
|
277
316
|
|
|
278
|
-
const
|
|
317
|
+
const PrefixContainer = styled.span`
|
|
318
|
+
position: absolute;
|
|
319
|
+
top: 50%;
|
|
320
|
+
left: 8px;
|
|
321
|
+
transform: translateY(-50%);
|
|
322
|
+
`
|
|
323
|
+
|
|
324
|
+
const SuffixContainer = styled.span`
|
|
325
|
+
position: absolute;
|
|
326
|
+
top: 50%;
|
|
327
|
+
right: 8px;
|
|
328
|
+
transform: translateY(-50%);
|
|
329
|
+
|
|
330
|
+
display: flex;
|
|
331
|
+
gap: 8px;
|
|
332
|
+
`
|
|
333
|
+
|
|
334
|
+
const Affix = styled.span`
|
|
335
|
+
user-select: none;
|
|
336
|
+
|
|
337
|
+
${theme((o) => [o.typography(14).preserveHalfLeading, o.font.text2])}
|
|
338
|
+
`
|
|
339
|
+
|
|
340
|
+
const StyledInput = styled.input<{
|
|
341
|
+
invalid: boolean
|
|
342
|
+
extraLeftPadding: number
|
|
343
|
+
extraRightPadding: number
|
|
344
|
+
}>`
|
|
279
345
|
border: none;
|
|
280
346
|
box-sizing: border-box;
|
|
281
347
|
outline: none;
|
|
348
|
+
font-family: inherit;
|
|
282
349
|
|
|
283
350
|
/* Prevent zooming for iOS Safari */
|
|
284
351
|
transform-origin: top left;
|
|
285
352
|
transform: scale(0.875);
|
|
286
353
|
width: calc(100% / 0.875);
|
|
287
|
-
height: calc(
|
|
354
|
+
height: calc(100% / 0.875);
|
|
288
355
|
font-size: calc(14px / 0.875);
|
|
289
356
|
line-height: calc(22px / 0.875);
|
|
290
|
-
padding: calc(9px / 0.875)
|
|
357
|
+
padding-top: calc(9px / 0.875);
|
|
358
|
+
padding-bottom: calc(9px / 0.875);
|
|
359
|
+
padding-left: calc((8px + ${(p) => p.extraLeftPadding}px) / 0.875);
|
|
360
|
+
padding-right: calc((8px + ${(p) => p.extraRightPadding}px) / 0.875);
|
|
291
361
|
border-radius: calc(4px / 0.875);
|
|
292
362
|
|
|
293
363
|
/* Display box-shadow for iOS Safari */
|
|
@@ -320,6 +390,7 @@ const StyledTextarea = styled.textarea<{ invalid: boolean }>`
|
|
|
320
390
|
box-sizing: border-box;
|
|
321
391
|
outline: none;
|
|
322
392
|
resize: none;
|
|
393
|
+
font-family: inherit;
|
|
323
394
|
|
|
324
395
|
/* Prevent zooming for iOS Safari */
|
|
325
396
|
transform-origin: top left;
|
|
@@ -359,11 +430,6 @@ const StyledTextarea = styled.textarea<{ invalid: boolean }>`
|
|
|
359
430
|
`
|
|
360
431
|
|
|
361
432
|
const SingleLineCounter = styled.span`
|
|
362
|
-
position: absolute;
|
|
363
|
-
top: 50%;
|
|
364
|
-
right: 8px;
|
|
365
|
-
transform: translateY(-50%);
|
|
366
|
-
|
|
367
433
|
${theme((o) => [o.typography(14).preserveHalfLeading, o.font.text3])}
|
|
368
434
|
`
|
|
369
435
|
|
|
@@ -10,26 +10,38 @@ import { light, dark } from '@charcoal-ui/theme'
|
|
|
10
10
|
|
|
11
11
|
expect.extend(toHaveNoViolations)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
interface StoryWithMetadata<ArgsType = any> {
|
|
15
|
+
filename: string
|
|
16
|
+
name: string
|
|
17
|
+
story: Story<ArgsType>
|
|
18
|
+
args: ArgsType
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const stories: StoryWithMetadata[] = glob
|
|
14
22
|
.sync(path.resolve(__dirname, '**/*.story.tsx'))
|
|
15
|
-
.flatMap((
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
.flatMap((filePath) => {
|
|
24
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
25
|
+
const exports = require(`./${path.relative(
|
|
26
|
+
__dirname,
|
|
27
|
+
filePath
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
)}`) as Record<string, any>
|
|
30
|
+
|
|
31
|
+
return Object.entries(exports)
|
|
23
32
|
.filter(
|
|
24
|
-
([exportName,
|
|
25
|
-
exportName !== 'default' && typeof
|
|
33
|
+
([exportName, exportValue]) =>
|
|
34
|
+
exportName !== 'default' && typeof exportValue === 'function'
|
|
26
35
|
)
|
|
27
|
-
.map
|
|
28
|
-
path.relative(__dirname,
|
|
29
|
-
exportName,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
.map(([exportName, exportValue]) => ({
|
|
37
|
+
filename: path.relative(__dirname, filePath),
|
|
38
|
+
name: exportName,
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
|
+
story: exportValue as Story<any>,
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
42
|
+
args: { ...exports.default.args, ...exportValue.args },
|
|
43
|
+
}))
|
|
44
|
+
})
|
|
33
45
|
|
|
34
46
|
const themes = Object.entries({
|
|
35
47
|
light,
|
|
@@ -43,10 +55,6 @@ const links = Object.entries({
|
|
|
43
55
|
const div = document.body.appendChild(document.createElement('div'))
|
|
44
56
|
|
|
45
57
|
beforeEach(() => {
|
|
46
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
47
|
-
// @ts-expect-error
|
|
48
|
-
global.__DEV__ = {}
|
|
49
|
-
|
|
50
58
|
global.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
|
51
59
|
observe() {
|
|
52
60
|
return null
|
|
@@ -60,14 +68,14 @@ beforeEach(() => {
|
|
|
60
68
|
describe.each(themes)('using %s theme', (_name, theme) => {
|
|
61
69
|
describe.each(links)('using %s component', (_name, link) => {
|
|
62
70
|
describe.each(stories)(
|
|
63
|
-
'storiesOf(
|
|
64
|
-
(
|
|
71
|
+
'storiesOf($filename).add($name)',
|
|
72
|
+
({ story, args }) => {
|
|
65
73
|
it('has no accessibility violations', async () => {
|
|
66
74
|
expect(() => {
|
|
67
75
|
render(
|
|
68
76
|
<ThemeProvider theme={theme}>
|
|
69
77
|
<ComponentAbstraction components={{ Link: link }}>
|
|
70
|
-
{story(
|
|
78
|
+
{story(args)}
|
|
71
79
|
</ComponentAbstraction>
|
|
72
80
|
</ThemeProvider>
|
|
73
81
|
)
|