@telus-uds/components-base 1.1.0 → 1.2.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 (50) hide show
  1. package/.ultra.cache.json +1 -1
  2. package/CHANGELOG.md +8 -0
  3. package/__fixtures__/Accessible.js +4 -2
  4. package/__fixtures__/Accessible.native.js +5 -2
  5. package/__fixtures__/testTheme.js +9 -0
  6. package/__tests__/HorizontalScroll/HorizontalScroll.test.jsx +1 -0
  7. package/__tests__/ToggleSwitch/ToggleSwitch.test.jsx +10 -0
  8. package/__tests__/ToggleSwitch/ToggleSwitchGroup.test.jsx +192 -0
  9. package/component-docs.json +614 -796
  10. package/lib/Button/ButtonBase.js +20 -6
  11. package/lib/Card/PressableCardBase.js +9 -3
  12. package/lib/Checkbox/Checkbox.js +0 -2
  13. package/lib/IconButton/IconButton.js +8 -3
  14. package/lib/Link/LinkBase.js +10 -3
  15. package/lib/Pagination/PageButton.js +3 -1
  16. package/lib/Pagination/Pagination.js +16 -4
  17. package/lib/Pagination/SideButton.js +3 -1
  18. package/lib/Radio/Radio.js +0 -2
  19. package/lib/Tabs/Tabs.js +12 -4
  20. package/lib/Tabs/TabsItem.js +12 -6
  21. package/lib/ToggleSwitch/ToggleSwitch.js +99 -37
  22. package/lib/ToggleSwitch/ToggleSwitchGroup.js +230 -0
  23. package/lib/ToggleSwitch/index.js +14 -4
  24. package/lib/index.js +13 -8
  25. package/lib/utils/index.js +10 -1
  26. package/lib/utils/propTypes.js +26 -1
  27. package/lib/utils/withLinkRouter.js +98 -0
  28. package/package.json +2 -2
  29. package/release-context.json +4 -4
  30. package/src/Button/ButtonBase.jsx +11 -4
  31. package/src/Card/PressableCardBase.jsx +6 -4
  32. package/src/Checkbox/Checkbox.jsx +0 -2
  33. package/src/IconButton/IconButton.jsx +6 -4
  34. package/src/Link/LinkBase.jsx +6 -4
  35. package/src/Pagination/PageButton.jsx +3 -2
  36. package/src/Pagination/Pagination.jsx +29 -2
  37. package/src/Pagination/SideButton.jsx +2 -2
  38. package/src/Radio/Radio.jsx +0 -2
  39. package/src/Tabs/Tabs.jsx +49 -22
  40. package/src/Tabs/TabsItem.jsx +11 -7
  41. package/src/ToggleSwitch/ToggleSwitch.jsx +92 -34
  42. package/src/ToggleSwitch/ToggleSwitchGroup.jsx +203 -0
  43. package/src/ToggleSwitch/index.js +2 -1
  44. package/src/index.js +1 -1
  45. package/src/utils/index.js +1 -0
  46. package/src/utils/propTypes.js +30 -0
  47. package/src/utils/withLinkRouter.jsx +68 -0
  48. package/stories/TextInput/TextArea.stories.jsx +1 -0
  49. package/stories/ToggleSwitch/ToggleSwitch.stories.jsx +5 -1
  50. package/stories/ToggleSwitch/ToggleSwitchGroup.stories.jsx +81 -0
@@ -1,3 +1,4 @@
1
1
  import ToggleSwitch from './ToggleSwitch'
2
+ import ToggleSwitchGroup from './ToggleSwitchGroup'
2
3
 
3
- export default ToggleSwitch
4
+ export { ToggleSwitch, ToggleSwitchGroup }
package/src/index.js CHANGED
@@ -37,7 +37,7 @@ export { default as StepTracker } from './StepTracker'
37
37
  export { default as Tabs } from './Tabs'
38
38
  export { default as Tags } from './Tags'
39
39
  export * from './TextInput'
40
- export { default as ToggleSwitch } from './ToggleSwitch'
40
+ export * from './ToggleSwitch'
41
41
  export { default as Tooltip } from './Tooltip'
42
42
  export { default as TooltipButton } from './TooltipButton'
43
43
  export { default as Typography } from './Typography'
@@ -12,3 +12,4 @@ export { default as useSpacingScale } from './useSpacingScale'
12
12
  export { default as useResponsiveProp } from './useResponsiveProp'
13
13
  export * from './useResponsiveProp'
14
14
  export { default as useUniqueId } from './useUniqueId'
15
+ export { default as withLinkRouter } from './withLinkRouter'
@@ -320,6 +320,36 @@ export const pressProps = {
320
320
  selectHandlers: getPropSelector(pressHandlerPropTypes)
321
321
  }
322
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
+
323
353
  const linkPropTypes = {
324
354
  ...pressPropTypes,
325
355
  href: PropTypes.string,
@@ -0,0 +1,68 @@
1
+ import React, { forwardRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ // Prototype-safe alternative to (linter-forbidden) someObject.hasOwnProperty()
5
+ const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop)
6
+
7
+ /**
8
+ * Higher-order component that has no effect unless an additional prop `LinkRouter` is passed.
9
+ * This may be used to provide custom wrappers for integrations with third party libraries.
10
+ *
11
+ * If LinkRouter is passed, LinkRouter is rendered in place of the main component and is passed:
12
+ *
13
+ * - `linkRouterProps`: an optional object passed alongside LinkRouter, for props needed by the wrapper
14
+ * that are not valid props for the wrapped component.
15
+ * - `Component`: automatically provided, the original component to render inside the wrapper.
16
+ * - `ref`: forwarded `ref` passed down to `Component`
17
+ * - All other props passed to the outer component
18
+ *
19
+ * @example A LinkRouter component to be used with link-like components might look like:
20
+ *
21
+ * ```jsx
22
+ * const LinkLinkRouter = forwardRef(({ Component, linkRouterProps: { to, options }, href, ...rest }, ref) => {
23
+ * const { href: resolvedHref, onClick } = useSomeRouterHook({ to, href, options })
24
+ * return <Component href={resolvedHref} onPress={onClick} {...rest} />
25
+ * })
26
+ * ```
27
+ *
28
+ * Any component that takes href and onPress props may then use this wrapper:
29
+ *
30
+ * ```jsx
31
+ * <Link href={href} LinkRouter={LinkLinkRouter} linkRouterProps={{ to, options }}>Some link</Link>
32
+ * <IconButton icon={SomeIcon} LinkRouter={LinkLinkRouter} linkRouterProps={{ to, options }} ref={iconRef} />
33
+ * ```
34
+ */
35
+ const withLinkRouter = (Component) => {
36
+ const wrappedComponent = forwardRef(({ LinkRouter, linkRouterProps, ...props }, ref) => {
37
+ if (!LinkRouter) return <Component {...props} ref={ref} />
38
+ return (
39
+ <LinkRouter linkRouterProps={linkRouterProps} Component={Component} ref={ref} {...props} />
40
+ )
41
+ })
42
+
43
+ // Ensure the returned component has appropriate outer properties set:
44
+ /* eslint-disable-next-line react/forbid-foreign-prop-types */
45
+ const { displayName, name, propTypes, ...otherProperties } = Component
46
+
47
+ // Apply unique component name as a displayName
48
+ wrappedComponent.displayName = Component.displayName || Component.name
49
+
50
+ // Apply proptypes including wrapper props - is safely { ...undefined, ...undefined } in prod
51
+ wrappedComponent.propTypes = { ...Component.propTypes, ...withLinkRouter.propTypes }
52
+
53
+ // Forward any other properties explicitly set e.g. Component.SubComponent
54
+ Object.keys(otherProperties).forEach((key) => {
55
+ // Skip internal React properties from wrappedComponent's forwardRef (render, $$typeof, etc)
56
+ if (hasOwnProperty(Component, key) && !hasOwnProperty(wrappedComponent, key)) {
57
+ wrappedComponent[key] = Component[key]
58
+ }
59
+ })
60
+ return wrappedComponent
61
+ }
62
+
63
+ withLinkRouter.propTypes = {
64
+ LinkRouter: PropTypes.elementType,
65
+ linkRouterProps: PropTypes.object
66
+ }
67
+
68
+ export default withLinkRouter
@@ -1,3 +1,4 @@
1
+ /* eslint-disable react/no-multi-comp */
1
2
  import React, { useEffect, useState } from 'react'
2
3
 
3
4
  import { TextArea } from '../../src'
@@ -9,7 +9,11 @@ Default.storyName = 'ToggleSwitch'
9
9
 
10
10
  export default {
11
11
  title: 'ToggleSwitch',
12
- component: ToggleSwitch
12
+ component: ToggleSwitch,
13
+ args: {
14
+ label: 'Enable data',
15
+ tooltip: 'Toggle this on to enable data'
16
+ }
13
17
  }
14
18
 
15
19
  export const ToggleSwitchControlled = (args) => {
@@ -0,0 +1,81 @@
1
+ /* eslint-disable react/no-multi-comp */
2
+ import React, { useState } from 'react'
3
+ import { View } from 'react-native'
4
+ import { ToggleSwitchGroup, Typography } from '../../src'
5
+ import { Container } from '../supports'
6
+
7
+ const defaultArgs = {
8
+ items: [
9
+ { label: 'First item', id: 'first' },
10
+ { label: 'Second item', id: 'second' },
11
+ { label: 'Third item', id: 'third' },
12
+ { label: 'Fourth item', id: 'fourth' },
13
+ { label: 'Fifth item', id: 'fifth' },
14
+ { label: 'Sixth item', id: 'sixth' }
15
+ ],
16
+ initialValues: ['second', 'fourth']
17
+ }
18
+ const defaultControlledArgs = {
19
+ ...defaultArgs,
20
+ values: defaultArgs.initialValues,
21
+ initialValues: undefined
22
+ }
23
+
24
+ export default {
25
+ title: 'ToggleSwitchGroup',
26
+ component: ToggleSwitchGroup,
27
+ args: {
28
+ ...defaultArgs
29
+ }
30
+ }
31
+
32
+ const UncontrolledTemplate = (args) => <ToggleSwitchGroup {...args} />
33
+ UncontrolledTemplate.propTypes = ToggleSwitchGroup.propTypes
34
+
35
+ const ControlledTemplate = ({ values, ...args }) => {
36
+ // Simulate saving and retrieving data from a server
37
+ const [isSaving, setIsSaving] = useState(false)
38
+ const [currentValues, setValues] = useState(values)
39
+ const handleChange = (newValue) => {
40
+ setIsSaving(true)
41
+ setTimeout(() => {
42
+ setValues(newValue)
43
+ setIsSaving(false)
44
+ }, 400)
45
+ }
46
+
47
+ const text = isSaving ? 'Saving...' : `Saved selected IDs: "${currentValues.join('", "')}".`
48
+ return (
49
+ <View>
50
+ <Container padding={4} margin={0}>
51
+ <Typography variant={{ size: 'small', colour: 'secondary' }}>{text}</Typography>
52
+ </Container>
53
+ <Container padding={4} margin={0}>
54
+ <ToggleSwitchGroup {...args} values={currentValues} onChange={handleChange} />
55
+ </Container>
56
+ </View>
57
+ )
58
+ }
59
+
60
+ ControlledTemplate.propTypes = ToggleSwitchGroup.propTypes
61
+
62
+ export const Default = UncontrolledTemplate.bind({})
63
+ Default.storyName = 'ToggleSwitchGroup'
64
+
65
+ export const ToggleSwitchGroupMaxThree = UncontrolledTemplate.bind({})
66
+ ToggleSwitchGroupMaxThree.args = { maxValues: 3 }
67
+
68
+ export const ToggleSwitchGroupMaxUnlimited = UncontrolledTemplate.bind({})
69
+ ToggleSwitchGroupMaxUnlimited.args = { maxValues: null }
70
+
71
+ export const ToggleSwitchGroupControlled = ControlledTemplate.bind({})
72
+ ToggleSwitchGroupControlled.args = { ...defaultControlledArgs }
73
+
74
+ export const ToggleSwitchGroupControlledMaxThree = ControlledTemplate.bind({})
75
+ ToggleSwitchGroupControlledMaxThree.args = { ...defaultControlledArgs, maxValues: 3 }
76
+
77
+ export const ToggleSwitchGroupControlledMaxUnlimited = ControlledTemplate.bind({})
78
+ ToggleSwitchGroupControlledMaxUnlimited.args = { ...defaultControlledArgs, maxValues: null }
79
+
80
+ export const ToggleSwitchGroupInactive = UncontrolledTemplate.bind({})
81
+ ToggleSwitchGroupInactive.args = { inactive: true }