@telus-uds/components-base 3.7.1 → 3.9.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 +34 -2
- package/lib/cjs/ActivityIndicator/FullScreenIndicator.js +89 -0
- package/lib/cjs/ActivityIndicator/InlineIndicator.js +64 -0
- package/lib/cjs/ActivityIndicator/OverlayIndicator.js +156 -0
- package/lib/cjs/ActivityIndicator/RenderActivityIndicator.js +88 -0
- package/lib/cjs/ActivityIndicator/index.js +91 -23
- package/lib/cjs/ActivityIndicator/shared.js +12 -1
- package/lib/cjs/ActivityIndicator/sharedProptypes.js +67 -0
- package/lib/cjs/Card/Card.js +38 -45
- package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +1 -1
- package/lib/cjs/List/ListItemMark.js +13 -2
- package/lib/cjs/MultiSelectFilter/ModalOverlay.js +19 -5
- package/lib/cjs/MultiSelectFilter/MultiSelectFilter.js +22 -9
- package/lib/cjs/ToggleSwitch/ToggleSwitch.js +13 -2
- package/lib/cjs/Validator/Validator.js +135 -64
- package/lib/cjs/utils/index.js +9 -1
- package/lib/cjs/utils/useDetectOutsideClick.js +39 -0
- package/lib/cjs/utils/useVariants.js +46 -0
- package/lib/esm/ActivityIndicator/FullScreenIndicator.js +82 -0
- package/lib/esm/ActivityIndicator/InlineIndicator.js +57 -0
- package/lib/esm/ActivityIndicator/OverlayIndicator.js +149 -0
- package/lib/esm/ActivityIndicator/RenderActivityIndicator.js +83 -0
- package/lib/esm/ActivityIndicator/index.js +89 -23
- package/lib/esm/ActivityIndicator/shared.js +11 -0
- package/lib/esm/ActivityIndicator/sharedProptypes.js +61 -0
- package/lib/esm/Card/Card.js +38 -45
- package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +1 -1
- package/lib/esm/List/ListItemMark.js +13 -2
- package/lib/esm/MultiSelectFilter/ModalOverlay.js +19 -5
- package/lib/esm/MultiSelectFilter/MultiSelectFilter.js +22 -9
- package/lib/esm/ToggleSwitch/ToggleSwitch.js +13 -2
- package/lib/esm/Validator/Validator.js +135 -64
- package/lib/esm/utils/index.js +2 -1
- package/lib/esm/utils/useDetectOutsideClick.js +31 -0
- package/lib/esm/utils/useVariants.js +41 -0
- package/lib/package.json +2 -2
- package/package.json +2 -2
- package/src/ActivityIndicator/FullScreenIndicator.jsx +65 -0
- package/src/ActivityIndicator/InlineIndicator.jsx +47 -0
- package/src/ActivityIndicator/OverlayIndicator.jsx +140 -0
- package/src/ActivityIndicator/RenderActivityIndicator.jsx +82 -0
- package/src/ActivityIndicator/index.jsx +113 -32
- package/src/ActivityIndicator/shared.js +11 -0
- package/src/ActivityIndicator/sharedProptypes.js +62 -0
- package/src/Card/Card.jsx +51 -54
- package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +1 -1
- package/src/List/ListItemMark.jsx +18 -2
- package/src/MultiSelectFilter/ModalOverlay.jsx +18 -5
- package/src/MultiSelectFilter/MultiSelectFilter.jsx +21 -10
- package/src/ToggleSwitch/ToggleSwitch.jsx +17 -2
- package/src/Validator/Validator.jsx +172 -85
- package/src/utils/index.js +1 -0
- package/src/utils/useDetectOutsideClick.js +35 -0
- package/src/utils/useVariants.js +44 -0
|
@@ -89,6 +89,7 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
89
89
|
inactive = false,
|
|
90
90
|
rowLimit = 12,
|
|
91
91
|
dictionary = defaultDictionary,
|
|
92
|
+
dismissWhenPressedOutside = false,
|
|
92
93
|
...rest
|
|
93
94
|
},
|
|
94
95
|
ref
|
|
@@ -138,6 +139,7 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
138
139
|
buttonBackgroundColor,
|
|
139
140
|
iconColorSelected,
|
|
140
141
|
buttonBackgroundColorSelected,
|
|
142
|
+
containerBorderColor,
|
|
141
143
|
...restTokens
|
|
142
144
|
} = useThemeTokens(
|
|
143
145
|
'MultiSelectFilter',
|
|
@@ -269,7 +271,7 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
269
271
|
<Row>
|
|
270
272
|
<View>
|
|
271
273
|
<Typography tokens={{ ...headerStyles, lineHeight: headerLineHeight }}>
|
|
272
|
-
{getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label
|
|
274
|
+
{getCopy('filterByLabel').replace(/%\{filterCategory\}/g, label)}
|
|
273
275
|
</Typography>
|
|
274
276
|
</View>
|
|
275
277
|
</Row>
|
|
@@ -285,7 +287,7 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
285
287
|
)}
|
|
286
288
|
<Spacer space={4} />
|
|
287
289
|
<View style={styles.options}>
|
|
288
|
-
<ScrollView onLayout={handleScrollViewLayout}>
|
|
290
|
+
<ScrollView onLayout={handleScrollViewLayout} style={styles.scrollContainer}>
|
|
289
291
|
<Row distribute="between" onLayout={handleRowLayout}>
|
|
290
292
|
{[...Array(colSize).keys()].map((i) => (
|
|
291
293
|
<Col xs={TOTAL_COLUMNS / colSize} key={i}>
|
|
@@ -305,16 +307,14 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
305
307
|
|
|
306
308
|
const controlsContent = (
|
|
307
309
|
<>
|
|
308
|
-
{isScrolling
|
|
310
|
+
{isScrolling && (
|
|
309
311
|
<Divider
|
|
310
312
|
tokens={{
|
|
311
313
|
color: dividerColor
|
|
312
314
|
}}
|
|
313
|
-
space={4}
|
|
314
315
|
/>
|
|
315
|
-
) : (
|
|
316
|
-
<Spacer space={4} />
|
|
317
316
|
)}
|
|
317
|
+
<Spacer space={4} />
|
|
318
318
|
<View style={selectControlsTokens(restTokens)}>
|
|
319
319
|
<Row horizontalAlign={buttonDirection === 'column' ? 'center' : 'start'}>
|
|
320
320
|
<StackView
|
|
@@ -367,7 +367,8 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
367
367
|
minWidth={windowWidth}
|
|
368
368
|
tokens={{
|
|
369
369
|
...tokens,
|
|
370
|
-
maxWidth: true
|
|
370
|
+
maxWidth: true,
|
|
371
|
+
borderColor: containerBorderColor
|
|
371
372
|
}}
|
|
372
373
|
copy={copy}
|
|
373
374
|
isReady={isReady}
|
|
@@ -386,8 +387,9 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
386
387
|
)}
|
|
387
388
|
{isOpen && viewport !== 'xs' && (
|
|
388
389
|
<ModalOverlay
|
|
389
|
-
|
|
390
|
+
dismissWhenPressedOutside={dismissWhenPressedOutside}
|
|
390
391
|
onClose={onClose}
|
|
392
|
+
overlaidPosition={overlaidPosition}
|
|
391
393
|
maxHeight={items.length > MAX_ITEMS_THRESHOLD ? true : maxHeight}
|
|
392
394
|
maxHeightSize={maxHeightSize}
|
|
393
395
|
maxWidthSize={maxWidthSize}
|
|
@@ -395,7 +397,8 @@ const MultiSelectFilter = React.forwardRef(
|
|
|
395
397
|
minWidth={minWidth}
|
|
396
398
|
tokens={{
|
|
397
399
|
...tokens,
|
|
398
|
-
maxWidth: items.length > MAX_ITEMS_THRESHOLD ? true : maxWidth
|
|
400
|
+
maxWidth: items.length > MAX_ITEMS_THRESHOLD ? true : maxWidth,
|
|
401
|
+
borderColor: containerBorderColor
|
|
399
402
|
}}
|
|
400
403
|
copy={copy}
|
|
401
404
|
isReady={isReady}
|
|
@@ -428,6 +431,9 @@ const styles = StyleSheet.create({
|
|
|
428
431
|
},
|
|
429
432
|
options: {
|
|
430
433
|
flex: 1
|
|
434
|
+
},
|
|
435
|
+
scrollContainer: {
|
|
436
|
+
padding: 1
|
|
431
437
|
}
|
|
432
438
|
})
|
|
433
439
|
|
|
@@ -539,7 +545,12 @@ MultiSelectFilter.propTypes = {
|
|
|
539
545
|
* Sets the maximum number of items in one column. If number of items are more
|
|
540
546
|
* than the `rowLimit`, they will be rendered in 2 columns.
|
|
541
547
|
*/
|
|
542
|
-
rowLimit: PropTypes.number
|
|
548
|
+
rowLimit: PropTypes.number,
|
|
549
|
+
/**
|
|
550
|
+
* If true, clicking outside the content will trigger the a close callback, dismissing the content.
|
|
551
|
+
* @deprecated This parameter will be removed in the next major release; detection will be always enabled by default.
|
|
552
|
+
*/
|
|
553
|
+
dismissWhenPressedOutside: PropTypes.bool
|
|
543
554
|
}
|
|
544
555
|
|
|
545
556
|
export default MultiSelectFilter
|
|
@@ -77,6 +77,18 @@ const selectSwitchStyles = ({
|
|
|
77
77
|
})
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
+
const selectMobileTokens = (tokens) => ({
|
|
81
|
+
...Platform.select({
|
|
82
|
+
web: {},
|
|
83
|
+
default: {
|
|
84
|
+
switchSize: tokens?.mobileSwitchSize,
|
|
85
|
+
trackHeight: tokens?.mobileTrackHeight,
|
|
86
|
+
width: tokens?.mobileWidth,
|
|
87
|
+
trackBorderWidth: tokens?.mobileTrackBorderWidth
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
80
92
|
const selectLabelStyles = ({ labelMarginLeft }) => ({ marginLeft: labelMarginLeft })
|
|
81
93
|
const selectLabelTokens = ({
|
|
82
94
|
labelColor,
|
|
@@ -123,7 +135,10 @@ const ToggleSwitch = React.forwardRef(
|
|
|
123
135
|
|
|
124
136
|
const handlePress = (event) => setValue(!currentValue, event)
|
|
125
137
|
const getButtonTokens = (buttonState) =>
|
|
126
|
-
selectButtonTokens(
|
|
138
|
+
selectButtonTokens(
|
|
139
|
+
getTokens(buttonState, selectMobileTokens(themeTokens)),
|
|
140
|
+
getTokens(themeTokens, selectMobileTokens(themeTokens))
|
|
141
|
+
)
|
|
127
142
|
const uniqueId = useUniqueId('toggleSwitch')
|
|
128
143
|
const inputId = id ?? uniqueId
|
|
129
144
|
|
|
@@ -157,7 +172,7 @@ const ToggleSwitch = React.forwardRef(
|
|
|
157
172
|
{...selectProps(rest)}
|
|
158
173
|
>
|
|
159
174
|
{(buttonState) => {
|
|
160
|
-
const stateTokens = getTokens(buttonState)
|
|
175
|
+
const stateTokens = getTokens(buttonState, selectMobileTokens(themeTokens))
|
|
161
176
|
const IconComponent = stateTokens.icon
|
|
162
177
|
const switchStyles = selectSwitchStyles(stateTokens)
|
|
163
178
|
const trackStyles = selectTrackStyles(stateTokens)
|
|
@@ -24,7 +24,7 @@ const Validator = React.forwardRef(
|
|
|
24
24
|
|
|
25
25
|
const { supportsProps } = selectProps(rest)
|
|
26
26
|
const strValidation = supportsProps.validation
|
|
27
|
-
const [
|
|
27
|
+
const [, setIndividualCodes] = React.useState({})
|
|
28
28
|
const [text, setText] = React.useState(value)
|
|
29
29
|
const validatorsLength = 6
|
|
30
30
|
const prefix = 'code'
|
|
@@ -44,75 +44,110 @@ const Validator = React.forwardRef(
|
|
|
44
44
|
const [codeReferences, singleCodes] = React.useMemo(() => {
|
|
45
45
|
const codes = []
|
|
46
46
|
const valueCodes = {}
|
|
47
|
-
|
|
47
|
+
Array.from({ length: validatorsLength }, (_, i) => {
|
|
48
48
|
codes[prefix + i] = React.createRef()
|
|
49
49
|
valueCodes[prefix + i] = ''
|
|
50
50
|
valueCodes[prefix + i + sufixValidation] = ''
|
|
51
|
-
|
|
52
|
-
return [codes, valueCodes]
|
|
53
|
-
}, [])
|
|
54
|
-
|
|
55
|
-
const handleSingleCodes = (codeId, val, validation) => {
|
|
56
|
-
singleCodes[codeId] = val
|
|
57
|
-
singleCodes[codeId + sufixValidation] = validation
|
|
58
|
-
/* eslint-disable no-unused-expressions */
|
|
59
|
-
setIndividualCodes({
|
|
60
|
-
...individualCodes,
|
|
61
|
-
[codeId]: val
|
|
51
|
+
return null
|
|
62
52
|
})
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const changeDataMasking = (boxElement) => {
|
|
66
|
-
let charMasking = ''
|
|
67
|
-
const element = boxElement
|
|
68
|
-
if (mask && mask.length === 1) charMasking = mask
|
|
69
|
-
else if (mask && mask.length > 1) charMasking = mask.substring(0, 1)
|
|
53
|
+
return [codes, valueCodes]
|
|
54
|
+
}, [validatorsLength, prefix, sufixValidation])
|
|
70
55
|
|
|
71
|
-
|
|
72
|
-
|
|
56
|
+
const handleSingleCodes = React.useCallback(
|
|
57
|
+
(codeId, val, validation) => {
|
|
58
|
+
singleCodes[codeId] = val
|
|
59
|
+
singleCodes[codeId + sufixValidation] = validation
|
|
60
|
+
setIndividualCodes((prev) => ({
|
|
61
|
+
...prev,
|
|
62
|
+
[codeId]: val
|
|
63
|
+
}))
|
|
64
|
+
},
|
|
65
|
+
[singleCodes, sufixValidation]
|
|
66
|
+
)
|
|
73
67
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
68
|
+
const changeDataMasking = React.useCallback(
|
|
69
|
+
(boxElement) => {
|
|
70
|
+
let charMasking = ''
|
|
71
|
+
const element = boxElement
|
|
72
|
+
if (mask && mask.length === 1) {
|
|
73
|
+
charMasking = mask
|
|
74
|
+
} else if (mask && mask.length > 1) {
|
|
75
|
+
charMasking = mask.substring(0, 1)
|
|
76
|
+
}
|
|
79
77
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
78
|
+
if (charMasking && element) {
|
|
79
|
+
element.value = charMasking
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
[mask]
|
|
83
|
+
)
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const handleChangeCode = React.useCallback(() => {
|
|
86
|
+
const code = Array.from(
|
|
87
|
+
{ length: validatorsLength },
|
|
88
|
+
(_, i) => singleCodes[prefix + i] || ''
|
|
89
|
+
).join('')
|
|
90
|
+
if (typeof onChange === 'function') {
|
|
91
|
+
onChange(code, singleCodes)
|
|
87
92
|
}
|
|
93
|
+
}, [validatorsLength, singleCodes, prefix, onChange])
|
|
88
94
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
95
|
+
const handleChangeCodeValues = React.useCallback(
|
|
96
|
+
(event, codeId, nextIndex) => {
|
|
97
|
+
const codeElement = codeReferences[codeId]?.current
|
|
98
|
+
const val = event.nativeEvent?.value || event.target?.value
|
|
99
|
+
|
|
100
|
+
// Only allow numeric characters and limit to single digit
|
|
101
|
+
const numericOnly = val.replace(/\D/g, '').substring(0, 1)
|
|
102
|
+
|
|
103
|
+
if (codeElement && codeElement.value) {
|
|
104
|
+
codeElement.value = numericOnly
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
handleSingleCodes(codeId, numericOnly, numericOnly ? 'success' : '')
|
|
108
|
+
handleChangeCode()
|
|
109
|
+
|
|
110
|
+
if (nextIndex === validatorsLength) {
|
|
111
|
+
codeElement?.blur()
|
|
112
|
+
changeDataMasking(codeElement)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (numericOnly.length > 0) {
|
|
117
|
+
const nextElement = codeReferences[prefix + nextIndex]?.current
|
|
118
|
+
nextElement?.focus()
|
|
119
|
+
changeDataMasking(codeElement)
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
[
|
|
123
|
+
codeReferences,
|
|
124
|
+
handleSingleCodes,
|
|
125
|
+
handleChangeCode,
|
|
126
|
+
validatorsLength,
|
|
127
|
+
changeDataMasking,
|
|
128
|
+
prefix
|
|
129
|
+
]
|
|
130
|
+
)
|
|
101
131
|
|
|
102
132
|
const handleKeyPress = (event, currentIndex, previousIndex) => {
|
|
103
|
-
if (!(event.keyCode === 8 || event.code === 'Backspace'))
|
|
133
|
+
if (!(event.keyCode === 8 || event.code === 'Backspace')) {
|
|
134
|
+
return
|
|
135
|
+
}
|
|
104
136
|
if (currentIndex > 0) {
|
|
105
|
-
codeReferences[prefix + currentIndex]
|
|
106
|
-
codeReferences[prefix + previousIndex]
|
|
137
|
+
const currentElement = codeReferences[prefix + currentIndex]?.current
|
|
138
|
+
const previousElement = codeReferences[prefix + previousIndex]?.current
|
|
139
|
+
|
|
140
|
+
if (currentElement && currentElement.value) {
|
|
141
|
+
currentElement.value = ''
|
|
142
|
+
}
|
|
143
|
+
previousElement?.focus()
|
|
107
144
|
}
|
|
108
145
|
handleSingleCodes(prefix + currentIndex, '', '')
|
|
109
146
|
handleChangeCode()
|
|
110
147
|
}
|
|
111
148
|
|
|
112
149
|
const getCodeComponents = () => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
for (let i = 0; validatorsLength && i < validatorsLength; i += 1) {
|
|
150
|
+
return Array.from({ length: validatorsLength }, (_, i) => {
|
|
116
151
|
const codeId = prefix + i
|
|
117
152
|
const codeInputProps = {
|
|
118
153
|
nativeID: codeId,
|
|
@@ -120,75 +155,127 @@ const Validator = React.forwardRef(
|
|
|
120
155
|
ref: codeReferences[codeId] ?? null,
|
|
121
156
|
validation: strValidation || singleCodes[codeId + sufixValidation],
|
|
122
157
|
tokens: selectCodeTextInputTokens(themeTokens),
|
|
123
|
-
onFocus: () =>
|
|
158
|
+
onFocus: () => {
|
|
159
|
+
const element = codeReferences[codeId]?.current
|
|
160
|
+
if (Platform.OS === 'web' && element?.select) {
|
|
161
|
+
return element.select() ?? null
|
|
162
|
+
}
|
|
163
|
+
if (element?.focus) {
|
|
164
|
+
return element.focus() ?? null
|
|
165
|
+
}
|
|
166
|
+
return null
|
|
167
|
+
},
|
|
124
168
|
onKeyPress: (event) => handleKeyPress(event, i, i - 1),
|
|
125
169
|
onMouseOver: handleMouseOver,
|
|
126
170
|
onMouseOut: handleMouseOut,
|
|
127
171
|
inactive
|
|
128
172
|
}
|
|
129
|
-
codeInputProps.validation
|
|
173
|
+
if (!codeInputProps.validation) {
|
|
174
|
+
delete codeInputProps.validation
|
|
175
|
+
}
|
|
130
176
|
|
|
131
|
-
|
|
177
|
+
return (
|
|
132
178
|
<View key={codeId} style={staticStyles.codeInputWidth}>
|
|
133
179
|
<TextInput {...codeInputProps} />
|
|
134
180
|
</View>
|
|
135
181
|
)
|
|
136
|
-
}
|
|
137
|
-
return components
|
|
182
|
+
})
|
|
138
183
|
}
|
|
139
184
|
|
|
140
185
|
React.useEffect(() => {
|
|
141
|
-
|
|
142
|
-
|
|
186
|
+
if (Number(value).toString() !== 'NaN') {
|
|
187
|
+
setText(value)
|
|
188
|
+
}
|
|
143
189
|
}, [value])
|
|
144
190
|
|
|
145
|
-
/* eslint-disable react-hooks/exhaustive-deps */
|
|
146
191
|
React.useEffect(() => {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
192
|
+
Array.from({ length: validatorsLength }, (_, i) => {
|
|
193
|
+
const element = codeReferences[prefix + i]?.current
|
|
194
|
+
if (element && element.value !== undefined) {
|
|
195
|
+
if (mask && text[i]) {
|
|
196
|
+
element.value = mask
|
|
197
|
+
} else {
|
|
198
|
+
element.value = text[i] ?? ''
|
|
199
|
+
}
|
|
200
|
+
}
|
|
150
201
|
handleSingleCodes(prefix + i, text[i] ?? '', text[i] ? 'success' : '')
|
|
151
|
-
|
|
152
|
-
|
|
202
|
+
return null
|
|
203
|
+
})
|
|
204
|
+
}, [text, mask, validatorsLength, prefix, codeReferences, handleSingleCodes])
|
|
153
205
|
|
|
154
|
-
/* eslint-disable react-hooks/exhaustive-deps */
|
|
155
206
|
React.useEffect(() => {
|
|
156
207
|
const handlePasteCode = (event) => {
|
|
208
|
+
event.preventDefault()
|
|
209
|
+
|
|
210
|
+
// Clear current state first
|
|
157
211
|
setText('')
|
|
212
|
+
|
|
213
|
+
// Clear all individual input fields and their state
|
|
214
|
+
Array.from({ length: validatorsLength }, (_, i) => {
|
|
215
|
+
const element = codeReferences[prefix + i]?.current
|
|
216
|
+
if (element && element.value !== undefined) {
|
|
217
|
+
element.value = ''
|
|
218
|
+
}
|
|
219
|
+
handleSingleCodes(prefix + i, '', '')
|
|
220
|
+
return null
|
|
221
|
+
})
|
|
222
|
+
|
|
158
223
|
const clipBoardText = event.clipboardData.getData('text')
|
|
159
|
-
|
|
224
|
+
|
|
225
|
+
// Validate that input contains only digits and truncate to 6 characters
|
|
226
|
+
const numericOnly = clipBoardText.replace(/\D/g, '').substring(0, validatorsLength)
|
|
227
|
+
|
|
228
|
+
if (numericOnly.length > 0) {
|
|
229
|
+
setText(numericOnly)
|
|
230
|
+
}
|
|
160
231
|
}
|
|
161
232
|
|
|
162
233
|
const handleCopy = (event) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
234
|
+
const clipBoardText = Array.from(
|
|
235
|
+
{ length: validatorsLength },
|
|
236
|
+
(_, i) => singleCodes[prefix + i] || ''
|
|
237
|
+
).join('')
|
|
166
238
|
event.clipboardData.setData('text/plain', clipBoardText)
|
|
167
239
|
event.preventDefault()
|
|
168
240
|
}
|
|
169
241
|
|
|
170
242
|
if (Platform.OS === 'web') {
|
|
171
|
-
|
|
172
|
-
codeReferences[prefix + i]
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return () => {
|
|
181
|
-
if (Platform.oldValue === 'web') {
|
|
182
|
-
for (let i = 0; i < validatorsLength; i += 1) {
|
|
183
|
-
codeReferences[prefix + i]?.current?.removeEventListener('paste', handlePasteCode)
|
|
184
|
-
codeReferences[prefix + i]?.current?.removeEventListener('copy', handleCopy)
|
|
185
|
-
codeReferences[prefix + i]?.current?.removeEventListener('input', (event) =>
|
|
243
|
+
Array.from({ length: validatorsLength }, (_, i) => {
|
|
244
|
+
const element = codeReferences[prefix + i]?.current
|
|
245
|
+
if (element && typeof element.addEventListener === 'function') {
|
|
246
|
+
element.addEventListener('paste', handlePasteCode)
|
|
247
|
+
element.addEventListener('copy', handleCopy)
|
|
248
|
+
element.addEventListener('input', (event) =>
|
|
186
249
|
handleChangeCodeValues(event, prefix + i, i + 1)
|
|
187
250
|
)
|
|
188
251
|
}
|
|
252
|
+
return null
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return () => {
|
|
257
|
+
if (Platform.OS === 'web') {
|
|
258
|
+
Array.from({ length: validatorsLength }, (_, i) => {
|
|
259
|
+
const element = codeReferences[prefix + i]?.current
|
|
260
|
+
if (element && typeof element.removeEventListener === 'function') {
|
|
261
|
+
element.removeEventListener('paste', handlePasteCode)
|
|
262
|
+
element.removeEventListener('copy', handleCopy)
|
|
263
|
+
element.removeEventListener('input', (event) =>
|
|
264
|
+
handleChangeCodeValues(event, prefix + i, i + 1)
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
return null
|
|
268
|
+
})
|
|
189
269
|
}
|
|
190
270
|
}
|
|
191
|
-
}, [
|
|
271
|
+
}, [
|
|
272
|
+
validatorsLength,
|
|
273
|
+
prefix,
|
|
274
|
+
codeReferences,
|
|
275
|
+
handleChangeCodeValues,
|
|
276
|
+
handleSingleCodes,
|
|
277
|
+
singleCodes
|
|
278
|
+
])
|
|
192
279
|
|
|
193
280
|
return (
|
|
194
281
|
<InputSupports
|
package/src/utils/index.js
CHANGED
|
@@ -24,3 +24,4 @@ export { transformGradient } from './transformGradient'
|
|
|
24
24
|
export { default as convertFromMegaByteToByte } from './convertFromMegaByteToByte'
|
|
25
25
|
export { default as formatImageSource } from './formatImageSource'
|
|
26
26
|
export { default as getSpacingScale } from './getSpacingScale'
|
|
27
|
+
export { default as useVariants } from './useVariants'
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Platform } from 'react-native'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hook to detect clicks outside of a ref, only on web.
|
|
6
|
+
*
|
|
7
|
+
* @param {React.RefObject<HTMLElement>} ref
|
|
8
|
+
* Reference to the element you want to “protect.”
|
|
9
|
+
* @param {() => void} onOutside
|
|
10
|
+
* Callback invoked when a click occurs outside that ref.
|
|
11
|
+
* @param {boolean} [enabled=true]
|
|
12
|
+
* Flag to enable or disable the outside-click detection at runtime.
|
|
13
|
+
* @deprecated Will be removed in next major release; detection will always be enabled.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
function useDetectOutsideClick(ref, onOutside, enabled = true) {
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
if (!enabled || Platform.OS !== 'web') {
|
|
19
|
+
return undefined
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const handleClickOutside = (e) => {
|
|
23
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
24
|
+
onOutside()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
document.addEventListener('mousedown', handleClickOutside)
|
|
29
|
+
return () => {
|
|
30
|
+
document.removeEventListener('mousedown', handleClickOutside)
|
|
31
|
+
}
|
|
32
|
+
}, [ref, onOutside, enabled])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default useDetectOutsideClick
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { getComponentTheme, useTheme } from '../ThemeProvider'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a label string for a variant based on the provided key and value.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} key - The name of the variant.
|
|
7
|
+
* @param {*} value - The value of the variant. If it's a string, it will be appended to the key.
|
|
8
|
+
* @returns {string} The formatted variant label (e.g., "color: red" or "size").
|
|
9
|
+
*/
|
|
10
|
+
const getVariantLabel = (key, value) => `${key}${typeof value === 'string' ? `: ${value}` : ''}`
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Retrieves the variant options for a given component from the theme.
|
|
14
|
+
*
|
|
15
|
+
* @param {string} componentName - The name of the component to get variants for.
|
|
16
|
+
* @returns {Array<Array>} An array of variant tuples. Each tuple contains:
|
|
17
|
+
* - {string|undefined} The variant key (e.g., 'size', 'color', or undefined for default).
|
|
18
|
+
* - {string|undefined} The variant value (e.g., 'small', 'primary', or undefined for default).
|
|
19
|
+
* - {string} The human-readable label for the variant.
|
|
20
|
+
* Returns [['default', {}]] if no componentName is provided.
|
|
21
|
+
* @throws {Error} If the theme does not define appearances for the given component.
|
|
22
|
+
*/
|
|
23
|
+
const useVariants = (componentName) => {
|
|
24
|
+
const theme = useTheme()
|
|
25
|
+
if (!componentName) return [['default', {}]]
|
|
26
|
+
|
|
27
|
+
const { appearances } = getComponentTheme(theme, componentName)
|
|
28
|
+
if (!appearances) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Theme ${theme.metadata?.name} does not have any appearances set for ${componentName}`
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const variants = Object.entries(appearances).reduce(
|
|
35
|
+
(pairs, [key, { values, type } = {}]) =>
|
|
36
|
+
type === 'variant'
|
|
37
|
+
? [...pairs, ...values.map((value) => [key, value, getVariantLabel(key, value)])]
|
|
38
|
+
: pairs,
|
|
39
|
+
[[undefined, undefined, 'default style']]
|
|
40
|
+
)
|
|
41
|
+
return variants
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default useVariants
|