@telus-uds/components-web 2.17.2 → 2.19.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.
- package/CHANGELOG.md +27 -2
- package/component-docs.json +854 -421
- package/lib/Badge/Badge.js +2 -2
- package/lib/BlockQuote/BlockQuote.js +9 -0
- package/lib/Callout/Callout.js +5 -0
- package/lib/DatePicker/DatePicker.js +24 -4
- package/lib/Disclaimer/Disclaimer.js +4 -0
- package/lib/ExpandCollapseMini/ExpandCollapseMini.js +7 -1
- package/lib/NavigationBar/NavigationBar.js +8 -2
- package/lib/NavigationBar/NavigationSubMenu.js +2 -4
- package/lib/OrderedList/OrderedList.js +4 -2
- package/lib/Ribbon/Ribbon.js +2 -1
- package/lib/Toast/Toast.js +2 -1
- package/lib/Video/Video.js +3 -1
- package/lib/WaffleGrid/WaffleGrid.js +20 -20
- package/lib/baseExports.js +12 -0
- package/lib/index.js +0 -18
- package/lib/utils/useOverlaidPosition.js +2 -2
- package/lib-module/Badge/Badge.js +1 -1
- package/lib-module/BlockQuote/BlockQuote.js +10 -1
- package/lib-module/Callout/Callout.js +6 -1
- package/lib-module/DatePicker/DatePicker.js +24 -4
- package/lib-module/Disclaimer/Disclaimer.js +4 -0
- package/lib-module/ExpandCollapseMini/ExpandCollapseMini.js +8 -2
- package/lib-module/NavigationBar/NavigationBar.js +8 -2
- package/lib-module/NavigationBar/NavigationSubMenu.js +1 -2
- package/lib-module/OrderedList/OrderedList.js +5 -3
- package/lib-module/Ribbon/Ribbon.js +3 -2
- package/lib-module/Toast/Toast.js +3 -2
- package/lib-module/Video/Video.js +4 -2
- package/lib-module/WaffleGrid/WaffleGrid.js +21 -21
- package/lib-module/baseExports.js +1 -1
- package/lib-module/index.js +0 -2
- package/lib-module/utils/useOverlaidPosition.js +2 -2
- package/package.json +3 -3
- package/src/Badge/Badge.jsx +1 -1
- package/src/BlockQuote/BlockQuote.jsx +10 -1
- package/src/Callout/Callout.jsx +11 -1
- package/src/DatePicker/DatePicker.jsx +20 -2
- package/src/Disclaimer/Disclaimer.jsx +3 -0
- package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +7 -2
- package/src/NavigationBar/NavigationBar.jsx +7 -2
- package/src/NavigationBar/NavigationSubMenu.jsx +1 -2
- package/src/OrderedList/OrderedList.jsx +4 -3
- package/src/Ribbon/Ribbon.jsx +8 -2
- package/src/Toast/Toast.jsx +4 -2
- package/src/Video/Video.jsx +9 -2
- package/src/WaffleGrid/WaffleGrid.jsx +21 -15
- package/src/baseExports.js +2 -0
- package/src/index.js +0 -2
- package/src/utils/useOverlaidPosition.js +2 -2
- package/types/Callout.d.ts +1 -0
- package/lib/Autocomplete/Autocomplete.js +0 -407
- package/lib/Autocomplete/Loading.js +0 -46
- package/lib/Autocomplete/Suggestions.js +0 -81
- package/lib/Autocomplete/constants.js +0 -19
- package/lib/Autocomplete/dictionary.js +0 -19
- package/lib/Autocomplete/index.js +0 -13
- package/lib/Listbox/GroupControl.js +0 -110
- package/lib/Listbox/Listbox.js +0 -179
- package/lib/Listbox/ListboxGroup.js +0 -145
- package/lib/Listbox/ListboxItem.js +0 -101
- package/lib/Listbox/ListboxOverlay.js +0 -91
- package/lib/Listbox/index.js +0 -13
- package/lib-module/Autocomplete/Autocomplete.js +0 -383
- package/lib-module/Autocomplete/Loading.js +0 -32
- package/lib-module/Autocomplete/Suggestions.js +0 -64
- package/lib-module/Autocomplete/constants.js +0 -5
- package/lib-module/Autocomplete/dictionary.js +0 -12
- package/lib-module/Autocomplete/index.js +0 -2
- package/lib-module/Listbox/GroupControl.js +0 -96
- package/lib-module/Listbox/Listbox.js +0 -158
- package/lib-module/Listbox/ListboxGroup.js +0 -122
- package/lib-module/Listbox/ListboxItem.js +0 -77
- package/lib-module/Listbox/ListboxOverlay.js +0 -69
- package/lib-module/Listbox/index.js +0 -2
- package/src/Autocomplete/Autocomplete.jsx +0 -357
- package/src/Autocomplete/Loading.jsx +0 -15
- package/src/Autocomplete/Suggestions.jsx +0 -52
- package/src/Autocomplete/constants.js +0 -6
- package/src/Autocomplete/dictionary.js +0 -12
- package/src/Autocomplete/index.js +0 -3
- package/src/Listbox/GroupControl.jsx +0 -82
- package/src/Listbox/Listbox.jsx +0 -163
- package/src/Listbox/ListboxGroup.jsx +0 -125
- package/src/Listbox/ListboxItem.jsx +0 -80
- package/src/Listbox/ListboxOverlay.jsx +0 -72
- package/src/Listbox/index.js +0 -3
|
@@ -1,357 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/require-default-props */
|
|
2
|
-
import React, { forwardRef, useRef, useState } from 'react'
|
|
3
|
-
import PropTypes from 'prop-types'
|
|
4
|
-
import { throttle } from 'lodash'
|
|
5
|
-
import {
|
|
6
|
-
InputSupports,
|
|
7
|
-
inputSupportsProps,
|
|
8
|
-
selectSystemProps,
|
|
9
|
-
TextInput,
|
|
10
|
-
textInputProps,
|
|
11
|
-
textInputHandlerProps,
|
|
12
|
-
Typography,
|
|
13
|
-
useCopy,
|
|
14
|
-
useSafeLayoutEffect,
|
|
15
|
-
useThemeTokens
|
|
16
|
-
} from '@telus-uds/components-base'
|
|
17
|
-
import Listbox from '../Listbox'
|
|
18
|
-
import { htmlAttrs, useOverlaidPosition } from '../utils'
|
|
19
|
-
import Loading from './Loading'
|
|
20
|
-
import Suggestions from './Suggestions'
|
|
21
|
-
import {
|
|
22
|
-
DEFAULT_MAX_SUGGESTIONS,
|
|
23
|
-
DEFAULT_MIN_TO_SUGGESTION,
|
|
24
|
-
INPUT_LEFT_PADDING,
|
|
25
|
-
MIN_LISTBOX_WIDTH
|
|
26
|
-
} from './constants'
|
|
27
|
-
import dictionary from './dictionary'
|
|
28
|
-
|
|
29
|
-
const [selectProps, selectedSystemPropTypes] = selectSystemProps([
|
|
30
|
-
htmlAttrs,
|
|
31
|
-
inputSupportsProps,
|
|
32
|
-
textInputHandlerProps,
|
|
33
|
-
textInputProps
|
|
34
|
-
])
|
|
35
|
-
|
|
36
|
-
// Returns JSX to display a bold string `str` with unbolded occurrences of the
|
|
37
|
-
// `substring` based in the array of `matchIndexes` provided
|
|
38
|
-
const highlightAllMatches = (str, substring = '', matchIndexes = [], resultsTextColor) => (
|
|
39
|
-
// Wrapping all in bold
|
|
40
|
-
<Typography variant={{ bold: false }} tokens={{ color: resultsTextColor }}>
|
|
41
|
-
{matchIndexes.reduce(
|
|
42
|
-
(acc, matchIndex, index) => [
|
|
43
|
-
...acc,
|
|
44
|
-
// Add a piece of the string up to the first occurrence of the substring
|
|
45
|
-
index === 0 && (str.slice(0, matchIndex) ?? ''),
|
|
46
|
-
// Unbold the occurrence of the substring (while keeping the original casing)
|
|
47
|
-
<Typography key={matchIndex} variant={{ bold: true }} tokens={{ color: resultsTextColor }}>
|
|
48
|
-
{str.slice(matchIndex, matchIndex + substring.length)}
|
|
49
|
-
</Typography>,
|
|
50
|
-
// Add the rest of the string until the next occurrence or the end of it
|
|
51
|
-
str.slice(matchIndex + substring.length, matchIndexes[index + 1] ?? str.length)
|
|
52
|
-
],
|
|
53
|
-
[]
|
|
54
|
-
)}
|
|
55
|
-
</Typography>
|
|
56
|
-
)
|
|
57
|
-
const highlight = (items = [], text = '', color) =>
|
|
58
|
-
items.reduce((acc, item) => {
|
|
59
|
-
const matches = Array.from(item.label.toLowerCase().matchAll(text.toLowerCase()))?.map(
|
|
60
|
-
({ index }) => index
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
if (matches?.length) {
|
|
64
|
-
return [...acc, { ...item, label: highlightAllMatches(item.label, text, matches, color) }]
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return [...acc, item]
|
|
68
|
-
}, [])
|
|
69
|
-
|
|
70
|
-
const Autocomplete = forwardRef(
|
|
71
|
-
(
|
|
72
|
-
{
|
|
73
|
-
children,
|
|
74
|
-
copy = 'en',
|
|
75
|
-
fullWidth = true,
|
|
76
|
-
initialItems,
|
|
77
|
-
initialValue,
|
|
78
|
-
isLoading = false,
|
|
79
|
-
items,
|
|
80
|
-
maxSuggestions = DEFAULT_MAX_SUGGESTIONS,
|
|
81
|
-
minToSuggestion = DEFAULT_MIN_TO_SUGGESTION,
|
|
82
|
-
noResults,
|
|
83
|
-
onChange,
|
|
84
|
-
onClear,
|
|
85
|
-
onSelect,
|
|
86
|
-
readOnly,
|
|
87
|
-
validation,
|
|
88
|
-
value,
|
|
89
|
-
...rest
|
|
90
|
-
},
|
|
91
|
-
ref
|
|
92
|
-
) => {
|
|
93
|
-
const { color: resultsTextColor } = useThemeTokens('Search', {}, { focus: true })
|
|
94
|
-
// The wrapped input is mostly responsible for controlled vs uncontrolled handling,
|
|
95
|
-
// but we also need to adjust suggestions based on the mode:
|
|
96
|
-
// - in controlled mode we rely entirely on the suggestions passed via the `items` prop,
|
|
97
|
-
// - in uncontrolled mode we filter the suggestions ourselves based on the `initialItems`
|
|
98
|
-
// prop and the text entered
|
|
99
|
-
const isControlled = value !== undefined
|
|
100
|
-
|
|
101
|
-
// We need to store current items for uncontrolled usage
|
|
102
|
-
const [currentItems, setCurrentItems] = useState(initialItems)
|
|
103
|
-
|
|
104
|
-
// We need to store the current value as well to be able to highlight it
|
|
105
|
-
const [currentValue, setCurrentValue] = useState(value ?? initialValue)
|
|
106
|
-
|
|
107
|
-
const inputTokens = { paddingLeft: INPUT_LEFT_PADDING }
|
|
108
|
-
|
|
109
|
-
// Setting up the overlay
|
|
110
|
-
const openOverlayRef = useRef()
|
|
111
|
-
const [isExpanded, setIsExpanded] = useState((value ?? initialValue)?.length >= minToSuggestion)
|
|
112
|
-
const {
|
|
113
|
-
overlaidPosition,
|
|
114
|
-
sourceRef: inputRef,
|
|
115
|
-
targetRef,
|
|
116
|
-
onTargetLayout,
|
|
117
|
-
isReady
|
|
118
|
-
} = useOverlaidPosition({
|
|
119
|
-
isShown: isExpanded,
|
|
120
|
-
offsets: { vertical: 4 }
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
// We limit the number of suggestions displayed to avoid huge lists
|
|
124
|
-
// TODO: add a way to make the `Listbox` occupy fixed height and be scrollable
|
|
125
|
-
// within that height, which will unlock similar behaviour for `AutoComplete` as well
|
|
126
|
-
const itemsToSuggest = (data = []) =>
|
|
127
|
-
maxSuggestions ? data.slice(0, maxSuggestions) : [...data]
|
|
128
|
-
|
|
129
|
-
const getCopy = useCopy({ dictionary, copy })
|
|
130
|
-
|
|
131
|
-
// Tracking input width changes to resize the listbox overlay accordingly
|
|
132
|
-
const [inputWidth, setInputWidth] = useState()
|
|
133
|
-
useSafeLayoutEffect(() => {
|
|
134
|
-
const updateInputWidth = () => {
|
|
135
|
-
setInputWidth(inputRef?.current?.clientWidth + 4) // adding back all the input borders / outlines
|
|
136
|
-
setIsExpanded(false) // close the suggestions while the input is changing
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const throttledUpdateInputWidth = throttle(updateInputWidth, 100, { leading: false })
|
|
140
|
-
|
|
141
|
-
updateInputWidth()
|
|
142
|
-
|
|
143
|
-
window.addEventListener('load', updateInputWidth)
|
|
144
|
-
window.addEventListener('resize', throttledUpdateInputWidth)
|
|
145
|
-
|
|
146
|
-
return () => {
|
|
147
|
-
window.removeEventListener('load', updateInputWidth)
|
|
148
|
-
window.removeEventListener('resize', throttledUpdateInputWidth)
|
|
149
|
-
}
|
|
150
|
-
}, [inputRef])
|
|
151
|
-
|
|
152
|
-
const handleChange = (newValue) => {
|
|
153
|
-
onChange?.(newValue)
|
|
154
|
-
setCurrentValue(newValue)
|
|
155
|
-
setIsExpanded(newValue?.length >= minToSuggestion)
|
|
156
|
-
if (!isControlled && initialItems !== undefined) {
|
|
157
|
-
setCurrentItems(
|
|
158
|
-
initialItems.filter(({ label }) =>
|
|
159
|
-
label?.toLowerCase()?.includes(newValue?.toLowerCase())
|
|
160
|
-
)
|
|
161
|
-
)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
const handleSelect = (selectedId) => {
|
|
165
|
-
onSelect?.(selectedId)
|
|
166
|
-
const { label: newValue } = (isControlled ? items : currentItems)?.find(
|
|
167
|
-
({ id }) => id === selectedId
|
|
168
|
-
)
|
|
169
|
-
onChange?.(newValue)
|
|
170
|
-
setCurrentValue(newValue)
|
|
171
|
-
if (!isControlled && inputRef?.current) inputRef.current.value = newValue
|
|
172
|
-
setIsExpanded(false)
|
|
173
|
-
}
|
|
174
|
-
const handleClose = (event) => {
|
|
175
|
-
if (event.type === 'keydown') {
|
|
176
|
-
if (event.key === 'Escape' || event.key === 27) {
|
|
177
|
-
setIsExpanded(false)
|
|
178
|
-
} else if (event.key === 'ArrowDown' && isExpanded && !isLoading && targetRef?.current) {
|
|
179
|
-
targetRef.current.focus()
|
|
180
|
-
}
|
|
181
|
-
} else if (
|
|
182
|
-
event.type === 'click' &&
|
|
183
|
-
openOverlayRef?.current &&
|
|
184
|
-
event.target &&
|
|
185
|
-
!openOverlayRef?.current?.contains(event.target)
|
|
186
|
-
) {
|
|
187
|
-
setIsExpanded(false)
|
|
188
|
-
} else if (
|
|
189
|
-
event.type === 'touchstart' &&
|
|
190
|
-
openOverlayRef?.current &&
|
|
191
|
-
event.touches[0].target &&
|
|
192
|
-
!openOverlayRef?.current?.contains(event.touches[0].target)
|
|
193
|
-
) {
|
|
194
|
-
setIsExpanded(false)
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const { supportsProps, ...selectedProps } = selectProps(rest)
|
|
199
|
-
|
|
200
|
-
return (
|
|
201
|
-
<>
|
|
202
|
-
<InputSupports
|
|
203
|
-
{...supportsProps}
|
|
204
|
-
accessibilityAutoComplete="list"
|
|
205
|
-
accessibilityControls="autocomplete"
|
|
206
|
-
accessibilityExpanded={isExpanded}
|
|
207
|
-
accessibilityRole="combobox"
|
|
208
|
-
{...selectedProps}
|
|
209
|
-
validation={validation}
|
|
210
|
-
ref={ref}
|
|
211
|
-
>
|
|
212
|
-
{({ inputId, ...props }) => {
|
|
213
|
-
if (typeof children === 'function')
|
|
214
|
-
return children({
|
|
215
|
-
inputId,
|
|
216
|
-
inputRef,
|
|
217
|
-
onChange: handleChange,
|
|
218
|
-
onKeyPress: handleClose,
|
|
219
|
-
readOnly,
|
|
220
|
-
tokens: inputTokens,
|
|
221
|
-
...selectedProps,
|
|
222
|
-
...props,
|
|
223
|
-
...(isControlled ? { value } : { initialValue })
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
return (
|
|
227
|
-
<TextInput
|
|
228
|
-
onChange={handleChange}
|
|
229
|
-
onClear={onClear}
|
|
230
|
-
onKeyPress={handleClose}
|
|
231
|
-
readOnly={readOnly}
|
|
232
|
-
ref={inputRef}
|
|
233
|
-
tokens={inputTokens}
|
|
234
|
-
validation={validation}
|
|
235
|
-
{...selectedProps}
|
|
236
|
-
{...props}
|
|
237
|
-
{...(isControlled ? { value } : { initialValue })}
|
|
238
|
-
/>
|
|
239
|
-
)
|
|
240
|
-
}}
|
|
241
|
-
</InputSupports>
|
|
242
|
-
{isExpanded && (
|
|
243
|
-
<>
|
|
244
|
-
<Listbox.Overlay
|
|
245
|
-
overlaidPosition={overlaidPosition}
|
|
246
|
-
isReady={isReady}
|
|
247
|
-
minWidth={fullWidth ? inputWidth : MIN_LISTBOX_WIDTH}
|
|
248
|
-
maxWidth={inputWidth}
|
|
249
|
-
onLayout={onTargetLayout}
|
|
250
|
-
ref={openOverlayRef}
|
|
251
|
-
>
|
|
252
|
-
{isLoading ? (
|
|
253
|
-
<Loading label={getCopy('loading')} />
|
|
254
|
-
) : (
|
|
255
|
-
<Suggestions
|
|
256
|
-
hasResults={getCopy('hasResults')}
|
|
257
|
-
id="autocomplete"
|
|
258
|
-
items={itemsToSuggest(
|
|
259
|
-
highlight(isControlled ? items : currentItems, currentValue, resultsTextColor)
|
|
260
|
-
)}
|
|
261
|
-
noResults={noResults ?? getCopy('noResults')}
|
|
262
|
-
onClose={handleClose}
|
|
263
|
-
onSelect={handleSelect}
|
|
264
|
-
parentRef={inputRef}
|
|
265
|
-
ref={targetRef}
|
|
266
|
-
/>
|
|
267
|
-
)}
|
|
268
|
-
</Listbox.Overlay>
|
|
269
|
-
{targetRef?.current && (
|
|
270
|
-
<div
|
|
271
|
-
// This catches and shifts focus to other interactive elements.
|
|
272
|
-
onFocus={() => targetRef?.current?.focus()}
|
|
273
|
-
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
274
|
-
tabIndex={0}
|
|
275
|
-
/>
|
|
276
|
-
)}
|
|
277
|
-
</>
|
|
278
|
-
)}
|
|
279
|
-
</>
|
|
280
|
-
)
|
|
281
|
-
}
|
|
282
|
-
)
|
|
283
|
-
Autocomplete.displayName = 'Autocomplete'
|
|
284
|
-
|
|
285
|
-
// If a language dictionary entry is provided, it must contain every key
|
|
286
|
-
const dictionaryContentShape = PropTypes.shape({
|
|
287
|
-
hasResults: PropTypes.string.isRequired,
|
|
288
|
-
loading: PropTypes.string.isRequired,
|
|
289
|
-
noResults: PropTypes.string.isRequired
|
|
290
|
-
})
|
|
291
|
-
|
|
292
|
-
Autocomplete.propTypes = {
|
|
293
|
-
...selectedSystemPropTypes,
|
|
294
|
-
/**
|
|
295
|
-
* Can be used to provide a function that renders a custom input:
|
|
296
|
-
* <Autocomplete items={items} value={currentValue}>
|
|
297
|
-
* {({ inputId, inputRef, onChange, onKeyPress, readOnly, tokens, value }) => (
|
|
298
|
-
* <Search
|
|
299
|
-
* nativeID={inputId}
|
|
300
|
-
* ref={inputRef}
|
|
301
|
-
* onChange={onChange}
|
|
302
|
-
* onKeyPress={onKeyPress}
|
|
303
|
-
* readOnly={readOnly}
|
|
304
|
-
* tokens={tokens}
|
|
305
|
-
* value={value}
|
|
306
|
-
* />
|
|
307
|
-
* )}
|
|
308
|
-
* </Autocomplete>
|
|
309
|
-
*/
|
|
310
|
-
children: PropTypes.func,
|
|
311
|
-
/**
|
|
312
|
-
* Copy language identifier
|
|
313
|
-
*/
|
|
314
|
-
copy: PropTypes.oneOfType([PropTypes.oneOf(['en', 'fr']), dictionaryContentShape]),
|
|
315
|
-
/**
|
|
316
|
-
* Set to true in order to display the loading indicator instead of results
|
|
317
|
-
*/
|
|
318
|
-
isLoading: PropTypes.bool,
|
|
319
|
-
/**
|
|
320
|
-
* List of items to display as suggestions
|
|
321
|
-
*/
|
|
322
|
-
items: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string, label: PropTypes.string })),
|
|
323
|
-
/**
|
|
324
|
-
* Label to display alongside the spinner when in a loading state
|
|
325
|
-
*/
|
|
326
|
-
loadingLabel: PropTypes.string,
|
|
327
|
-
/**
|
|
328
|
-
* Minimum number of characters typed for a list of suggestions to appear
|
|
329
|
-
*/
|
|
330
|
-
minToSuggestion: PropTypes.number,
|
|
331
|
-
/**
|
|
332
|
-
* Maximum number of suggestions provided at the same time
|
|
333
|
-
*/
|
|
334
|
-
maxSuggestions: PropTypes.number,
|
|
335
|
-
/**
|
|
336
|
-
* Text or JSX to render when no results are available
|
|
337
|
-
*/
|
|
338
|
-
noResults: PropTypes.node,
|
|
339
|
-
/**
|
|
340
|
-
* Handler function to be called when the input value changes
|
|
341
|
-
*/
|
|
342
|
-
onChange: PropTypes.func,
|
|
343
|
-
/**
|
|
344
|
-
* Handler function to be called when the clear button (appears if the handler is passed) is pressed
|
|
345
|
-
*/
|
|
346
|
-
onClear: PropTypes.func,
|
|
347
|
-
/**
|
|
348
|
-
* Callback function to be called when an item is selected from the list
|
|
349
|
-
*/
|
|
350
|
-
onSelect: PropTypes.func,
|
|
351
|
-
/**
|
|
352
|
-
* Input value for controlled usage
|
|
353
|
-
*/
|
|
354
|
-
value: PropTypes.string
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export default Autocomplete
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import { Box, StackView } from '@telus-uds/components-base'
|
|
4
|
-
import Spinner from '../Spinner'
|
|
5
|
-
|
|
6
|
-
const Loading = ({ label }) => (
|
|
7
|
-
<Box space={3}>
|
|
8
|
-
<StackView direction="row" space={2} tokens={{ alignItems: 'center' }}>
|
|
9
|
-
<Spinner inline={true} show={true} label={label} labelPosition="right" />
|
|
10
|
-
</StackView>
|
|
11
|
-
</Box>
|
|
12
|
-
)
|
|
13
|
-
Loading.propTypes = { label: PropTypes.string }
|
|
14
|
-
|
|
15
|
-
export default Loading
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import React, { forwardRef } from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import { A11yText, Box, Typography } from '@telus-uds/components-base'
|
|
4
|
-
import Listbox from '../Listbox'
|
|
5
|
-
|
|
6
|
-
const Suggestions = forwardRef(
|
|
7
|
-
({ hasResults, items = [], noResults, onClose, onSelect, parentRef }, ref) => {
|
|
8
|
-
const pressableItems = items.map(({ id, ...rest }) => ({
|
|
9
|
-
id,
|
|
10
|
-
onPress: () => onSelect(id),
|
|
11
|
-
...rest
|
|
12
|
-
}))
|
|
13
|
-
if (items?.length)
|
|
14
|
-
return (
|
|
15
|
-
<>
|
|
16
|
-
<A11yText accessibilityLiveRegion="polite" text={hasResults} />
|
|
17
|
-
<Listbox
|
|
18
|
-
items={pressableItems}
|
|
19
|
-
firstItemRef={ref}
|
|
20
|
-
parentRef={parentRef}
|
|
21
|
-
onClose={onClose}
|
|
22
|
-
/>
|
|
23
|
-
</>
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<Box space={3}>
|
|
28
|
-
{typeof noResults === 'string' ? (
|
|
29
|
-
<>
|
|
30
|
-
<Typography accessibilityLiveRegion="polite" variant={{ size: 'small' }}>
|
|
31
|
-
{noResults}
|
|
32
|
-
</Typography>
|
|
33
|
-
</>
|
|
34
|
-
) : (
|
|
35
|
-
noResults
|
|
36
|
-
)}
|
|
37
|
-
</Box>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
)
|
|
41
|
-
Suggestions.displayName = 'Suggestions'
|
|
42
|
-
Suggestions.propTypes = {
|
|
43
|
-
hasResults: PropTypes.string.isRequired,
|
|
44
|
-
items: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string, label: PropTypes.node }))
|
|
45
|
-
.isRequired,
|
|
46
|
-
noResults: PropTypes.node.isRequired,
|
|
47
|
-
onClose: PropTypes.func.isRequired,
|
|
48
|
-
onSelect: PropTypes.func.isRequired,
|
|
49
|
-
parentRef: PropTypes.object.isRequired
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default Suggestions
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
en: {
|
|
3
|
-
hasResults: 'Some results are available',
|
|
4
|
-
loading: 'Searching...',
|
|
5
|
-
noResults: 'No results found'
|
|
6
|
-
},
|
|
7
|
-
fr: {
|
|
8
|
-
hasResults: 'Quelques suggestions sont disponible',
|
|
9
|
-
loading: 'Recherche...',
|
|
10
|
-
noResults: 'Aucun résultat trouvé'
|
|
11
|
-
}
|
|
12
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import styled from 'styled-components'
|
|
3
|
-
import PropTypes from 'prop-types'
|
|
4
|
-
import { Icon, Spacer, useThemeTokens, useListboxContext } from '@telus-uds/components-base'
|
|
5
|
-
|
|
6
|
-
const StyledControlWrapper = styled.div(
|
|
7
|
-
({
|
|
8
|
-
groupFontName,
|
|
9
|
-
groupFontWeight,
|
|
10
|
-
groupFontSize,
|
|
11
|
-
groupColor,
|
|
12
|
-
groupBackgroundColor,
|
|
13
|
-
groupBorderColor,
|
|
14
|
-
groupBorderWidth,
|
|
15
|
-
groupBorderRadius,
|
|
16
|
-
groupPaddingLeft,
|
|
17
|
-
groupPaddingRight,
|
|
18
|
-
groupPaddingTop,
|
|
19
|
-
groupPaddingBottom,
|
|
20
|
-
itemTextDecoration,
|
|
21
|
-
itemOutline,
|
|
22
|
-
groupHeight
|
|
23
|
-
}) => ({
|
|
24
|
-
fontFamily: `${groupFontName}${groupFontWeight}normal`,
|
|
25
|
-
fontSize: groupFontSize,
|
|
26
|
-
color: groupColor,
|
|
27
|
-
textDecoration: itemTextDecoration,
|
|
28
|
-
backgroundColor: groupBackgroundColor,
|
|
29
|
-
outline: itemOutline,
|
|
30
|
-
width: '100%',
|
|
31
|
-
height: groupHeight,
|
|
32
|
-
display: 'flex',
|
|
33
|
-
alignItems: 'center',
|
|
34
|
-
justifyContent: 'space-between',
|
|
35
|
-
boxSizing: 'border-box',
|
|
36
|
-
border: `${groupBorderWidth}px solid ${groupBorderColor}`,
|
|
37
|
-
borderRadius: groupBorderRadius,
|
|
38
|
-
paddingLeft: groupPaddingLeft - groupBorderWidth,
|
|
39
|
-
paddingRight: groupPaddingRight - groupBorderWidth,
|
|
40
|
-
paddingTop: groupPaddingTop - groupBorderWidth,
|
|
41
|
-
paddingBottom: groupPaddingBottom - groupBorderWidth
|
|
42
|
-
})
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
const GroupControl = ({ expanded, pressed, hover, focus, label, id }) => {
|
|
46
|
-
const { selectedId, setSelectedId } = useListboxContext()
|
|
47
|
-
const tokens = useThemeTokens(
|
|
48
|
-
'Listbox',
|
|
49
|
-
{},
|
|
50
|
-
{},
|
|
51
|
-
{
|
|
52
|
-
expanded,
|
|
53
|
-
pressed,
|
|
54
|
-
hover,
|
|
55
|
-
current: selectedId === id && id !== undefined,
|
|
56
|
-
focus
|
|
57
|
-
}
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<StyledControlWrapper onClick={() => setSelectedId(id)} {...tokens}>
|
|
62
|
-
{label}
|
|
63
|
-
<Spacer space={1} direction="row" />
|
|
64
|
-
<Icon
|
|
65
|
-
icon={tokens.groupIcon}
|
|
66
|
-
tokens={{ color: tokens.groupColor }}
|
|
67
|
-
variant={{ size: 'micro' }}
|
|
68
|
-
/>
|
|
69
|
-
</StyledControlWrapper>
|
|
70
|
-
)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
GroupControl.propTypes = {
|
|
74
|
-
id: PropTypes.string,
|
|
75
|
-
expanded: PropTypes.bool,
|
|
76
|
-
pressed: PropTypes.bool,
|
|
77
|
-
hover: PropTypes.bool,
|
|
78
|
-
focus: PropTypes.bool,
|
|
79
|
-
label: PropTypes.string
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export default GroupControl
|
package/src/Listbox/Listbox.jsx
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import PropTypes from 'prop-types'
|
|
3
|
-
import styled from 'styled-components'
|
|
4
|
-
import {
|
|
5
|
-
ExpandCollapse,
|
|
6
|
-
useThemeTokens,
|
|
7
|
-
withLinkRouter,
|
|
8
|
-
ListboxContext
|
|
9
|
-
} from '@telus-uds/components-base'
|
|
10
|
-
import ListboxGroup from './ListboxGroup'
|
|
11
|
-
import ListboxItem from './ListboxItem'
|
|
12
|
-
import DropdownOverlay from './ListboxOverlay'
|
|
13
|
-
|
|
14
|
-
const StyledList = styled.ul({
|
|
15
|
-
margin: 0,
|
|
16
|
-
padding: 0,
|
|
17
|
-
listStyle: 'none'
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const getInitialOpen = (items, selectedId) =>
|
|
21
|
-
items
|
|
22
|
-
.filter(
|
|
23
|
-
(item) =>
|
|
24
|
-
item.items &&
|
|
25
|
-
item.items.some((nestedItem) => (nestedItem.id ?? nestedItem.label) === selectedId)
|
|
26
|
-
)
|
|
27
|
-
.map((item) => item.id ?? item.label)
|
|
28
|
-
|
|
29
|
-
const Listbox = ({
|
|
30
|
-
items = [],
|
|
31
|
-
firstItemRef = null, // focus will be moved to this one once within the menu
|
|
32
|
-
parentRef = null, // to return focus to after leaving the last menu item
|
|
33
|
-
selectedId: defaultSelectedId,
|
|
34
|
-
LinkRouter,
|
|
35
|
-
itemRouterProps,
|
|
36
|
-
onClose,
|
|
37
|
-
variant,
|
|
38
|
-
tokens
|
|
39
|
-
}) => {
|
|
40
|
-
const initialOpen = getInitialOpen(items, defaultSelectedId)
|
|
41
|
-
|
|
42
|
-
const [selectedId, setSelectedId] = useState(defaultSelectedId)
|
|
43
|
-
|
|
44
|
-
const { minHeight, minWidth } = useThemeTokens('Listbox', variant, tokens)
|
|
45
|
-
|
|
46
|
-
// We need to keep track of each item's ref in order to be able to
|
|
47
|
-
// focus on a specific item via keyboard navigation
|
|
48
|
-
const itemRefs = useRef([])
|
|
49
|
-
if (firstItemRef?.current) itemRefs.current[0] = firstItemRef.current
|
|
50
|
-
const [focusedIndex, setFocusedIndex] = useState(0)
|
|
51
|
-
const handleKeydown = useCallback(
|
|
52
|
-
(event) => {
|
|
53
|
-
const nextItemRef = itemRefs.current[focusedIndex + 1]
|
|
54
|
-
const prevItemRef = itemRefs.current[focusedIndex - 1]
|
|
55
|
-
if (event.key === 'ArrowUp' || (event.shiftKey && event.key === 'Tab')) {
|
|
56
|
-
// Move the focus to the previous item or to the parent one if on the first
|
|
57
|
-
if (prevItemRef) {
|
|
58
|
-
event.preventDefault()
|
|
59
|
-
prevItemRef.focus()
|
|
60
|
-
} else if (parentRef) parentRef.current?.focus()
|
|
61
|
-
setFocusedIndex(focusedIndex - 1)
|
|
62
|
-
} else if ((event.key === 'ArrowDown' || event.key === 'Tab') && nextItemRef) {
|
|
63
|
-
event.preventDefault()
|
|
64
|
-
setFocusedIndex(focusedIndex + 1)
|
|
65
|
-
nextItemRef.focus()
|
|
66
|
-
} else if (event.key === 'Escape') {
|
|
67
|
-
// Close the dropdown
|
|
68
|
-
parentRef?.current?.click()
|
|
69
|
-
// Return focus to the dropdown control after leaving the last item
|
|
70
|
-
parentRef?.current?.focus()
|
|
71
|
-
if (onClose) onClose(event)
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
[focusedIndex, onClose, parentRef]
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
// Add listeners for mouse clicks outside and for key presses
|
|
78
|
-
useEffect(() => {
|
|
79
|
-
window.addEventListener('click', onClose)
|
|
80
|
-
window.addEventListener('keydown', handleKeydown)
|
|
81
|
-
window.addEventListener('touchstart', onClose)
|
|
82
|
-
|
|
83
|
-
return () => {
|
|
84
|
-
window.removeEventListener('click', onClose)
|
|
85
|
-
window.removeEventListener('keydown', handleKeydown)
|
|
86
|
-
window.removeEventListener('touchstart', onClose)
|
|
87
|
-
}
|
|
88
|
-
}, [onClose, handleKeydown])
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<ListboxContext.Provider value={{ selectedId, setSelectedId }}>
|
|
92
|
-
<ExpandCollapse initialOpen={initialOpen} maxOpen={1}>
|
|
93
|
-
{(expandProps) => (
|
|
94
|
-
<StyledList role="listbox" style={{ minHeight, minWidth }}>
|
|
95
|
-
{items.map((item, index) => {
|
|
96
|
-
const { id, label, items: nestedItems } = item
|
|
97
|
-
const itemId = id ?? label
|
|
98
|
-
|
|
99
|
-
// Give `firstItemRef` to the first focusable item
|
|
100
|
-
const itemRef =
|
|
101
|
-
(index === 0 && !itemId !== selectedId) ||
|
|
102
|
-
(index === 1 && items[0].id === selectedId)
|
|
103
|
-
? firstItemRef
|
|
104
|
-
: (ref) => {
|
|
105
|
-
itemRefs.current[index] = ref
|
|
106
|
-
return ref
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return nestedItems ? (
|
|
110
|
-
<ListboxGroup
|
|
111
|
-
{...item}
|
|
112
|
-
expandProps={expandProps}
|
|
113
|
-
LinkRouter={LinkRouter}
|
|
114
|
-
itemRouterProps={itemRouterProps}
|
|
115
|
-
prevItemRef={itemRefs.current[index - 1] ?? null}
|
|
116
|
-
nextItemRef={itemRefs.current[index + 1] ?? null}
|
|
117
|
-
ref={itemRef}
|
|
118
|
-
key={itemId}
|
|
119
|
-
/>
|
|
120
|
-
) : (
|
|
121
|
-
<ListboxItem
|
|
122
|
-
{...item}
|
|
123
|
-
key={itemId}
|
|
124
|
-
id={itemId}
|
|
125
|
-
LinkRouter={LinkRouter}
|
|
126
|
-
itemRouterProps={itemRouterProps}
|
|
127
|
-
prevItemRef={itemRefs.current[index - 1] ?? null}
|
|
128
|
-
nextItemRef={itemRefs.current[index + 1] ?? null}
|
|
129
|
-
ref={itemRef}
|
|
130
|
-
/>
|
|
131
|
-
)
|
|
132
|
-
})}
|
|
133
|
-
</StyledList>
|
|
134
|
-
)}
|
|
135
|
-
</ExpandCollapse>
|
|
136
|
-
</ListboxContext.Provider>
|
|
137
|
-
)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
Listbox.propTypes = {
|
|
141
|
-
...withLinkRouter.propTypes,
|
|
142
|
-
/**
|
|
143
|
-
* Focus will be moved to the item with this ref once within the menu.
|
|
144
|
-
*/
|
|
145
|
-
firstItemRef: PropTypes.object,
|
|
146
|
-
/**
|
|
147
|
-
* Focus will be returned to the dropdown control with this ref after leaving
|
|
148
|
-
* the last menu item.
|
|
149
|
-
*/
|
|
150
|
-
parentRef: PropTypes.object,
|
|
151
|
-
/**
|
|
152
|
-
* `Listbox` items
|
|
153
|
-
*/
|
|
154
|
-
items: PropTypes.array,
|
|
155
|
-
/**
|
|
156
|
-
* To select an item by default
|
|
157
|
-
*/
|
|
158
|
-
selectedId: PropTypes.string
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
Listbox.Overlay = DropdownOverlay
|
|
162
|
-
|
|
163
|
-
export default Listbox
|