@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.
@@ -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
- {showCount && maxLength && (
141
- <SingleLineCounter>
142
- {count}/{maxLength}
143
- </SingleLineCounter>
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 StyledInput = styled.input<{ invalid: boolean }>`
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(40px / 0.875);
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) calc(8px / 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
- const stories = glob
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((filename) =>
16
- Object.entries(
17
- // eslint-disable-next-line @typescript-eslint/no-var-requires
18
- require(`./${path.relative(__dirname, filename)}`) as Record<
19
- string,
20
- Story<any>
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, story]) =>
25
- exportName !== 'default' && typeof story === 'function'
33
+ ([exportName, exportValue]) =>
34
+ exportName !== 'default' && typeof exportValue === 'function'
26
35
  )
27
- .map<[string, string, Story<any>]>(([exportName, story]) => [
28
- path.relative(__dirname, filename),
29
- exportName,
30
- story,
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(%s).add(%s)',
64
- (_filename, _exportName, story) => {
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(story.args)}
78
+ {story(args)}
71
79
  </ComponentAbstraction>
72
80
  </ThemeProvider>
73
81
  )
package/src/type.d.ts CHANGED
@@ -10,7 +10,3 @@ declare module 'react' {
10
10
  css?: CSSProp<DefaultTheme>
11
11
  }
12
12
  }
13
-
14
- declare global {
15
- const __DEV__: object | undefined // actually object|false, but using undefined allows ! assertion
16
- }