@telus-uds/components-base 1.2.0 → 1.3.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 (116) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +12 -0
  3. package/__tests__/FlexGrid/Col.test.jsx +6 -10
  4. package/__tests__/utils/props.test.js +36 -0
  5. package/__tests__/utils/semantics.test.jsx +1 -10
  6. package/component-docs.json +64 -53
  7. package/generate-component-docs.js +3 -0
  8. package/lib/A11yText/index.js +2 -2
  9. package/lib/ActivityIndicator/index.js +3 -3
  10. package/lib/Box/Box.js +4 -4
  11. package/lib/Button/Button.js +2 -2
  12. package/lib/Button/ButtonGroup.js +10 -12
  13. package/lib/Button/ButtonLink.js +4 -4
  14. package/lib/Button/propTypes.js +4 -2
  15. package/lib/Card/Card.js +2 -2
  16. package/lib/Card/CardBase.js +6 -9
  17. package/lib/Card/PressableCardBase.js +3 -3
  18. package/lib/Checkbox/Checkbox.js +3 -3
  19. package/lib/Divider/Divider.js +2 -2
  20. package/lib/FlexGrid/FlexGrid.js +4 -4
  21. package/lib/FlexGrid/helpers/index.js +1 -4
  22. package/lib/IconButton/IconButton.js +3 -3
  23. package/lib/Link/LinkBase.js +10 -10
  24. package/lib/List/List.js +3 -3
  25. package/lib/Progress/Progress.js +2 -2
  26. package/lib/Progress/ProgressBar.js +2 -2
  27. package/lib/Radio/Radio.js +3 -3
  28. package/lib/Spacer/Spacer.js +2 -2
  29. package/lib/StackView/getStackedContent.js +1 -1
  30. package/lib/StepTracker/StepTracker.js +2 -2
  31. package/lib/Tags/Tags.js +9 -9
  32. package/lib/ThemeProvider/useThemeTokens.js +3 -3
  33. package/lib/ThemeProvider/utils/theme-tokens.js +3 -3
  34. package/lib/ToggleSwitch/ToggleSwitch.js +6 -6
  35. package/lib/ToggleSwitch/ToggleSwitchGroup.js +8 -8
  36. package/lib/utils/a11y/semantics.js +4 -3
  37. package/lib/utils/index.js +4 -4
  38. package/lib/utils/pressability.js +2 -2
  39. package/lib/utils/props/a11yProps.js +153 -0
  40. package/lib/utils/props/clickProps.js +36 -0
  41. package/lib/utils/props/componentPropType.js +70 -0
  42. package/lib/utils/props/copyPropTypes.js +14 -0
  43. package/lib/utils/props/getPropSelector.js +13 -0
  44. package/lib/utils/props/hrefAttrsProp.js +41 -0
  45. package/lib/utils/props/index.js +149 -0
  46. package/lib/utils/props/linkProps.js +64 -0
  47. package/lib/utils/props/paddingProp.js +20 -0
  48. package/lib/utils/props/pressProps.js +57 -0
  49. package/lib/utils/props/rectProp.js +20 -0
  50. package/lib/utils/props/responsiveProps.js +40 -0
  51. package/lib/utils/props/selectSystemProps.js +31 -0
  52. package/lib/utils/props/spacingProps.js +71 -0
  53. package/lib/utils/props/tokens.js +145 -0
  54. package/lib/utils/props/variantProp.js +28 -0
  55. package/lib/utils/props/viewProps.js +34 -0
  56. package/lib/utils/useResponsiveProp.js +1 -1
  57. package/lib/utils/useSpacingScale.js +4 -4
  58. package/package.json +3 -3
  59. package/release-context.json +4 -4
  60. package/src/A11yText/index.jsx +1 -1
  61. package/src/ActivityIndicator/index.jsx +1 -1
  62. package/src/Box/Box.jsx +5 -4
  63. package/src/Button/Button.jsx +1 -1
  64. package/src/Button/ButtonGroup.jsx +17 -8
  65. package/src/Button/ButtonLink.jsx +1 -1
  66. package/src/Button/propTypes.js +2 -1
  67. package/src/Card/Card.jsx +1 -1
  68. package/src/Card/CardBase.jsx +6 -5
  69. package/src/Card/PressableCardBase.jsx +1 -1
  70. package/src/Checkbox/Checkbox.jsx +1 -1
  71. package/src/Divider/Divider.jsx +2 -2
  72. package/src/FlexGrid/FlexGrid.jsx +11 -5
  73. package/src/FlexGrid/helpers/index.js +1 -3
  74. package/src/IconButton/IconButton.jsx +1 -1
  75. package/src/Link/LinkBase.jsx +1 -1
  76. package/src/List/List.jsx +1 -1
  77. package/src/Progress/Progress.jsx +1 -1
  78. package/src/Progress/ProgressBar.jsx +1 -1
  79. package/src/Radio/Radio.jsx +1 -1
  80. package/src/Spacer/Spacer.jsx +2 -2
  81. package/src/StackView/getStackedContent.jsx +1 -1
  82. package/src/StepTracker/StepTracker.jsx +1 -1
  83. package/src/Tags/Tags.jsx +1 -7
  84. package/src/ThemeProvider/useThemeTokens.js +3 -3
  85. package/src/ThemeProvider/utils/theme-tokens.js +3 -3
  86. package/src/ToggleSwitch/ToggleSwitch.jsx +1 -7
  87. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +1 -1
  88. package/src/utils/a11y/semantics.js +3 -2
  89. package/src/utils/index.js +1 -1
  90. package/src/utils/pressability.js +1 -1
  91. package/src/utils/props/a11yProps.js +151 -0
  92. package/src/utils/props/clickProps.js +31 -0
  93. package/src/utils/props/componentPropType.js +67 -0
  94. package/src/utils/props/copyPropTypes.js +3 -0
  95. package/src/utils/props/getPropSelector.js +14 -0
  96. package/src/utils/props/hrefAttrsProp.js +25 -0
  97. package/src/utils/props/index.js +15 -0
  98. package/src/utils/props/linkProps.js +43 -0
  99. package/src/utils/props/paddingProp.js +10 -0
  100. package/src/utils/props/pressProps.js +45 -0
  101. package/src/utils/props/rectProp.js +10 -0
  102. package/src/utils/props/responsiveProps.js +30 -0
  103. package/src/utils/props/selectSystemProps.js +25 -0
  104. package/src/utils/props/spacingProps.js +58 -0
  105. package/src/utils/props/tokens.js +150 -0
  106. package/src/utils/props/variantProp.js +20 -0
  107. package/src/utils/props/viewProps.js +23 -0
  108. package/src/utils/useResponsiveProp.js +1 -1
  109. package/src/utils/useSpacingScale.js +4 -4
  110. package/.ultra.cache.json +0 -1
  111. package/lib/utils/a11y/propTypes.js +0 -61
  112. package/lib/utils/a11y/propTypes.native.js +0 -47
  113. package/lib/utils/propTypes.js +0 -566
  114. package/src/utils/a11y/propTypes.js +0 -61
  115. package/src/utils/a11y/propTypes.native.js +0 -39
  116. package/src/utils/propTypes.js +0 -561
@@ -1,561 +0,0 @@
1
- import PropTypes from 'prop-types'
2
- import { Linking, Platform } from 'react-native'
3
- import { components as tokenKeys } from '@telus-uds/system-theme-tokens'
4
- import a11yPropTypes from './a11y/propTypes'
5
-
6
- export const paddingProp = {
7
- propType: PropTypes.shape({
8
- paddingBottom: PropTypes.number,
9
- paddingLeft: PropTypes.number,
10
- paddingRight: PropTypes.number,
11
- paddingTop: PropTypes.number
12
- })
13
- }
14
-
15
- export const rectProp = {
16
- propType: PropTypes.shape({
17
- bottom: PropTypes.number,
18
- left: PropTypes.number,
19
- right: PropTypes.number,
20
- top: PropTypes.number
21
- })
22
- }
23
-
24
- /**
25
- * @typedef {{[key: string]: string|number|boolean}} AppearanceSet
26
- * @typedef {AppearanceSet} VariantProp
27
- */
28
- export const variantProp = {
29
- /**
30
- * 'variant' is an optional object prop on all themable components.
31
- *
32
- * Contains an object with keys that correspond to the current component theme's allowed
33
- * appearances and values that correspond to the allowed values of that component.
34
- *
35
- * Since the particular keys and values depend on which theme is currently active,
36
- * the exact shape of variant props is not enforced using PropTypes.
37
- */
38
- propType: PropTypes.objectOf(
39
- PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool])
40
- )
41
- }
42
-
43
- // Tokens can be primitive values (e.g. `'rgba(0,0,0,0'`, `12`), or objects of such values,
44
- // such as tokens that describe shadow (e.g. shadow: { inset: true, color: 'rgba(...)' ... }),
45
- // or components (e.g. Icon tokens)
46
- const tokenValue = PropTypes.oneOfType([
47
- PropTypes.string,
48
- PropTypes.number,
49
- PropTypes.bool,
50
- PropTypes.elementType
51
- ])
52
- const tokenValueType = PropTypes.oneOfType([tokenValue, PropTypes.objectOf(tokenValue)])
53
-
54
- export const getTokenNames = (componentName) => {
55
- const componentTokenSchema = tokenKeys[componentName]
56
- if (!componentTokenSchema) {
57
- throw new Error(`No "${componentName}" tokenKeys in @telus-uds/system-theme-tokens`)
58
- }
59
- return Object.keys(componentTokenSchema)
60
- }
61
-
62
- /**
63
- * Returns the subset of a set of tokens that may be accepted by the `tokens` prop of a named component
64
- * or by a provided array of tokens. A prefix may be provided, for example:
65
- *
66
- * @example
67
- * ```jsx
68
- * // returns tokens from `themeTokens` that exist in the theme schema for `Button`.
69
- * selectTokens('Button', themeTokens)
70
- * ```
71
- *
72
- * @example
73
- * ```jsx
74
- * // returns `{ backgroundColor, width }` where the values of those keys come from
75
- * // the source object's `{ containerBackgroundColor, containerWidth }` properties.
76
- * selectTokens(['backgroundColor', 'width'], themeTokens, 'container')
77
- * ```
78
- *
79
- * @example
80
- * ```jsx
81
- * // returns tokens that are defined in the `Button` theme schema, from tokens with
82
- * // a prefix `'button'` e.g. a token `buttonBorderWidth` outputs as `borderWidth`.
83
- * selectTokens('Button', themeTokens, 'button')
84
- * ```
85
- *
86
- * @param {string[]|string} specifier - a name of a component in the theme schema, or an array of token names
87
- * @param {object} tokens - a source object of theme tokens
88
- * @param {string} [prefix] - if provided, matches keys in the source object with this as the first camelCase item
89
- * @returns {object} - subset of theme tokens
90
- */
91
- export const selectTokens = (specifier, tokens, prefix) => {
92
- const tokenNames = typeof specifier === 'string' ? getTokenNames(specifier) : specifier
93
- const filteredTokens = tokenNames.reduce((validTokens, key) => {
94
- const prefixedKey = prefix ? `${prefix}${key[0].toUpperCase()}${key.slice(1)}` : key
95
- const token = tokens[prefixedKey]
96
- return token !== undefined ? { ...validTokens, [key]: token } : validTokens
97
- }, {})
98
- return filteredTokens
99
- }
100
-
101
- /**
102
- * @typedef {string|number|boolean|{[key: string]:string|number|boolean}} TokenValue
103
- * @typedef {{[key: string]: TokenValue}} TokensSet
104
- * @typedef {(AppearanceSet) => TokensSet} TokensGetter
105
- * @typedef {TokensSet|TokensGetter} TokensProp
106
- */
107
- /**
108
- * 'tokens' is an optional object prop on all themable components. Its keys must match the
109
- * token keys in the component's theme schema.
110
- *
111
- * This prop is intended to be used as an 'escape hatch' for difficult or exceptional cases
112
- * where the main theming system doesn't apply. It is intentionally permissive about values.
113
- *
114
- * @param {...string} componentsNames - one or more ComponentName, which tokens keys are accepted
115
- * @return {function} - a custom PropTypes validator
116
- *
117
- * @example
118
- * Component.propTypes = {
119
- * // accepts all tokens keys defined in Component schema
120
- * tokens: getTokensPropType('Component')
121
- * }
122
- *
123
- * Component.propTypes = {
124
- * // accepts all tokens keys defined in schemas for Component1 and Component2
125
- * tokens: getTokensPropType('Component1', 'Component2')
126
- * }
127
- */
128
- export const getTokensPropType = (...componentsNames) => (props, propName, componentName) => {
129
- PropTypes.checkPropTypes(
130
- {
131
- [propName]: PropTypes.oneOfType([PropTypes.object, PropTypes.func])
132
- },
133
- props,
134
- propName,
135
- componentName
136
- )
137
-
138
- if (typeof props[propName] !== 'function') {
139
- PropTypes.checkPropTypes(
140
- {
141
- [propName]: PropTypes.exact(
142
- Object.fromEntries(
143
- componentsNames.flatMap((component) =>
144
- getTokenNames(component).map((key) => [key, tokenValueType])
145
- )
146
- )
147
- )
148
- },
149
- props,
150
- propName,
151
- componentName
152
- )
153
- }
154
- }
155
-
156
- /**
157
- * Get a proptypes validator for a set of tokens that is not based on a component in the theme schema.
158
- *
159
- * For example, for a set of tokens used in a common style, or for a set of tokens required by
160
- * a themeless component whose tokens are set by a parent but requires tokens of a certain shape.
161
- *
162
- * By default, requires an object with a complete set of tokens (allowing `null`, but not `undefined`).
163
- *
164
- * @param {string[]} componentTokenKeys - array of strings of token names
165
- * @param {object} [options]
166
- * @param {boolean} [options.partial] - if true, allows tokens to be undefined
167
- * @param {boolean} [options.allowFunction] - if true, allows functions as well as tokens objects
168
- * @returns
169
- */
170
- export const getTokensSetPropType = (
171
- componentTokenKeys,
172
- { partial = false, allowFunction = false } = {}
173
- ) => {
174
- const tokensObjectValidator = PropTypes.exact(
175
- Object.fromEntries(
176
- componentTokenKeys.map((key) => [
177
- key,
178
- partial
179
- ? tokenValueType
180
- : // Some theme tokens can validly resolve to `null`, but .isRequired treats null as undefined
181
- (props, propName, ...args) =>
182
- props[propName] !== null && tokenValueType.isRequired(props, propName, ...args)
183
- ])
184
- )
185
- )
186
- return allowFunction
187
- ? PropTypes.oneOfType([tokensObjectValidator, PropTypes.func])
188
- : tokensObjectValidator
189
- }
190
-
191
- function getPropSelector(propTypes, regexp) {
192
- const keys = Object.keys(propTypes)
193
- return (props) =>
194
- Object.entries(props).reduce(
195
- (items, [key, value]) =>
196
- keys.includes(key) || (regexp && regexp.test(key))
197
- ? {
198
- ...items,
199
- [key]: value
200
- }
201
- : items,
202
- {}
203
- )
204
- }
205
-
206
- export const a11yProps = {
207
- /**
208
- * Proptypes for recognised React Native accessiblity (a11y) props.
209
- * Spread this in the propTypes of components that accept React Native a11y props.
210
- */
211
- types: a11yPropTypes,
212
- /**
213
- * Filters a props object, returning only recognised React Native accessiblity (a11y) props.
214
- *
215
- * Where components accept React Native a11y props, pass { ...rest } from its props to this,
216
- * then spread the returned object into the component's props (usually its outer container).
217
- */
218
- select: getPropSelector(a11yPropTypes, /^aria-/),
219
- /**
220
- * Use this to disable focus for elements which are visually hidden but still rendered.
221
- */
222
- nonFocusableProps: {
223
- focusable: false, // for android, and for keyboard nav in web
224
- ...Platform.select({
225
- web: {
226
- accessibilityHidden: true // web screenreaders
227
- },
228
- android: {
229
- importantForAccessibility: 'no-hide-descendants'
230
- },
231
- ios: {
232
- accessibilityElementsHidden: true
233
- }
234
- })
235
- },
236
- /**
237
- * Generates an object of platform-appropriate a11y props describing an item that has an
238
- * ordered position in a set. Examples of usage by accessibility tools includes screenreaders
239
- * saying "Item X of Y" when this item is select.
240
- *
241
- * @param {number} itemsCount - the number of items in the set
242
- * @param {number} index - the current item's index in the set
243
- * @returns {object} - platform-applicable a11y props describing this position (if available)
244
- */
245
- getPositionInSet: (itemsCount, index) =>
246
- Platform.select({
247
- web: {
248
- // accessibilityPosInSet etc exists in React Native Web main branch
249
- // but not in a release compatible with Expo etc; just use `aria-*`
250
- 'aria-setsize': itemsCount,
251
- 'aria-posinset': index + 1
252
- },
253
- // No equivalents exist on the native side
254
- default: {}
255
- })
256
- }
257
-
258
- // Props related to HTML <a> anchor link attributes.
259
- const targetValues = ['_self', '_blank', '_parent', '_top']
260
- export const hrefAttrsProp = {
261
- types: {
262
- // React Native Web >= 0.15.0 supports hrefAttrs prop with only these properties
263
- // and passes them to <a> if an element has a href prop or accessibilityRole "link"
264
- download: PropTypes.string,
265
- rel: PropTypes.string,
266
- target: PropTypes.oneOf(targetValues)
267
- },
268
- /**
269
- * Takes a props object, bundles any hrefAttrs props present into one object
270
- * and returns that keyed as `hrefAttrs` and non-hrefAttrs props keyed as `rest`
271
- */
272
- bundle: ({ download, rel, target, ...rest }) => ({
273
- hrefAttrs: {
274
- download,
275
- rel,
276
- target
277
- },
278
- rest
279
- })
280
- }
281
-
282
- const pressHandlerPropTypes = {
283
- onPress: PropTypes.func,
284
- onPressIn: PropTypes.func,
285
- onPressOut: PropTypes.func,
286
- ...Platform.select({
287
- web: {
288
- onMouseEnter: PropTypes.func,
289
- onMouseLeave: PropTypes.func,
290
- onFocus: PropTypes.func,
291
- onBlur: PropTypes.func
292
- },
293
- default: {
294
- onLongPress: PropTypes.func
295
- }
296
- })
297
- }
298
-
299
- const pressPropTypes = {
300
- ...pressHandlerPropTypes,
301
- disabled: PropTypes.bool,
302
- ...Platform.select({
303
- web: {},
304
- default: {
305
- hitSlop: PropTypes.number,
306
- pressRetentionOffset: PropTypes.oneOfType([PropTypes.number, rectProp.propType])
307
- }
308
- })
309
- }
310
-
311
- export const pressProps = {
312
- /**
313
- * Proptypes for clickable or pressable components, including web-only props
314
- */
315
- types: pressPropTypes,
316
- /**
317
- * Filters a props object, returning only the platform-relevant press props defined above
318
- */
319
- select: getPropSelector(pressPropTypes),
320
- selectHandlers: getPropSelector(pressHandlerPropTypes)
321
- }
322
-
323
- const clickHandlerMapping = {
324
- onClick: 'onPress',
325
- mouseDown: 'onPressIn',
326
- mouseUp: 'onPressOut'
327
- }
328
-
329
- export const clickProps = {
330
- /**
331
- * Web-oriented HTML click handlers that may be mapped to React Native press handlers
332
- */
333
- types: Object.fromEntries(
334
- Object.keys(clickHandlerMapping).map((mouseName) => [mouseName, PropTypes.func])
335
- ),
336
- /**
337
- * Takes a set of props and converts HTML mouse click oriented event handlers to closest
338
- * equivalent React Native press event handler.
339
- *
340
- * Use this when a component that expects press-oriented props may need to support third-party
341
- * web-oriented tooling that injects web-oriented event handlers directly. For example, for
342
- * to support use with NextJS's 'next/link' component, which injects `onClick` prop into its child.
343
- */
344
- toPressProps: (props) =>
345
- Object.fromEntries(
346
- Object.entries(props).map(([originalName, value]) => {
347
- const translatedName = clickHandlerMapping[originalName]
348
- return translatedName ? [translatedName, value] : [originalName, value]
349
- })
350
- )
351
- }
352
-
353
- const linkPropTypes = {
354
- ...pressPropTypes,
355
- href: PropTypes.string,
356
- hrefAttrs: PropTypes.shape(hrefAttrsProp.types),
357
- ...a11yPropTypes
358
- }
359
-
360
- export const linkProps = {
361
- /**
362
- * Proptypes for components that, on Web, can output <a href="..."> link elements
363
- */
364
- types: linkPropTypes,
365
- /**
366
- * Filters a props object, returning only the platform-relevant link props defined above
367
- */
368
- select: getPropSelector(linkPropTypes),
369
- /**
370
- * Turn hrefs into press handlers on Native and throw if not given `onPress` or `href`.
371
- *
372
- * @param {({ onPress?: () => void, href?: string })}
373
- * @returns {(() => void)|undefined} Returns a press handler, or undefined on web if href
374
- * string is provided. Expects calling component to use href string on web (e.g. in `<a>`).
375
- */
376
- handleHref: ({ onPress, href }) => {
377
- if (!href && !onPress) {
378
- throw new Error('handleHref requires either href or onPress')
379
- }
380
- return Platform.select({
381
- web: onPress,
382
- default: (...args) => {
383
- if (onPress) onPress(...args)
384
- if (href) Linking.openURL(href)
385
- }
386
- })
387
- }
388
- }
389
-
390
- const viewPropTypes = {
391
- pointerEvents: PropTypes.oneOf(['all', 'none', 'box-only', 'box-none']),
392
- onLayout: PropTypes.func,
393
- nativeID: PropTypes.string,
394
- testID: PropTypes.string,
395
- dataSet: PropTypes.object
396
- }
397
-
398
- export const viewProps = {
399
- /**
400
- * Subset of `View` proptypes that could conceivably be needed on any component
401
- * that renders a single View.
402
- */
403
- types: viewPropTypes,
404
- /**
405
- * Filters a props object, returning only cross-platform View props that are potentially
406
- * relevant to any basic layout component that renders one View.
407
- */
408
- select: getPropSelector(viewPropTypes)
409
- }
410
-
411
- const getByViewport = (propType) => ({
412
- xs: propType,
413
- sm: propType,
414
- md: propType,
415
- lg: propType,
416
- xl: propType
417
- })
418
- /**
419
- * Utilities for props that allow different values to be applied at different viewports.
420
- *
421
- * These should apply viewport inheritance such that if a viewport is undefined, the value is
422
- * taken from the next smallest viewport for which a value is available. For example, a
423
- * responsive prop { xs: 2, lg: 3 } should apply 2 at sizes sm and md, and 3 at size xl.
424
- *
425
- * @property {Function} getByViewport - returns an object where each each viewport has a key
426
- * containing the provided argument.
427
- * @property {Function} getTypeByViewport - returns a PropTypes shape validator for an object where
428
- * each viewport has a key containing the provided proptype.
429
- * @property {Function} getTypeOptionallyByViewport - returns a PropTypes validator that accepts
430
- * either the provided proptype on its own, or the output of getTypeByViewport
431
- */
432
- export const responsiveProps = {
433
- getByViewport,
434
- getTypeByViewport: (propType) => PropTypes.shape(getByViewport(propType)),
435
- getTypeOptionallyByViewport: (propType) =>
436
- PropTypes.oneOfType([propType, PropTypes.shape(getByViewport(propType))])
437
- }
438
-
439
- /**
440
- * @typedef {0|1|2|3|4|5|6|7|8|9|10|11} SpacingIndex - value used to select a size on the spacing scale
441
- *
442
- * @typedef SpacingOptions
443
- * @property {VariantProp} [SpacingOptions.variant] - optional theme scale variants e.g. compact, wide
444
- * @property {number} [SpacingOptions.size] - optional override to force a particular pixel size
445
- * @property {number} [SpacingOptions.subtract] - optional number to subtract from final pixel size
446
- *
447
- * @typedef SpacingObject
448
- * @property {SpacingIndex} [SpacingObject.xs] - space scale index to use above xs viewport
449
- * @property {SpacingIndex} [SpacingObject.sm] - space scale index to use above sm viewport
450
- * @property {SpacingIndex} [SpacingObject.md] - space scale index to use above md viewport
451
- * @property {SpacingIndex} [SpacingObject.lg] - space scale index to use above lg viewport
452
- * @property {SpacingIndex} [SpacingObject.xl] - space scale index to use above xl viewport
453
- * @property {SpacingIndex} [SpacingObject.space] - space scale index to use at all viewports
454
- * @property {SpacingOptions} [SpacingObject.options] - optional options for this spacing
455
- *
456
- * @typedef {SpacingIndex|SpacingObject} SpacingValue
457
- */
458
- const spacingScale = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
459
- const spacingIndexPropType = PropTypes.oneOf(spacingScale)
460
- const spacingObjectPropType = PropTypes.shape({
461
- ...responsiveProps.getByViewport(spacingIndexPropType),
462
- space: spacingIndexPropType,
463
- options: PropTypes.shape({
464
- variant: variantProp.propType,
465
- size: PropTypes.number
466
- })
467
- })
468
- /**
469
- * Components and utilities that assign fixed space between components use a theme-defined spacing scale.
470
- *
471
- * They typically take one or more props of the {@link SpacingValue} type and turn it into a pixel size value
472
- * using the hook `useSpacingScale`, which resolves any options or responsive behaviour and returns the
473
- * appropriate value from the theme spacing scale.
474
- *
475
- * - see /ADRs/inter-component-spacing/README.md - ADR on this structure
476
- * - see /src/utils/spacing/useSpacingScale.js - hook that processes spacing values
477
- * - @see {@link SpacingIndex} - themes provide spacing scales of up to 12 sizes with optional theme rules.
478
- * - @see {@link SpacingValue} - either a simple number referencing an index position on the spacing
479
- * scale, or an object with an optional `options` key and one or more spacing indexes keyed either by
480
- * viewports or `space` to apply at all viewports.
481
- */
482
- export const spacingProps = {
483
- scale: spacingScale,
484
- types: {
485
- spacingIndex: spacingIndexPropType,
486
- spacingObject: spacingObjectPropType,
487
-
488
- // Most spacing components and utilities take this prop / arg type:
489
- spacingValue: PropTypes.oneOfType([spacingIndexPropType, spacingObjectPropType])
490
- }
491
- }
492
-
493
- /**
494
- * Returns a prop type validator which checks whether a prop is either a component or an array of
495
- * components of a given type, based on their `name` or `displayName` properties.
496
- * Use an array of strings for `passedName` to accept more than one component type.
497
- * For an array the validation fails on the first occurrence of an invalid element.
498
- *
499
- * @param {string|string[]} passedName
500
- * @return {function}
501
- */
502
- export const componentPropType = (passedName) => {
503
- const passedNames = typeof passedName === 'string' ? [passedName] : passedName
504
-
505
- const checkProp = (props, propName, componentName) => {
506
- if (props[propName] === undefined || props[propName] === null) {
507
- return undefined
508
- }
509
-
510
- if (Array.isArray(props[propName])) {
511
- // Iterates through every child and try to find the first element that does not match the passed name
512
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean
513
- return props[propName]
514
- .map((_, index) => checkProp(props[propName], index, componentName))
515
- .find(Boolean)
516
- }
517
-
518
- const nameInProp = props[propName].type?.displayName || props[propName].type?.name
519
- if (!nameInProp || !passedNames.includes(nameInProp)) {
520
- const propDescription = nameInProp ? `Component ${nameInProp}` : typeof props[propName]
521
- return new Error(
522
- `${componentName}: ${propDescription} was passed to \`${propName}\` prop; should be ${passedNames.join(
523
- ' or '
524
- )}`
525
- )
526
- }
527
- return undefined
528
- }
529
-
530
- const checkRequired = (props, propName, componentName) => {
531
- if (props[propName] === undefined) {
532
- return new Error(
533
- `The prop \`${propName}\` is marked as required in \`${componentName}\`, but its value is ${props[propName]}.`
534
- )
535
- }
536
- return undefined
537
- }
538
-
539
- const createValidate = (isRequired) => {
540
- if (isRequired) {
541
- return (props, propName, componentName) => {
542
- const checkForError = checkProp(props, propName, componentName)
543
-
544
- if (checkForError) {
545
- return checkForError
546
- }
547
-
548
- return checkRequired(props, propName, componentName)
549
- }
550
- }
551
-
552
- return checkProp
553
- }
554
-
555
- const validate = createValidate()
556
- validate.isRequired = createValidate(true)
557
-
558
- return validate
559
- }
560
-
561
- export const copyPropTypes = PropTypes.oneOf(['en', 'fr'])