@telus-uds/components-base 0.0.2-prerelease.1 → 0.0.2-prerelease.5

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 (161) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/__fixtures__/testTheme.js +264 -84
  3. package/__tests__/Box/Box.test.jsx +81 -58
  4. package/__tests__/Card/Card.test.jsx +63 -0
  5. package/__tests__/Divider/Divider.test.jsx +26 -5
  6. package/__tests__/Feedback/Feedback.test.jsx +42 -0
  7. package/__tests__/FlexGrid/Col.test.jsx +5 -0
  8. package/__tests__/Pagination/Pagination.test.jsx +160 -0
  9. package/__tests__/Spacer/Spacer.test.jsx +63 -0
  10. package/__tests__/StackView/StackView.test.jsx +242 -0
  11. package/__tests__/StackView/StackWrap.test.jsx +47 -0
  12. package/__tests__/StackView/getStackedContent.test.jsx +295 -0
  13. package/__tests__/TextInput/TextInput.test.jsx +146 -0
  14. package/__tests__/ThemeProvider/useThemeTokens.test.jsx +5 -3
  15. package/__tests__/utils/spacing.test.jsx +273 -0
  16. package/__tests__/utils/useUniqueId.test.js +31 -0
  17. package/babel.config.json +8 -0
  18. package/jest.config.js +7 -6
  19. package/lib/A11yInfoProvider/index.js +2 -2
  20. package/lib/A11yText/index.js +1 -3
  21. package/lib/ActivityIndicator/Spinner.web.js +3 -5
  22. package/lib/Box/Box.js +117 -82
  23. package/lib/Button/Button.js +1 -3
  24. package/lib/Button/ButtonBase.js +9 -21
  25. package/lib/Button/ButtonGroup.js +14 -25
  26. package/lib/Button/ButtonLink.js +1 -3
  27. package/lib/Card/Card.js +103 -0
  28. package/lib/Card/index.js +2 -0
  29. package/lib/Divider/Divider.js +40 -4
  30. package/lib/ExpandCollapse/Accordion.js +1 -3
  31. package/lib/ExpandCollapse/Control.js +3 -5
  32. package/lib/ExpandCollapse/Panel.js +2 -4
  33. package/lib/Feedback/Feedback.js +110 -0
  34. package/lib/Feedback/index.js +2 -0
  35. package/lib/FlexGrid/Col/Col.js +3 -5
  36. package/lib/FlexGrid/FlexGrid.js +1 -3
  37. package/lib/FlexGrid/Row/Row.js +1 -3
  38. package/lib/FlexGrid/providers/GutterContext.js +1 -1
  39. package/lib/Icon/Icon.js +1 -1
  40. package/lib/InputLabel/InputLabel.js +86 -0
  41. package/lib/InputLabel/LabelContent.native.js +8 -0
  42. package/lib/InputLabel/LabelContent.web.js +17 -0
  43. package/lib/InputLabel/index.js +2 -0
  44. package/lib/Link/ChevronLink.js +1 -3
  45. package/lib/Link/Link.js +1 -3
  46. package/lib/Link/LinkBase.js +11 -7
  47. package/lib/Link/TextButton.js +1 -3
  48. package/lib/Pagination/PageButton.js +85 -0
  49. package/lib/Pagination/Pagination.js +118 -0
  50. package/lib/Pagination/SideButton.js +108 -0
  51. package/lib/Pagination/dictionary.js +18 -0
  52. package/lib/Pagination/index.js +2 -0
  53. package/lib/Pagination/useCopy.js +10 -0
  54. package/lib/Pagination/usePagination.js +70 -0
  55. package/lib/SideNav/Item.js +4 -6
  56. package/lib/SideNav/ItemsGroup.js +11 -11
  57. package/lib/SideNav/SideNav.js +2 -4
  58. package/lib/Spacer/Spacer.js +98 -0
  59. package/lib/Spacer/index.js +2 -0
  60. package/lib/StackView/StackView.js +105 -0
  61. package/lib/StackView/StackWrap.js +32 -0
  62. package/lib/StackView/StackWrap.native.js +3 -0
  63. package/lib/StackView/StackWrapBox.js +85 -0
  64. package/lib/StackView/StackWrapGap.js +45 -0
  65. package/lib/StackView/common.js +30 -0
  66. package/lib/StackView/getStackedContent.js +111 -0
  67. package/lib/StackView/index.js +5 -0
  68. package/lib/TextInput/TextInput.js +337 -0
  69. package/lib/TextInput/index.js +2 -0
  70. package/lib/ThemeProvider/ThemeProvider.js +2 -2
  71. package/lib/ThemeProvider/useThemeTokens.js +34 -6
  72. package/lib/ThemeProvider/utils/theme-tokens.js +37 -9
  73. package/lib/ToggleSwitch/ToggleSwitch.js +17 -47
  74. package/lib/Typography/Typography.js +1 -7
  75. package/lib/ViewportProvider/index.js +1 -1
  76. package/lib/index.js +8 -1
  77. package/lib/utils/index.js +2 -1
  78. package/lib/utils/input.js +3 -1
  79. package/lib/utils/propTypes.js +103 -8
  80. package/lib/utils/spacing/index.js +2 -0
  81. package/lib/utils/spacing/useSpacingScale.js +102 -0
  82. package/lib/utils/spacing/utils.js +32 -0
  83. package/lib/utils/useUniqueId.js +12 -0
  84. package/package.json +6 -9
  85. package/release-context.json +4 -4
  86. package/src/Box/Box.jsx +117 -80
  87. package/src/Button/ButtonBase.jsx +8 -21
  88. package/src/Button/ButtonGroup.jsx +13 -17
  89. package/src/Card/Card.jsx +101 -0
  90. package/src/Card/index.js +3 -0
  91. package/src/Divider/Divider.jsx +38 -3
  92. package/src/ExpandCollapse/Control.jsx +2 -3
  93. package/src/Feedback/Feedback.jsx +99 -0
  94. package/src/Feedback/index.js +3 -0
  95. package/src/FlexGrid/Col/Col.jsx +4 -2
  96. package/src/Icon/Icon.jsx +2 -1
  97. package/src/InputLabel/InputLabel.jsx +99 -0
  98. package/src/InputLabel/LabelContent.native.jsx +6 -0
  99. package/src/InputLabel/LabelContent.web.jsx +13 -0
  100. package/src/InputLabel/index.js +3 -0
  101. package/src/Link/LinkBase.jsx +9 -3
  102. package/src/Pagination/PageButton.jsx +80 -0
  103. package/src/Pagination/Pagination.jsx +135 -0
  104. package/src/Pagination/SideButton.jsx +93 -0
  105. package/src/Pagination/dictionary.js +18 -0
  106. package/src/Pagination/index.js +3 -0
  107. package/src/Pagination/useCopy.js +7 -0
  108. package/src/Pagination/usePagination.js +69 -0
  109. package/src/SideNav/Item.jsx +3 -3
  110. package/src/SideNav/ItemsGroup.jsx +11 -13
  111. package/src/Spacer/Spacer.jsx +91 -0
  112. package/src/Spacer/index.js +3 -0
  113. package/src/StackView/StackView.jsx +103 -0
  114. package/src/StackView/StackWrap.jsx +33 -0
  115. package/src/StackView/StackWrap.native.jsx +4 -0
  116. package/src/StackView/StackWrapBox.jsx +82 -0
  117. package/src/StackView/StackWrapGap.jsx +39 -0
  118. package/src/StackView/common.jsx +28 -0
  119. package/src/StackView/getStackedContent.jsx +106 -0
  120. package/src/StackView/index.js +6 -0
  121. package/src/TextInput/TextInput.jsx +325 -0
  122. package/src/TextInput/index.js +3 -0
  123. package/src/ThemeProvider/useThemeTokens.js +34 -7
  124. package/src/ThemeProvider/utils/theme-tokens.js +37 -8
  125. package/src/ToggleSwitch/ToggleSwitch.jsx +23 -43
  126. package/src/Typography/Typography.jsx +0 -4
  127. package/src/index.js +8 -1
  128. package/src/utils/index.js +1 -0
  129. package/src/utils/input.js +2 -1
  130. package/src/utils/propTypes.js +105 -16
  131. package/src/utils/spacing/index.js +3 -0
  132. package/src/utils/spacing/useSpacingScale.js +93 -0
  133. package/src/utils/spacing/utils.js +28 -0
  134. package/src/utils/useUniqueId.js +14 -0
  135. package/stories/A11yText/A11yText.stories.jsx +11 -5
  136. package/stories/ActivityIndicator/ActivityIndicator.stories.jsx +11 -2
  137. package/stories/Box/Box.stories.jsx +46 -17
  138. package/stories/Button/Button.stories.jsx +17 -21
  139. package/stories/Button/ButtonGroup.stories.jsx +2 -1
  140. package/stories/Button/ButtonLink.stories.jsx +6 -4
  141. package/stories/Card/Card.stories.jsx +62 -0
  142. package/stories/Divider/Divider.stories.jsx +26 -2
  143. package/stories/ExpandCollapse/ExpandCollapse.stories.jsx +74 -79
  144. package/stories/Feedback/Feedback.stories.jsx +97 -0
  145. package/stories/FlexGrid/01 FlexGrid.stories.jsx +20 -7
  146. package/stories/Icon/Icon.stories.jsx +11 -3
  147. package/stories/InputLabel/InputLabel.stories.jsx +37 -0
  148. package/stories/Link/ChevronLink.stories.jsx +20 -4
  149. package/stories/Link/Link.stories.jsx +24 -3
  150. package/stories/Link/TextButton.stories.jsx +24 -3
  151. package/stories/Pagination/Pagination.stories.jsx +64 -0
  152. package/stories/SideNav/SideNav.stories.jsx +17 -2
  153. package/stories/Spacer/Spacer.stories.jsx +33 -0
  154. package/stories/StackView/StackView.stories.jsx +65 -0
  155. package/stories/StackView/StackWrap.stories.jsx +52 -0
  156. package/stories/TextInput/TextInput.stories.jsx +103 -0
  157. package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +16 -3
  158. package/stories/Typography/Typography.stories.jsx +12 -3
  159. package/stories/platform-supports.web.jsx +1 -1
  160. package/stories/supports.jsx +113 -13
  161. package/babel.config.js +0 -3
@@ -1,6 +1,11 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import { Linking, Platform } from 'react-native';
3
3
  import { tokenKeys } from '@telus-uds/tools-theme';
4
+ /**
5
+ * @typedef {{[key: string]: string|number|boolean}} AppearanceSet
6
+ * @typedef {AppearanceSet} VariantProp
7
+ */
8
+
4
9
  export const variantProp = {
5
10
  /**
6
11
  * 'variant' is an optional object prop on all themable components.
@@ -12,8 +17,11 @@ export const variantProp = {
12
17
  * the exact shape of variant props is not enforced using PropTypes.
13
18
  */
14
19
  propType: PropTypes.objectOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]))
15
- };
16
- const tokenValueType = PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]);
20
+ }; // Tokens can be primitive values (e.g. `'rgba(0,0,0,0'`, `12`), or objects of such values
21
+ // such as tokens that describe shadow (e.g. shadow: { inset: true, color: 'rgba(...)' ... })
22
+
23
+ const tokenValue = PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]);
24
+ const tokenValueType = PropTypes.oneOfType([tokenValue, PropTypes.objectOf(tokenValue)]);
17
25
 
18
26
  const getTokenNames = componentName => {
19
27
  const componentTokenNames = tokenKeys[componentName];
@@ -36,15 +44,19 @@ export const selectTokens = (componentName, tokens) => {
36
44
  } : validTokens, {});
37
45
  return filteredTokens;
38
46
  };
47
+ /**
48
+ * @typedef {string|number|boolean|{[key: string]:string|number|boolean}} TokenValue
49
+ * @typedef {{[key: string]: TokenValue}} TokensSet
50
+ * @typedef {(AppearanceSet) => TokensSet} TokensGetter
51
+ * @typedef {TokensSet|TokensGetter} TokensProp
52
+ */
53
+
39
54
  /**
40
55
  * 'tokens' is an optional object prop on all themable components. Its keys must match the
41
56
  * token keys in the component's theme schema.
42
57
  *
43
58
  * This prop is intended to be used as an 'escape hatch' for difficult or exceptional cases
44
59
  * where the main theming system doesn't apply. It is intentionally permissive about values.
45
- *
46
- * Be careful about passing a token key with value `undefined`, as this will override any tokens from
47
- * the theme. If a key is set on a `tokens` prop object, tokens from the theme will be overridden.
48
60
  */
49
61
 
50
62
  export const getTokensPropType = componentName => PropTypes.oneOfType([PropTypes.shape(Object.fromEntries(getTokenNames(componentName).map(key => [key, tokenValueType]))), PropTypes.func // function that takes current appearances and returns above shape
@@ -247,14 +259,88 @@ export const linkProps = {
247
259
  return Platform.OS !== 'web' && href ? () => Linking.openURL(href) : onPress;
248
260
  }
249
261
  };
250
- export const levelsPropType = PropTypes.oneOf([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
251
- export const responsivePropTypeFactory = propType => PropTypes.oneOfType([propType, PropTypes.shape({
262
+
263
+ const getByViewport = propType => ({
252
264
  xs: propType,
253
265
  sm: propType,
254
266
  md: propType,
255
267
  lg: propType,
256
268
  xl: propType
257
- })]);
269
+ });
270
+ /**
271
+ * Utilities for props that allow different values to be applied at different viewports.
272
+ *
273
+ * These should apply viewport inheritance such that if a viewport is undefined, the value is
274
+ * taken from the next smallest viewport for which a value is available. For example, a
275
+ * responsive prop { xs: 2, lg: 3 } should apply 2 at sizes sm and md, and 3 at size xl.
276
+ *
277
+ * @property {Function} getByViewport - returns an object where each each viewport has a key
278
+ * containing the provided argument.
279
+ * @property {Function} getTypeByViewport - returns a PropTypes shape validator for an object where
280
+ * each viewport has a key containing the provided proptype.
281
+ * @property {Function} getTypeOptionallyByViewport - returns a PropTypes validator that accepts
282
+ * either the provided proptype on its own, or the output of getTypeByViewport
283
+ */
284
+
285
+
286
+ export const responsiveProps = {
287
+ getByViewport,
288
+ getTypeByViewport: propType => PropTypes.shape(getByViewport(propType)),
289
+ getTypeOptionallyByViewport: propType => PropTypes.oneOfType([propType, PropTypes.shape(getByViewport(propType))])
290
+ };
291
+ /**
292
+ * @typedef {0|1|2|3|4|5|6|7|8|9|10|11} SpacingIndex - value used to select a size on the spacing scale
293
+ *
294
+ * @typedef SpacingOptions
295
+ * @property {VariantProp} [SpacingOptions.variant] - optional theme scale variants e.g. compact, wide
296
+ * @property {number} [SpacingOptions.size] - optional override to force a particular pixel size
297
+ * @property {number} [SpacingOptions.subtract] - optional number to subtract from final pixel size
298
+ *
299
+ * @typedef SpacingObject
300
+ * @property {SpacingIndex} [SpacingObject.xs] - space scale index to use above xs viewport
301
+ * @property {SpacingIndex} [SpacingObject.sm] - space scale index to use above sm viewport
302
+ * @property {SpacingIndex} [SpacingObject.md] - space scale index to use above md viewport
303
+ * @property {SpacingIndex} [SpacingObject.lg] - space scale index to use above lg viewport
304
+ * @property {SpacingIndex} [SpacingObject.xl] - space scale index to use above xl viewport
305
+ * @property {SpacingIndex} [SpacingObject.space] - space scale index to use at all viewports
306
+ * @property {SpacingOptions} [SpacingObject.options] - optional options for this spacing
307
+ *
308
+ * @typedef {SpacingIndex|SpacingObject} SpacingValue
309
+ */
310
+
311
+ const spacingScale = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
312
+ const spacingIndexPropType = PropTypes.oneOf(spacingScale);
313
+ const spacingObjectPropType = PropTypes.shape({ ...responsiveProps.getByViewport(spacingIndexPropType),
314
+ space: spacingIndexPropType,
315
+ options: PropTypes.shape({
316
+ variant: variantProp.propType,
317
+ size: PropTypes.number
318
+ })
319
+ });
320
+ /**
321
+ * Components and utilities that assign fixed space between components use a theme-defined spacing scale.
322
+ *
323
+ * They typically take one or more props of the {@link SpacingValue} type and turn it into a pixel size value
324
+ * using the hook `useSpacingScale`, which resolves any options or responsive behaviour and returns the
325
+ * appropriate value from the theme spacing scale.
326
+ *
327
+ * - see /ADRs/inter-component-spacing/README.md - ADR on this structure
328
+ * - see /src/utils/spacing/useSpacingScale.js - hook that processes spacing values
329
+ * - @see {@link SpacingIndex} - themes provide spacing scales of up to 12 sizes with optional theme rules.
330
+ * - @see {@link SpacingValue} - either a simple number referencing an index position on the spacing
331
+ * scale, or an object with an optional `options` key and one or more spacing indexes keyed either by
332
+ * viewports or `space` to apply at all viewports.
333
+ */
334
+
335
+ export const spacingProps = {
336
+ scale: spacingScale,
337
+ types: {
338
+ spacingIndex: spacingIndexPropType,
339
+ spacingObject: spacingObjectPropType,
340
+ // Most spacing components and utilities take this prop / arg type:
341
+ spacingValue: PropTypes.oneOfType([spacingIndexPropType, spacingObjectPropType])
342
+ }
343
+ };
258
344
  /**
259
345
  * Returns a prop type validator which checks whether a prop is either a component or an array of components of a given
260
346
  * type, based on their `name` property.
@@ -318,4 +404,13 @@ export const componentPropType = (passedName, checkDisplayName = false) => {
318
404
  const validate = createValidate();
319
405
  validate.isRequired = createValidate(true);
320
406
  return validate;
407
+ };
408
+ export const copyPropTypes = PropTypes.oneOf(['en', 'fr']);
409
+ export const paddingProp = {
410
+ propType: PropTypes.shape({
411
+ paddingBottom: PropTypes.number,
412
+ paddingLeft: PropTypes.number,
413
+ paddingRight: PropTypes.number,
414
+ paddingTop: PropTypes.number
415
+ })
321
416
  };
@@ -0,0 +1,2 @@
1
+ export { default as useSpacingScale } from './useSpacingScale';
2
+ export * from './utils';
@@ -0,0 +1,102 @@
1
+ import { useViewport } from '../../ViewportProvider';
2
+ import { useThemeTokens } from '../../ThemeProvider';
3
+ import { resolveSpacingValue, resolveSpacingOptions } from './utils';
4
+ /**
5
+ * @typedef {import('@telus-uds/system-constants/viewports').Viewport} Viewport
6
+ * @typedef {import('../propTypes.js').SpacingValue} SpacingValue
7
+ * @typedef {import('../propTypes.js').SpacingIndex} SpacingIndex
8
+ * @typedef {import('../propTypes.js').SpacingObject} SpacingObject
9
+ * @typedef {import('../propTypes.js').SpacingOptions} SpacingOptions
10
+ */
11
+
12
+ /**
13
+ * Pass a {@link SpacingValue}, which is one of:
14
+ *
15
+ * - A number 0-11 ({@link SpacingIndex}) pointing to an index on the theme's spacing scale
16
+ * - Or, an object ({@link SpacingObject}), with optional properties:
17
+ * - `xs`, `sm`, `md`, `lg`, `xl`: {@link SpacingIndex} to apply at or above these specified {@link Viewport}
18
+ * - `options`: an optional {@link SpacingOptions} object, see below
19
+ * - `space`: a {@link SpacingIndex} to apply to all viewports (can be used alongside `options`)
20
+ *
21
+ * ## Example
22
+ *
23
+ * If the theme's spacing scale is `[0, 4, 8, 12, 16, 24, 48]` with no theme rules:
24
+ *
25
+ * - `useSpacingScale(0)` returns `0`
26
+ * - `useSpacingScale(2)` returns `8`
27
+ * - `useSpacingScale({ xs: 3, lg: 4 })` returns `12` at 'xs', 'sm' or 'md' viewports, and `16` at 'lg' or 'xl'.
28
+ *
29
+ * These viewport properties are intended to support case-specific responsive layout changes, for example, where a
30
+ * grid item drops or adds spacing on a particular side at viewports where the parent changes the layout shape.
31
+ *
32
+ * ## Theming
33
+ *
34
+ * A theme's `'spacingScale'` has theme rules and appearances same as components, and may support `viewport`.
35
+ * For example, a theme with the following rule would change index [2] above from `8` to `12` on large viewports:
36
+ *
37
+ * ```json
38
+ * { if: { space: 2, viewport: ['lg', 'xl'] }, tokens: { size: 12 }}
39
+ * ```
40
+ *
41
+ * Setting responsive spacing in the theme is the preferred way to adapt the aesthetic tightness or looseness of
42
+ * a theme to the available space without changing the shape of the layout itself.
43
+ *
44
+ * ## Options
45
+ *
46
+ * Space values passed as objects may have an `options` key, with the following optional properties:
47
+ *
48
+ * - `variant`: Themes may choose to have spacing scale variants, same as variants in component themes.
49
+ * For example, if a theme rule contains `{ if: { space: 2, compact: true }, tokens: { size: 6 }}`,
50
+ * this tighter spacing can be accessed with:
51
+ *
52
+ * ```jsx
53
+ * const compactSize = useSpacingScale({ space: 2, options: { variant: { compact: true }}})`
54
+ * ```
55
+ *
56
+ * - `subtract`: Sometimes on-brand spacing needs to be reduced by another value to achieve an on-brand result.
57
+ * For example, a component with a variable border may want to subtract its border width from its padding so
58
+ * the total distance from content edge to bounding box is a valid theme value, regardless of border width:
59
+ *
60
+ * ```jsx
61
+ * const padding = useSpacingScale({ space: 2, options: { subtract: themeTokens.borderWidth }})
62
+ * ```
63
+ *
64
+ * - `size`: In exceptional cases, the theme's spacing scale may be bypassed by passing a `size` option.
65
+ * This can result in layouts that may be rejected for being off-brand so should only be used as a
66
+ * last resort for fixing layout problems (e.g. when working around non-branded embedded content).
67
+ * Where possible, fixing layout issues using a spacing scale value and the `subtract` option is preferred.
68
+ *
69
+ * ```jsx
70
+ * // Comments should be included when `size` is used, stating why this off-brand size is needed
71
+ * const iframeOffset = useSpacingScale({ options: { size: 13 }})`
72
+ * ```
73
+ *
74
+ * ## References
75
+ *
76
+ * `/ADRs/inter-component-spacing/README.md` - ADR on this structure
77
+ *
78
+ * @param {SpacingValue} [spaceValue] - a {@link SpacingIndex} number, or a {@link SpacingObject}
79
+ * @returns {number}
80
+ */
81
+
82
+ const useSpacingScale = spaceValue => {
83
+ // In future, may need to consider window height as well as width, particularly for native apps,
84
+ // e.g. to ensure designs don't look lost on large, tall, not-so-wide portrait screens.
85
+ const viewport = useViewport();
86
+ const {
87
+ tokens,
88
+ variant,
89
+ overridden,
90
+ subtract = 0
91
+ } = resolveSpacingOptions(spaceValue);
92
+ const space = overridden ? null : resolveSpacingValue(spaceValue, viewport);
93
+ const {
94
+ size
95
+ } = useThemeTokens('spacingScale', tokens, variant, {
96
+ space,
97
+ viewport
98
+ });
99
+ return Math.max(size - subtract, 0);
100
+ };
101
+
102
+ export default useSpacingScale;
@@ -0,0 +1,32 @@
1
+ import { viewports as systemViewports } from '@telus-uds/system-constants';
2
+ export const resolveSpacingValue = (space, viewport) => {
3
+ // If spacing value has been passed as undefined or nullish, get the 0-index
4
+ if (!space) return 0; // Pass through simple non-responsive numbers (which may be in objects alongside options)
5
+
6
+ if (typeof space === 'number') return space;
7
+ if (typeof space.space === 'number') return space.space; // Get the appropriate space value for the current viewport.
8
+ // If no viewports available (e.g. SSR), default to the smallest.
9
+ // If no values are found (e.g. empty space object), default to 0.
10
+
11
+ return (viewport && systemViewports.inherit(space)[viewport]) ?? space.xs ?? 0;
12
+ };
13
+ export const resolveSpacingOptions = space => {
14
+ if (!space?.options) return {};
15
+ const {
16
+ size,
17
+ variant,
18
+ subtract = 0
19
+ } = space.options;
20
+ const overridden = typeof size === 'number'; // Might need an option that adapts the size value by current user's system font scale, so that
21
+ // meaningful spacing between items doesn't disappear on the highest a11y font scale settings.
22
+ // https://github.com/telus/universal-design-system/issues/583
23
+
24
+ return {
25
+ tokens: {
26
+ size
27
+ },
28
+ variant,
29
+ overridden,
30
+ subtract
31
+ };
32
+ };
@@ -0,0 +1,12 @@
1
+ import { useState } from 'react';
2
+ let id = 0;
3
+
4
+ function useUniqueId(prefix = '') {
5
+ const [uniqueId] = useState(() => {
6
+ id += 1;
7
+ return `${prefix}-${id}`;
8
+ });
9
+ return uniqueId;
10
+ }
11
+
12
+ export default useUniqueId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telus-uds/components-base",
3
- "version": "0.0.2-prerelease.1",
3
+ "version": "0.0.2-prerelease.5",
4
4
  "description": "Base components",
5
5
  "keywords": [
6
6
  "base"
@@ -24,7 +24,7 @@
24
24
  "lint": "telus-standard",
25
25
  "lint:fix": "telus-standard --fix",
26
26
  "format": "prettier --write .",
27
- "build": "babel --root-mode upward src --out-dir lib",
27
+ "build": "babel src -d lib",
28
28
  "dev": "yarn build --watch",
29
29
  "release": "standard-version"
30
30
  },
@@ -37,21 +37,18 @@
37
37
  "peerDependencies": {
38
38
  "react": "*",
39
39
  "react-native": "*",
40
- "react-native-web": "^0.14.13"
40
+ "react-native-web": "~0.14.13"
41
41
  },
42
42
  "devDependencies": {
43
- "@babel/cli": "^7.9.0",
44
- "@babel/plugin-transform-react-jsx": "^7.14.9",
45
43
  "@testing-library/jest-native": "^4.0.1",
46
44
  "@testing-library/react-hooks": "^7.0.1",
47
45
  "@testing-library/react-native": "^7.2.0",
48
46
  "react-test-renderer": "^16.3.2"
49
47
  },
50
48
  "dependencies": {
51
- "@telus-uds/system-constants": "^0.0.2-prerelease.0",
52
- "@telus-uds/tools-theme": "^0.0.2-prerelease.0",
49
+ "@telus-uds/system-constants": "^0.0.2-prerelease.1",
50
+ "@telus-uds/tools-theme": "^0.0.2-prerelease.3",
53
51
  "lodash.merge": "^4.6.2",
54
52
  "prop-types": "^15.7.2"
55
- },
56
- "gitHead": "a4930bde09eae51c0c77e78c8fd1bdeb918f5bd3"
53
+ }
57
54
  }
@@ -1,7 +1,7 @@
1
1
  {
2
- "previousReleaseTag": "@telus-uds/components-base/v0.0.2-prerelease.0",
3
- "changelog": "### [0.0.2-prerelease.1](https://github.com/telus/universal-design-system/compare/@telus-uds/components-base/v0.0.2-prerelease.0...@telus-uds/components-base/v0.0.2-prerelease.1) (2021-10-04)\n\n\n### Features\n\n* **base:** add ExpandCollapse component ([#584](https://github.com/telus/universal-design-system/issues/584)) ([12f0c68](https://github.com/telus/universal-design-system/commit/12f0c68de703fc032b2e1997dda52a4c370d4a3d))\n\n\n### Bug Fixes\n\n* **base:** fix button size when in block div ([#575](https://github.com/telus/universal-design-system/issues/575)) ([da7a80a](https://github.com/telus/universal-design-system/commit/da7a80ac6ff7bddb310895a3319e7822d35be969))\n",
4
- "releaseTag": "@telus-uds/components-base/v0.0.2-prerelease.1",
5
- "newVersion": "0.0.2-prerelease.1",
2
+ "previousReleaseTag": "@telus-uds/components-base/v0.0.2-prerelease.4",
3
+ "changelog": "### [0.0.2-prerelease.5](https://github.com/telus/universal-design-system/compare/@telus-uds/components-base/v0.0.2-prerelease.4...@telus-uds/components-base/v0.0.2-prerelease.5) (2021-10-27)\n\n\n### Features\n\n* **allium-web:** Add `ExpandCollapseMiniControl` component ([#661](https://github.com/telus/universal-design-system/issues/661)) ([227407e](https://github.com/telus/universal-design-system/commit/227407ec6a48c0a170b8e39761ba33293f13eb3c)), closes [#605](https://github.com/telus/universal-design-system/issues/605) [#605](https://github.com/telus/universal-design-system/issues/605) [#605](https://github.com/telus/universal-design-system/issues/605) [#605](https://github.com/telus/universal-design-system/issues/605) [#605](https://github.com/telus/universal-design-system/issues/605)\n* **allium:** add Allium `Card` ([#639](https://github.com/telus/universal-design-system/issues/639)) ([f88179c](https://github.com/telus/universal-design-system/commit/f88179c503dfe574bf7bac6bb36ce4726a9af338))\n* **base, allium:** add reverse directions and switch Allium card to using `StackView` ([#676](https://github.com/telus/universal-design-system/issues/676)) ([819a15f](https://github.com/telus/universal-design-system/commit/819a15f059faa47d1bc4e96c6370e9694effc003))\n* **base:** add StackView and Spacer ([#662](https://github.com/telus/universal-design-system/issues/662)) ([cc3965e](https://github.com/telus/universal-design-system/commit/cc3965e83c1ec2fa0f0dd006a03eeeeda5384940))\n* **base:** add StackWrap component ([#666](https://github.com/telus/universal-design-system/issues/666)) ([f34fb60](https://github.com/telus/universal-design-system/commit/f34fb60d0a923b1e4b3105c21b4a762b630ab309))\n* **base:** add TextInput component ([#649](https://github.com/telus/universal-design-system/issues/649)) ([245c073](https://github.com/telus/universal-design-system/commit/245c073ed3ba3a022f989d234fbf5cf972edec25))\n* **base:** add the Feedback component ([#656](https://github.com/telus/universal-design-system/issues/656)) ([5d7a5b6](https://github.com/telus/universal-design-system/commit/5d7a5b69c870ce1077adfdb230fddd1aa120b373))\n\n\n### Bug Fixes\n\n* **base:** control stretch from a row parent ([#668](https://github.com/telus/universal-design-system/issues/668)) ([fa13c37](https://github.com/telus/universal-design-system/commit/fa13c37b2bd6b4118dbeb39bc2cdf59d13d5b151))\n* **base:** fix icon link width in block container ([#645](https://github.com/telus/universal-design-system/issues/645)) ([ff60d1d](https://github.com/telus/universal-design-system/commit/ff60d1d1d0a66b7735b902098c7ca22e442265e1))\n",
4
+ "releaseTag": "@telus-uds/components-base/v0.0.2-prerelease.5",
5
+ "newVersion": "0.0.2-prerelease.5",
6
6
  "packageName": "@telus-uds/components-base"
7
7
  }
package/src/Box/Box.jsx CHANGED
@@ -1,112 +1,149 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import { View, ScrollView } from 'react-native'
4
- import { useViewport } from '../ViewportProvider'
5
- import { useTheme, useThemeTokens } from '../ThemeProvider'
6
- import {
7
- levelsPropType,
8
- responsivePropTypeFactory,
9
- variantProp,
10
- getTokensPropType
11
- } from '../utils/propTypes'
12
-
13
- const propToStyleMap = {
14
- top: 'paddingTop',
15
- below: 'marginBottom',
16
- bottom: 'paddingBottom',
17
- left: 'paddingLeft',
18
- right: 'paddingRight',
19
- vertical: 'paddingVertical',
20
- horizontal: 'paddingHorizontal'
21
- }
22
-
23
- const transformPropToStyle = (key) => propToStyleMap[key]
24
-
25
- const getStyleByType = (currentViewPort, styleProp, levels) => {
26
- if (typeof styleProp === 'undefined') return null
27
- if (typeof styleProp === 'number') return levels[currentViewPort][styleProp]
28
- const spacingValue = styleProp[currentViewPort]
29
- return levels[currentViewPort][spacingValue]
30
- }
4
+ import { useThemeTokens } from '../ThemeProvider'
5
+ import { a11yProps, spacingProps, variantProp, getTokensPropType, useSpacingScale } from '../utils'
6
+ /**
7
+ * @typedef {import('../utils/propTypes.js').SpacingValue} SpacingValue
8
+ * @typedef {import('../utils/propTypes.js').SpacingIndex} SpacingIndex
9
+ * @typedef {import('../utils/propTypes.js').SpacingObject} SpacingObject
10
+ * @typedef {import('../utils/propTypes.js').SpacingOptions} SpacingOptions
11
+ */
31
12
 
32
- const getBaseStyle = (currentViewPort, styleProps, levels) => {
33
- const styleObject = {}
34
- Object.entries(styleProps).forEach(([key, styleProp]) => {
35
- const styleKey = transformPropToStyle(key)
36
- styleObject[styleKey] = getStyleByType(currentViewPort, styleProp, levels)
13
+ const selectBoxStyles = (tokens) => {
14
+ const styles = { backgroundColor: tokens.backgroundColor }
15
+ const paddings = ['paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom']
16
+ // Only set on styles if token provided because we spread this object after the spacing scale values
17
+ paddings.forEach((side) => {
18
+ if (tokens[side]) {
19
+ styles[side] = tokens[side]
20
+ }
37
21
  })
38
-
39
- return styleObject
22
+ return styles
40
23
  }
41
24
 
42
25
  /**
43
- * A layout utility component
26
+ * A layout utility component. Use Box to create space (padding) around content.
27
+ *
28
+ * ## Spacing
29
+ *
30
+ * For most simple uses, pass a number to Box's `space` prop referring to an index in the theme's
31
+ * spacing scale. For example, for a box with the theme's smallest non-zero amount of padding on all sides:
32
+ *
33
+ * ```jsx
34
+ * <Box space={1}>
35
+ * ```
36
+ *
37
+ * #### Targetting specific sides
38
+ *
39
+ * Box allows spacing value props to be assigned to any side or all sides using the following props:
40
+ *
41
+ * - `space` sets the default for all sides, which is overridden by other props below
42
+ * - `horizontal` sets defaults for `left` and `right` (overriding `space` there if it is set)
43
+ * - `vertical` sets defaults for `top` and `bottom` (overriding `space` there if it is set)
44
+ * - `left` sets the left side padding, inside the box's bounds
45
+ * - `right` sets the right side padding, inside the box's bounds
46
+ * - `top` sets the top side padding, inside the box's bounds
47
+ * - `bottom` sets the bottom side padding, inside the box's bounds
48
+ *
49
+ * Box only controls spacing within its bounds. If space is needed around a box outside of its bounds,
50
+ * use `Spacer`, `StackView` or wrap the `Box` inside another `Box`.
44
51
  *
45
- * > TODO decide on the API we want to offer here
52
+ * #### Viewport-specific spacing
53
+ *
54
+ * Responsive behaviours may be set by passing any of the above props an object keyed by viewports.
55
+ *
56
+ * For example, if a theme's spacing scale is [0, 4, 8...], this below will have padding of 4px on top,
57
+ * bottom, and right, and its left padding will be 0px on xs, sm and md viewports and 8px on lg and xl viewports:
58
+ *
59
+ * ```jsx
60
+ * <Box space={1} left={{ xs: 0, lg: 2 }} />`
61
+ * ```
62
+ *
63
+ * #### Subtracting from spacing
64
+ *
65
+ * A parent may sometimes need to reduce the spacing size on one or more sides of a `Box` by some variable.
66
+ * For example, the parent may have a border on one side and want to reduce the spacing of a child box
67
+ * by the width of that border on that side. This can be achieved using the `subtract` option of the
68
+ * spacing object, for example:
69
+ *
70
+ * ```jsx
71
+ * <Box space={2} left={{ space: 2, options: { subtract: themeTokens.borderWidthLeft } }} />`
72
+ * ```
73
+ *
74
+ * See `useSpacingScale` hook for other spacing value options and documentation.
75
+ *
76
+ * ## Theming
77
+ *
78
+ * Box is intended for layout, so minimal theming is supported. Use components like `Card` for
79
+ * more sophisticated theming.
80
+ *
81
+ * ## Scroll
82
+ *
83
+ * If passed, the box will be scrollable. If an object is passed, it will be passed to React Native's
84
+ * `ScrollView` component as props.
85
+ *
86
+ * When building native iOS and Android apps, it is important to remember to ensure any screen containing
87
+ * text content is inside a scrollable box, as screens are not scrollable by default and even very
88
+ * short text will require scrolling on small devices at the highest accessibility text scaling settings.
46
89
  */
47
90
  const Box = ({
91
+ space,
92
+ horizontal = space,
93
+ vertical = space,
94
+ top = vertical,
95
+ bottom = vertical,
96
+ left = horizontal,
97
+ right = horizontal,
48
98
  children,
49
- top,
50
- below,
51
- bottom,
52
- left,
53
- right,
54
- vertical,
55
- horizontal,
56
99
  variant,
57
100
  tokens,
58
- scroll = false,
59
- scrollProps = null
101
+ scroll,
102
+ testID,
103
+ ...rest
60
104
  }) => {
61
- const theme = useTheme()
105
+ const a11y = a11yProps.select(rest)
62
106
 
63
- // TODO: integrate level resolution with other tokens as part of
64
- // https://github.com/telus/universal-design-system/issues/267
65
- const { levels } = theme.components.Box
66
-
67
- // TODO: update Box logic to use viewport as an appearance context and make levels themable
68
- // https://github.com/telus/universal-design-system/issues/87
69
- const viewport = useViewport()
70
- const { backgroundColor } = useThemeTokens('Box', tokens, variant)
71
- const bkgStyle = { backgroundColor }
72
-
73
- const styleProps = {
74
- below,
75
- bottom,
76
- left,
77
- right,
78
- top,
79
- vertical,
80
- horizontal
107
+ const themeTokens = useThemeTokens('Box', tokens, variant)
108
+ const styles = {
109
+ paddingLeft: useSpacingScale(left),
110
+ paddingRight: useSpacingScale(right),
111
+ paddingTop: useSpacingScale(top),
112
+ paddingBottom: useSpacingScale(bottom),
113
+ ...selectBoxStyles(themeTokens)
81
114
  }
82
- const baseStyle = getBaseStyle(viewport, styleProps, levels)
83
- const styles = [baseStyle, bkgStyle]
84
115
 
85
116
  if (scroll) {
117
+ const scrollProps = typeof scroll === 'object' ? scroll : {}
118
+ scrollProps.contentContainerStyle = [styles, scrollProps.contentContainerStyle]
86
119
  return (
87
- <ScrollView contentContainerStyle={styles} {...scrollProps}>
120
+ <ScrollView {...scrollProps} {...a11y} testID={testID}>
88
121
  {children}
89
122
  </ScrollView>
90
123
  )
91
124
  }
92
- return <View style={styles}>{children}</View>
125
+ return (
126
+ <View {...a11y} style={styles} testID={testID}>
127
+ {children}
128
+ </View>
129
+ )
93
130
  }
94
131
 
95
- // TODO apply props as separate
96
- const responsiveLevelsPropType = responsivePropTypeFactory(levelsPropType)
97
-
98
132
  Box.propTypes = {
99
- below: responsiveLevelsPropType,
100
- bottom: responsiveLevelsPropType,
101
- left: responsiveLevelsPropType,
102
- right: responsiveLevelsPropType,
103
- top: responsiveLevelsPropType,
104
- vertical: responsiveLevelsPropType,
105
- horizontal: responsiveLevelsPropType,
106
- scroll: PropTypes.bool,
107
- scrollProps: PropTypes.object,
133
+ space: spacingProps.types.spacingValue,
134
+ vertical: spacingProps.types.spacingValue,
135
+ horizontal: spacingProps.types.spacingValue,
136
+ bottom: spacingProps.types.spacingValue,
137
+ left: spacingProps.types.spacingValue,
138
+ right: spacingProps.types.spacingValue,
139
+ top: spacingProps.types.spacingValue,
140
+ scroll: PropTypes.oneOfType([
141
+ PropTypes.bool,
142
+ ScrollView.propTypes ? PropTypes.shape(ScrollView.propTypes) : PropTypes.object
143
+ ]),
108
144
  variant: variantProp.propType,
109
145
  tokens: getTokensPropType('Box'),
146
+ testID: PropTypes.string,
110
147
  children: PropTypes.node.isRequired
111
148
  }
112
149