@instructure/ui-options 8.14.1-snapshot.7 → 8.14.1-snapshot.70

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 (33) hide show
  1. package/es/Options/Item/__examples__/Item.examples.js +28 -2
  2. package/es/Options/Item/index.js +11 -4
  3. package/es/Options/Item/props.js +5 -1
  4. package/es/Options/Item/styles.js +46 -6
  5. package/es/Options/Item/theme.js +8 -1
  6. package/es/Options/index.js +4 -4
  7. package/lib/Options/Item/__examples__/Item.examples.js +28 -2
  8. package/lib/Options/Item/index.js +11 -4
  9. package/lib/Options/Item/props.js +5 -1
  10. package/lib/Options/Item/styles.js +46 -6
  11. package/lib/Options/Item/theme.js +8 -1
  12. package/lib/Options/index.js +5 -3
  13. package/package.json +15 -15
  14. package/src/Options/Item/__examples__/Item.examples.ts +44 -5
  15. package/src/Options/Item/index.tsx +14 -4
  16. package/src/Options/Item/props.ts +31 -3
  17. package/src/Options/Item/styles.ts +51 -5
  18. package/src/Options/Item/theme.ts +10 -1
  19. package/src/Options/README.md +78 -2
  20. package/src/Options/index.tsx +14 -6
  21. package/tsconfig.build.tsbuildinfo +1 -1
  22. package/types/Options/Item/OptionsItemLocator.d.ts +4 -4
  23. package/types/Options/Item/__examples__/Item.examples.d.ts +3 -26
  24. package/types/Options/Item/__examples__/Item.examples.d.ts.map +1 -1
  25. package/types/Options/Item/index.d.ts +5 -5
  26. package/types/Options/Item/index.d.ts.map +1 -1
  27. package/types/Options/Item/props.d.ts +18 -3
  28. package/types/Options/Item/props.d.ts.map +1 -1
  29. package/types/Options/Item/styles.d.ts.map +1 -1
  30. package/types/Options/Item/theme.d.ts.map +1 -1
  31. package/types/Options/OptionsLocator.d.ts +14 -14
  32. package/types/Options/index.d.ts +8 -7
  33. package/types/Options/index.d.ts.map +1 -1
@@ -44,7 +44,7 @@ type ItemProps = {
44
44
  */
45
45
  variant?: 'default' | 'highlighted' | 'selected' | 'disabled'
46
46
  /**
47
- * The aria role of the element
47
+ * The ARIA role of the element
48
48
  */
49
49
  role?: string
50
50
  }
@@ -60,7 +60,22 @@ type OptionsItemOwnProps = ItemProps & {
60
60
  * (if you pass a function, it has the `props` as its parameter)
61
61
  */
62
62
  renderAfterLabel?: React.ReactNode | ((props: ItemProps) => React.ReactNode)
63
- children?: React.ReactNode | (() => React.ReactNode)
63
+ /**
64
+ * Sets the vAlign of renderBeforeLabel content
65
+ */
66
+ beforeLabelContentVAlign?: 'start' | 'center' | 'end'
67
+ /**
68
+ * Sets the vAlign of renderAfterLabel content
69
+ */
70
+ afterLabelContentVAlign?: 'start' | 'center' | 'end'
71
+ /**
72
+ * Additional "secondary" description text
73
+ */
74
+ description?: React.ReactNode | (() => React.ReactNode)
75
+ /**
76
+ * The ARIA role of the description element
77
+ */
78
+ descriptionRole?: string
64
79
  }
65
80
 
66
81
  type PropKeys = keyof OptionsItemOwnProps
@@ -72,7 +87,12 @@ type OptionsItemProps = OptionsItemOwnProps &
72
87
  OtherHTMLAttributes<OptionsItemOwnProps>
73
88
 
74
89
  type OptionsItemStyle = ComponentStyle<
75
- 'item' | 'container' | 'content' | 'contentBefore' | 'contentAfter'
90
+ | 'item'
91
+ | 'container'
92
+ | 'content'
93
+ | 'contentBefore'
94
+ | 'contentAfter'
95
+ | 'description'
76
96
  >
77
97
 
78
98
  const propTypes: PropValidators<PropKeys> = {
@@ -81,6 +101,10 @@ const propTypes: PropValidators<PropKeys> = {
81
101
  role: PropTypes.string,
82
102
  renderBeforeLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
83
103
  renderAfterLabel: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
104
+ beforeLabelContentVAlign: PropTypes.oneOf(['start', 'center', 'end']),
105
+ afterLabelContentVAlign: PropTypes.oneOf(['start', 'center', 'end']),
106
+ description: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
107
+ descriptionRole: PropTypes.string,
84
108
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func])
85
109
  }
86
110
 
@@ -90,6 +114,10 @@ const allowedProps: AllowedPropKeys = [
90
114
  'role',
91
115
  'renderBeforeLabel',
92
116
  'renderAfterLabel',
117
+ 'beforeLabelContentVAlign',
118
+ 'afterLabelContentVAlign',
119
+ 'description',
120
+ 'descriptionRole',
93
121
  'children'
94
122
  ]
95
123
 
@@ -45,11 +45,14 @@ const generateStyle = (
45
45
  variant,
46
46
  children,
47
47
  renderBeforeLabel: hasContentBeforeLabel,
48
- renderAfterLabel: hasContentAfterLabel
48
+ renderAfterLabel: hasContentAfterLabel,
49
+ beforeLabelContentVAlign,
50
+ afterLabelContentVAlign
49
51
  } = props
50
52
 
51
53
  const containsList = matchComponentTypes(children, ['Options'])
52
54
 
55
+ // used for label and description too
53
56
  const variantVariants = {
54
57
  highlighted: {
55
58
  background: componentTheme.highlightedBackground,
@@ -63,6 +66,34 @@ const generateStyle = (
63
66
  default: {}
64
67
  }
65
68
 
69
+ const getContentVAlign = (type: 'before' | 'after') => {
70
+ const vAlign =
71
+ type === 'before' ? beforeLabelContentVAlign : afterLabelContentVAlign
72
+
73
+ const vOffset =
74
+ type === 'before'
75
+ ? componentTheme.beforeLabelContentVOffset
76
+ : componentTheme.afterLabelContentVOffset
77
+
78
+ return {
79
+ start: {
80
+ alignItems: 'flex-start',
81
+ paddingBlockStart: vOffset
82
+ },
83
+ center: {
84
+ alignItems: 'center',
85
+ paddingBlockStart: vOffset,
86
+ paddingBlockEnd: vOffset
87
+ },
88
+ end: {
89
+ alignItems: 'flex-end',
90
+ paddingBlockEnd: vOffset
91
+ }
92
+ }[vAlign!]
93
+ }
94
+
95
+ const transition = 'background 200ms'
96
+
66
97
  return {
67
98
  item: {
68
99
  label: 'optionItem',
@@ -76,7 +107,7 @@ const generateStyle = (
76
107
  lineHeight: componentTheme.lineHeight,
77
108
  outline: 'none',
78
109
  position: 'relative',
79
- transition: 'background 200ms',
110
+ transition,
80
111
  userSelect: 'none',
81
112
  ...variantVariants[variant!],
82
113
  ...(containsList && { cursor: 'default' }),
@@ -115,9 +146,9 @@ const generateStyle = (
115
146
  },
116
147
  content: {
117
148
  label: 'optionItem__content',
118
- alignItems: 'center',
119
149
  display: 'flex',
120
150
  height: '100%',
151
+ boxSizing: 'border-box',
121
152
  pointerEvents: 'none',
122
153
  position: 'absolute',
123
154
  top: '0'
@@ -125,12 +156,27 @@ const generateStyle = (
125
156
  contentBefore: {
126
157
  label: 'optionItem__content--before',
127
158
  insetInlineEnd: 'auto',
128
- insetInlineStart: componentTheme.iconPadding
159
+ insetInlineStart: componentTheme.iconPadding,
160
+ ...getContentVAlign('before')
129
161
  },
130
162
  contentAfter: {
131
163
  label: 'optionItem__content--after',
132
164
  insetInlineEnd: componentTheme.iconPadding,
133
- insetInlineStart: 'auto'
165
+ insetInlineStart: 'auto',
166
+ ...getContentVAlign('after')
167
+ },
168
+ description: {
169
+ label: 'optionItem__description',
170
+ display: 'block',
171
+ transition,
172
+ paddingBlockStart: componentTheme.descriptionPaddingStart,
173
+ fontWeight: componentTheme.descriptionFontWeight,
174
+ fontSize: componentTheme.descriptionFontSize,
175
+ lineHeight: componentTheme.descriptionLineHeight,
176
+ color: componentTheme.descriptionColor,
177
+
178
+ ...variantVariants[variant!],
179
+ background: 'none' // needed to clear variant background
134
180
  }
135
181
  }
136
182
  }
@@ -55,7 +55,16 @@ const generateComponentTheme = (theme: Theme): OptionsItemTheme => {
55
55
 
56
56
  padding: `${spacing?.xSmall} ${spacing?.small}`,
57
57
  iconPadding: spacing?.small,
58
- nestedPadding: spacing?.medium
58
+ nestedPadding: spacing?.medium,
59
+
60
+ beforeLabelContentVOffset: '0.625rem',
61
+ afterLabelContentVOffset: '0.625rem',
62
+
63
+ descriptionFontSize: typography.fontSizeSmall,
64
+ descriptionFontWeight: typography.fontWeightNormal,
65
+ descriptionLineHeight: typography.lineHeight,
66
+ descriptionPaddingStart: '0.25em',
67
+ descriptionColor: colors.textDark
59
68
  }
60
69
 
61
70
  return {
@@ -130,7 +130,11 @@ class Example extends React.Component {
130
130
  width="300px"
131
131
  shadow="above"
132
132
  >
133
- <Options onKeyDown={this.handleKeyDown} tabIndex="0">
133
+ <Options
134
+ onKeyDown={this.handleKeyDown}
135
+ onMouseOut={(e) => this.setState({ highlighted: -1 })}
136
+ tabIndex="0"
137
+ >
134
138
  {this.props.options.map((option, index) => {
135
139
  let variant = 'default'
136
140
  if (this.state.highlighted === index) {
@@ -225,7 +229,11 @@ class Example extends React.Component {
225
229
  width="300px"
226
230
  shadow="above"
227
231
  >
228
- <Options onKeyDown={this.handleKeyDown} tabIndex="0">
232
+ <Options
233
+ onKeyDown={this.handleKeyDown}
234
+ onMouseOut={(e) => this.setState({ highlighted: -1 })}
235
+ tabIndex="0"
236
+ >
229
237
  {this.props.options.map((option, index) => {
230
238
  let variant = 'default'
231
239
  if (this.state.highlighted === index) {
@@ -296,3 +304,71 @@ render(
296
304
  ]} />
297
305
  )
298
306
  ```
307
+
308
+ Additional/secondary text can be added via the `description` prop, and the ARIA role of it can be set with the `descriptionRole` prop.
309
+
310
+ For longer, multi-line options the problem of the vertical alignment comes up. The content of the `renderBeforeLabel` and `renderAfterLabel` props are vertically centered by default. This can be changed with the `beforeLabelContentVAlign` and `afterLabelContentVAlign` props.
311
+
312
+ ```js
313
+ ---
314
+ render: false
315
+ example: true
316
+ ---
317
+ class Example extends React.Component {
318
+ constructor(props) {
319
+ super(props)
320
+
321
+ this.state = {
322
+ highlighted: -1
323
+ }
324
+ }
325
+
326
+ handleMouseOver = (index) => {
327
+ this.setState({ highlighted: index })
328
+ }
329
+
330
+ render() {
331
+ return (
332
+ <View display="block" width="300px">
333
+ <Options onMouseOut={(e) => this.setState({ highlighted: -1 })}>
334
+ <Options.Item
335
+ onMouseOver={(e) => this.handleMouseOver(1)}
336
+ variant={this.state.highlighted === 1 ? 'highlighted' : 'default'}
337
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
338
+ renderBeforeLabel={IconCheckSolid}
339
+ renderAfterLabel={IconArrowOpenEndSolid}
340
+ beforeLabelContentVAlign="start"
341
+ afterLabelContentVAlign="start"
342
+ >
343
+ Option one
344
+ </Options.Item>
345
+ <Options.Item
346
+ onMouseOver={(e) => this.handleMouseOver(2)}
347
+ variant={this.state.highlighted === 2 ? 'highlighted' : 'default'}
348
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
349
+ renderBeforeLabel={IconCheckSolid}
350
+ renderAfterLabel={IconArrowOpenEndSolid}
351
+ beforeLabelContentVAlign="center"
352
+ afterLabelContentVAlign="center"
353
+ >
354
+ Option two
355
+ </Options.Item>
356
+ <Options.Item
357
+ onMouseOver={(e) => this.handleMouseOver(3)}
358
+ variant={this.state.highlighted === 3 ? 'highlighted' : 'default'}
359
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
360
+ renderBeforeLabel={IconCheckSolid}
361
+ renderAfterLabel={IconArrowOpenEndSolid}
362
+ beforeLabelContentVAlign="end"
363
+ afterLabelContentVAlign="end"
364
+ >
365
+ Option three
366
+ </Options.Item>
367
+ </Options>
368
+ </View>
369
+ )
370
+ }
371
+ }
372
+
373
+ render(<Example />)
374
+ ```
@@ -23,7 +23,7 @@
23
23
  */
24
24
 
25
25
  /** @jsx jsx */
26
- import { Component, Children, ReactElement } from 'react'
26
+ import React, { Component, Children } from 'react'
27
27
 
28
28
  import {
29
29
  omitProps,
@@ -42,10 +42,18 @@ import generateStyles from './styles'
42
42
  import generateComponentTheme from './theme'
43
43
 
44
44
  import { Item } from './Item'
45
+ import type { OptionsItemProps } from './Item/props'
45
46
  import { Separator } from './Separator'
47
+ import type { OptionsSeparatorProps } from './Separator/props'
48
+
46
49
  import type { OptionsProps } from './props'
47
50
  import { allowedProps, propTypes } from './props'
48
51
 
52
+ type ItemChild = React.ComponentElement<OptionsItemProps, Item>
53
+ type SeparatorChild = React.ComponentElement<OptionsSeparatorProps, Separator>
54
+ type OptionsChild = React.ComponentElement<OptionsProps, Options>
55
+ type OptionsChildren = (ItemChild | SeparatorChild | OptionsChild)[]
56
+
49
57
  /**
50
58
  ---
51
59
  category: components
@@ -109,11 +117,11 @@ class Options extends Component<OptionsProps> {
109
117
  )
110
118
  }
111
119
 
112
- renderSubList(children: React.ReactNode) {
120
+ renderSubList(subOptions: OptionsChild) {
113
121
  const { styles } = this.props
114
122
  return (
115
123
  <Item as={this.childAs} role="presentation" css={styles?.label}>
116
- {children}
124
+ {subOptions}
117
125
  </Item>
118
126
  )
119
127
  }
@@ -121,12 +129,12 @@ class Options extends Component<OptionsProps> {
121
129
  renderChildren() {
122
130
  const { children } = this.props
123
131
 
124
- return Children.map(children, (child) => {
125
- if (matchComponentTypes(child, ['Options'])) {
132
+ return Children.map(children as OptionsChildren, (child) => {
133
+ if (matchComponentTypes<OptionsChild>(child, ['Options'])) {
126
134
  return this.renderSubList(child)
127
135
  }
128
136
  if (matchComponentTypes(child, ['Item', 'Separator'])) {
129
- return safeCloneElement(child as ReactElement, { as: this.childAs })
137
+ return safeCloneElement(child, { as: this.childAs })
130
138
  }
131
139
  return undefined
132
140
  })