@telus-uds/components-base 2.2.0 → 2.4.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 (46) hide show
  1. package/CHANGELOG.md +36 -2
  2. package/lib/Autocomplete/Autocomplete.js +8 -3
  3. package/lib/Card/CardBase.js +7 -1
  4. package/lib/Card/PressableCardBase.js +1 -1
  5. package/lib/FileUpload/FileUpload.js +57 -22
  6. package/lib/FileUpload/dictionary.js +6 -2
  7. package/lib/InputLabel/InputLabel.js +36 -2
  8. package/lib/InputSupports/InputSupports.js +31 -8
  9. package/lib/InputSupports/dictionary.js +12 -0
  10. package/lib/InputSupports/useInputSupports.js +12 -3
  11. package/lib/Link/LinkBase.js +17 -15
  12. package/lib/Listbox/ListboxOverlay.js +6 -3
  13. package/lib/Modal/Modal.js +2 -2
  14. package/lib/MultiSelectFilter/MultiSelectFilter.js +54 -41
  15. package/lib/Notification/Notification.js +7 -3
  16. package/lib/Search/Search.js +39 -8
  17. package/lib/Select/Picker.native.js +8 -4
  18. package/lib/Select/constants.js +4 -2
  19. package/lib/StepTracker/StepTracker.js +10 -3
  20. package/lib/TextInput/TextArea.js +7 -6
  21. package/lib/TextInput/TextInput.js +7 -6
  22. package/lib/TextInput/TextInputBase.js +48 -14
  23. package/lib/utils/props/inputSupportsProps.js +15 -3
  24. package/package.json +3 -3
  25. package/src/Autocomplete/Autocomplete.jsx +9 -2
  26. package/src/Card/CardBase.jsx +5 -2
  27. package/src/Card/PressableCardBase.jsx +12 -1
  28. package/src/FileUpload/FileUpload.jsx +71 -28
  29. package/src/FileUpload/dictionary.js +6 -2
  30. package/src/InputLabel/InputLabel.jsx +39 -2
  31. package/src/InputSupports/InputSupports.jsx +33 -7
  32. package/src/InputSupports/dictionary.js +12 -0
  33. package/src/InputSupports/useInputSupports.js +24 -3
  34. package/src/Link/LinkBase.jsx +17 -15
  35. package/src/Listbox/ListboxOverlay.jsx +5 -3
  36. package/src/Modal/Modal.jsx +1 -1
  37. package/src/MultiSelectFilter/MultiSelectFilter.jsx +55 -27
  38. package/src/Notification/Notification.jsx +9 -3
  39. package/src/Search/Search.jsx +52 -24
  40. package/src/Select/Picker.native.jsx +10 -4
  41. package/src/Select/constants.js +4 -1
  42. package/src/StepTracker/StepTracker.jsx +16 -5
  43. package/src/TextInput/TextArea.jsx +12 -5
  44. package/src/TextInput/TextInput.jsx +13 -6
  45. package/src/TextInput/TextInputBase.jsx +57 -10
  46. package/src/utils/props/inputSupportsProps.js +15 -3
package/package.json CHANGED
@@ -13,10 +13,10 @@
13
13
  "@gorhom/portal": "^1.0.14",
14
14
  "@react-native-picker/picker": "^2.9.0",
15
15
  "@telus-uds/system-constants": "^2.0.0",
16
- "@telus-uds/system-theme-tokens": "^3.1.0",
16
+ "@telus-uds/system-theme-tokens": "^3.3.0",
17
17
  "airbnb-prop-types": "^2.16.0",
18
18
  "css-mediaquery": "^0.1.2",
19
- "expo-document-picker": "~10.3.0",
19
+ "expo-document-picker": "^13.0.1",
20
20
  "expo-linear-gradient": "^12.5.0",
21
21
  "expo-modules-core": "^1.12.24",
22
22
  "lodash.debounce": "^4.0.8",
@@ -81,6 +81,6 @@
81
81
  "standard-engine": {
82
82
  "skip": true
83
83
  },
84
- "version": "2.2.0",
84
+ "version": "2.4.0",
85
85
  "types": "types/index.d.ts"
86
86
  }
@@ -11,7 +11,8 @@ import {
11
11
  useCopy,
12
12
  htmlAttrs,
13
13
  useOverlaidPosition,
14
- useSafeLayoutEffect
14
+ useSafeLayoutEffect,
15
+ getTokensPropType
15
16
  } from '../utils'
16
17
  import { useThemeTokens } from '../ThemeProvider'
17
18
  import Listbox from '../Listbox'
@@ -103,11 +104,15 @@ const Autocomplete = React.forwardRef(
103
104
  value,
104
105
  helpText = '',
105
106
  loadingLabel,
107
+ tokens,
106
108
  ...rest
107
109
  },
108
110
  ref
109
111
  ) => {
110
- const { color: resultsTextColor } = useThemeTokens('Search', {}, { focus: true })
112
+ const { color: resultsTextColor, ...restOfTokens } = useThemeTokens('Autocomplete', tokens, {
113
+ focus: true
114
+ })
115
+
111
116
  // The wrapped input is mostly responsible for controlled vs uncontrolled handling,
112
117
  // but we also need to adjust suggestions based on the mode:
113
118
  // - in controlled mode we rely entirely on the suggestions passed via the `items` prop,
@@ -371,6 +376,7 @@ const Autocomplete = React.forwardRef(
371
376
  minWidth={fullWidth ? inputWidth : MIN_LISTBOX_WIDTH}
372
377
  maxWidth={inputWidth}
373
378
  onLayout={handleMeasure}
379
+ tokens={restOfTokens}
374
380
  ref={openOverlayRef}
375
381
  >
376
382
  {isLoading ? (
@@ -433,6 +439,7 @@ Autocomplete.propTypes = {
433
439
  * </Autocomplete>
434
440
  */
435
441
  children: PropTypes.func,
442
+ tokens: getTokensPropType('Autocomplete'),
436
443
  /**
437
444
  * Copy language identifier
438
445
  */
@@ -22,7 +22,9 @@ const selectStyles = ({
22
22
  minWidth,
23
23
  shadow,
24
24
  backgroundGradient,
25
- gradient
25
+ gradient,
26
+ maxHeight,
27
+ overflowY
26
28
  }) => {
27
29
  return {
28
30
  flex,
@@ -48,7 +50,8 @@ const selectStyles = ({
48
50
  ? {
49
51
  backgroundImage: `linear-gradient(${backgroundGradient.angle}deg, ${backgroundGradient.stops[0].color}, ${backgroundGradient.stops[1].color})`
50
52
  }
51
- : {})
53
+ : {}),
54
+ ...(Platform.OS === 'web' ? { maxHeight, overflowY } : {})
52
55
  }
53
56
  }
54
57
 
@@ -48,7 +48,18 @@ const tokenKeys = [
48
48
  // https://github.com/telus/universal-design-system/issues/782
49
49
  'outerBorderColor',
50
50
  'outerBorderWidth',
51
- 'outerBorderGap'
51
+ 'outerBorderGap',
52
+ 'icon',
53
+ 'iconBackgroundColor',
54
+ 'iconColor',
55
+ 'iconSize',
56
+ 'inputBackgroundColor',
57
+ 'inputBorderColor',
58
+ 'inputBorderRadius',
59
+ 'inputBorderWidth',
60
+ 'inputHeight',
61
+ 'inputShadow',
62
+ 'inputWidth'
52
63
  ]
53
64
  export const selectPressableCardTokens = (tokens) =>
54
65
  Object.fromEntries(tokenKeys.map((key) => [key, tokens[key]]))
@@ -90,6 +90,8 @@ const selectComponentTokens = (tokens, componentIdentifier) => {
90
90
  * @param {boolean} props.allowMultipleFiles - Whether multiple files can be uploaded.
91
91
  * @param {number} props.maxFileSize - The maximum file size in megabytes.
92
92
  * @param {number} props.maxFilesCount - The maximum number of files that can be uploaded.
93
+ * @param {number} props.minFilesCount - The minimum number of files that can be uploaded.
94
+ * @param {number} props.minFileSize - The minimum file size in megabytes.
93
95
  * @param {Function} props.onUpload - The callback function for file upload.
94
96
  * @param {Function} props.onDelete - The callback function for file deletion.
95
97
  * @param {Object} props.documentPicker - The document picker object.
@@ -106,6 +108,8 @@ const FileUpload = React.forwardRef(
106
108
  allowMultipleFiles = false,
107
109
  maxFileSize,
108
110
  maxFilesCount = 1,
111
+ minFilesCount = 1,
112
+ minFileSize = 0,
109
113
  onUpload,
110
114
  onDelete,
111
115
  documentPicker,
@@ -113,6 +117,10 @@ const FileUpload = React.forwardRef(
113
117
  },
114
118
  ref
115
119
  ) => {
120
+ if (minFilesCount <= 0) {
121
+ throw new Error('minFilesCount should be greater than 0')
122
+ }
123
+
116
124
  const themeTokens = useThemeTokens('FileUpload', tokens, variant)
117
125
  const getCopy = useCopy({ dictionary, copy })
118
126
 
@@ -125,50 +133,78 @@ const FileUpload = React.forwardRef(
125
133
  setRenderTrigger((prev) => prev + 1)
126
134
  }, [filesStatus])
127
135
 
128
- const areFileTypesExtensionsValid = (files) =>
129
- files.some((file) => {
136
+ const areFileTypesExtensionsValid = (files) => {
137
+ const invalidFiles = files.filter((file) => {
130
138
  const fileExtension = file.name.split('.').pop().toLowerCase()
139
+ return !fileTypes.includes(fileExtension)
140
+ })
141
+ if (invalidFiles.length > 0) {
142
+ setFilesStatus([
143
+ {
144
+ description: getCopy('wrongFileType').replace(
145
+ '%{fileType}',
146
+ stringifyFileTypesList(fileTypes, getCopy)
147
+ ),
148
+ style: 'error'
149
+ }
150
+ ])
151
+ return false
152
+ }
153
+ return true
154
+ }
131
155
 
132
- if (!fileTypes.includes(fileExtension)) {
156
+ const areFileSizesWithinLimit = (files) => {
157
+ const invalidFiles = files.filter((file) => {
158
+ const fileSize = file.size
159
+ return (
160
+ fileSize < convertFromMegaByteToByte(minFileSize) ||
161
+ fileSize > convertFromMegaByteToByte(maxFileSize)
162
+ )
163
+ })
164
+
165
+ if (invalidFiles.length > 0) {
166
+ const invalidFile = invalidFiles[0]
167
+ const fileSize = invalidFile.size
168
+ if (fileSize < convertFromMegaByteToByte(minFileSize)) {
133
169
  setFilesStatus([
134
170
  {
135
- description: getCopy('wrongFileType').replace(
136
- '%{fileType}',
137
- stringifyFileTypesList(fileTypes, getCopy)
138
- ),
171
+ description: getCopy('fileTooSmall').replace('%{size}', minFileSize),
139
172
  style: 'error'
140
173
  }
141
174
  ])
142
- return false
143
- }
144
- return true
145
- })
146
-
147
- const areFileSizesWithinLimit = (files) =>
148
- files.some((file) => {
149
- if (file.size > convertFromMegaByteToByte(maxFileSize)) {
175
+ } else {
150
176
  setFilesStatus([
151
177
  {
152
178
  description: getCopy('fileTooBig').replace('%{size}', maxFileSize),
153
179
  style: 'error'
154
180
  }
155
181
  ])
156
- return false
157
182
  }
158
- return true
159
- })
183
+ return false
184
+ }
185
+ return true
186
+ }
160
187
 
161
- const areMoreFilesUploadedThanAllowed = (files) => {
162
- if (files.length > maxFilesCount) {
188
+ const areFilesCountWithinLimit = (files) => {
189
+ if (maxFilesCount && files.length > maxFilesCount) {
163
190
  setFilesStatus([
164
191
  {
165
192
  description: getCopy('tooManyFiles').replace('%{maxFilesCount}', maxFilesCount),
166
193
  style: 'error'
167
194
  }
168
195
  ])
169
- return true
196
+ return false
170
197
  }
171
- return false
198
+ if (minFilesCount && files.length < minFilesCount) {
199
+ setFilesStatus([
200
+ {
201
+ description: getCopy('fewFiles').replace('%{minFilesCount}', minFilesCount),
202
+ style: 'error'
203
+ }
204
+ ])
205
+ return false
206
+ }
207
+ return true
172
208
  }
173
209
 
174
210
  const handleFilesUpload = async (files) => {
@@ -212,9 +248,8 @@ const FileUpload = React.forwardRef(
212
248
  }
213
249
 
214
250
  const handleFileDeletion = async (file) => {
215
- const copyFile = { ...file }
216
251
  try {
217
- const result = await onDelete(copyFile)
252
+ const result = await onDelete(file)
218
253
  setFilesStatus((prevStatus) => {
219
254
  const filteredFilesStatus = prevStatus.filter((f) => f.file && f.file.name !== file.name)
220
255
  return result.error
@@ -269,7 +304,7 @@ const FileUpload = React.forwardRef(
269
304
 
270
305
  if (maxFileSize && !areFileSizesWithinLimit(files)) return
271
306
 
272
- if (maxFilesCount && areMoreFilesUploadedThanAllowed(files)) return
307
+ if ((maxFilesCount || minFilesCount) && !areFilesCountWithinLimit(files)) return
273
308
 
274
309
  await handleFilesUpload(files)
275
310
 
@@ -285,8 +320,8 @@ const FileUpload = React.forwardRef(
285
320
  multiple: allowMultipleFiles
286
321
  })
287
322
  if (result) {
288
- const files = Array.isArray(result) ? result : [result]
289
- if (files.find((file) => file.type === 'cancel')) return
323
+ if (result.canceled) return
324
+ const files = Array.isArray(result.assets) ? result.assets : [result.assets]
290
325
  const event = { target: { files } }
291
326
  await handleOnChange(event)
292
327
  }
@@ -390,7 +425,15 @@ FileUpload.propTypes = {
390
425
  /**
391
426
  * The document picker to use for mobile
392
427
  */
393
- documentPicker: PropTypes.object
428
+ documentPicker: PropTypes.object,
429
+ /**
430
+ * The minimum number of files allowed for upload.
431
+ */
432
+ minFilesCount: PropTypes.number,
433
+ /**
434
+ * The minimum file size allowed for upload in MB.
435
+ */
436
+ minFileSize: PropTypes.number
394
437
  }
395
438
 
396
439
  export default FileUpload
@@ -16,7 +16,9 @@ export default {
16
16
  uploadSuccess: 'uploaded successfully.',
17
17
  uploadError: 'was not uploaded due to',
18
18
  deleteProblem: 'was not deleted due to',
19
- tooManyFiles: 'You can only select up to %{maxFilesCount} files at the same time.'
19
+ tooManyFiles: 'You can only select up to %{maxFilesCount} files at the same time.',
20
+ fileTooSmall: 'The selected file must be at least %{size}MB.',
21
+ fewFiles: 'You must select at least %{minFilesCount} files.'
20
22
  },
21
23
  fr: {
22
24
  label: 'Téléverser les fichiers"',
@@ -35,6 +37,8 @@ export default {
35
37
  uploadSuccess: 'téléchargé avec succès.',
36
38
  uploadError: `n'a pas été téléchargé en raison de`,
37
39
  deleteProblem: `n'a pas été supprimé en raison de`,
38
- tooManyFiles: `Vous ne pouvez sélectionner que jusqu'à %{maxFilesCount} fichiers en même temps.`
40
+ tooManyFiles: `Vous ne pouvez sélectionner que jusqu'à %{maxFilesCount} fichiers en même temps.`,
41
+ fileTooSmall: `Le fichier sélectionné doit faire au moins %{size}Mo.`,
42
+ fewFiles: `Vous devez sélectionner au moins %{minFilesCount} fichiers.`
39
43
  }
40
44
  }
@@ -47,6 +47,8 @@ const InputLabel = React.forwardRef(
47
47
  tooltip,
48
48
  tokens,
49
49
  variant,
50
+ characterCount,
51
+ maxCharacterAllowed,
50
52
  ...rest
51
53
  },
52
54
  ref
@@ -95,12 +97,30 @@ const InputLabel = React.forwardRef(
95
97
  <Tooltip content={tooltip} copy={copy} />
96
98
  </View>
97
99
  )}
100
+ {maxCharacterAllowed && isHintInline && (
101
+ <Text style={[selectHintStyles({ ...themeTokens }), staticStyles.characterCountlabel]}>
102
+ {characterCount}/{maxCharacterAllowed}
103
+ </Text>
104
+ )}
98
105
  </View>
99
- {hint && !isHintInline && (
106
+ {hint && !maxCharacterAllowed && !isHintInline && (
100
107
  <Text style={[selectHintStyles(themeTokens), staticStyles.hintBelow]} nativeID={hintId}>
101
108
  {hint}
102
109
  </Text>
103
110
  )}
111
+ {hint && maxCharacterAllowed && !isHintInline && (
112
+ <View style={staticStyles.container}>
113
+ <Text
114
+ style={[selectHintStyles(themeTokens), staticStyles.flexHintBelow]}
115
+ nativeID={hintId}
116
+ >
117
+ {hint}
118
+ </Text>
119
+ <Text style={[selectHintStyles(themeTokens), staticStyles.characterCountlabel]}>
120
+ {characterCount}/{maxCharacterAllowed}
121
+ </Text>
122
+ </View>
123
+ )}
104
124
  </>
105
125
  )
106
126
  }
@@ -137,6 +157,14 @@ InputLabel.propTypes = {
137
157
  * Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
138
158
  */
139
159
  tooltip: PropTypes.string,
160
+ /**
161
+ * Current number of characterts of an input text.
162
+ */
163
+ characterCount: PropTypes.number,
164
+ /**
165
+ * Max number of characters that allows an input text.
166
+ */
167
+ maxCharacterAllowed: PropTypes.number,
140
168
  tokens: getTokensPropType('InputLabel'),
141
169
  variant: variantProp.propType
142
170
  }
@@ -148,13 +176,22 @@ const staticStyles = StyleSheet.create({
148
176
  flexShrink: 1,
149
177
  flexDirection: 'row'
150
178
  },
179
+ characterCountlabel: {
180
+ marginLeft: 'auto',
181
+ marginTop: 'auto'
182
+ },
151
183
  label: {
152
- flexShrink: 1
184
+ flexShrink: 1,
185
+ alignSelf: 'center'
153
186
  },
154
187
  hintBelow: {
155
188
  flexBasis: '100%',
156
189
  flexShrink: 0
157
190
  },
191
+ flexHintBelow: {
192
+ flexBasis: '100%',
193
+ flexShrink: 1
194
+ },
158
195
  tooltipAlign: {
159
196
  alignSelf: 'center',
160
197
  justifyContent: 'center'
@@ -6,7 +6,8 @@ import Feedback from '../Feedback'
6
6
  import StackView from '../StackView'
7
7
  import { useThemeTokens } from '../ThemeProvider'
8
8
  import useInputSupports from './useInputSupports'
9
- import { getTokensPropType } from '../utils'
9
+ import { getTokensPropType, useCopy } from '../utils'
10
+ import dictionary from './dictionary'
10
11
 
11
12
  const InputSupports = React.forwardRef(
12
13
  (
@@ -21,18 +22,31 @@ const InputSupports = React.forwardRef(
21
22
  feedbackProps = {},
22
23
  tooltip,
23
24
  validation,
25
+ maxCharacterAllowed,
26
+ inputValue,
24
27
  nativeID
25
28
  },
26
29
  ref
27
30
  ) => {
28
31
  const { space } = useThemeTokens('InputSupports')
32
+ const getCopy = useCopy({ dictionary, copy })
33
+
34
+ const maxCharsReachedErrorMessage =
35
+ inputValue?.length > maxCharacterAllowed
36
+ ? getCopy('maxCharsMessage').replace(/%\{charCount\}/g, maxCharacterAllowed)
37
+ : ''
38
+
39
+ const feedbackValidation = inputValue?.length > maxCharacterAllowed ? 'error' : validation
29
40
 
30
41
  const { inputId, hintId, feedbackId, a11yProps } = useInputSupports({
31
42
  feedback,
32
43
  hint,
33
44
  label,
34
45
  validation,
35
- nativeID
46
+ nativeID,
47
+ copy,
48
+ maxCharacterAllowed,
49
+ charactersCount: maxCharacterAllowed - inputValue?.length
36
50
  })
37
51
 
38
52
  return (
@@ -46,14 +60,18 @@ const InputSupports = React.forwardRef(
46
60
  hintId={hintId}
47
61
  tooltip={tooltip}
48
62
  forId={inputId}
63
+ characterCount={inputValue?.length}
64
+ maxCharacterAllowed={maxCharacterAllowed}
49
65
  />
50
66
  )}
51
- {typeof children === 'function' ? children({ inputId, ...a11yProps }) : children}
52
- {feedback ? (
67
+ {typeof children === 'function'
68
+ ? children({ inputId, ...a11yProps, validation: feedbackValidation })
69
+ : children}
70
+ {feedback || maxCharsReachedErrorMessage ? (
53
71
  <Feedback
54
72
  id={feedbackId}
55
- title={feedback}
56
- validation={validation}
73
+ title={feedback || maxCharsReachedErrorMessage}
74
+ validation={feedbackValidation}
57
75
  tokens={feedbackTokens}
58
76
  variant={{ icon: feedbackProps.showFeedbackIcon }}
59
77
  {...feedbackProps}
@@ -107,7 +125,15 @@ InputSupports.propTypes = {
107
125
  /**
108
126
  * ID for DOM element on web
109
127
  */
110
- nativeID: PropTypes.string
128
+ nativeID: PropTypes.string,
129
+ /**
130
+ * The text value of a TextArea or TextInput
131
+ */
132
+ inputValue: PropTypes.string,
133
+ /**
134
+ * Max number of characters that allows an input text.
135
+ */
136
+ maxCharacterAllowed: PropTypes.number
111
137
  }
112
138
 
113
139
  export default InputSupports
@@ -0,0 +1,12 @@
1
+ export default {
2
+ en: {
3
+ maxCharacters: 'Maximum %{charCount} characters',
4
+ charactersRemaining: '%{charCount} characters remaining',
5
+ maxCharsMessage: 'Must not exceed %{charCount} characters'
6
+ },
7
+ fr: {
8
+ maxCharacters: '%{charCount} caractères maximum',
9
+ charactersRemaining: '%{charCount} caractères restants',
10
+ maxCharsMessage: 'Ne doit pas dépasser %{charCount} caractères'
11
+ }
12
+ }
@@ -1,8 +1,20 @@
1
1
  import useUniqueId from '../utils/useUniqueId'
2
+ import { useCopy } from '../utils'
3
+ import dictionary from './dictionary'
2
4
 
3
5
  const joinDefined = (array) => array.filter((item) => item !== undefined).join(' ')
4
6
 
5
- const useInputSupports = ({ label, feedback, validation, hint, nativeID }) => {
7
+ const useInputSupports = ({
8
+ label,
9
+ feedback,
10
+ validation,
11
+ hint,
12
+ nativeID,
13
+ copy,
14
+ maxCharacterAllowed,
15
+ charactersCount
16
+ }) => {
17
+ const getCopy = useCopy({ dictionary, copy })
6
18
  const hasValidationError = validation === 'error'
7
19
 
8
20
  const inputId = useUniqueId('input')
@@ -11,10 +23,19 @@ const useInputSupports = ({ label, feedback, validation, hint, nativeID }) => {
11
23
 
12
24
  const a11yProps = {
13
25
  accessibilityLabel: label,
14
- accessibilityHint: joinDefined([!hasValidationError && feedback, hint]), // native only -> replaced with describedBy on web
26
+ accessibilityHint: joinDefined([
27
+ !hasValidationError && feedback,
28
+ hint,
29
+ maxCharacterAllowed
30
+ ? getCopy('maxCharacters').replace(/%\{charCount\}/g, maxCharacterAllowed)
31
+ : undefined
32
+ ]), // native only -> replaced with describedBy on web
15
33
  accessibilityDescribedBy: joinDefined([
16
34
  !hasValidationError && feedback && feedbackId, // feedback receives a11yRole=alert on error, so there's no need to include it here
17
- hint && hintId
35
+ hint && hintId,
36
+ charactersCount
37
+ ? getCopy('charactersRemaining').replace(/%\{charCount\}/g, charactersCount)
38
+ : undefined
18
39
  ]),
19
40
  accessibilityInvalid: hasValidationError
20
41
  }
@@ -32,21 +32,15 @@ const selectOuterBorderStyles = ({
32
32
  outerBorderGap,
33
33
  borderRadius,
34
34
  outerBorderOutline
35
- }) =>
36
- // A view wrapper with a border on native messes up inline text alignment
37
- // so for now make focus styles strictly web-only
38
- Platform.OS === 'web'
39
- ? {
40
- // Allow theme to define outline, or, turn off outline and use border if rounded corners required
41
- outline: outerBorderOutline,
42
- ...applyOuterBorder({
43
- outerBorderColor,
44
- outerBorderWidth,
45
- outerBorderGap
46
- }),
47
- borderRadius
48
- }
49
- : {}
35
+ }) => ({
36
+ outline: outerBorderOutline,
37
+ ...applyOuterBorder({
38
+ outerBorderColor,
39
+ outerBorderWidth,
40
+ outerBorderGap
41
+ }),
42
+ borderRadius
43
+ })
50
44
 
51
45
  const selectTextStyles = ({ color, blockFontSize }) => {
52
46
  return {
@@ -174,6 +168,7 @@ const LinkBase = React.forwardRef(
174
168
  const decorationStyles = selectDecorationStyles(themeTokens)
175
169
  return [
176
170
  outerBorderStyles,
171
+ staticStyles.outerBorderStyles,
177
172
  blockLeftStyle,
178
173
  decorationStyles,
179
174
  hasIcon && staticStyles.rowContainer
@@ -273,6 +268,13 @@ const staticStyles = StyleSheet.create({
273
268
  pointerEvents: 'none'
274
269
  }
275
270
  })
271
+ },
272
+ outerBorderStyles: {
273
+ ...(Platform.OS !== 'web' && {
274
+ margin: 0,
275
+ marginHorizontal: 2,
276
+ padding: 0
277
+ })
276
278
  }
277
279
  })
278
280
 
@@ -22,7 +22,7 @@ const paddingVertical = 0
22
22
  const paddingHorizontal = 0
23
23
 
24
24
  const DropdownOverlay = React.forwardRef(
25
- ({ children, isReady = false, overlaidPosition, maxWidth, minWidth, onLayout }, ref) => {
25
+ ({ children, isReady = false, overlaidPosition, maxWidth, minWidth, onLayout, tokens }, ref) => {
26
26
  const systemTokens = useThemeTokens('Listbox', {}, {})
27
27
 
28
28
  return (
@@ -42,7 +42,8 @@ const DropdownOverlay = React.forwardRef(
42
42
  paddingBottom: paddingVertical,
43
43
  paddingTop: paddingVertical,
44
44
  paddingLeft: paddingHorizontal,
45
- paddingRight: paddingHorizontal
45
+ paddingRight: paddingHorizontal,
46
+ ...tokens
46
47
  }}
47
48
  >
48
49
  {children}
@@ -75,7 +76,8 @@ DropdownOverlay.propTypes = {
75
76
  }),
76
77
  maxWidth: PropTypes.number,
77
78
  minWidth: PropTypes.number,
78
- onLayout: PropTypes.func
79
+ onLayout: PropTypes.func,
80
+ tokens: PropTypes.object
79
81
  }
80
82
 
81
83
  export default Platform.OS === 'web' ? withPortal(DropdownOverlay) : DropdownOverlay
@@ -149,7 +149,7 @@ const Modal = React.forwardRef(
149
149
  a[href], button, textarea, input, select,
150
150
  [tabindex]:not([tabindex="-1"]),
151
151
  [contenteditable="true"]
152
- `)
152
+ `) || []
153
153
  )
154
154
 
155
155
  const firstElement = focusableElements[0]