@utilitywarehouse/hearth-react-native 0.31.1 → 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 +55 -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/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/docs/adding-shadows.mdx +2 -2
- package/docs/changelog.mdx +16 -0
- package/docs/components/AllComponents.web.tsx +30 -1
- package/docs/dark-mode-best-practice.mdx +328 -0
- package/package.json +3 -3
- 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/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
|
@@ -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;
|
|
@@ -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';
|