@instructure/ui-options 11.6.0 → 11.6.1-snapshot-129

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 (149) hide show
  1. package/CHANGELOG.md +36 -294
  2. package/es/Options/{Item → v1/Item}/index.js +1 -1
  3. package/es/Options/{Separator → v1/Separator}/index.js +1 -1
  4. package/es/Options/{index.js → v1/index.js} +2 -2
  5. package/es/Options/v2/Item/index.js +127 -0
  6. package/es/Options/v2/Item/props.js +26 -0
  7. package/es/Options/v2/Item/styles.js +189 -0
  8. package/es/Options/v2/Item/theme.js +72 -0
  9. package/es/Options/v2/Separator/index.js +69 -0
  10. package/es/Options/v2/Separator/props.js +26 -0
  11. package/es/Options/v2/Separator/styles.js +46 -0
  12. package/es/Options/v2/Separator/theme.js +45 -0
  13. package/es/Options/v2/index.js +147 -0
  14. package/es/Options/v2/props.js +26 -0
  15. package/es/Options/v2/styles.js +57 -0
  16. package/es/Options/v2/theme.js +47 -0
  17. package/es/{index.js → exports/a.js} +6 -6
  18. package/es/exports/b.js +29 -0
  19. package/lib/Options/{Item → v1/Item}/index.js +1 -1
  20. package/lib/Options/{Separator → v1/Separator}/index.js +1 -1
  21. package/lib/Options/v1/index.js +153 -0
  22. package/lib/Options/v2/Item/index.js +132 -0
  23. package/lib/Options/v2/Item/props.js +31 -0
  24. package/lib/Options/v2/Item/styles.js +195 -0
  25. package/lib/Options/v2/Item/theme.js +78 -0
  26. package/lib/Options/v2/Separator/index.js +74 -0
  27. package/lib/Options/v2/Separator/props.js +31 -0
  28. package/lib/Options/v2/Separator/styles.js +52 -0
  29. package/lib/Options/v2/Separator/theme.js +51 -0
  30. package/lib/Options/{index.js → v2/index.js} +4 -5
  31. package/lib/Options/v2/props.js +31 -0
  32. package/lib/Options/v2/styles.js +63 -0
  33. package/lib/Options/v2/theme.js +53 -0
  34. package/lib/{index.js → exports/a.js} +7 -7
  35. package/lib/exports/b.js +47 -0
  36. package/package.json +40 -18
  37. package/src/Options/{Item → v1/Item}/index.tsx +1 -1
  38. package/src/Options/{Separator → v1/Separator}/index.tsx +1 -1
  39. package/src/Options/{index.tsx → v1/index.tsx} +2 -2
  40. package/src/Options/v2/Item/index.tsx +180 -0
  41. package/src/Options/v2/Item/props.ts +136 -0
  42. package/src/Options/v2/Item/styles.ts +205 -0
  43. package/src/Options/v2/Item/theme.ts +79 -0
  44. package/src/Options/v2/README.md +409 -0
  45. package/src/Options/v2/Separator/index.tsx +79 -0
  46. package/src/Options/v2/Separator/props.ts +52 -0
  47. package/src/Options/v2/Separator/styles.ts +52 -0
  48. package/src/Options/v2/Separator/theme.ts +48 -0
  49. package/src/Options/v2/index.tsx +188 -0
  50. package/src/Options/v2/props.ts +76 -0
  51. package/src/Options/v2/styles.ts +63 -0
  52. package/src/Options/v2/theme.ts +52 -0
  53. package/src/{index.ts → exports/a.ts} +9 -9
  54. package/src/exports/b.ts +38 -0
  55. package/tsconfig.build.tsbuildinfo +1 -1
  56. package/types/Options/v1/Item/index.d.ts.map +1 -0
  57. package/types/Options/v1/Item/props.d.ts.map +1 -0
  58. package/types/Options/v1/Item/styles.d.ts.map +1 -0
  59. package/types/Options/v1/Item/theme.d.ts.map +1 -0
  60. package/types/Options/v1/Separator/index.d.ts.map +1 -0
  61. package/types/Options/v1/Separator/props.d.ts.map +1 -0
  62. package/types/Options/v1/Separator/styles.d.ts.map +1 -0
  63. package/types/Options/v1/Separator/theme.d.ts.map +1 -0
  64. package/types/Options/v1/index.d.ts.map +1 -0
  65. package/types/Options/v1/props.d.ts.map +1 -0
  66. package/types/Options/v1/styles.d.ts.map +1 -0
  67. package/types/Options/v1/theme.d.ts.map +1 -0
  68. package/types/Options/v2/Item/index.d.ts +31 -0
  69. package/types/Options/v2/Item/index.d.ts.map +1 -0
  70. package/types/Options/v2/Item/props.d.ts +70 -0
  71. package/types/Options/v2/Item/props.d.ts.map +1 -0
  72. package/types/Options/v2/Item/styles.d.ts +15 -0
  73. package/types/Options/v2/Item/styles.d.ts.map +1 -0
  74. package/types/Options/v2/Item/theme.d.ts +11 -0
  75. package/types/Options/v2/Item/theme.d.ts.map +1 -0
  76. package/types/Options/v2/Separator/index.d.ts +22 -0
  77. package/types/Options/v2/Separator/index.d.ts.map +1 -0
  78. package/types/Options/v2/Separator/props.d.ts +17 -0
  79. package/types/Options/v2/Separator/props.d.ts.map +1 -0
  80. package/types/Options/v2/Separator/styles.d.ts +15 -0
  81. package/types/Options/v2/Separator/styles.d.ts.map +1 -0
  82. package/types/Options/v2/Separator/theme.d.ts +11 -0
  83. package/types/Options/v2/Separator/theme.d.ts.map +1 -0
  84. package/types/Options/v2/index.d.ts +43 -0
  85. package/types/Options/v2/index.d.ts.map +1 -0
  86. package/types/Options/v2/props.d.ts +32 -0
  87. package/types/Options/v2/props.d.ts.map +1 -0
  88. package/types/Options/v2/styles.d.ts +15 -0
  89. package/types/Options/v2/styles.d.ts.map +1 -0
  90. package/types/Options/v2/theme.d.ts +11 -0
  91. package/types/Options/v2/theme.d.ts.map +1 -0
  92. package/types/exports/a.d.ts +10 -0
  93. package/types/exports/a.d.ts.map +1 -0
  94. package/types/exports/b.d.ts +10 -0
  95. package/types/exports/b.d.ts.map +1 -0
  96. package/types/Options/Item/index.d.ts.map +0 -1
  97. package/types/Options/Item/props.d.ts.map +0 -1
  98. package/types/Options/Item/styles.d.ts.map +0 -1
  99. package/types/Options/Item/theme.d.ts.map +0 -1
  100. package/types/Options/Separator/index.d.ts.map +0 -1
  101. package/types/Options/Separator/props.d.ts.map +0 -1
  102. package/types/Options/Separator/styles.d.ts.map +0 -1
  103. package/types/Options/Separator/theme.d.ts.map +0 -1
  104. package/types/Options/index.d.ts.map +0 -1
  105. package/types/Options/props.d.ts.map +0 -1
  106. package/types/Options/styles.d.ts.map +0 -1
  107. package/types/Options/theme.d.ts.map +0 -1
  108. package/types/index.d.ts +0 -10
  109. package/types/index.d.ts.map +0 -1
  110. /package/es/Options/{Item → v1/Item}/props.js +0 -0
  111. /package/es/Options/{Item → v1/Item}/styles.js +0 -0
  112. /package/es/Options/{Item → v1/Item}/theme.js +0 -0
  113. /package/es/Options/{Separator → v1/Separator}/props.js +0 -0
  114. /package/es/Options/{Separator → v1/Separator}/styles.js +0 -0
  115. /package/es/Options/{Separator → v1/Separator}/theme.js +0 -0
  116. /package/es/Options/{props.js → v1/props.js} +0 -0
  117. /package/es/Options/{styles.js → v1/styles.js} +0 -0
  118. /package/es/Options/{theme.js → v1/theme.js} +0 -0
  119. /package/lib/Options/{Item → v1/Item}/props.js +0 -0
  120. /package/lib/Options/{Item → v1/Item}/styles.js +0 -0
  121. /package/lib/Options/{Item → v1/Item}/theme.js +0 -0
  122. /package/lib/Options/{Separator → v1/Separator}/props.js +0 -0
  123. /package/lib/Options/{Separator → v1/Separator}/styles.js +0 -0
  124. /package/lib/Options/{Separator → v1/Separator}/theme.js +0 -0
  125. /package/lib/Options/{props.js → v1/props.js} +0 -0
  126. /package/lib/Options/{styles.js → v1/styles.js} +0 -0
  127. /package/lib/Options/{theme.js → v1/theme.js} +0 -0
  128. /package/src/Options/{Item → v1/Item}/props.ts +0 -0
  129. /package/src/Options/{Item → v1/Item}/styles.ts +0 -0
  130. /package/src/Options/{Item → v1/Item}/theme.ts +0 -0
  131. /package/src/Options/{README.md → v1/README.md} +0 -0
  132. /package/src/Options/{Separator → v1/Separator}/props.ts +0 -0
  133. /package/src/Options/{Separator → v1/Separator}/styles.ts +0 -0
  134. /package/src/Options/{Separator → v1/Separator}/theme.ts +0 -0
  135. /package/src/Options/{props.ts → v1/props.ts} +0 -0
  136. /package/src/Options/{styles.ts → v1/styles.ts} +0 -0
  137. /package/src/Options/{theme.ts → v1/theme.ts} +0 -0
  138. /package/types/Options/{Item → v1/Item}/index.d.ts +0 -0
  139. /package/types/Options/{Item → v1/Item}/props.d.ts +0 -0
  140. /package/types/Options/{Item → v1/Item}/styles.d.ts +0 -0
  141. /package/types/Options/{Item → v1/Item}/theme.d.ts +0 -0
  142. /package/types/Options/{Separator → v1/Separator}/index.d.ts +0 -0
  143. /package/types/Options/{Separator → v1/Separator}/props.d.ts +0 -0
  144. /package/types/Options/{Separator → v1/Separator}/styles.d.ts +0 -0
  145. /package/types/Options/{Separator → v1/Separator}/theme.d.ts +0 -0
  146. /package/types/Options/{index.d.ts → v1/index.d.ts} +0 -0
  147. /package/types/Options/{props.d.ts → v1/props.d.ts} +0 -0
  148. /package/types/Options/{styles.d.ts → v1/styles.d.ts} +0 -0
  149. /package/types/Options/{theme.d.ts → v1/theme.d.ts} +0 -0
@@ -0,0 +1,205 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import { matchComponentTypes } from '@instructure/ui-react-utils'
26
+
27
+ import type { NewComponentTypes } from '@instructure/ui-themes'
28
+ import type { OptionsItemProps, OptionsItemStyle } from './props'
29
+ import { ReactNode } from 'react'
30
+
31
+ /**
32
+ * ---
33
+ * private: true
34
+ * ---
35
+ * Generates the style object from the theme and provided additional information
36
+ * @param {Object} componentTheme The theme variable object.
37
+ * @param {Object} props the props of the component, the style is applied to
38
+ * @param {Object} state the state of the component, the style is applied to
39
+ * @return {Object} The final style object, which will be used in the component
40
+ */
41
+ const generateStyle = (
42
+ componentTheme: NewComponentTypes['OptionsItem'],
43
+ props: OptionsItemProps
44
+ ): OptionsItemStyle => {
45
+ const {
46
+ variant,
47
+ children,
48
+ renderBeforeLabel: hasContentBeforeLabel,
49
+ renderAfterLabel: hasContentAfterLabel,
50
+ beforeLabelContentVAlign,
51
+ afterLabelContentVAlign
52
+ } = props
53
+ // TODO if children are () => ReactNode this wont match anything
54
+ const containsList = matchComponentTypes(children as ReactNode, ['Options'])
55
+
56
+ // used for label and description too
57
+ const variantVariants = {
58
+ highlighted: {
59
+ background: componentTheme.highlightedBackground,
60
+ color: componentTheme.highlightedLabelColor
61
+ },
62
+ selected: {
63
+ background: componentTheme.selectedBackground,
64
+ color: componentTheme.selectedLabelColor
65
+ },
66
+ disabled: { cursor: 'not-allowed', opacity: 0.68 },
67
+ 'highlighted-disabled': {
68
+ background: componentTheme.highlightedBackground,
69
+ color: componentTheme.highlightedLabelColor,
70
+ cursor: 'not-allowed'
71
+ },
72
+ 'selected-highlighted': {
73
+ background: componentTheme.selectedHighlightedBackground,
74
+ color: componentTheme.highlightedLabelColor
75
+ },
76
+ default: {
77
+ transition: 'background 200ms'
78
+ }
79
+ }
80
+
81
+ const getContentVAlign = (type: 'before' | 'after') => {
82
+ const vAlign =
83
+ type === 'before' ? beforeLabelContentVAlign : afterLabelContentVAlign
84
+
85
+ const vOffset =
86
+ type === 'before'
87
+ ? componentTheme.beforeLabelContentVOffset
88
+ : componentTheme.afterLabelContentVOffset
89
+
90
+ return {
91
+ start: {
92
+ alignItems: 'flex-start',
93
+ paddingBlockStart: vOffset
94
+ },
95
+ center: {
96
+ alignItems: 'center',
97
+ paddingBlockStart: vOffset,
98
+ paddingBlockEnd: vOffset
99
+ },
100
+ end: {
101
+ alignItems: 'flex-end',
102
+ paddingBlockEnd: vOffset
103
+ }
104
+ }[vAlign!]
105
+ }
106
+
107
+ const linkStyles = { textDecoration: 'none', color: 'currentColor' }
108
+
109
+ return {
110
+ item: {
111
+ label: 'optionItem',
112
+ background: componentTheme.background,
113
+ color: componentTheme.color,
114
+ cursor: 'pointer',
115
+ display: 'block',
116
+ fontSize: componentTheme.fontSize,
117
+ fontFamily: componentTheme.fontFamily,
118
+ fontWeight: props.isSelected
119
+ ? componentTheme.fontWeightSelected
120
+ : componentTheme.fontWeight,
121
+ lineHeight: componentTheme.lineHeight,
122
+ outline: 'none',
123
+ position: 'relative',
124
+ userSelect: 'none',
125
+ ...variantVariants[variant!],
126
+ ...(containsList && { cursor: 'default' }),
127
+
128
+ // for nested items
129
+ '[class*=-optionItem] &': {
130
+ // except if it has icon before
131
+ ...(!hasContentBeforeLabel && {
132
+ '[class$=-optionItem__container]': {
133
+ paddingInlineStart: componentTheme.nestedPadding
134
+ },
135
+ '[class$=-optionItem__content--before]': {
136
+ insetInlineStart: componentTheme.nestedPadding
137
+ }
138
+ })
139
+ }
140
+ },
141
+ container: {
142
+ label: 'optionItem__container',
143
+ display: 'block',
144
+ outline: 'none',
145
+ padding: `${componentTheme.paddingVertical} ${componentTheme.paddingHorizontal}`,
146
+ ...(containsList && { padding: '0' }),
147
+ ...(hasContentBeforeLabel && {
148
+ paddingInlineEnd: componentTheme.iconPadding,
149
+ paddingInlineStart: `calc(${componentTheme.iconPadding} * 2 + 1em)`
150
+ }),
151
+ ...(hasContentAfterLabel && {
152
+ paddingInlineEnd: `calc(${componentTheme.iconPadding} * 2 + 1em)`,
153
+ paddingInlineStart: componentTheme.iconPadding
154
+ }),
155
+ ...(hasContentBeforeLabel &&
156
+ hasContentAfterLabel && {
157
+ paddingInlineEnd: `calc(${componentTheme.iconPadding} * 2 + 1em)`,
158
+ paddingInlineStart: `calc(${componentTheme.iconPadding} * 2 + 1em)`
159
+ }),
160
+
161
+ // NOTE: needs separate groups for `:is()` and `:-webkit-any()` because of css selector group validation (see https://www.w3.org/TR/selectors-3/#grouping)
162
+ '&:is(a)': {
163
+ '&, &:link, &:visited, &:active, &:hover, &:focus': linkStyles
164
+ },
165
+ '&:-webkit-any(a)': {
166
+ '&, &:link, &:visited, &:active, &:hover, &:focus': linkStyles
167
+ }
168
+ },
169
+ content: {
170
+ label: 'optionItem__content',
171
+ display: 'flex',
172
+ height: '100%',
173
+ boxSizing: 'border-box',
174
+ pointerEvents: 'none',
175
+ position: 'absolute',
176
+ top: '0'
177
+ },
178
+ contentBefore: {
179
+ label: 'optionItem__content--before',
180
+ insetInlineEnd: 'auto',
181
+ insetInlineStart: componentTheme.iconPadding,
182
+ ...getContentVAlign('before')
183
+ },
184
+ contentAfter: {
185
+ label: 'optionItem__content--after',
186
+ insetInlineEnd: componentTheme.iconPadding,
187
+ insetInlineStart: 'auto',
188
+ ...getContentVAlign('after')
189
+ },
190
+ description: {
191
+ label: 'optionItem__description',
192
+ display: 'block',
193
+ paddingBlockStart: componentTheme.descriptionPaddingStart,
194
+ fontWeight: componentTheme.descriptionFontWeight,
195
+ fontSize: componentTheme.descriptionFontSize,
196
+ lineHeight: componentTheme.descriptionLineHeight,
197
+ color: componentTheme.descriptionColor,
198
+
199
+ ...variantVariants[variant!],
200
+ background: 'none' // needed to clear variant background
201
+ }
202
+ }
203
+ }
204
+
205
+ export default generateStyle
@@ -0,0 +1,79 @@
1
+ /*
2
+ * The MIT License (MIT)
3
+ *
4
+ * Copyright (c) 2015 - present Instructure, Inc.
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+
25
+ import type { Theme, ThemeSpecificStyle } from '@instructure/ui-themes'
26
+ import { OptionsItemTheme } from '@instructure/shared-types'
27
+
28
+ /**
29
+ * Generates the theme object for the component from the theme and provided additional information
30
+ * @param {Object} theme The actual theme object.
31
+ * @return {Object} The final theme object with the overrides and component variables
32
+ */
33
+ const generateComponentTheme = (theme: Theme): OptionsItemTheme => {
34
+ const { colors, typography, spacing, key: themeName } = theme
35
+
36
+ const themeSpecificStyle: ThemeSpecificStyle<OptionsItemTheme> = {
37
+ canvas: {
38
+ color: theme['ic-brand-font-color-dark'],
39
+ highlightedBackground: theme['ic-brand-primary']
40
+ }
41
+ }
42
+
43
+ const componentVariables: OptionsItemTheme = {
44
+ fontSize: typography?.fontSizeMedium,
45
+ fontFamily: typography?.fontFamily,
46
+ fontWeight: typography?.fontWeightNormal,
47
+ lineHeight: typography?.lineHeightCondensed,
48
+ fontWeightSelected: typography?.fontWeightNormal,
49
+
50
+ color: colors?.contrasts?.grey125125,
51
+ background: colors?.contrasts?.white1010,
52
+ highlightedLabelColor: colors?.contrasts?.white1010,
53
+ highlightedBackground: colors?.contrasts?.blue4570,
54
+ selectedLabelColor: colors?.contrasts?.white1010,
55
+ selectedBackground: colors?.contrasts?.grey4570,
56
+ selectedHighlightedBackground: colors?.contrasts?.blue5782,
57
+
58
+ padding: `${spacing?.xSmall} ${spacing?.small}`,
59
+ iconPadding: spacing?.small,
60
+ nestedPadding: spacing?.small,
61
+
62
+ beforeLabelContentVOffset: '0.625rem',
63
+ afterLabelContentVOffset: '0.625rem',
64
+
65
+ descriptionFontSize: typography.fontSizeSmall,
66
+ descriptionFontWeight: typography.fontWeightNormal,
67
+ descriptionLineHeight: typography.lineHeight,
68
+ descriptionPaddingStart: '0.25em',
69
+ descriptionColor: colors?.contrasts?.grey5782
70
+ }
71
+
72
+ return {
73
+ ...componentVariables,
74
+ ...themeSpecificStyle[themeName]
75
+ }
76
+ }
77
+
78
+ export { generateComponentTheme as optionsItemThemeGenerator }
79
+ export default generateComponentTheme
@@ -0,0 +1,409 @@
1
+ ---
2
+ describes: Options
3
+ ---
4
+
5
+ `Options` is a view-only component for creating option lists and menus, like those used in [Select](Select) and [Menu](Menu). It should only be used if an existing component doesn't offer the level of customization needed.
6
+
7
+ The `variant` prop of `Option.Item` provides several visual treatments. Use the `highlighted` variant to indicate that an option is being hovered, focused, or otherwise interacted with and the `selected` variant to indicate the selected option.
8
+
9
+ ```js
10
+ ---
11
+ type: example
12
+ ---
13
+ <View display="block" width="300px">
14
+ <Options>
15
+ <Options.Item onClick={() => console.log('clicked!')}>
16
+ Default option
17
+ </Options.Item>
18
+ <Options.Item variant="highlighted">
19
+ Highlighted option
20
+ </Options.Item>
21
+ <Options.Item variant="selected">
22
+ Selected option
23
+ </Options.Item>
24
+ <Options.Item variant="disabled">
25
+ Disabled option
26
+ </Options.Item>
27
+ <Options.Item variant="highlighted-disabled">
28
+ Highlighted disabled option
29
+ </Options.Item>
30
+ </Options>
31
+ </View>
32
+ ```
33
+
34
+ `Options` components can be nested to create sub menus. Icons may be added to any `Options.Item` before or after its text content using `renderBeforeLabel` and `renderAfterLabel` respectively.
35
+
36
+ > `Options` and `Options.Item` receive default roles of `list` and `listitem` respectively, but the most applicable roles should be used. These will commonly be `listbox`/`option` or `menu`/`menuitem`.
37
+
38
+ ```js
39
+ ---
40
+ type: example
41
+ ---
42
+ <View display="block" width="300px">
43
+ <Options role="menu" as="ul">
44
+ <Options.Item role="menuitem">
45
+ Option one
46
+ </Options.Item>
47
+ <Options.Item role="menuitem" variant="highlighted">
48
+ Option two
49
+ </Options.Item>
50
+ <Options.Item role="menuitem" renderAfterLabel={IconArrowOpenEndSolid}>
51
+ Flyout menu option
52
+ </Options.Item>
53
+ <Options.Separator as="li" />
54
+ <Options role="menu" as="ul" renderLabel={'Sub menu'}>
55
+ <Options.Item role="menuitem">
56
+ Sub option one
57
+ </Options.Item>
58
+ <Options.Item role="menuitem">
59
+ Sub option two
60
+ </Options.Item>
61
+ </Options>
62
+ <Options.Separator />
63
+ <Options role="menu" as="ul" renderLabel={'Radio group'}>
64
+ <Options.Item
65
+ role="menuitemradio"
66
+ aria-checked="true"
67
+ renderBeforeLabel={IconCheckSolid}
68
+ >
69
+ Radio option one
70
+ </Options.Item>
71
+ <Options.Item
72
+ role="menuitemradio"
73
+ aria-checked="false"
74
+ renderBeforeLabel={
75
+ <IconCheckLine style={{opacity: 0}} />
76
+ }
77
+ >
78
+ Radio option two
79
+ </Options.Item>
80
+ </Options>
81
+ <Options.Separator />
82
+ <Options.Item role="menuitem">
83
+ Option three
84
+ </Options.Item>
85
+ </Options>
86
+ </View>
87
+ ```
88
+
89
+ `Options` does not manage any state or react to any user interaction. The consuming component or app should listen for events on items and update the `variant` props accordingly via its own state.
90
+
91
+ ```js
92
+ ---
93
+ type: example
94
+ ---
95
+ const Example = ({ options }) => {
96
+ const [highlighted, setHighlighted] = useState(-1)
97
+ const [selected, setSelected] = useState(-1)
98
+
99
+ const handleKeyDown = (event) => {
100
+ let index = highlighted
101
+
102
+ if (event.keyCode === 40 && highlighted < options.length - 1) {
103
+ // down arrow
104
+ event.preventDefault()
105
+ index = highlighted + 1
106
+ } else if (event.keyCode === 38 && highlighted > 0) {
107
+ // up arrow
108
+ event.preventDefault()
109
+ index = highlighted - 1
110
+ } else if (event.keyCode === 13 && highlighted > -1) {
111
+ // enter
112
+ setSelected(index)
113
+ }
114
+
115
+ setHighlighted(index)
116
+ }
117
+
118
+ const handleFocus = (index) => {
119
+ setHighlighted(index)
120
+ }
121
+
122
+ const handleMouseOver = (index) => {
123
+ setHighlighted(index)
124
+ }
125
+
126
+ const handleClick = (index) => {
127
+ setSelected(index)
128
+ }
129
+
130
+ return (
131
+ <View display="block" width="300px" shadow="above">
132
+ <Options
133
+ onKeyDown={handleKeyDown}
134
+ onMouseOut={() => setHighlighted(-1)}
135
+ tabIndex="0"
136
+ >
137
+ {options.map((option, index) => {
138
+ let variant = 'default'
139
+ if (highlighted === index) {
140
+ variant = 'highlighted'
141
+ } else if (selected === index) {
142
+ variant = 'selected'
143
+ }
144
+
145
+ return (
146
+ <Options.Item
147
+ key={option}
148
+ variant={variant}
149
+ tabIndex="-1"
150
+ onMouseOver={(e) => handleMouseOver(index)}
151
+ onFocus={() => handleFocus(index)}
152
+ onClick={() => handleClick(index)}
153
+ >
154
+ {option}
155
+ </Options.Item>
156
+ )
157
+ })}
158
+ </Options>
159
+ </View>
160
+ )
161
+ }
162
+
163
+ render(
164
+ <Example
165
+ options={[
166
+ 'Option one',
167
+ 'Option two',
168
+ 'Option three',
169
+ 'Option four',
170
+ 'Option five'
171
+ ]}
172
+ />
173
+ )
174
+ ```
175
+
176
+ You can recolor the text and the background of the items for their `default`, `highlighted` and `selected` variants.
177
+
178
+ By default, the icons in the `Option.Item` have the same color as the text. If you want to set the color of the icon separately, pass a function to the `renderBeforeLabel` or `renderAfterLabel` prop. This function will have a `props` parameter, so you can access the properties of that `Option.Item` (e.g. the current `variant`). The available props are: `[ variant, as, role, children ]`.
179
+
180
+ ```js
181
+ ---
182
+ type: example
183
+ ---
184
+ const Example = ({ options }) => {
185
+ const [highlighted, setHighlighted] = useState(-1)
186
+ const [selected, setSelected] = useState(-1)
187
+
188
+ const handleKeyDown = (event) => {
189
+ let index = highlighted
190
+
191
+ if (event.keyCode === 40 && highlighted < options.length - 1) {
192
+ // down arrow
193
+ event.preventDefault()
194
+ index = highlighted + 1
195
+ } else if (event.keyCode === 38 && highlighted > 0) {
196
+ // up arrow
197
+ event.preventDefault()
198
+ index = highlighted - 1
199
+ } else if (event.keyCode === 13 && highlighted > -1) {
200
+ // enter
201
+ setSelected(index)
202
+ }
203
+
204
+ setHighlighted(index)
205
+ }
206
+
207
+ const handleFocus = (event, index) => {
208
+ setHighlighted(index)
209
+ }
210
+
211
+ const handleMouseOver = (event, index) => {
212
+ setHighlighted(index)
213
+ }
214
+
215
+ const handleClick = (event, index) => {
216
+ setSelected(index)
217
+ }
218
+
219
+ return (
220
+ <View display="block" width="300px" shadow="above">
221
+ <Options
222
+ onKeyDown={handleKeyDown}
223
+ onMouseOut={() => setHighlighted(-1)}
224
+ tabIndex="0"
225
+ >
226
+ {options.map((option, index) => {
227
+ let variant = 'default'
228
+ if (highlighted === index) {
229
+ variant = 'highlighted'
230
+ } else if (selected === index) {
231
+ variant = 'selected'
232
+ }
233
+
234
+ return (
235
+ <Options.Item
236
+ key={option.label}
237
+ variant={variant}
238
+ tabIndex="-1"
239
+ onMouseOver={(e) => handleMouseOver(e, index)}
240
+ onFocus={(e) => handleFocus(e, index)}
241
+ onClick={(e) => handleClick(e, index)}
242
+ {...option.extraProps}
243
+ >
244
+ {option.label}
245
+ </Options.Item>
246
+ )
247
+ })}
248
+ </Options>
249
+ </View>
250
+ )
251
+ }
252
+
253
+ render(
254
+ <Example
255
+ options={[
256
+ {
257
+ label: 'Default item',
258
+ extraProps: {
259
+ renderBeforeLabel: IconCheckSolid
260
+ }
261
+ },
262
+ {
263
+ label: 'Text is green',
264
+ extraProps: {
265
+ renderBeforeLabel: IconCheckSolid,
266
+ themeOverride: { color: '#0B874B' }
267
+ }
268
+ },
269
+ {
270
+ label: 'Highlighted text is black',
271
+ extraProps: {
272
+ renderBeforeLabel: IconCheckSolid,
273
+ themeOverride: { highlightedLabelColor: '#2D3B45' }
274
+ }
275
+ },
276
+ {
277
+ label: 'Highlighted background is purple',
278
+ extraProps: {
279
+ renderBeforeLabel: IconCheckSolid,
280
+ themeOverride: { highlightedBackground: '#BF32A4' }
281
+ }
282
+ },
283
+ {
284
+ label: 'Only the icon is red',
285
+ extraProps: {
286
+ renderBeforeLabel: (props) => {
287
+ return (
288
+ <IconCheckSolid
289
+ {...(props.variant === 'default' && { color: 'warning' })}
290
+ />
291
+ )
292
+ }
293
+ }
294
+ }
295
+ ]}
296
+ />
297
+ )
298
+ ```
299
+
300
+ Additional/secondary text can be added via the `description` prop, and the ARIA role of it can be set with the `descriptionRole` prop.
301
+
302
+ 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.
303
+
304
+ ```js
305
+ ---
306
+ type: example
307
+ ---
308
+ const Example = () => {
309
+ const [highlighted, setHighlighted] = useState(-1)
310
+
311
+ const handleMouseOver = (index) => {
312
+ setHighlighted(index)
313
+ }
314
+
315
+ return (
316
+ <View display="block" width="300px">
317
+ <Options onMouseOut={() => setHighlighted(-1)}>
318
+ <Options.Item
319
+ onMouseOver={() => handleMouseOver(1)}
320
+ variant={highlighted === 1 ? 'highlighted' : 'default'}
321
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
322
+ renderBeforeLabel={IconCheckSolid}
323
+ renderAfterLabel={IconArrowOpenEndSolid}
324
+ beforeLabelContentVAlign="start"
325
+ afterLabelContentVAlign="start"
326
+ >
327
+ Option one
328
+ </Options.Item>
329
+ <Options.Item
330
+ onMouseOver={() => handleMouseOver(2)}
331
+ variant={highlighted === 2 ? 'highlighted' : 'default'}
332
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
333
+ renderBeforeLabel={IconCheckSolid}
334
+ renderAfterLabel={IconArrowOpenEndSolid}
335
+ beforeLabelContentVAlign="center"
336
+ afterLabelContentVAlign="center"
337
+ >
338
+ Option two
339
+ </Options.Item>
340
+ <Options.Item
341
+ onMouseOver={() => handleMouseOver(3)}
342
+ variant={highlighted === 3 ? 'highlighted' : 'default'}
343
+ description="Curabitur fringilla, urna ut efficitur molestie, nibh lacus tincidunt elit, ut tempor ipsum nunc sit amet massa."
344
+ renderBeforeLabel={IconCheckSolid}
345
+ renderAfterLabel={IconArrowOpenEndSolid}
346
+ beforeLabelContentVAlign="end"
347
+ afterLabelContentVAlign="end"
348
+ >
349
+ Option three
350
+ </Options.Item>
351
+ </Options>
352
+ </View>
353
+ )
354
+ }
355
+
356
+ render(<Example />)
357
+ ```
358
+
359
+ Providing a `href` prop will render the option as `<a>` link element.
360
+
361
+ **WARNING!** Since `Options` is a view-only component, you have to make sure it is accessible, and if set the variant to disabled, disable the links as well!
362
+
363
+ ```js
364
+ ---
365
+ type: example
366
+ ---
367
+ const Example = () => {
368
+ const [highlighted, setHighlighted] = useState(-1)
369
+
370
+ const handleMouseOver = (index) => {
371
+ setHighlighted(index)
372
+ }
373
+
374
+ return (
375
+ <View display="block" width="300px">
376
+ <Options onMouseOut={() => setHighlighted(-1)}>
377
+ <Options.Item
378
+ onMouseOver={() => handleMouseOver(1)}
379
+ variant={highlighted === 1 ? 'highlighted' : 'default'}
380
+ href="/"
381
+ >
382
+ Link one
383
+ </Options.Item>
384
+ <Options.Item
385
+ onMouseOver={() => handleMouseOver(2)}
386
+ variant={highlighted === 2 ? 'highlighted' : 'default'}
387
+ href="/"
388
+ >
389
+ Link two
390
+ </Options.Item>
391
+ <Options.Item
392
+ onMouseOver={() => handleMouseOver(3)}
393
+ variant={highlighted === 3 ? 'highlighted' : 'default'}
394
+ variant="disabled"
395
+ aria-disabled="true"
396
+ onClick={(e) => {
397
+ e.preventDefault()
398
+ }}
399
+ href="/"
400
+ >
401
+ Link three
402
+ </Options.Item>
403
+ </Options>
404
+ </View>
405
+ )
406
+ }
407
+
408
+ render(<Example />)
409
+ ```