@telus-uds/components-base 1.51.0 → 1.52.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 (75) hide show
  1. package/CHANGELOG.md +22 -3
  2. package/component-docs.json +318 -131
  3. package/lib/A11yText/index.js +8 -0
  4. package/lib/Divider/Divider.js +4 -0
  5. package/lib/IconButton/IconButton.js +16 -0
  6. package/lib/Link/ChevronLink.js +4 -0
  7. package/lib/Listbox/ListboxContext.js +20 -0
  8. package/lib/Listbox/PressableItem.js +170 -0
  9. package/lib/Listbox/index.js +32 -0
  10. package/lib/Modal/Modal.js +17 -2
  11. package/lib/MultiSelectFilter/MultiSelectFilter.js +19 -5
  12. package/lib/Pagination/PageButton.js +12 -0
  13. package/lib/Pagination/Pagination.js +12 -0
  14. package/lib/QuickLinks/QuickLinks.js +12 -0
  15. package/lib/Responsive/Responsive.js +7 -0
  16. package/lib/Select/Group.js +4 -0
  17. package/lib/Select/Group.native.js +4 -0
  18. package/lib/Select/Item.js +4 -0
  19. package/lib/SideNav/ItemsGroup.js +4 -0
  20. package/lib/SideNav/SideNav.js +8 -0
  21. package/lib/StepTracker/StepTracker.js +16 -0
  22. package/lib/Tabs/Tabs.js +20 -0
  23. package/lib/Timeline/Timeline.js +2 -2
  24. package/lib/index.js +14 -0
  25. package/lib/utils/htmlAttrs.js +33 -0
  26. package/lib/utils/index.js +10 -1
  27. package/lib-module/A11yText/index.js +8 -0
  28. package/lib-module/Divider/Divider.js +4 -0
  29. package/lib-module/IconButton/IconButton.js +16 -0
  30. package/lib-module/Link/ChevronLink.js +4 -0
  31. package/lib-module/Listbox/ListboxContext.js +6 -0
  32. package/lib-module/Listbox/PressableItem.js +150 -0
  33. package/lib-module/Listbox/index.js +2 -0
  34. package/lib-module/Modal/Modal.js +18 -3
  35. package/lib-module/MultiSelectFilter/MultiSelectFilter.js +15 -5
  36. package/lib-module/Pagination/PageButton.js +12 -0
  37. package/lib-module/Pagination/Pagination.js +12 -0
  38. package/lib-module/QuickLinks/QuickLinks.js +12 -0
  39. package/lib-module/Responsive/Responsive.js +7 -0
  40. package/lib-module/Select/Group.js +4 -0
  41. package/lib-module/Select/Group.native.js +4 -0
  42. package/lib-module/Select/Item.js +4 -0
  43. package/lib-module/SideNav/ItemsGroup.js +4 -0
  44. package/lib-module/SideNav/SideNav.js +8 -0
  45. package/lib-module/StepTracker/StepTracker.js +16 -0
  46. package/lib-module/Tabs/Tabs.js +20 -0
  47. package/lib-module/Timeline/Timeline.js +2 -2
  48. package/lib-module/index.js +1 -0
  49. package/lib-module/utils/htmlAttrs.js +22 -0
  50. package/lib-module/utils/index.js +2 -1
  51. package/package.json +2 -2
  52. package/src/A11yText/index.jsx +6 -0
  53. package/src/Divider/Divider.jsx +3 -0
  54. package/src/IconButton/IconButton.jsx +12 -0
  55. package/src/Link/ChevronLink.jsx +3 -0
  56. package/src/Listbox/ListboxContext.js +6 -0
  57. package/src/Listbox/PressableItem.jsx +143 -0
  58. package/src/Listbox/index.js +2 -0
  59. package/src/Modal/Modal.jsx +13 -3
  60. package/src/MultiSelectFilter/MultiSelectFilter.jsx +10 -4
  61. package/src/Pagination/PageButton.jsx +9 -0
  62. package/src/Pagination/Pagination.jsx +9 -0
  63. package/src/QuickLinks/QuickLinks.jsx +9 -0
  64. package/src/Responsive/Responsive.jsx +6 -0
  65. package/src/Select/Group.jsx +3 -0
  66. package/src/Select/Group.native.jsx +3 -0
  67. package/src/Select/Item.jsx +3 -0
  68. package/src/SideNav/ItemsGroup.jsx +3 -0
  69. package/src/SideNav/SideNav.jsx +6 -0
  70. package/src/StepTracker/StepTracker.jsx +12 -0
  71. package/src/Tabs/Tabs.jsx +15 -0
  72. package/src/Timeline/Timeline.jsx +2 -2
  73. package/src/index.js +1 -0
  74. package/src/utils/htmlAttrs.js +29 -0
  75. package/src/utils/index.js +1 -0
@@ -108,9 +108,21 @@ IconButton.propTypes = {
108
108
  ...selectedSystemPropTypes,
109
109
  variant: variantProp.propType,
110
110
  tokens: getTokensPropType('IconButton'),
111
+ /**
112
+ * Defines the icon to be rendered
113
+ */
111
114
  icon: PropTypes.elementType.isRequired,
115
+ /**
116
+ * URL to navigate to when the `Iconbutton` is pressed
117
+ */
112
118
  href: PropTypes.string,
119
+ /**
120
+ * URL options to navigate to when the `Iconbutton` is pressed
121
+ */
113
122
  hrefAttrs: PropTypes.shape(hrefAttrsProp.types),
123
+ /**
124
+ * Function to execute when the `Iconbutton` is pressed
125
+ */
114
126
  onPress: PropTypes.func
115
127
  }
116
128
 
@@ -48,6 +48,9 @@ ChevronLink.displayName = 'ChevronLink'
48
48
  ChevronLink.propTypes = {
49
49
  ...LinkBase.propTypes,
50
50
  tokens: getTokensPropType('ChevronLink', 'Link'),
51
+ /**
52
+ * Changes direction of chevron icon
53
+ */
51
54
  direction: PropTypes.oneOf(['left', 'right'])
52
55
  }
53
56
 
@@ -0,0 +1,6 @@
1
+ import React, { useContext } from 'react'
2
+
3
+ const ListboxContext = React.createContext({})
4
+ const useListboxContext = () => useContext(ListboxContext)
5
+
6
+ export { ListboxContext, useListboxContext }
@@ -0,0 +1,143 @@
1
+ /* eslint-disable react/require-default-props */
2
+ import React, { forwardRef } from 'react'
3
+ import PropTypes from 'prop-types'
4
+ import { Pressable, Text } from 'react-native'
5
+ import { selectSystemProps, resolvePressableTokens, htmlAttrs } from '../utils'
6
+ import { useListboxContext } from './ListboxContext'
7
+
8
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])
9
+
10
+ const getItemStyles = ({
11
+ groupFontName,
12
+ groupFontWeight,
13
+ itemFontSize,
14
+ itemPaddingTop,
15
+ itemPaddingBottom,
16
+ itemPaddingLeft,
17
+ itemBorderLeftWidth,
18
+ itemPaddingRight,
19
+ itemBackgroundColor,
20
+ itemColor,
21
+ itemDisplay,
22
+ itemOutline,
23
+ itemTextDecoration,
24
+ itemBorderLeftColor,
25
+ itemBorderRightWidth,
26
+ itemBorderRightColor,
27
+ itemBorderTopWidth,
28
+ itemBorderTopColor,
29
+ itemBorderBottomWidth,
30
+ itemBorderBottomColor,
31
+ itemBorderRadius,
32
+ itemHeight
33
+ }) => ({
34
+ fontFamily: `${groupFontName}${groupFontWeight}normal`,
35
+ fontSize: itemFontSize,
36
+ paddingTop: itemPaddingTop - itemBorderTopWidth,
37
+ paddingBottom: itemPaddingBottom - itemBorderBottomWidth,
38
+ paddingLeft: itemPaddingLeft - itemBorderLeftWidth,
39
+ paddingRight: itemPaddingRight - itemBorderRightWidth,
40
+ width: '100%',
41
+ backgroundColor: itemBackgroundColor,
42
+ color: itemColor,
43
+ display: itemDisplay,
44
+ outline: itemOutline,
45
+ textDecoration: itemTextDecoration,
46
+ borderLeft: `${itemBorderLeftWidth}px solid ${itemBorderLeftColor}`,
47
+ borderRight: `${itemBorderRightWidth}px solid ${itemBorderRightColor}`,
48
+ borderTop: `${itemBorderTopWidth}px solid ${itemBorderTopColor}`,
49
+ borderBottom: `${itemBorderBottomWidth}px solid ${itemBorderBottomColor}`,
50
+ borderRadius: itemBorderRadius,
51
+ height: itemHeight,
52
+ justifyContent: 'center'
53
+ })
54
+
55
+ const PressableItem = forwardRef(
56
+ (
57
+ {
58
+ children,
59
+ href,
60
+ isChild = false,
61
+ onBlur,
62
+ onPress,
63
+ tabIndex = 0,
64
+ nextItemRef,
65
+ prevItemRef,
66
+ tokens,
67
+ id,
68
+ ...rest
69
+ },
70
+ ref
71
+ ) => {
72
+ const { selectedId, setSelectedId } = useListboxContext()
73
+
74
+ const selectTextStyles = ({
75
+ groupFontName,
76
+ groupFontWeight,
77
+ itemFontSize,
78
+ itemColor,
79
+ lineHeight
80
+ }) => ({
81
+ fontFamily: `${groupFontName}${groupFontWeight}normal`,
82
+ fontSize: itemFontSize,
83
+ color: itemColor,
84
+ lineHeight: lineHeight * itemFontSize
85
+ })
86
+
87
+ const resolveButtonTokens = (pressableState) => {
88
+ const themeTokens = resolvePressableTokens(tokens, pressableState, {
89
+ isChild,
90
+ current: id === selectedId && id !== undefined
91
+ })
92
+ return themeTokens
93
+ }
94
+
95
+ const handleKeyPress = (event) => {
96
+ if (['Enter', ' '].includes(event?.key)) {
97
+ onPress?.(event)
98
+ } else if (event?.key === 'ArrowDown') {
99
+ nextItemRef.current.focus()
100
+ } else if (event?.key === 'ArrowUp' && prevItemRef?.current) {
101
+ prevItemRef.current.focus()
102
+ }
103
+ }
104
+
105
+ return (
106
+ <Pressable
107
+ isChild={isChild}
108
+ style={(pressableState) => getItemStyles(resolveButtonTokens(pressableState))}
109
+ onBlur={onBlur}
110
+ onClick={onPress}
111
+ onKeyPress={handleKeyPress}
112
+ ref={ref}
113
+ tabIndex={tabIndex}
114
+ {...(href && { href })}
115
+ {...(onPress && { onClick: onPress })}
116
+ {...selectProps(rest)}
117
+ onPress={() => {
118
+ setSelectedId(id)
119
+ onPress()
120
+ }}
121
+ >
122
+ {(pressableState) => {
123
+ return (
124
+ <Text style={selectTextStyles(resolveButtonTokens(pressableState))}>{children} </Text>
125
+ )
126
+ }}
127
+ </Pressable>
128
+ )
129
+ }
130
+ )
131
+ PressableItem.displayName = 'PressableItem'
132
+ PressableItem.propTypes = {
133
+ ...selectedSystemPropTypes,
134
+ href: PropTypes.string,
135
+ isChild: PropTypes.bool,
136
+ children: PropTypes.node.isRequired,
137
+ onBlur: PropTypes.func,
138
+ onPress: PropTypes.func,
139
+ nextItemRef: PropTypes.object,
140
+ prevItemRef: PropTypes.object
141
+ }
142
+
143
+ export default PressableItem
@@ -0,0 +1,2 @@
1
+ export * from './ListboxContext'
2
+ export { default as PressableItem } from './PressableItem'
@@ -16,8 +16,7 @@ import {
16
16
  selectSystemProps,
17
17
  useCopy,
18
18
  variantProp,
19
- viewProps,
20
- selectTokens
19
+ viewProps
21
20
  } from '../utils'
22
21
  import { useViewport } from '../ViewportProvider'
23
22
  import IconButton from '../IconButton'
@@ -138,7 +137,6 @@ const Modal = forwardRef(
138
137
  icon={CloseIconComponent}
139
138
  accessibilityRole="button"
140
139
  accessibilityLabel={closeLabel}
141
- tokens={selectTokens('IconButton', themeTokens, 'close')}
142
140
  />
143
141
  )}
144
142
  </View>
@@ -161,9 +159,21 @@ Modal.displayName = 'Modal'
161
159
  Modal.propTypes = {
162
160
  ...selectedSystemPropTypes,
163
161
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
162
+ /**
163
+ * To define the locale of the copy
164
+ */
164
165
  copy: copyPropTypes,
166
+ /**
167
+ * Setting this to `true` will open the `Modal`
168
+ */
165
169
  isOpen: PropTypes.bool,
170
+ /**
171
+ * This function is triggered when the `Modal` is closed
172
+ */
166
173
  onClose: PropTypes.func,
174
+ /**
175
+ * `max-width` is applied to the `Modal` container
176
+ */
167
177
  maxWidth: PropTypes.bool,
168
178
  tokens: getTokensPropType('Modal'),
169
179
  variant: variantProp.propType,
@@ -1,6 +1,7 @@
1
1
  import React, { useState, useEffect } from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
 
4
+ import { View, StyleSheet } from 'react-native'
4
5
  import { useThemeTokens, useThemeTokensCallback, applyTextStyles } from '../ThemeProvider'
5
6
  import {
6
7
  containUniqueFields,
@@ -104,7 +105,6 @@ const MultiSelectFilter = ({
104
105
  if (!containUniqueFields(items, uniqueFields)) {
105
106
  throw new Error(`MultiSelectFilter items must have unique ${uniqueFields.join(', ')}`)
106
107
  }
107
-
108
108
  // Pass an object of relevant component state as first argument for any passed-in press handlers
109
109
  const pressHandlers = getPressHandlersWithArgs(rest, [{ id, label, currentValues }])
110
110
 
@@ -239,9 +239,11 @@ const MultiSelectFilter = ({
239
239
  onLayout={onTargetLayout}
240
240
  >
241
241
  <Row>
242
- <Typography tokens={{ ...headerStyles, lineHeight: headerLineHeight }}>
243
- {getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label.toLowerCase())}
244
- </Typography>
242
+ <View style={styles.textContainerStyle}>
243
+ <Typography tokens={{ ...headerStyles, lineHeight: headerLineHeight }}>
244
+ {getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label.toLowerCase())}
245
+ </Typography>
246
+ </View>
245
247
  </Row>
246
248
  {subtitle && (
247
249
  <>
@@ -301,6 +303,10 @@ const MultiSelectFilter = ({
301
303
 
302
304
  MultiSelectFilter.displayName = 'MultiSelectFilter'
303
305
 
306
+ const styles = StyleSheet.create({
307
+ textContainerStyle: { marginRight: 52 }
308
+ })
309
+
304
310
  MultiSelectFilter.propTypes = {
305
311
  /**
306
312
  * The text displayed to the user in a ButtonDropdown.
@@ -60,8 +60,17 @@ PageButton.displayName = 'PageButton'
60
60
 
61
61
  PageButton.propTypes = {
62
62
  ...linkProps.types,
63
+ /**
64
+ * To set custom label for the button
65
+ */
63
66
  label: PropTypes.string,
67
+ /**
68
+ * To set `PageButton` to active state
69
+ */
64
70
  isActive: PropTypes.bool,
71
+ /**
72
+ * To change the language for labels
73
+ */
65
74
  copy: copyPropTypes,
66
75
  variant: variantProp.propType,
67
76
  tokens: getTokensPropType('PaginationPageButton')
@@ -207,10 +207,19 @@ Pagination.propTypes = {
207
207
  ...selectedSystemPropTypes,
208
208
  ...withLinkRouter.propTypes,
209
209
  children: componentPropType('PageButton'),
210
+ /**
211
+ * To change the language for labels
212
+ */
210
213
  copy: copyPropTypes,
211
214
  variant: variantProp.propType,
212
215
  tokens: getTokensPropType('Pagination'),
216
+ /**
217
+ * When passed as `{{ compact: true }}`, `Pagination` does not render labels along side buttons
218
+ */
213
219
  sideButtonVariant: variantProp.propType,
220
+ /**
221
+ * Custom tokens for `PaginationSideButton`
222
+ */
214
223
  sideButtonTokens: getTokensPropType('PaginationSideButton')
215
224
  }
216
225
 
@@ -51,8 +51,17 @@ QuickLinks.displayName = 'QuickLinks'
51
51
 
52
52
  QuickLinks.propTypes = {
53
53
  tokens: getTokensPropType('QuickLinks'),
54
+ /**
55
+ * Custom tokens override for `Card`
56
+ */
54
57
  cardTokens: getTokensPropType('Card'),
58
+ /**
59
+ * Custom tokens override for `QuickLinksList`
60
+ */
55
61
  listTokens: getTokensPropType('QuickLinksList'),
62
+ /**
63
+ * The HTML tag to render the list as
64
+ */
56
65
  tag: PropTypes.string,
57
66
  variant: variantProp.propType,
58
67
  children: PropTypes.node
@@ -25,7 +25,13 @@ const Responsive = ({ min = 'xs', max, children }) => {
25
25
  }
26
26
 
27
27
  Responsive.propTypes = {
28
+ /**
29
+ * To hide children of `Responsive` if the current viewport is smaller than `min`
30
+ */
28
31
  min: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
32
+ /**
33
+ * To hide children of `Responsive` if the current viewport is larger than `max`
34
+ */
29
35
  max: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
30
36
  children: PropTypes.node.isRequired
31
37
  }
@@ -11,5 +11,8 @@ export default Group
11
11
 
12
12
  Group.propTypes = {
13
13
  children: componentPropType('Item'),
14
+ /**
15
+ * The label for the group.
16
+ */
14
17
  label: PropTypes.string.isRequired
15
18
  }
@@ -10,5 +10,8 @@ export default Group
10
10
 
11
11
  Group.propTypes = {
12
12
  children: componentPropType('Item'),
13
+ /**
14
+ * The label for the group.
15
+ */
13
16
  label: PropTypes.string.isRequired
14
17
  }
@@ -7,5 +7,8 @@ export default Item
7
7
 
8
8
  Item.propTypes = {
9
9
  children: PropTypes.string.isRequired,
10
+ /**
11
+ * The value of the option
12
+ */
10
13
  value: PropTypes.string.isRequired
11
14
  }
@@ -109,6 +109,9 @@ ItemsGroup.propTypes = {
109
109
  isActive: PropTypes.bool,
110
110
  variant: variantProp.propType,
111
111
  tokens: getTokensPropType('SideNavItemsGroup'),
112
+ /**
113
+ * Custom tokens for `SideNav.Item`
114
+ */
112
115
  itemTokens: getTokensPropType('SideNavItem')
113
116
  }
114
117
 
@@ -120,7 +120,13 @@ SideNav.propTypes = {
120
120
  accordion: PropTypes.bool,
121
121
  variant: variantProp.propType,
122
122
  tokens: getTokensPropType('SideNav'),
123
+ /**
124
+ * Custom tokens for `SideNav.Item`
125
+ */
123
126
  itemTokens: getTokensPropType('SideNavItem'),
127
+ /**
128
+ * Custom tokens for `SideNavItemsGroup`
129
+ */
124
130
  groupTokens: getTokensPropType('SideNavItemsGroup')
125
131
  }
126
132
 
@@ -173,12 +173,24 @@ const dictionaryContentShape = PropTypes.shape({
173
173
 
174
174
  StepTracker.propTypes = {
175
175
  ...selectedSystemPropTypes,
176
+ /**
177
+ * The current step, 0-based number
178
+ */
176
179
  current: PropTypes.number,
180
+ /**
181
+ * The language to use for the labels
182
+ */
177
183
  copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), dictionaryContentShape]),
184
+ /**
185
+ * Custom dictionary containing the labels to use for the steps
186
+ */
178
187
  dictionary: PropTypes.shape({
179
188
  en: dictionaryContentShape,
180
189
  fr: dictionaryContentShape
181
190
  }),
191
+ /**
192
+ * An array of strings defining the step titles
193
+ */
182
194
  steps: PropTypes.arrayOf(PropTypes.string),
183
195
  tokens: getTokensPropType('StepTracker'),
184
196
  variant: variantProp.propType
package/src/Tabs/Tabs.jsx CHANGED
@@ -154,6 +154,9 @@ Tabs.displayName = 'Tabs'
154
154
  Tabs.propTypes = {
155
155
  ...selectedSystemPropTypes,
156
156
  ...withLinkRouter.PropTypes,
157
+ /**
158
+ * Array of `TabsItem`s
159
+ */
157
160
  items: PropTypes.arrayOf(
158
161
  PropTypes.shape({
159
162
  ...selectedItemPropTypes,
@@ -164,11 +167,23 @@ Tabs.propTypes = {
164
167
  ref: ABBPropTypes.ref()
165
168
  })
166
169
  ),
170
+ /**
171
+ * `id` property of the current tab in the items array
172
+ */
167
173
  value: PropTypes.string,
168
174
  initialValue: PropTypes.string,
175
+ /**
176
+ * Callback for when the selected tab changes
177
+ */
169
178
  onChange: PropTypes.func,
170
179
  tokens: getTokensPropType('Tabs'),
180
+ /**
181
+ * Custom tokens for `TabsItem`
182
+ */
171
183
  itemTokens: getTokensPropType('TabsItem'),
184
+ /**
185
+ * Custom tokens for `HorizontalScrollButton`
186
+ */
172
187
  scrollButtonTokens: getTokensPropType('HorizontalScrollButton'),
173
188
  variant: variantProp.propType
174
189
  }
@@ -23,10 +23,10 @@ const selectDotStyles = ({ dotWidth, timelineColor, dotBorderWidth, dotColor })
23
23
  borderColor: timelineColor
24
24
  })
25
25
 
26
- const selectConnectorStyles = ({ timelineColor, connectorHeight, connectorWidth }) => ({
26
+ const selectConnectorStyles = ({ timelineConnectorColor, connectorHeight, connectorWidth }) => ({
27
27
  width: connectorWidth,
28
28
  height: connectorHeight,
29
- backgroundColor: timelineColor
29
+ backgroundColor: timelineConnectorColor
30
30
  })
31
31
 
32
32
  const selectTimelineContainerStyle = ({ timelineContainerDirection }) => ({
package/src/index.js CHANGED
@@ -4,6 +4,7 @@ export { default as Box } from './Box'
4
4
  export * from './Button'
5
5
  export { default as Card, PressableCardBase } from './Card'
6
6
  export * from './Carousel'
7
+ export * from './Listbox'
7
8
  export { default as Checkbox } from './Checkbox'
8
9
  export * from './Checkbox'
9
10
  export { default as Divider } from './Divider'
@@ -0,0 +1,29 @@
1
+ import PropTypes from 'prop-types'
2
+
3
+ const htmlAttrTypes = {
4
+ dataSet: PropTypes.object,
5
+ id: PropTypes.string,
6
+ loading: PropTypes.oneOf(['eager', 'lazy']),
7
+ // @todo figure out if we need to enum all the possible roles or maybe use
8
+ // an npm package
9
+ role: PropTypes.string,
10
+ src: PropTypes.string,
11
+ tabIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
12
+ testID: PropTypes.string,
13
+ title: PropTypes.string
14
+ }
15
+
16
+ export default {
17
+ types: htmlAttrTypes,
18
+ select: (props) =>
19
+ Object.entries(props).reduce(
20
+ (items, [key, value]) =>
21
+ Object.keys(htmlAttrTypes).includes(key) || /^(data|aria)-/.test(key)
22
+ ? {
23
+ ...items,
24
+ [key]: value
25
+ }
26
+ : items,
27
+ {}
28
+ )
29
+ }
@@ -19,3 +19,4 @@ export { default as withLinkRouter } from './withLinkRouter'
19
19
  export * from './ssr'
20
20
  export { default as containUniqueFields } from './containUniqueFields'
21
21
  export { default as BaseView } from './BaseView'
22
+ export { default as htmlAttrs } from './htmlAttrs'