@utilitywarehouse/hearth-react-native 0.31.0 → 0.32.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-lint.log +13 -13
- package/CHANGELOG.md +71 -0
- package/build/components/Rating/Rating.d.ts +6 -0
- package/build/components/Rating/Rating.js +76 -0
- package/build/components/Rating/Rating.props.d.ts +18 -0
- package/build/components/Rating/Rating.props.js +1 -0
- package/build/components/Rating/RatingStarEmpty.d.ts +6 -0
- package/build/components/Rating/RatingStarEmpty.js +9 -0
- package/build/components/Rating/RatingStarFilled.d.ts +6 -0
- package/build/components/Rating/RatingStarFilled.js +9 -0
- package/build/components/Rating/index.d.ts +2 -0
- package/build/components/Rating/index.js +1 -0
- package/build/components/Roundel/Roundel.d.ts +6 -0
- package/build/components/Roundel/Roundel.js +40 -0
- package/build/components/Roundel/Roundel.props.d.ts +6 -0
- package/build/components/Roundel/Roundel.props.js +1 -0
- package/build/components/Roundel/index.d.ts +2 -0
- package/build/components/Roundel/index.js +1 -0
- package/build/components/StepperInput/StepperButton.d.ts +22 -0
- package/build/components/StepperInput/StepperButton.js +55 -0
- package/build/components/StepperInput/StepperInput.d.ts +6 -0
- package/build/components/StepperInput/StepperInput.js +196 -0
- package/build/components/StepperInput/StepperInput.props.d.ts +31 -0
- package/build/components/StepperInput/StepperInput.props.js +1 -0
- package/build/components/StepperInput/index.d.ts +2 -0
- package/build/components/StepperInput/index.js +1 -0
- package/build/components/Table/TableHeaderCell.js +10 -1
- package/build/components/Textarea/Textarea.d.ts +1 -1
- package/build/components/Textarea/Textarea.js +10 -3
- package/build/components/Textarea/Textarea.props.d.ts +11 -0
- package/build/components/index.d.ts +3 -0
- package/build/components/index.js +3 -0
- package/build/core/themes.d.ts +92 -88
- package/build/tokens/color.d.ts +82 -80
- package/build/tokens/color.js +41 -40
- package/build/tokens/components/dark/alert.d.ts +6 -6
- package/build/tokens/components/dark/alert.js +6 -6
- package/build/tokens/components/dark/bottom-navigation.d.ts +2 -2
- package/build/tokens/components/dark/bottom-navigation.js +2 -2
- package/build/tokens/components/dark/checkbox.d.ts +1 -1
- package/build/tokens/components/dark/checkbox.js +1 -1
- package/build/tokens/components/dark/icon-button.d.ts +3 -3
- package/build/tokens/components/dark/icon-button.js +3 -3
- package/build/tokens/components/dark/inline-link.d.ts +1 -1
- package/build/tokens/components/dark/inline-link.js +1 -1
- package/build/tokens/components/dark/link.d.ts +3 -3
- package/build/tokens/components/dark/link.js +3 -3
- package/build/tokens/components/dark/navigation.d.ts +2 -2
- package/build/tokens/components/dark/navigation.js +2 -2
- package/build/tokens/components/dark/parts.d.ts +2 -2
- package/build/tokens/components/dark/parts.js +2 -2
- package/build/tokens/components/dark/progress-bar.d.ts +3 -3
- package/build/tokens/components/dark/progress-bar.js +3 -3
- package/build/tokens/components/dark/progress-stepper.d.ts +1 -1
- package/build/tokens/components/dark/progress-stepper.js +1 -1
- package/build/tokens/components/dark/spinner.d.ts +1 -1
- package/build/tokens/components/dark/spinner.js +1 -1
- package/build/tokens/components/dark/table.d.ts +2 -0
- package/build/tokens/components/dark/table.js +2 -0
- package/build/tokens/components/dark/time-picker.d.ts +1 -0
- package/build/tokens/components/dark/time-picker.js +1 -0
- package/build/tokens/components/light/parts.d.ts +3 -3
- package/build/tokens/components/light/parts.js +3 -3
- package/build/tokens/components/light/table.d.ts +2 -0
- package/build/tokens/components/light/table.js +2 -0
- package/build/tokens/components/light/time-picker.d.ts +1 -0
- package/build/tokens/components/light/time-picker.js +1 -0
- package/build/tokens/semantic-dark.d.ts +40 -40
- package/build/tokens/semantic-dark.js +40 -40
- package/docs/adding-shadows.mdx +2 -2
- package/docs/changelog.mdx +165 -0
- package/docs/components/AllComponents.web.tsx +30 -1
- package/docs/dark-mode-best-practice.mdx +328 -0
- package/package.json +1 -1
- package/src/components/Modal/Modal.docs.mdx +58 -4
- package/src/components/NavModal/NavModal.docs.mdx +2 -2
- package/src/components/Rating/Rating.docs.mdx +178 -0
- package/src/components/Rating/Rating.figma.tsx +20 -0
- package/src/components/Rating/Rating.props.ts +22 -0
- package/src/components/Rating/Rating.stories.tsx +95 -0
- package/src/components/Rating/Rating.tsx +140 -0
- package/src/components/Rating/RatingStarEmpty.tsx +22 -0
- package/src/components/Rating/RatingStarFilled.tsx +27 -0
- package/src/components/Rating/index.ts +2 -0
- package/src/components/Roundel/Roundel.docs.mdx +48 -0
- package/src/components/Roundel/Roundel.figma.tsx +17 -0
- package/src/components/Roundel/Roundel.props.ts +8 -0
- package/src/components/Roundel/Roundel.stories.tsx +49 -0
- package/src/components/Roundel/Roundel.tsx +51 -0
- package/src/components/Roundel/index.ts +2 -0
- package/src/components/StepperInput/StepperButton.tsx +83 -0
- package/src/components/StepperInput/StepperInput.docs.mdx +121 -0
- package/src/components/StepperInput/StepperInput.figma.tsx +45 -0
- package/src/components/StepperInput/StepperInput.props.ts +39 -0
- package/src/components/StepperInput/StepperInput.stories.tsx +270 -0
- package/src/components/StepperInput/StepperInput.tsx +349 -0
- package/src/components/StepperInput/index.ts +2 -0
- package/src/components/Table/TableHeaderCell.tsx +10 -1
- package/src/components/Textarea/Textarea.docs.mdx +2 -0
- package/src/components/Textarea/Textarea.props.ts +11 -0
- package/src/components/Textarea/Textarea.stories.tsx +14 -0
- package/src/components/Textarea/Textarea.tsx +11 -2
- package/src/components/index.ts +3 -0
- package/src/tokens/color.ts +41 -40
- package/src/tokens/components/dark/alert.ts +6 -6
- package/src/tokens/components/dark/bottom-navigation.ts +2 -2
- package/src/tokens/components/dark/checkbox.ts +1 -1
- package/src/tokens/components/dark/icon-button.ts +3 -3
- package/src/tokens/components/dark/inline-link.ts +1 -1
- package/src/tokens/components/dark/link.ts +3 -3
- package/src/tokens/components/dark/navigation.ts +2 -2
- package/src/tokens/components/dark/parts.ts +2 -2
- package/src/tokens/components/dark/progress-bar.ts +3 -3
- package/src/tokens/components/dark/progress-stepper.ts +1 -1
- package/src/tokens/components/dark/spinner.ts +1 -1
- package/src/tokens/components/dark/table.ts +2 -0
- package/src/tokens/components/dark/time-picker.ts +1 -0
- package/src/tokens/components/light/parts.ts +3 -3
- package/src/tokens/components/light/table.ts +2 -0
- package/src/tokens/components/light/time-picker.ts +1 -0
- package/src/tokens/semantic-dark.ts +40 -40
- package/vercel.json +0 -21
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { AddSmallIcon, MinusSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
|
|
2
|
+
import { useEffect, useImperativeHandle, useRef, useState } from 'react';
|
|
3
|
+
import type { TextInput, TextInputFocusEvent } from 'react-native';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
6
|
+
import { FormField } from '../FormField';
|
|
7
|
+
import { InputComponent, InputField } from '../Input/Input';
|
|
8
|
+
import StepperButton from './StepperButton';
|
|
9
|
+
import StepperInputProps from './StepperInput.props';
|
|
10
|
+
|
|
11
|
+
const normalizeValue = (value?: string | number) => {
|
|
12
|
+
if (value === undefined || value === null || value === '') {
|
|
13
|
+
return '';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return `${value}`;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const getDecimalPlaces = (value?: number | string) => {
|
|
20
|
+
if (value === undefined || value === null || value === '') {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const normalizedValue = `${value}`;
|
|
25
|
+
const decimalPart = normalizedValue.split('.')[1];
|
|
26
|
+
|
|
27
|
+
return decimalPart ? decimalPart.length : 0;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const formatNumber = (value: number, precision: number) => {
|
|
31
|
+
if (precision <= 0) {
|
|
32
|
+
return `${Math.trunc(value)}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return value
|
|
36
|
+
.toFixed(precision)
|
|
37
|
+
.replace(/\.0+$/, '')
|
|
38
|
+
.replace(/(\.\d*?)0+$/, '$1');
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const sanitizeValue = (value: string, allowNegative: boolean, allowDecimal: boolean) => {
|
|
42
|
+
const strippedValue = value.replace(
|
|
43
|
+
allowDecimal ? /[^\d,.-]/g : allowNegative ? /[^\d-]/g : /\D/g,
|
|
44
|
+
''
|
|
45
|
+
);
|
|
46
|
+
const normalizedValue = allowDecimal ? strippedValue.replace(/,/g, '.') : strippedValue;
|
|
47
|
+
|
|
48
|
+
if (!allowNegative) {
|
|
49
|
+
const unsignedValue = normalizedValue.replace(/-/g, '');
|
|
50
|
+
|
|
51
|
+
if (!allowDecimal) {
|
|
52
|
+
return unsignedValue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const [integerPart = '', ...decimalParts] = unsignedValue.split('.');
|
|
56
|
+
const decimalPart = decimalParts.join('');
|
|
57
|
+
|
|
58
|
+
return decimalParts.length > 0 ? `${integerPart}.${decimalPart}` : integerPart;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const hasLeadingMinus = normalizedValue.startsWith('-');
|
|
62
|
+
const unsignedValue = normalizedValue.replace(/-/g, '');
|
|
63
|
+
|
|
64
|
+
if (!allowDecimal) {
|
|
65
|
+
return `${hasLeadingMinus ? '-' : ''}${unsignedValue}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const [integerPart = '', ...decimalParts] = unsignedValue.split('.');
|
|
69
|
+
const decimalPart = decimalParts.join('');
|
|
70
|
+
const composedValue = decimalParts.length > 0 ? `${integerPart}.${decimalPart}` : integerPart;
|
|
71
|
+
|
|
72
|
+
return `${hasLeadingMinus ? '-' : ''}${composedValue}`;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const parseValue = (value: string) => {
|
|
76
|
+
if (!value || value === '-' || value === '.' || value === '-.') {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const parsedValue = Number(value);
|
|
81
|
+
return Number.isNaN(parsedValue) ? null : parsedValue;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const clampValue = (value: number, min?: number, max?: number) => {
|
|
85
|
+
let nextValue = value;
|
|
86
|
+
|
|
87
|
+
if (typeof min === 'number') {
|
|
88
|
+
nextValue = Math.max(min, nextValue);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (typeof max === 'number') {
|
|
92
|
+
nextValue = Math.min(max, nextValue);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return nextValue;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const StepperInput = ({
|
|
99
|
+
value,
|
|
100
|
+
defaultValue,
|
|
101
|
+
onChangeText,
|
|
102
|
+
onChangeValue,
|
|
103
|
+
min,
|
|
104
|
+
max,
|
|
105
|
+
step = 1,
|
|
106
|
+
focusInputOnStepPress = false,
|
|
107
|
+
validationStatus = 'initial',
|
|
108
|
+
disabled = false,
|
|
109
|
+
readonly = false,
|
|
110
|
+
focused = false,
|
|
111
|
+
inBottomSheet = false,
|
|
112
|
+
required = true,
|
|
113
|
+
label,
|
|
114
|
+
labelVariant = 'body',
|
|
115
|
+
helperText,
|
|
116
|
+
helperIcon,
|
|
117
|
+
validText,
|
|
118
|
+
invalidText,
|
|
119
|
+
style,
|
|
120
|
+
decrementAccessibilityLabel = 'Decrease value',
|
|
121
|
+
incrementAccessibilityLabel = 'Increase value',
|
|
122
|
+
onFocus,
|
|
123
|
+
onBlur,
|
|
124
|
+
ref,
|
|
125
|
+
...props
|
|
126
|
+
}: StepperInputProps) => {
|
|
127
|
+
const inputRef = useRef<TextInput>(null);
|
|
128
|
+
const isControlled = value !== undefined;
|
|
129
|
+
const [internalValue, setInternalValue] = useState(() => normalizeValue(defaultValue));
|
|
130
|
+
const [isInputFocused, setIsInputFocused] = useState(false);
|
|
131
|
+
|
|
132
|
+
const displayValue = isControlled ? normalizeValue(value) : internalValue;
|
|
133
|
+
const parsedValue = parseValue(displayValue);
|
|
134
|
+
const resolvedFocused = focused || isInputFocused;
|
|
135
|
+
const allowNegative = typeof min !== 'number' || min < 0 || (typeof max === 'number' && max < 0);
|
|
136
|
+
const decimalPrecision = Math.max(
|
|
137
|
+
getDecimalPlaces(value),
|
|
138
|
+
getDecimalPlaces(defaultValue),
|
|
139
|
+
getDecimalPlaces(min),
|
|
140
|
+
getDecimalPlaces(max),
|
|
141
|
+
getDecimalPlaces(step)
|
|
142
|
+
);
|
|
143
|
+
const allowDecimal = decimalPrecision > 0;
|
|
144
|
+
const keyboardType = allowNegative || allowDecimal ? 'numeric' : 'number-pad';
|
|
145
|
+
const inputMode = allowDecimal ? 'decimal' : 'numeric';
|
|
146
|
+
|
|
147
|
+
useImperativeHandle(ref, () => inputRef.current as TextInput, []);
|
|
148
|
+
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
if (!isControlled && defaultValue !== undefined) {
|
|
151
|
+
setInternalValue(normalizeValue(defaultValue));
|
|
152
|
+
}
|
|
153
|
+
}, [defaultValue, isControlled]);
|
|
154
|
+
|
|
155
|
+
const updateValue = (nextValue: string) => {
|
|
156
|
+
if (!isControlled) {
|
|
157
|
+
setInternalValue(nextValue);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
onChangeText?.(nextValue);
|
|
161
|
+
|
|
162
|
+
const nextParsedValue = parseValue(nextValue);
|
|
163
|
+
if (nextParsedValue !== null) {
|
|
164
|
+
onChangeValue?.(clampValue(nextParsedValue, min, max));
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const handleChangeText = (nextText: string) => {
|
|
169
|
+
const sanitizedValue = sanitizeValue(nextText, allowNegative, allowDecimal);
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
sanitizedValue === '' ||
|
|
173
|
+
sanitizedValue === '-' ||
|
|
174
|
+
sanitizedValue === '.' ||
|
|
175
|
+
sanitizedValue === '-.' ||
|
|
176
|
+
(allowDecimal && sanitizedValue.endsWith('.'))
|
|
177
|
+
) {
|
|
178
|
+
updateValue(sanitizedValue);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const nextParsedValue = parseValue(sanitizedValue);
|
|
183
|
+
|
|
184
|
+
if (nextParsedValue === null) {
|
|
185
|
+
updateValue(sanitizedValue);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const clampedText = formatNumber(clampValue(nextParsedValue, min, max), decimalPrecision);
|
|
190
|
+
updateValue(clampedText);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const handleStepPress = (direction: 1 | -1) => {
|
|
194
|
+
const baseValue = parsedValue ?? (typeof min === 'number' ? min : 0);
|
|
195
|
+
const nextValue = clampValue(baseValue + direction * step, min, max);
|
|
196
|
+
const normalizedValue = formatNumber(nextValue, decimalPrecision);
|
|
197
|
+
|
|
198
|
+
updateValue(normalizedValue);
|
|
199
|
+
if (focusInputOnStepPress) {
|
|
200
|
+
inputRef.current?.focus();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const decrementDisabled =
|
|
205
|
+
disabled || readonly || (typeof min === 'number' && parsedValue !== null && parsedValue <= min);
|
|
206
|
+
const incrementDisabled =
|
|
207
|
+
disabled || readonly || (typeof max === 'number' && parsedValue !== null && parsedValue >= max);
|
|
208
|
+
|
|
209
|
+
const handleFocus = (event: TextInputFocusEvent) => {
|
|
210
|
+
setIsInputFocused(true);
|
|
211
|
+
onFocus?.(event);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const handleBlur = (event: TextInputFocusEvent) => {
|
|
215
|
+
setIsInputFocused(false);
|
|
216
|
+
onBlur?.(event);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const getAccessibilityLabel = () => {
|
|
220
|
+
let accessibilityLabel = '';
|
|
221
|
+
|
|
222
|
+
if (label) {
|
|
223
|
+
accessibilityLabel = accessibilityLabel + label;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (required) {
|
|
227
|
+
accessibilityLabel = accessibilityLabel + ', required';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return accessibilityLabel || props.accessibilityLabel;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const getAccessibilityHint = () => {
|
|
234
|
+
let accessibilityHint = '';
|
|
235
|
+
|
|
236
|
+
if (helperText) {
|
|
237
|
+
accessibilityHint = accessibilityHint + helperText;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (validationStatus !== 'initial') {
|
|
241
|
+
if (accessibilityHint.length > 0) {
|
|
242
|
+
accessibilityHint = accessibilityHint + ', ';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (validationStatus === 'invalid' && invalidText) {
|
|
246
|
+
accessibilityHint = accessibilityHint + invalidText;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (validationStatus === 'valid' && validText) {
|
|
250
|
+
accessibilityHint = accessibilityHint + validText;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return accessibilityHint || props.accessibilityHint;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return (
|
|
258
|
+
<FormField
|
|
259
|
+
label={label}
|
|
260
|
+
labelVariant={labelVariant}
|
|
261
|
+
helperText={helperText}
|
|
262
|
+
helperIcon={helperIcon}
|
|
263
|
+
validText={validText}
|
|
264
|
+
invalidText={invalidText}
|
|
265
|
+
required={required}
|
|
266
|
+
validationStatus={validationStatus}
|
|
267
|
+
disabled={disabled}
|
|
268
|
+
readonly={readonly}
|
|
269
|
+
accessibilityHandledByChildren
|
|
270
|
+
style={[styles.root, style]}
|
|
271
|
+
>
|
|
272
|
+
<View style={styles.controls}>
|
|
273
|
+
<StepperButton
|
|
274
|
+
icon={MinusSmallIcon}
|
|
275
|
+
disabled={decrementDisabled}
|
|
276
|
+
accessibilityLabel={decrementAccessibilityLabel}
|
|
277
|
+
onPress={() => handleStepPress(-1)}
|
|
278
|
+
/>
|
|
279
|
+
<InputComponent
|
|
280
|
+
validationStatus={validationStatus}
|
|
281
|
+
isInvalid={validationStatus === 'invalid'}
|
|
282
|
+
isReadOnly={readonly}
|
|
283
|
+
isDisabled={disabled}
|
|
284
|
+
isFocused={resolvedFocused}
|
|
285
|
+
isRequired={required}
|
|
286
|
+
style={styles.inputRoot}
|
|
287
|
+
>
|
|
288
|
+
<InputField
|
|
289
|
+
// @ts-expect-error - ref forwarding issue mirrors the base Input component
|
|
290
|
+
ref={inputRef}
|
|
291
|
+
inputMode={inputMode}
|
|
292
|
+
keyboardType={keyboardType}
|
|
293
|
+
inBottomSheet={inBottomSheet}
|
|
294
|
+
editable={!disabled && !readonly}
|
|
295
|
+
textAlign="center"
|
|
296
|
+
value={displayValue}
|
|
297
|
+
onFocus={handleFocus}
|
|
298
|
+
onBlur={handleBlur}
|
|
299
|
+
onChangeText={handleChangeText}
|
|
300
|
+
accessibilityLabel={getAccessibilityLabel()}
|
|
301
|
+
accessibilityHint={getAccessibilityHint()}
|
|
302
|
+
accessibilityState={{
|
|
303
|
+
...(props.accessibilityState ?? {}),
|
|
304
|
+
disabled: disabled || readonly,
|
|
305
|
+
}}
|
|
306
|
+
aria-disabled={disabled || readonly}
|
|
307
|
+
aria-readonly={readonly}
|
|
308
|
+
aria-required={required}
|
|
309
|
+
aria-invalid={validationStatus === 'invalid'}
|
|
310
|
+
{...props}
|
|
311
|
+
style={styles.inputField}
|
|
312
|
+
/>
|
|
313
|
+
</InputComponent>
|
|
314
|
+
<StepperButton
|
|
315
|
+
icon={AddSmallIcon}
|
|
316
|
+
disabled={incrementDisabled}
|
|
317
|
+
accessibilityLabel={incrementAccessibilityLabel}
|
|
318
|
+
onPress={() => handleStepPress(1)}
|
|
319
|
+
/>
|
|
320
|
+
</View>
|
|
321
|
+
</FormField>
|
|
322
|
+
);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
StepperInput.displayName = 'StepperInput';
|
|
326
|
+
|
|
327
|
+
const styles = StyleSheet.create(theme => ({
|
|
328
|
+
root: {
|
|
329
|
+
width: '100%',
|
|
330
|
+
maxWidth: theme.components.input.maxWidth,
|
|
331
|
+
},
|
|
332
|
+
controls: {
|
|
333
|
+
flexDirection: 'row',
|
|
334
|
+
alignItems: 'center',
|
|
335
|
+
gap: theme.components.input.stepper.gap,
|
|
336
|
+
},
|
|
337
|
+
inputRoot: {
|
|
338
|
+
width: 80,
|
|
339
|
+
minWidth: 80,
|
|
340
|
+
paddingHorizontal: 0,
|
|
341
|
+
justifyContent: 'center',
|
|
342
|
+
},
|
|
343
|
+
inputField: {
|
|
344
|
+
textAlign: 'center',
|
|
345
|
+
paddingHorizontal: 0,
|
|
346
|
+
},
|
|
347
|
+
}));
|
|
348
|
+
|
|
349
|
+
export default StepperInput;
|
|
@@ -11,9 +11,10 @@ const renderContent = (
|
|
|
11
11
|
weight: 'regular' | 'semibold' = 'semibold',
|
|
12
12
|
color: 'purple' | 'white' = 'white'
|
|
13
13
|
) => {
|
|
14
|
+
styles.useVariants({ color });
|
|
14
15
|
if (typeof children === 'string' || typeof children === 'number') {
|
|
15
16
|
return (
|
|
16
|
-
<BodyText size="md" weight={weight} style={styles.text}
|
|
17
|
+
<BodyText size="md" weight={weight} style={styles.text}>
|
|
17
18
|
{children}
|
|
18
19
|
</BodyText>
|
|
19
20
|
);
|
|
@@ -129,6 +130,14 @@ const styles = StyleSheet.create(theme => ({
|
|
|
129
130
|
color: theme.color.text.primary,
|
|
130
131
|
},
|
|
131
132
|
},
|
|
133
|
+
color: {
|
|
134
|
+
purple: {
|
|
135
|
+
color: theme.components.table.headerCell.foregoundColorInverted,
|
|
136
|
+
},
|
|
137
|
+
white: {
|
|
138
|
+
color: theme.components.table.headerCell.foregoundColor,
|
|
139
|
+
},
|
|
140
|
+
},
|
|
132
141
|
},
|
|
133
142
|
},
|
|
134
143
|
}));
|
|
@@ -72,6 +72,7 @@ all of the React Native [`View` props](https://reactnative.dev/docs/view).
|
|
|
72
72
|
| validText | `string` | `-` | Text to display when validation status is 'valid'. **(Only to be used if the input has no children)** |
|
|
73
73
|
| invalidText | `string` | `-` | Text to display when validation status is 'invalid'. |
|
|
74
74
|
| required | `boolean` | `true` | Whether the input is required. **(Only to be used if the input has no children)** |
|
|
75
|
+
| defaultHeight | `number` | `96` | The initial height of the textarea in pixels when `resizable` is `true`. |
|
|
75
76
|
| resizable | `boolean` | `false` | Adds a bottom-right drag handle so the textarea can be resized vertically. |
|
|
76
77
|
| value | `string` | `-` | The value of the input. **(Only to be used if the input has no children)** |
|
|
77
78
|
| onChange | `function` | `-` | Callback function that is triggered when the input value changes. **(Only to be used if the input has no children)** **(Only to be used if the input has no children)** |
|
|
@@ -157,6 +158,7 @@ const MyComponent = () => {
|
|
|
157
158
|
helperText="Drag the corner handle to resize"
|
|
158
159
|
placeholder="Enter your text here..."
|
|
159
160
|
resizable
|
|
161
|
+
defaultHeight={140}
|
|
160
162
|
/>
|
|
161
163
|
);
|
|
162
164
|
};
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import type { TextInputProps, ViewProps } from 'react-native';
|
|
2
2
|
|
|
3
3
|
export interface TextareaBaseProps {
|
|
4
|
+
/**
|
|
5
|
+
* Sets the initial height of a resizable textarea in pixels.
|
|
6
|
+
* Has no effect unless `resizable` is enabled.
|
|
7
|
+
*
|
|
8
|
+
* @type number
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <Textarea resizable defaultHeight={140} />
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
defaultHeight?: number;
|
|
4
15
|
/**
|
|
5
16
|
* If true, the textarea can be resized vertically using a drag handle.
|
|
6
17
|
*
|
|
@@ -66,6 +66,10 @@ const meta = {
|
|
|
66
66
|
description: 'Enables a drag handle to resize the Textarea vertically',
|
|
67
67
|
defaultValue: false,
|
|
68
68
|
},
|
|
69
|
+
defaultHeight: {
|
|
70
|
+
control: { type: 'number', min: 64, step: 8 },
|
|
71
|
+
description: 'Sets the initial height of the Textarea in pixels',
|
|
72
|
+
},
|
|
69
73
|
},
|
|
70
74
|
args: {
|
|
71
75
|
placeholder: 'Textarea placeholder',
|
|
@@ -90,3 +94,13 @@ export const Resizable: Story = {
|
|
|
90
94
|
resizable: true,
|
|
91
95
|
},
|
|
92
96
|
};
|
|
97
|
+
|
|
98
|
+
export const DefaultHeight: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
label: 'Notes',
|
|
101
|
+
helperText: 'Starts taller by default',
|
|
102
|
+
placeholder: 'Add more detail here...',
|
|
103
|
+
resizable: true,
|
|
104
|
+
defaultHeight: 140,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
@@ -33,6 +33,7 @@ const Textarea = ({
|
|
|
33
33
|
validationStatus = 'initial',
|
|
34
34
|
children,
|
|
35
35
|
resizable = false,
|
|
36
|
+
defaultHeight,
|
|
36
37
|
disabled,
|
|
37
38
|
focused,
|
|
38
39
|
readonly,
|
|
@@ -56,8 +57,9 @@ const Textarea = ({
|
|
|
56
57
|
const textareaDisabled = disabled ?? formFieldContext?.disabled;
|
|
57
58
|
const textareaReadonly = readonly ?? formFieldContext?.readonly;
|
|
58
59
|
const textareaValidationStatus = formFieldContext?.validationStatus ?? validationStatus;
|
|
59
|
-
const
|
|
60
|
-
const
|
|
60
|
+
const textareaDefaultHeight = defaultHeight ?? DEFAULT_TEXTAREA_HEIGHT;
|
|
61
|
+
const textareaHeight = useSharedValue(textareaDefaultHeight);
|
|
62
|
+
const resizeStartHeight = useSharedValue(textareaDefaultHeight);
|
|
61
63
|
const theme = useTheme();
|
|
62
64
|
|
|
63
65
|
useEffect(() => {
|
|
@@ -66,6 +68,13 @@ const Textarea = ({
|
|
|
66
68
|
}
|
|
67
69
|
}, [formFieldContext]);
|
|
68
70
|
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!hasMeasuredHeight.current) {
|
|
73
|
+
textareaHeight.value = textareaDefaultHeight;
|
|
74
|
+
resizeStartHeight.value = textareaDefaultHeight;
|
|
75
|
+
}
|
|
76
|
+
}, [resizeStartHeight, textareaDefaultHeight, textareaHeight]);
|
|
77
|
+
|
|
69
78
|
const getAccessibilityLabel = () => {
|
|
70
79
|
let accessibilityLabel = '';
|
|
71
80
|
if (textareaLabel) {
|
package/src/components/index.ts
CHANGED
|
@@ -48,11 +48,14 @@ export * from './ProgressBar';
|
|
|
48
48
|
export * from './ProgressStepper';
|
|
49
49
|
export * from './Radio';
|
|
50
50
|
export * from './RadioCard';
|
|
51
|
+
export * from './Rating';
|
|
52
|
+
export * from './Roundel';
|
|
51
53
|
export * from './SectionHeader';
|
|
52
54
|
export * from './SegmentedControl';
|
|
53
55
|
export * from './Select';
|
|
54
56
|
export * from './Skeleton';
|
|
55
57
|
export * from './Spinner';
|
|
58
|
+
export * from './StepperInput';
|
|
56
59
|
export * from './Switch';
|
|
57
60
|
export * from './Table';
|
|
58
61
|
export * from './Tabs';
|
package/src/tokens/color.ts
CHANGED
|
@@ -94,6 +94,7 @@ export const grey = {
|
|
|
94
94
|
'700': '#4c4c4c',
|
|
95
95
|
'800': '#3f3f3f',
|
|
96
96
|
'900': '#3a3837',
|
|
97
|
+
'925': '#2f2d2d',
|
|
97
98
|
'950': '#232323',
|
|
98
99
|
'975': '#191919',
|
|
99
100
|
'1000': '#101010',
|
|
@@ -480,25 +481,25 @@ export const light = {
|
|
|
480
481
|
|
|
481
482
|
export const dark = {
|
|
482
483
|
background: {
|
|
483
|
-
brand: '#
|
|
484
|
+
brand: '#442484',
|
|
484
485
|
loading: '#30302c',
|
|
485
486
|
primary: '#191917',
|
|
486
|
-
secondary: '#
|
|
487
|
+
secondary: '#2f2d2d',
|
|
487
488
|
},
|
|
488
489
|
border: {
|
|
489
|
-
strong: '#
|
|
490
|
-
subtle: '#
|
|
490
|
+
strong: '#888888',
|
|
491
|
+
subtle: '#5b5b5b',
|
|
491
492
|
},
|
|
492
493
|
feedback: {
|
|
493
494
|
danger: {
|
|
494
|
-
border: '#
|
|
495
|
+
border: '#f4412a',
|
|
495
496
|
foreground: {
|
|
496
|
-
default: '#
|
|
497
|
+
default: '#ffffff',
|
|
497
498
|
subtle: '#ff7964',
|
|
498
499
|
},
|
|
499
500
|
surface: {
|
|
500
|
-
default: '#
|
|
501
|
-
subtle: '#
|
|
501
|
+
default: '#de2612',
|
|
502
|
+
subtle: '#6b1f1a',
|
|
502
503
|
},
|
|
503
504
|
},
|
|
504
505
|
functional: {
|
|
@@ -513,36 +514,36 @@ export const dark = {
|
|
|
513
514
|
},
|
|
514
515
|
},
|
|
515
516
|
info: {
|
|
516
|
-
border: '#
|
|
517
|
+
border: '#2786f1',
|
|
517
518
|
foreground: {
|
|
518
|
-
default: '#
|
|
519
|
+
default: '#ffffff',
|
|
519
520
|
subtle: '#6bb0ff',
|
|
520
521
|
},
|
|
521
522
|
surface: {
|
|
522
|
-
default: '#
|
|
523
|
-
subtle: '#
|
|
523
|
+
default: '#1c6cd4',
|
|
524
|
+
subtle: '#0b3375',
|
|
524
525
|
},
|
|
525
526
|
},
|
|
526
527
|
positive: {
|
|
527
|
-
border: '#
|
|
528
|
+
border: '#19a660',
|
|
528
529
|
foreground: {
|
|
529
|
-
default: '#
|
|
530
|
+
default: '#ffffff',
|
|
530
531
|
subtle: '#58ca93',
|
|
531
532
|
},
|
|
532
533
|
surface: {
|
|
533
|
-
default: '#
|
|
534
|
-
subtle: '#
|
|
534
|
+
default: '#0f834a',
|
|
535
|
+
subtle: '#074b2a',
|
|
535
536
|
},
|
|
536
537
|
},
|
|
537
538
|
warning: {
|
|
538
|
-
border: '#
|
|
539
|
+
border: '#f56e00',
|
|
539
540
|
foreground: {
|
|
540
|
-
default: '#
|
|
541
|
+
default: '#ffffff',
|
|
541
542
|
subtle: '#ff9639',
|
|
542
543
|
},
|
|
543
544
|
surface: {
|
|
544
|
-
default: '#
|
|
545
|
-
subtle: '#
|
|
545
|
+
default: '#cf5d00',
|
|
546
|
+
subtle: '#893900',
|
|
546
547
|
},
|
|
547
548
|
},
|
|
548
549
|
},
|
|
@@ -585,9 +586,9 @@ export const dark = {
|
|
|
585
586
|
},
|
|
586
587
|
surface: {
|
|
587
588
|
strong: {
|
|
588
|
-
active: '#
|
|
589
|
-
default: '#
|
|
590
|
-
hover: '#
|
|
589
|
+
active: '#ddd5eb',
|
|
590
|
+
default: '#af90de',
|
|
591
|
+
hover: '#c6b5e2',
|
|
591
592
|
},
|
|
592
593
|
},
|
|
593
594
|
},
|
|
@@ -670,52 +671,52 @@ export const dark = {
|
|
|
670
671
|
},
|
|
671
672
|
},
|
|
672
673
|
shadow: {
|
|
673
|
-
brand: '#
|
|
674
|
-
broadband: '#
|
|
675
|
-
cashback: '#
|
|
676
|
-
default: '#
|
|
677
|
-
energy: '#
|
|
678
|
-
insurance: '#
|
|
679
|
-
mobile: '#
|
|
680
|
-
pig: '#
|
|
674
|
+
brand: '#442484',
|
|
675
|
+
broadband: '#4f6b20',
|
|
676
|
+
cashback: '#7429b5',
|
|
677
|
+
default: '#3f3f3f',
|
|
678
|
+
energy: '#2c6370',
|
|
679
|
+
insurance: '#7f4518',
|
|
680
|
+
mobile: '#8a3260',
|
|
681
|
+
pig: '#7a1f7e',
|
|
681
682
|
},
|
|
682
683
|
surface: {
|
|
683
684
|
brand: {
|
|
684
|
-
default: '#
|
|
685
|
+
default: '#af90de',
|
|
685
686
|
strong: '#26164f',
|
|
686
687
|
subtle: '#442484',
|
|
687
688
|
},
|
|
688
689
|
broadband: {
|
|
689
690
|
default: '#506c21',
|
|
690
|
-
subtle: '#
|
|
691
|
+
subtle: '#4f6b20',
|
|
691
692
|
},
|
|
692
693
|
cashback: {
|
|
693
694
|
default: '#8b2bc9',
|
|
694
|
-
subtle: '#
|
|
695
|
+
subtle: '#7429b5',
|
|
695
696
|
},
|
|
696
697
|
energy: {
|
|
697
698
|
default: '#326e7a',
|
|
698
|
-
subtle: '#
|
|
699
|
+
subtle: '#2c6370',
|
|
699
700
|
},
|
|
700
701
|
highlight: {
|
|
701
702
|
default: '#ffb921',
|
|
702
|
-
subtle: '#
|
|
703
|
+
subtle: '#82692b',
|
|
703
704
|
},
|
|
704
705
|
insurance: {
|
|
705
706
|
default: '#9b4c0e',
|
|
706
|
-
subtle: '#
|
|
707
|
+
subtle: '#7f4518',
|
|
707
708
|
},
|
|
708
709
|
mobile: {
|
|
709
710
|
default: '#a7266d',
|
|
710
|
-
subtle: '#
|
|
711
|
+
subtle: '#8a3260',
|
|
711
712
|
},
|
|
712
713
|
neutral: {
|
|
713
|
-
strong: '#
|
|
714
|
+
strong: '#2f2d2d',
|
|
714
715
|
subtle: '#191917',
|
|
715
716
|
},
|
|
716
717
|
pig: {
|
|
717
718
|
default: '#8f358f',
|
|
718
|
-
subtle: '#
|
|
719
|
+
subtle: '#7a1f7e',
|
|
719
720
|
},
|
|
720
721
|
},
|
|
721
722
|
text: {
|
|
@@ -10,15 +10,15 @@ export default {
|
|
|
10
10
|
gap: 8,
|
|
11
11
|
iconButton: {
|
|
12
12
|
unstyled: {
|
|
13
|
-
foregroundColor: '#
|
|
14
|
-
foregroundColorActive: '#
|
|
15
|
-
foregroundColorHover: '#
|
|
13
|
+
foregroundColor: '#ebebeb',
|
|
14
|
+
foregroundColorActive: '#b2afae',
|
|
15
|
+
foregroundColorHover: '#d3d3d3',
|
|
16
16
|
},
|
|
17
17
|
},
|
|
18
18
|
link: {
|
|
19
|
-
color: '#
|
|
20
|
-
colorActive: '#
|
|
21
|
-
colorHover: '#
|
|
19
|
+
color: '#ebebeb',
|
|
20
|
+
colorActive: '#b2afae',
|
|
21
|
+
colorHover: '#d3d3d3',
|
|
22
22
|
},
|
|
23
23
|
padding: 14,
|
|
24
24
|
} as const;
|