@instructure/ui-options 8.14.1-snapshot.7 → 8.15.1-snapshot.2

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 (34) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/es/Options/Item/__examples__/Item.examples.js +28 -2
  3. package/es/Options/Item/index.js +11 -4
  4. package/es/Options/Item/props.js +5 -1
  5. package/es/Options/Item/styles.js +46 -6
  6. package/es/Options/Item/theme.js +8 -1
  7. package/es/Options/index.js +4 -4
  8. package/lib/Options/Item/__examples__/Item.examples.js +28 -2
  9. package/lib/Options/Item/index.js +11 -4
  10. package/lib/Options/Item/props.js +5 -1
  11. package/lib/Options/Item/styles.js +46 -6
  12. package/lib/Options/Item/theme.js +8 -1
  13. package/lib/Options/index.js +5 -3
  14. package/package.json +15 -15
  15. package/src/Options/Item/__examples__/Item.examples.ts +44 -5
  16. package/src/Options/Item/index.tsx +14 -4
  17. package/src/Options/Item/props.ts +31 -3
  18. package/src/Options/Item/styles.ts +51 -5
  19. package/src/Options/Item/theme.ts +10 -1
  20. package/src/Options/README.md +78 -2
  21. package/src/Options/index.tsx +14 -6
  22. package/tsconfig.build.tsbuildinfo +1 -1
  23. package/types/Options/Item/OptionsItemLocator.d.ts +4 -4
  24. package/types/Options/Item/__examples__/Item.examples.d.ts +3 -26
  25. package/types/Options/Item/__examples__/Item.examples.d.ts.map +1 -1
  26. package/types/Options/Item/index.d.ts +5 -5
  27. package/types/Options/Item/index.d.ts.map +1 -1
  28. package/types/Options/Item/props.d.ts +18 -3
  29. package/types/Options/Item/props.d.ts.map +1 -1
  30. package/types/Options/Item/styles.d.ts.map +1 -1
  31. package/types/Options/Item/theme.d.ts.map +1 -1
  32. package/types/Options/OptionsLocator.d.ts +14 -14
  33. package/types/Options/index.d.ts +8 -7
  34. package/types/Options/index.d.ts.map +1 -1
@@ -58,9 +58,9 @@ class Item extends Component<OptionsItemProps> {
58
58
  as: 'span',
59
59
  variant: 'default',
60
60
  role: 'listitem',
61
- renderBeforeLabel: null,
62
- renderAfterLabel: null,
63
- children: null
61
+ descriptionRole: 'note',
62
+ beforeLabelContentVAlign: 'center',
63
+ afterLabelContentVAlign: 'center'
64
64
  } as const
65
65
 
66
66
  ref: Element | null = null
@@ -104,6 +104,8 @@ class Item extends Component<OptionsItemProps> {
104
104
  as,
105
105
  role,
106
106
  styles,
107
+ description,
108
+ descriptionRole,
107
109
  renderBeforeLabel,
108
110
  renderAfterLabel,
109
111
  children
@@ -112,6 +114,9 @@ class Item extends Component<OptionsItemProps> {
112
114
  const ElementType = getElementType(Item, this.props, () => as!)
113
115
  const passthroughProps = omitProps(this.props, Item.allowedProps)
114
116
 
117
+ const childrenContent = callRenderProp(children)
118
+ const descriptionContent = callRenderProp(description)
119
+
115
120
  return (
116
121
  <ElementType
117
122
  role="none"
@@ -122,7 +127,12 @@ class Item extends Component<OptionsItemProps> {
122
127
  }}
123
128
  >
124
129
  <span {...passthroughProps} css={styles?.container} role={role}>
125
- {children}
130
+ {childrenContent}
131
+ {descriptionContent && (
132
+ <span css={styles?.description} role={descriptionRole}>
133
+ {descriptionContent}
134
+ </span>
135
+ )}
126
136
  </span>
127
137
  {renderBeforeLabel &&
128
138
  this.renderContent(renderBeforeLabel, styles?.contentBefore)}
@@ -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
  })