@telus-uds/components-base 1.93.0 → 1.95.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 (101) hide show
  1. package/CHANGELOG.md +32 -3
  2. package/lib/Autocomplete/Autocomplete.js +2 -1
  3. package/lib/Button/ButtonGroup.js +17 -1
  4. package/lib/Card/Card.js +12 -0
  5. package/lib/Card/CardBase.js +37 -2
  6. package/lib/Carousel/Carousel.js +55 -13
  7. package/lib/Carousel/CarouselItem/CarouselItem.js +86 -12
  8. package/lib/DownloadApp/DownloadApp.js +168 -0
  9. package/lib/DownloadApp/dictionary.js +17 -0
  10. package/lib/DownloadApp/index.js +10 -0
  11. package/lib/Fieldset/FieldsetContainer.js +7 -2
  12. package/lib/Fieldset/FieldsetContainer.native.js +4 -1
  13. package/lib/FileUpload/FileUpload.js +336 -0
  14. package/lib/FileUpload/NotificationContent.js +60 -0
  15. package/lib/FileUpload/dictionary.js +47 -0
  16. package/lib/FileUpload/index.js +10 -0
  17. package/lib/Icon/IconText.js +19 -2
  18. package/lib/Link/LinkBase.js +2 -2
  19. package/lib/Link/TextButton.js +7 -17
  20. package/lib/Modal/Modal.js +1 -1
  21. package/lib/Modal/ModalContent.js +12 -6
  22. package/lib/MultiSelectFilter/ModalOverlay.js +49 -30
  23. package/lib/MultiSelectFilter/MultiSelectFilter.js +170 -131
  24. package/lib/Notification/Notification.js +11 -2
  25. package/lib/Status/Status.js +9 -4
  26. package/lib/TabBar/TabBar.js +133 -0
  27. package/lib/TabBar/TabBarItem.js +184 -0
  28. package/lib/TabBar/index.js +10 -0
  29. package/lib/TextInput/TextInputBase.js +2 -1
  30. package/lib/Tooltip/getTooltipPosition.js +8 -9
  31. package/lib/index.js +24 -0
  32. package/lib/utils/convertFromMegaByteToByte.js +16 -0
  33. package/lib/utils/formatImageSource.js +34 -0
  34. package/lib/utils/index.js +17 -1
  35. package/lib-module/Autocomplete/Autocomplete.js +2 -1
  36. package/lib-module/Button/ButtonGroup.js +17 -1
  37. package/lib-module/Card/Card.js +13 -1
  38. package/lib-module/Card/CardBase.js +38 -3
  39. package/lib-module/Carousel/Carousel.js +55 -13
  40. package/lib-module/Carousel/CarouselItem/CarouselItem.js +86 -12
  41. package/lib-module/DownloadApp/DownloadApp.js +160 -0
  42. package/lib-module/DownloadApp/dictionary.js +10 -0
  43. package/lib-module/DownloadApp/index.js +2 -0
  44. package/lib-module/Fieldset/FieldsetContainer.js +7 -2
  45. package/lib-module/Fieldset/FieldsetContainer.native.js +4 -1
  46. package/lib-module/FileUpload/FileUpload.js +329 -0
  47. package/lib-module/FileUpload/NotificationContent.js +55 -0
  48. package/lib-module/FileUpload/dictionary.js +40 -0
  49. package/lib-module/FileUpload/index.js +2 -0
  50. package/lib-module/Icon/IconText.js +19 -2
  51. package/lib-module/Link/LinkBase.js +2 -2
  52. package/lib-module/Link/TextButton.js +7 -17
  53. package/lib-module/Modal/Modal.js +1 -1
  54. package/lib-module/Modal/ModalContent.js +12 -6
  55. package/lib-module/MultiSelectFilter/ModalOverlay.js +49 -30
  56. package/lib-module/MultiSelectFilter/MultiSelectFilter.js +171 -132
  57. package/lib-module/Notification/Notification.js +11 -2
  58. package/lib-module/Status/Status.js +9 -4
  59. package/lib-module/TabBar/TabBar.js +125 -0
  60. package/lib-module/TabBar/TabBarItem.js +177 -0
  61. package/lib-module/TabBar/index.js +2 -0
  62. package/lib-module/TextInput/TextInputBase.js +2 -1
  63. package/lib-module/Tooltip/getTooltipPosition.js +8 -9
  64. package/lib-module/index.js +3 -0
  65. package/lib-module/utils/convertFromMegaByteToByte.js +10 -0
  66. package/lib-module/utils/formatImageSource.js +27 -0
  67. package/lib-module/utils/index.js +3 -1
  68. package/package.json +4 -3
  69. package/src/Autocomplete/Autocomplete.jsx +2 -1
  70. package/src/Button/ButtonGroup.jsx +9 -1
  71. package/src/Card/Card.jsx +18 -2
  72. package/src/Card/CardBase.jsx +47 -12
  73. package/src/Carousel/Carousel.jsx +59 -13
  74. package/src/Carousel/CarouselItem/CarouselItem.jsx +94 -9
  75. package/src/DownloadApp/DownloadApp.jsx +165 -0
  76. package/src/DownloadApp/dictionary.js +10 -0
  77. package/src/DownloadApp/index.js +3 -0
  78. package/src/Fieldset/FieldsetContainer.jsx +4 -3
  79. package/src/Fieldset/FieldsetContainer.native.jsx +9 -6
  80. package/src/FileUpload/FileUpload.jsx +396 -0
  81. package/src/FileUpload/NotificationContent.jsx +44 -0
  82. package/src/FileUpload/dictionary.js +40 -0
  83. package/src/FileUpload/index.js +3 -0
  84. package/src/Icon/IconText.jsx +21 -4
  85. package/src/Link/LinkBase.jsx +2 -2
  86. package/src/Link/TextButton.jsx +10 -17
  87. package/src/Modal/Modal.jsx +1 -1
  88. package/src/Modal/ModalContent.jsx +8 -3
  89. package/src/MultiSelectFilter/ModalOverlay.jsx +44 -18
  90. package/src/MultiSelectFilter/MultiSelectFilter.jsx +188 -126
  91. package/src/Notification/Notification.jsx +12 -4
  92. package/src/Status/Status.jsx +15 -4
  93. package/src/TabBar/TabBar.jsx +123 -0
  94. package/src/TabBar/TabBarItem.jsx +149 -0
  95. package/src/TabBar/index.js +3 -0
  96. package/src/TextInput/TextInputBase.jsx +1 -1
  97. package/src/Tooltip/getTooltipPosition.js +11 -12
  98. package/src/index.js +3 -0
  99. package/src/utils/convertFromMegaByteToByte.js +11 -0
  100. package/src/utils/formatImageSource.js +29 -0
  101. package/src/utils/index.js +2 -0
@@ -0,0 +1,396 @@
1
+ import React from 'react'
2
+ import { View, Platform } from 'react-native'
3
+ import PropTypes from 'prop-types'
4
+
5
+ import { useThemeTokens } from '../ThemeProvider'
6
+ import {
7
+ a11yProps,
8
+ copyPropTypes,
9
+ getTokensPropType,
10
+ selectSystemProps,
11
+ useCopy,
12
+ variantProp,
13
+ viewProps,
14
+ convertFromMegaByteToByte
15
+ } from '../utils'
16
+
17
+ import InputLabel from '../InputLabel'
18
+ import { Button } from '../Button'
19
+ import Notification from '../Notification'
20
+ import Spacer from '../Spacer'
21
+ import StackView from '../StackView'
22
+
23
+ import NotificationContent from './NotificationContent'
24
+ import dictionary from './dictionary'
25
+
26
+ const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
27
+
28
+ const getHintFromFileTypes = (fileTypes) => {
29
+ const acceptedFileTypes = fileTypes.map((type) => type.toUpperCase())
30
+
31
+ return acceptedFileTypes.reduce((acc, curr, index) => {
32
+ if (index === 0) {
33
+ return `${curr}`
34
+ }
35
+ return `${acc}, ${curr}`
36
+ }, '')
37
+ }
38
+
39
+ const stringifyFileTypesList = (fileTypes, getCopy) => {
40
+ const acceptedFileTypes = fileTypes.map((type) => `.${type.toUpperCase()}`)
41
+
42
+ return acceptedFileTypes.reduce((acc, curr, index) => {
43
+ if (index === 0) {
44
+ return curr
45
+ }
46
+ if (index === acceptedFileTypes.length - 1) {
47
+ return `${acc} ${getCopy('or')} ${curr}`
48
+ }
49
+ return `${acc}, ${curr}`
50
+ }, '')
51
+ }
52
+
53
+ const selectComponentTokens = (tokens, componentIdentifier) => {
54
+ return Object.entries(tokens).reduce((acc, [key, value]) => {
55
+ if (key.startsWith(componentIdentifier) && value !== null) {
56
+ const newKey =
57
+ key.replace(componentIdentifier, '').replace('Text', '').charAt(0).toLowerCase() +
58
+ key.replace(componentIdentifier, '').replace('Text', '').slice(1)
59
+ acc[newKey] = value
60
+ }
61
+ return acc
62
+ }, {})
63
+ }
64
+
65
+ /**
66
+ * FileUpload component for uploading files.
67
+ *
68
+ * @component
69
+ * @example
70
+ * // Usage:
71
+ * <FileUpload
72
+ * tokens={tokens}
73
+ * variant={variant}
74
+ * copy={copy}
75
+ * fileTypes={fileTypes}
76
+ * allowMultipleFiles={allowMultipleFiles}
77
+ * maxFileSize={maxFileSize}
78
+ * maxFilesCount={maxFilesCount}
79
+ * onUpload={onUpload}
80
+ * onDelete={onDelete}
81
+ * documentPicker={documentPicker}
82
+ * {...rest}
83
+ * />
84
+ *
85
+ * @param {Object} props - The component props.
86
+ * @param {Object} props.tokens - The tokens for styling the component.
87
+ * @param {string} props.variant - The variant of the component.
88
+ * @param {string} props.copy - The copy for the component.
89
+ * @param {string[]} props.fileTypes - The allowed file types for upload.
90
+ * @param {boolean} props.allowMultipleFiles - Whether multiple files can be uploaded.
91
+ * @param {number} props.maxFileSize - The maximum file size in megabytes.
92
+ * @param {number} props.maxFilesCount - The maximum number of files that can be uploaded.
93
+ * @param {Function} props.onUpload - The callback function for file upload.
94
+ * @param {Function} props.onDelete - The callback function for file deletion.
95
+ * @param {Object} props.documentPicker - The document picker object.
96
+ * @param {any} props.rest - Additional props to be spread to the component.
97
+ * @returns {JSX.Element} The rendered FileUpload component.
98
+ */
99
+ const FileUpload = React.forwardRef(
100
+ (
101
+ {
102
+ tokens,
103
+ variant,
104
+ copy = 'en',
105
+ fileTypes,
106
+ allowMultipleFiles = false,
107
+ maxFileSize,
108
+ maxFilesCount = 1,
109
+ onUpload,
110
+ onDelete,
111
+ documentPicker,
112
+ ...rest
113
+ },
114
+ ref
115
+ ) => {
116
+ const themeTokens = useThemeTokens('FileUpload', tokens, variant)
117
+ const getCopy = useCopy({ dictionary, copy })
118
+
119
+ const inputFileRef = React.useRef(null)
120
+
121
+ const [filesStatus, setFilesStatus] = React.useState([])
122
+ const [renderTrigger, setRenderTrigger] = React.useState(0)
123
+
124
+ React.useEffect(() => {
125
+ setRenderTrigger((prev) => prev + 1)
126
+ }, [filesStatus])
127
+
128
+ const areFileTypesExtensionsValid = (files) =>
129
+ files.some((file) => {
130
+ const fileExtension = file.name.split('.').pop().toLowerCase()
131
+
132
+ if (!fileTypes.includes(fileExtension)) {
133
+ setFilesStatus([
134
+ {
135
+ description: getCopy('wrongFileType').replace(
136
+ '%{fileType}',
137
+ stringifyFileTypesList(fileTypes, getCopy)
138
+ ),
139
+ style: 'error'
140
+ }
141
+ ])
142
+ return false
143
+ }
144
+ return true
145
+ })
146
+
147
+ const areFileSizesWithinLimit = (files) =>
148
+ files.some((file) => {
149
+ if (file.size > convertFromMegaByteToByte(maxFileSize)) {
150
+ setFilesStatus([
151
+ {
152
+ description: getCopy('fileTooBig').replace('%{size}', maxFileSize),
153
+ style: 'error'
154
+ }
155
+ ])
156
+ return false
157
+ }
158
+ return true
159
+ })
160
+
161
+ const areMoreFilesUploadedThanAllowed = (files) => {
162
+ if (files.length > maxFilesCount) {
163
+ setFilesStatus([
164
+ {
165
+ description: getCopy('tooManyFiles').replace('%{maxFilesCount}', maxFilesCount),
166
+ style: 'error'
167
+ }
168
+ ])
169
+ return true
170
+ }
171
+ return false
172
+ }
173
+
174
+ const handleFilesUpload = async (files) => {
175
+ try {
176
+ const results = await onUpload(files)
177
+ const currentFilesStatus = files.map((file, index) => {
178
+ if (results[index].error) {
179
+ return {
180
+ description: (
181
+ <NotificationContent
182
+ file={file}
183
+ color={selectComponentTokens(themeTokens, 'notification').color}
184
+ >{` ${getCopy('uploadError')} ${results[index].error}.`}</NotificationContent>
185
+ ),
186
+ style: 'error'
187
+ }
188
+ }
189
+ return {
190
+ description: (
191
+ <NotificationContent
192
+ file={file}
193
+ color={selectComponentTokens(themeTokens, 'notification').color}
194
+ >{` ${getCopy('uploadSuccess')}`}</NotificationContent>
195
+ ),
196
+ style: 'success',
197
+ file,
198
+ onDismiss: () => handleFileDeletion(file)
199
+ }
200
+ })
201
+ setFilesStatus(currentFilesStatus)
202
+ } catch (error) {
203
+ setFilesStatus([
204
+ {
205
+ description: allowMultipleFiles
206
+ ? getCopy('problemUploadingMultipleFiles')
207
+ : getCopy('problemUploading'),
208
+ style: 'error'
209
+ }
210
+ ])
211
+ }
212
+ }
213
+
214
+ const handleFileDeletion = async (file) => {
215
+ const copyFile = { ...file }
216
+ try {
217
+ const result = await onDelete(copyFile)
218
+ setFilesStatus((prevStatus) => {
219
+ const filteredFilesStatus = prevStatus.filter((f) => f.file && f.file.name !== file.name)
220
+ return result.error
221
+ ? [
222
+ ...filteredFilesStatus,
223
+ {
224
+ description: (
225
+ <NotificationContent
226
+ file={file}
227
+ color={selectComponentTokens(themeTokens, 'notification').color}
228
+ >{` ${getCopy('uploadSuccess')}`}</NotificationContent>
229
+ ),
230
+ style: 'success',
231
+ file,
232
+ onDismiss: () => handleFileDeletion(file)
233
+ },
234
+ {
235
+ description: (
236
+ <NotificationContent
237
+ file={file}
238
+ color={selectComponentTokens(themeTokens, 'notification').color}
239
+ >{` ${getCopy('deleteProblem')} ${result.error}.`}</NotificationContent>
240
+ ),
241
+ style: 'error',
242
+ file
243
+ }
244
+ ]
245
+ : filteredFilesStatus
246
+ })
247
+ } catch (error) {
248
+ setFilesStatus([
249
+ {
250
+ description: (
251
+ <NotificationContent
252
+ file={file}
253
+ color={selectComponentTokens(themeTokens, 'notification').color}
254
+ >{` ${getCopy('problemDeleting')}`}</NotificationContent>
255
+ ),
256
+ style: 'error'
257
+ }
258
+ ])
259
+ }
260
+ }
261
+
262
+ const handleOnChange = async (event) => {
263
+ const targetFiles = event.target.files
264
+ const files = Array.from(targetFiles)
265
+
266
+ if (files.length === 0) return
267
+
268
+ if (fileTypes && !areFileTypesExtensionsValid(files)) return
269
+
270
+ if (maxFileSize && !areFileSizesWithinLimit(files)) return
271
+
272
+ if (maxFilesCount && areMoreFilesUploadedThanAllowed(files)) return
273
+
274
+ await handleFilesUpload(files)
275
+
276
+ if (inputFileRef.current) inputFileRef.current.value = ''
277
+ }
278
+
279
+ const onFileUploadButtonClick = async () => {
280
+ if (Platform.OS === 'web') {
281
+ inputFileRef.current.click()
282
+ } else {
283
+ try {
284
+ const result = await documentPicker.getDocumentAsync({
285
+ multiple: allowMultipleFiles
286
+ })
287
+ if (result) {
288
+ const files = Array.isArray(result) ? result : [result]
289
+ if (files.find((file) => file.type === 'cancel')) return
290
+ const event = { target: { files } }
291
+ await handleOnChange(event)
292
+ }
293
+ } catch (_) {
294
+ setFilesStatus([
295
+ {
296
+ description: allowMultipleFiles
297
+ ? getCopy('problemUploadingFiles')
298
+ : getCopy('problemUploading'),
299
+ style: 'error'
300
+ }
301
+ ])
302
+ }
303
+ }
304
+ }
305
+
306
+ return (
307
+ <View ref={ref} {...selectProps(rest)}>
308
+ <InputLabel
309
+ label={getCopy('label')}
310
+ hint={
311
+ fileTypes &&
312
+ getCopy('allowedFileTypes').replace('%{fileTypes}', getHintFromFileTypes(fileTypes))
313
+ }
314
+ hintPosition="below"
315
+ />
316
+ <Spacer space={4} />
317
+ {Platform.OS === 'web' && (
318
+ <input
319
+ type="file"
320
+ multiple={allowMultipleFiles}
321
+ ref={inputFileRef}
322
+ hidden={true}
323
+ accept={fileTypes && fileTypes.map((type) => `.${type}`).join(', ')}
324
+ onChange={handleOnChange}
325
+ />
326
+ )}
327
+ <Button
328
+ onClick={onFileUploadButtonClick}
329
+ tokens={selectComponentTokens(themeTokens, 'button')}
330
+ >
331
+ {getCopy('buttonLabel')}
332
+ </Button>
333
+ {filesStatus.length > 0 && (
334
+ <View>
335
+ <Spacer space={4} />
336
+ <StackView space={4}>
337
+ {filesStatus.map(({ description, style, onDismiss }, index) => (
338
+ <Notification
339
+ key={`${index.toString()}-${renderTrigger}`}
340
+ variant={{ style }}
341
+ dismissible={!!onDismiss}
342
+ onDismiss={onDismiss}
343
+ tokens={selectComponentTokens(themeTokens, 'notification')}
344
+ >
345
+ {description}
346
+ </Notification>
347
+ ))}
348
+ </StackView>
349
+ </View>
350
+ )}
351
+ </View>
352
+ )
353
+ }
354
+ )
355
+
356
+ FileUpload.displayName = 'FileUpload'
357
+
358
+ FileUpload.propTypes = {
359
+ ...selectedSystemPropTypes,
360
+ tokens: getTokensPropType('FileUpload'),
361
+ variant: variantProp.propType,
362
+ /**
363
+ * To define the locale of the copy
364
+ */
365
+ copy: copyPropTypes,
366
+ /**
367
+ * The file types allowed for upload. Example: fileTypes={['pdf', 'docx']}.
368
+ */
369
+ fileTypes: PropTypes.arrayOf(PropTypes.string),
370
+ /**
371
+ * Whether to allow multiple files to be uploaded.
372
+ */
373
+ allowMultipleFiles: PropTypes.bool,
374
+ /**
375
+ * The maximum file size allowed for upload in MB.
376
+ */
377
+ maxFileSize: PropTypes.number,
378
+ /**
379
+ * The maximum number of files allowed for upload.
380
+ */
381
+ maxFilesCount: PropTypes.number,
382
+ /**
383
+ * Callback function called when a file is uploaded.
384
+ */
385
+ onUpload: PropTypes.func,
386
+ /**
387
+ * Callback function called when a file is deleted
388
+ */
389
+ onDelete: PropTypes.func,
390
+ /**
391
+ * The document picker to use for mobile
392
+ */
393
+ documentPicker: PropTypes.object
394
+ }
395
+
396
+ export default FileUpload
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import Typography from '../Typography'
4
+
5
+ /**
6
+ * Renders the content of a notification for a file upload.
7
+ *
8
+ * @component
9
+ * @param {Object} props - The component props.
10
+ * @param {Object} props.file - The file object.
11
+ * @param {string} props.children - The content to be displayed.
12
+ * @param {string} props.color - The color of the typography.
13
+ * @param {React.Ref} ref - The ref to be forwarded to the Typography component.
14
+ * @returns {JSX.Element} The rendered NotificationContent component.
15
+ */
16
+ const NotificationContent = React.forwardRef(({ file, color, children }, ref) => {
17
+ return (
18
+ <Typography tokens={{ color }} ref={ref}>
19
+ <Typography variant={{ bold: true }} tokens={{ color }}>
20
+ {file.name}
21
+ </Typography>
22
+ {children}
23
+ </Typography>
24
+ )
25
+ })
26
+
27
+ NotificationContent.displayName = 'NotificationContent'
28
+
29
+ NotificationContent.propTypes = {
30
+ /**
31
+ * The file object.
32
+ */
33
+ file: PropTypes.object,
34
+ /**
35
+ * The content to be displayed.
36
+ */
37
+ children: PropTypes.string,
38
+ /**
39
+ * The color of the typography.
40
+ */
41
+ color: PropTypes.string
42
+ }
43
+
44
+ export default NotificationContent
@@ -0,0 +1,40 @@
1
+ export default {
2
+ en: {
3
+ label: 'Upload files',
4
+ buttonLabel: 'Choose files',
5
+ dismissButtonLabel: 'Remove file',
6
+ wrongFileType: 'The selected file must be a %{fileType}.',
7
+ allowedFileTypes: 'Allowed file types: %{fileTypes}.',
8
+ fileTooBig: 'The selected file must be smaller than %{size}MB.',
9
+ fileIsEmpty: 'The selected file is empty.',
10
+ problemUploading: 'The selected file could not be uploaded. Try again.',
11
+ problemDeleting: 'The selected file could not be deleted. Try again.',
12
+ problemUploadingMultipleFiles: 'The selected files could not be uploaded. Try again.',
13
+ only: 'only',
14
+ and: 'and',
15
+ or: 'or',
16
+ uploadSuccess: 'uploaded successfully.',
17
+ uploadError: 'was not uploaded due to',
18
+ deleteProblem: 'was not deleted due to',
19
+ tooManyFiles: 'You can only select up to %{maxFilesCount} files at the same time.'
20
+ },
21
+ fr: {
22
+ label: 'Téléverser les fichiers"',
23
+ buttonLabel: 'Choisir les fichiers',
24
+ dismissButtonLabel: 'Supprimer le fichier',
25
+ wrongFileType: 'Le fichier sélectionné doit être au format %{fileType}.',
26
+ allowedFileTypes: 'Formats de fichiers autorisés : %{fileTypes}.',
27
+ fileTooBig: 'Le fichier sélectionné doit être inférieur à %{size}Mo.',
28
+ fileIsEmpty: 'Le fichier sélectionné est vide.',
29
+ problemUploading: 'Impossible de téléverser le fichier sélectionné. Veuillez réessayer.',
30
+ problemDeleting: `Le fichier sélectionné n'a pas pu être supprimé. Réessayez.`,
31
+ problemUploadingMultipleFiles: `Les fichiers sélectionnés n'ont pas pu être téléchargés. Veuillez réessayer.`,
32
+ only: 'uniquement',
33
+ and: 'et',
34
+ or: 'ou',
35
+ uploadSuccess: 'téléchargé avec succès.',
36
+ uploadError: `n'a pas été téléchargé en raison de`,
37
+ deleteProblem: `n'a pas été supprimé en raison de`,
38
+ tooManyFiles: `Vous ne pouvez sélectionner que jusqu'à %{maxFilesCount} fichiers en même temps.`
39
+ }
40
+ }
@@ -0,0 +1,3 @@
1
+ import FileUpload from './FileUpload'
2
+
3
+ export default FileUpload
@@ -1,7 +1,6 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { Platform, View } from 'react-native'
4
-
3
+ import { Platform, View, Text, StyleSheet } from 'react-native'
5
4
  import Icon, { iconComponentPropTypes } from './Icon'
6
5
  import { getStackedContent } from '../StackView'
7
6
  import { spacingProps } from '../utils'
@@ -46,6 +45,18 @@ const IconText = React.forwardRef(
46
45
  const mobile = Platform.OS === 'android' ? iconAdjustedAndroid : iconAdjustedIOS
47
46
  const adjustedContainer = Platform.OS === 'web' ? iconContent : mobile
48
47
 
48
+ const adjustedContainerWithPosition = (
49
+ <View style={staticStyles.adjustedContainer}>{adjustedContainer}</View>
50
+ )
51
+
52
+ if (iconPosition === 'inline') {
53
+ return (
54
+ <Text>
55
+ {children} {adjustedContainerWithPosition}
56
+ </Text>
57
+ )
58
+ }
59
+
49
60
  return getStackedContent(
50
61
  iconPosition === 'left' ? [adjustedContainer, children] : [children, adjustedContainer],
51
62
  { space, direction: 'row' }
@@ -62,9 +73,9 @@ IconText.propTypes = {
62
73
  */
63
74
  space: spacingProps.types.spacingValue,
64
75
  /**
65
- * Whether the icon should be to the left of the content or the right of the content.
76
+ * Whether the icon should be to the left of the content, the right of the content or inline with the content.
66
77
  */
67
- iconPosition: PropTypes.oneOf(['left', 'right']),
78
+ iconPosition: PropTypes.oneOf(['left', 'right', 'inline']),
68
79
  /**
69
80
  * A valid UDS icon component imported from a UDS palette.
70
81
  */
@@ -83,4 +94,10 @@ IconText.propTypes = {
83
94
  /* eslint-enable react/no-unused-prop-types */
84
95
  }
85
96
 
97
+ const staticStyles = StyleSheet.create({
98
+ adjustedContainer: {
99
+ position: 'absolute'
100
+ }
101
+ })
102
+
86
103
  export default IconText
@@ -240,9 +240,9 @@ LinkBase.propTypes = {
240
240
  */
241
241
  icon: PropTypes.elementType,
242
242
  /**
243
- * When `icon` is provided, use `iconPosition` to place the Icon to the left or right side of Link.
243
+ * When `icon` is provided, use `iconPosition` to place the Icon to the left, right or inline with Link.
244
244
  */
245
- iconPosition: PropTypes.oneOf(['left', 'right']),
245
+ iconPosition: PropTypes.oneOf(['left', 'right', 'inline']),
246
246
  /**
247
247
  * On Web if href is passed, React Native Web maps this object's props to
248
248
  * `rel`, `target` and `download` attrs.
@@ -1,6 +1,5 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { View, StyleSheet } from 'react-native'
4
3
  import { useThemeTokensCallback } from '../ThemeProvider'
5
4
  import LinkBase from './LinkBase'
6
5
  import { variantProp } from '../utils'
@@ -14,25 +13,19 @@ const TextButton = React.forwardRef(
14
13
  ({ onPress, children, variant, tokens, accessibilityRole = 'button', ...linkProps }, ref) => {
15
14
  const getTokens = useThemeTokensCallback('Link', tokens, variant)
16
15
  return (
17
- <View style={styles.textButton}>
18
- <LinkBase
19
- onPress={onPress}
20
- accessibilityRole={accessibilityRole}
21
- tokens={getTokens}
22
- ref={ref}
23
- {...linkProps}
24
- >
25
- {children}
26
- </LinkBase>
27
- </View>
16
+ <LinkBase
17
+ onPress={onPress}
18
+ accessibilityRole={accessibilityRole}
19
+ tokens={getTokens}
20
+ ref={ref}
21
+ {...linkProps}
22
+ >
23
+ {children}
24
+ </LinkBase>
28
25
  )
29
26
  }
30
27
  )
31
- const styles = StyleSheet.create({
32
- textButton: {
33
- flex: 1
34
- }
35
- })
28
+
36
29
  TextButton.displayName = 'TextButton'
37
30
 
38
31
  TextButton.propTypes = {
@@ -147,7 +147,7 @@ const Modal = React.forwardRef(
147
147
  if (Platform.OS === 'web') {
148
148
  const handleFocus = () => {
149
149
  // If the focus is on the last item of the web modal container, move it to the close button
150
- if (document.activeElement === focusTrapRef.current) {
150
+ if (document.activeElement === focusTrapRef.current && closeButtonRef.current) {
151
151
  closeButtonRef.current.focus()
152
152
  }
153
153
  return undefined
@@ -133,9 +133,11 @@ const ModalContent = React.forwardRef(
133
133
  </Button>
134
134
  )}
135
135
  {hasCancelButton ? (
136
- <CancelButton tokens={{ color: cancelButtonColor }} onPress={onCancel}>
137
- {cancelButtonText}
138
- </CancelButton>
136
+ <View style={styles.cancelButton}>
137
+ <CancelButton tokens={{ color: cancelButtonColor }} onPress={onCancel}>
138
+ {cancelButtonText}
139
+ </CancelButton>
140
+ </View>
139
141
  ) : null}
140
142
  </View>
141
143
  )}
@@ -151,6 +153,9 @@ const styles = StyleSheet.create({
151
153
  flex: 1,
152
154
  flexDirection: 'column',
153
155
  minHeight: Platform.OS === 'web' ? '100%' : 'auto'
156
+ },
157
+ cancelButton: {
158
+ flex: 1
154
159
  }
155
160
  })
156
161