@utilitywarehouse/hearth-react-native 0.31.1 → 0.32.1

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 (87) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +15 -18
  3. package/CHANGELOG.md +62 -0
  4. package/build/components/Card/Card.props.d.ts +1 -1
  5. package/build/components/Card/CardRoot.js +19 -0
  6. package/build/components/Input/Input.js +13 -31
  7. package/build/components/Rating/Rating.d.ts +6 -0
  8. package/build/components/Rating/Rating.js +76 -0
  9. package/build/components/Rating/Rating.props.d.ts +18 -0
  10. package/build/components/Rating/Rating.props.js +1 -0
  11. package/build/components/Rating/RatingStarEmpty.d.ts +6 -0
  12. package/build/components/Rating/RatingStarEmpty.js +9 -0
  13. package/build/components/Rating/RatingStarFilled.d.ts +6 -0
  14. package/build/components/Rating/RatingStarFilled.js +9 -0
  15. package/build/components/Rating/index.d.ts +2 -0
  16. package/build/components/Rating/index.js +1 -0
  17. package/build/components/Roundel/Roundel.d.ts +6 -0
  18. package/build/components/Roundel/Roundel.js +40 -0
  19. package/build/components/Roundel/Roundel.props.d.ts +6 -0
  20. package/build/components/Roundel/Roundel.props.js +1 -0
  21. package/build/components/Roundel/index.d.ts +2 -0
  22. package/build/components/Roundel/index.js +1 -0
  23. package/build/components/StepperInput/StepperButton.d.ts +22 -0
  24. package/build/components/StepperInput/StepperButton.js +55 -0
  25. package/build/components/StepperInput/StepperInput.d.ts +6 -0
  26. package/build/components/StepperInput/StepperInput.js +179 -0
  27. package/build/components/StepperInput/StepperInput.props.d.ts +31 -0
  28. package/build/components/StepperInput/StepperInput.props.js +1 -0
  29. package/build/components/StepperInput/index.d.ts +2 -0
  30. package/build/components/StepperInput/index.js +1 -0
  31. package/build/components/Textarea/Textarea.d.ts +1 -1
  32. package/build/components/Textarea/Textarea.js +21 -32
  33. package/build/components/Textarea/Textarea.props.d.ts +11 -0
  34. package/build/components/VerificationInput/VerificationInput.js +12 -22
  35. package/build/components/index.d.ts +3 -0
  36. package/build/components/index.js +3 -0
  37. package/build/hooks/index.d.ts +1 -0
  38. package/build/hooks/index.js +1 -0
  39. package/build/hooks/useFormFieldAccessibility.d.ts +17 -0
  40. package/build/hooks/useFormFieldAccessibility.js +32 -0
  41. package/build/hooks/useFormFieldAccessibility.test.d.ts +1 -0
  42. package/build/hooks/useFormFieldAccessibility.test.js +56 -0
  43. package/docs/adding-shadows.mdx +2 -2
  44. package/docs/changelog.mdx +16 -0
  45. package/docs/components/AllComponents.web.tsx +30 -1
  46. package/docs/dark-mode-best-practice.mdx +328 -0
  47. package/package.json +6 -4
  48. package/src/components/Banner/Banner.stories.tsx +14 -0
  49. package/src/components/Card/Card.docs.mdx +16 -17
  50. package/src/components/Card/Card.props.ts +1 -0
  51. package/src/components/Card/Card.stories.tsx +35 -21
  52. package/src/components/Card/CardRoot.tsx +19 -0
  53. package/src/components/Icon/Icon.docs.mdx +1 -1
  54. package/src/components/Input/Input.tsx +14 -35
  55. package/src/components/List/List.docs.mdx +4 -2
  56. package/src/components/Modal/Modal.docs.mdx +58 -4
  57. package/src/components/NavModal/NavModal.docs.mdx +2 -2
  58. package/src/components/Rating/Rating.docs.mdx +178 -0
  59. package/src/components/Rating/Rating.figma.tsx +20 -0
  60. package/src/components/Rating/Rating.props.ts +22 -0
  61. package/src/components/Rating/Rating.stories.tsx +95 -0
  62. package/src/components/Rating/Rating.tsx +140 -0
  63. package/src/components/Rating/RatingStarEmpty.tsx +22 -0
  64. package/src/components/Rating/RatingStarFilled.tsx +27 -0
  65. package/src/components/Rating/index.ts +2 -0
  66. package/src/components/Roundel/Roundel.docs.mdx +48 -0
  67. package/src/components/Roundel/Roundel.figma.tsx +17 -0
  68. package/src/components/Roundel/Roundel.props.ts +8 -0
  69. package/src/components/Roundel/Roundel.stories.tsx +49 -0
  70. package/src/components/Roundel/Roundel.tsx +51 -0
  71. package/src/components/Roundel/index.ts +2 -0
  72. package/src/components/StepperInput/StepperButton.tsx +83 -0
  73. package/src/components/StepperInput/StepperInput.docs.mdx +121 -0
  74. package/src/components/StepperInput/StepperInput.figma.tsx +45 -0
  75. package/src/components/StepperInput/StepperInput.props.ts +39 -0
  76. package/src/components/StepperInput/StepperInput.stories.tsx +270 -0
  77. package/src/components/StepperInput/StepperInput.tsx +322 -0
  78. package/src/components/StepperInput/index.ts +2 -0
  79. package/src/components/Textarea/Textarea.docs.mdx +2 -0
  80. package/src/components/Textarea/Textarea.props.ts +11 -0
  81. package/src/components/Textarea/Textarea.stories.tsx +14 -0
  82. package/src/components/Textarea/Textarea.tsx +22 -34
  83. package/src/components/VerificationInput/VerificationInput.tsx +13 -25
  84. package/src/components/index.ts +3 -0
  85. package/src/hooks/index.ts +1 -0
  86. package/src/hooks/useFormFieldAccessibility.test.tsx +74 -0
  87. package/src/hooks/useFormFieldAccessibility.ts +67 -0
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.31.1 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.32.1 build /home/runner/work/hearth/hearth/packages/react-native
3
3
  > tsc
4
4
 
@@ -1,5 +1,5 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.31.1 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.32.1 lint /home/runner/work/hearth/hearth/packages/react-native
3
3
  > TIMING=1 eslint .
4
4
 
5
5
 
@@ -27,9 +27,6 @@
27
27
  /home/runner/work/hearth/hearth/packages/react-native/src/components/DatePicker/DatePickerYears.tsx
28
28
  52:6 warning React Hook useCallback has a missing dependency: 'containerHeight'. Either include it or remove the dependency array. Outer scope values like 'styles' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
29
29
 
30
- /home/runner/work/hearth/hearth/packages/react-native/src/components/Input/Input.tsx
31
- 78:8 warning React Hook useEffect has a missing dependency: 'formFieldContext'. Either include it or remove the dependency array react-hooks/exhaustive-deps
32
-
33
30
  /home/runner/work/hearth/hearth/packages/react-native/src/components/PillGroup/PillGroup.tsx
34
31
  17:9 warning The 'normalizedValue' conditional could make the dependencies of useMemo Hook (at line 33) change on every render. Move it inside the useMemo callback. Alternatively, wrap the initialization of 'normalizedValue' in its own useMemo() Hook react-hooks/exhaustive-deps
35
32
 
@@ -46,17 +43,17 @@
46
43
  14:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
47
44
  106:14 warning Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components react-refresh/only-export-components
48
45
 
49
- 21 problems (0 errors, 21 warnings)
50
-
51
- Rule | Time (ms) | Relative
52
- :-------------------------------------------------|----------:|--------:
53
- @typescript-eslint/no-unused-vars | 1546.291 | 60.2%
54
- react-hooks/exhaustive-deps | 108.680 | 4.2%
55
- no-global-assign | 99.957 | 3.9%
56
- react-hooks/rules-of-hooks | 90.616 | 3.5%
57
- no-misleading-character-class | 53.413 | 2.1%
58
- no-unexpected-multiline | 51.887 | 2.0%
59
- @typescript-eslint/ban-ts-comment | 49.938 | 1.9%
60
- @typescript-eslint/no-unnecessary-type-constraint | 38.622 | 1.5%
61
- no-loss-of-precision | 33.899 | 1.3%
62
- no-useless-escape | 30.813 | 1.2%
46
+ 20 problems (0 errors, 20 warnings)
47
+
48
+ Rule | Time (ms) | Relative
49
+ :-----------------------------------------|----------:|--------:
50
+ @typescript-eslint/no-unused-vars | 1452.998 | 57.8%
51
+ react-hooks/exhaustive-deps | 126.724 | 5.0%
52
+ no-global-assign | 93.240 | 3.7%
53
+ react-hooks/rules-of-hooks | 91.457 | 3.6%
54
+ @typescript-eslint/ban-ts-comment | 64.809 | 2.6%
55
+ no-unexpected-multiline | 56.861 | 2.3%
56
+ @typescript-eslint/triple-slash-reference | 35.707 | 1.4%
57
+ no-misleading-character-class | 35.239 | 1.4%
58
+ no-regex-spaces | 34.132 | 1.4%
59
+ no-useless-escape | 32.069 | 1.3%
package/CHANGELOG.md CHANGED
@@ -1,5 +1,67 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.32.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1144](https://github.com/utilitywarehouse/hearth/pull/1144) [`85459f2`](https://github.com/utilitywarehouse/hearth/commit/85459f2f4d7dcd8a99685a11dcda070530cee8dc) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Add the missing `highlight` color scheme support across the `Banner` and `Card` components.
8
+
9
+ ## 0.32.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#1134](https://github.com/utilitywarehouse/hearth/pull/1134) [`8824186`](https://github.com/utilitywarehouse/hearth/commit/ebccb55afebcbd47508d7992614b2495c7839cc6) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `Roundel` status indicator component.
14
+
15
+ `Roundel` is a compact status indicator with `success`, `pending`, and `error` variants, intended for inline state cues.
16
+
17
+ **Components affected**:
18
+
19
+ - `Roundel`
20
+
21
+ **Developer changes**:
22
+
23
+ Import and use `Roundel` from `@utilitywarehouse/hearth-react-native`:
24
+
25
+ ```tsx
26
+ import { Roundel } from '@utilitywarehouse/hearth-react-native';
27
+
28
+ <Roundel variant="success" />;
29
+ ```
30
+
31
+ - [#1132](https://github.com/utilitywarehouse/hearth/pull/1132) [`8824186`](https://github.com/utilitywarehouse/hearth/commit/882418633ee8c3a11e204329d07363dc411996dc) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add the `Rating` component
32
+
33
+ **Components affected**:
34
+ - `Rating`
35
+
36
+ **Developer changes**:
37
+
38
+ ```tsx
39
+ import { Rating } from '@utilitywarehouse/hearth-react-native';
40
+
41
+ const MyComponent = () => <Rating value={3} />;
42
+ ```
43
+
44
+ - [#1129](https://github.com/utilitywarehouse/hearth/pull/1129) [`ec385a8`](https://github.com/utilitywarehouse/hearth/commit/ec385a8185bfa4ec7f4d5f1366ecc069a98cbba8) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `StepperInput` for controlled numeric input with increment and decrement buttons.
45
+
46
+ `StepperInput` is a new React Native form component for adjusting numeric values with direct text entry and dedicated step controls. It supports min and max bounds, configurable step size, validation and helper text through `FormField`, and an opt-in `focusInputOnStepPress` prop for keyboard-first flows.
47
+
48
+ **Components affected**:
49
+ - `StepperInput`
50
+
51
+ **Developer changes**:
52
+
53
+ Import and use `StepperInput` from `@utilitywarehouse/hearth-react-native`:
54
+
55
+ ```tsx
56
+ import { StepperInput } from '@utilitywarehouse/hearth-react-native';
57
+
58
+ <StepperInput label="Guests" min={1} max={10} value={value} onChangeText={setValue} />;
59
+ ```
60
+
61
+ ### Patch Changes
62
+
63
+ - [#1133](https://github.com/utilitywarehouse/hearth/pull/1133) [`5cae98e`](https://github.com/utilitywarehouse/hearth/commit/5cae98e640a708a7d99eaf0395b7b52e71b8e6ec) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Add a `defaultHeight` prop to `Textarea` so the initial height can be configured.
64
+
3
65
  ## 0.31.1
4
66
 
5
67
  ### Patch Changes
@@ -2,7 +2,7 @@ import { PressableProps } from 'react-native';
2
2
  import { DisplayProps, FlexLayoutProps, GapProps, MarginProps, SpacingValues } from '../../types';
3
3
  interface CardProps extends PressableProps, MarginProps, GapProps, FlexLayoutProps, Omit<DisplayProps, 'direction'> {
4
4
  variant?: 'emphasis' | 'subtle';
5
- colorScheme?: 'neutralStrong' | 'neutralSubtle' | 'brand' | 'energy' | 'broadband' | 'mobile' | 'insurance' | 'cashback' | 'pig';
5
+ colorScheme?: 'neutralStrong' | 'neutralSubtle' | 'brand' | 'energy' | 'broadband' | 'highlight' | 'mobile' | 'insurance' | 'cashback' | 'pig';
6
6
  shadowColor?: 'functional' | 'brand' | 'energy' | 'broadband' | 'mobile' | 'insurance' | 'cashback' | 'pig';
7
7
  noPadding?: boolean;
8
8
  disabled?: boolean;
@@ -111,6 +111,9 @@ const styles = StyleSheet.create(theme => ({
111
111
  pig: {
112
112
  borderWidth: theme.components.card.brand.borderWidth,
113
113
  },
114
+ highlight: {
115
+ borderWidth: theme.components.card.brand.borderWidth,
116
+ },
114
117
  },
115
118
  shadowColor: {
116
119
  functional: {
@@ -278,6 +281,22 @@ const styles = StyleSheet.create(theme => ({
278
281
  borderColor: theme.color.border.strong,
279
282
  },
280
283
  },
284
+ {
285
+ variant: 'subtle',
286
+ colorScheme: 'highlight',
287
+ styles: {
288
+ backgroundColor: theme.color.surface.highlight.subtle,
289
+ borderColor: theme.color.border.strong,
290
+ },
291
+ },
292
+ {
293
+ variant: 'emphasis',
294
+ colorScheme: 'highlight',
295
+ styles: {
296
+ backgroundColor: theme.color.surface.highlight.default,
297
+ borderColor: theme.color.border.strong,
298
+ },
299
+ },
281
300
  ],
282
301
  },
283
302
  }));
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { createInput } from '@gluestack-ui/input';
3
3
  import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
4
4
  import { CloseSmallIcon, EyeOffSmallIcon, EyeSmallIcon, SearchMediumIcon, } from '@utilitywarehouse/hearth-react-native-icons';
5
- import { useTheme } from '../../hooks';
5
+ import { useFormFieldAccessibility, useTheme } from '../../hooks';
6
6
  import { BodyText } from '../BodyText';
7
7
  import { FormField, useFormFieldContext } from '../FormField';
8
8
  import { Spinner } from '../Spinner';
@@ -34,7 +34,7 @@ const Input = forwardRef(({ validationStatus = 'initial', children, disabled, fo
34
34
  if (formFieldContext?.setShouldHandleAccessibility) {
35
35
  formFieldContext.setShouldHandleAccessibility(true);
36
36
  }
37
- }, []);
37
+ }, [formFieldContext]);
38
38
  const [fieldType, setFieldType] = useState(type === 'password' ? 'password' : 'text');
39
39
  const { color } = useTheme();
40
40
  const inputRef = useRef(null);
@@ -42,6 +42,16 @@ const Input = forwardRef(({ validationStatus = 'initial', children, disabled, fo
42
42
  useImperativeHandle(ref, () => inputRef.current, []);
43
43
  const shouldShowPasswordToggle = type === 'password' && showPasswordToggle;
44
44
  const shouldShowClear = clearable && !!props?.value;
45
+ const { accessibilityHint, accessibilityLabel } = useFormFieldAccessibility({
46
+ label: inputLabel,
47
+ helperText: inputHelperText,
48
+ validText: inputValidText,
49
+ invalidText: inputInvalidText,
50
+ required: inputRequired,
51
+ validationStatus: inputValidationStatus,
52
+ fallbackLabel: props.accessibilityLabel,
53
+ fallbackHint: props.accessibilityHint,
54
+ });
45
55
  const toggleFieldType = () => {
46
56
  setFieldType(fieldType === 'password' ? 'text' : 'password');
47
57
  };
@@ -57,39 +67,11 @@ const Input = forwardRef(({ validationStatus = 'initial', children, disabled, fo
57
67
  }
58
68
  return undefined;
59
69
  })();
60
- const getAccessibilityLabel = () => {
61
- let accessibilityLabel = '';
62
- if (inputLabel) {
63
- accessibilityLabel = accessibilityLabel + inputLabel;
64
- }
65
- if (inputRequired) {
66
- accessibilityLabel = accessibilityLabel + ', required';
67
- }
68
- return accessibilityLabel || props.accessibilityLabel;
69
- };
70
- const getAccessibilityHint = () => {
71
- let accessibilityHint = '';
72
- if (inputHelperText) {
73
- accessibilityHint = accessibilityHint + inputHelperText;
74
- }
75
- if (inputValidationStatus !== 'initial') {
76
- if (accessibilityHint.length > 0) {
77
- accessibilityHint = accessibilityHint + ', ';
78
- }
79
- if (inputValidationStatus === 'invalid' && inputInvalidText) {
80
- accessibilityHint = accessibilityHint + inputInvalidText;
81
- }
82
- if (inputValidationStatus === 'valid' && inputValidText) {
83
- accessibilityHint = accessibilityHint + inputValidText;
84
- }
85
- }
86
- return accessibilityHint || props.accessibilityHint;
87
- };
88
70
  return (_jsx(FormField, { label: label, labelVariant: labelVariant, helperText: helperText, helperIcon: helperIcon, validText: validText, invalidText: invalidText, required: required, validationStatus: validationStatus, disabled: disabled, readonly: readonly, accessibilityHandledByChildren: true, children: _jsx(InputComponent, { ...(children ? props : {}), validationStatus: inputValidationStatus, isInvalid: inputValidationStatus === 'invalid', isReadOnly: inputReadonly, isDisabled: inputDisabled, isFocused: focused, type: type, isRequired: inputRequired, style: style, children: children ? (_jsx(_Fragment, { children: children })) : (_jsxs(_Fragment, { children: [!!leadingIconComponent && (_jsx(InputSlot, { children: _jsx(InputIcon, { as: leadingIconComponent }) })), !!prefix && (_jsx(InputSlot, { children: typeof prefix === 'string' || typeof prefix === 'number' ? (_jsx(BodyText, { children: prefix })) : (prefix) })), _jsx(InputField
89
71
  // @ts-expect-error - ref forwarding issue
90
72
  , {
91
73
  // @ts-expect-error - ref forwarding issue
92
- ref: inputRef, type: fieldType, inputMode: getInputMode, inBottomSheet: inBottomSheet, ...props, "aria-label": getAccessibilityLabel(), accessibilityHint: getAccessibilityHint() }), shouldShowClear && (_jsx(InputSlot, { children: _jsx(UnstyledIconButton, { onPress: onClear, icon: CloseSmallIcon }) })), loading && (_jsx(InputSlot, { children: _jsx(Spinner, { size: "xs", color: color.icon.primary }) })), shouldShowPasswordToggle && (_jsx(InputSlot, { children: _jsx(UnstyledIconButton, { onPress: toggleFieldType, icon: fieldType === 'password' ? EyeSmallIcon : EyeOffSmallIcon }) })), !!suffix && (_jsx(InputSlot, { children: typeof suffix === 'string' || typeof suffix === 'number' ? (_jsx(BodyText, { children: suffix })) : (suffix) })), !!trailingIcon && (_jsx(InputSlot, { children: _jsx(InputIcon, { as: trailingIcon }) }))] })) }) }));
74
+ ref: inputRef, type: fieldType, inputMode: getInputMode, inBottomSheet: inBottomSheet, ...props, "aria-label": accessibilityLabel, accessibilityHint: accessibilityHint }), shouldShowClear && (_jsx(InputSlot, { children: _jsx(UnstyledIconButton, { onPress: onClear, icon: CloseSmallIcon }) })), loading && (_jsx(InputSlot, { children: _jsx(Spinner, { size: "xs", color: color.icon.primary }) })), shouldShowPasswordToggle && (_jsx(InputSlot, { children: _jsx(UnstyledIconButton, { onPress: toggleFieldType, icon: fieldType === 'password' ? EyeSmallIcon : EyeOffSmallIcon }) })), !!suffix && (_jsx(InputSlot, { children: typeof suffix === 'string' || typeof suffix === 'number' ? (_jsx(BodyText, { children: suffix })) : (suffix) })), !!trailingIcon && (_jsx(InputSlot, { children: _jsx(InputIcon, { as: trailingIcon }) }))] })) }) }));
93
75
  });
94
76
  Input.displayName = 'Input';
95
77
  export default Input;
@@ -0,0 +1,6 @@
1
+ import type RatingProps from './Rating.props';
2
+ declare const Rating: {
3
+ ({ value, defaultValue, onChange, disabled, labels, hideLabel, style, accessibilityLabel, ...props }: RatingProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default Rating;
@@ -0,0 +1,76 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useMemo, useState } from 'react';
3
+ import { Pressable, View } from 'react-native';
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { BodyText } from '../BodyText';
6
+ import RatingStarEmpty from './RatingStarEmpty';
7
+ import RatingStarFilled from './RatingStarFilled';
8
+ const MAX_RATING = 5;
9
+ const STAR_WIDTH = 32;
10
+ const STAR_HEIGHT = 30;
11
+ const STAR_CONTAINER_SIZE = 40;
12
+ const DEFAULT_LABELS = {
13
+ 0: 'Select a rating',
14
+ 1: 'Awful',
15
+ 2: 'Bad',
16
+ 3: 'Okay',
17
+ 4: 'Good',
18
+ 5: 'Great!',
19
+ };
20
+ const clampRating = (value) => Math.min(MAX_RATING, Math.max(0, Math.round(value)));
21
+ const Rating = ({ value, defaultValue = 0, onChange, disabled = false, labels, hideLabel = false, style, accessibilityLabel, ...props }) => {
22
+ const isControlled = value !== undefined;
23
+ const [internalValue, setInternalValue] = useState(clampRating(defaultValue));
24
+ useEffect(() => {
25
+ if (!isControlled) {
26
+ setInternalValue(clampRating(defaultValue));
27
+ }
28
+ }, [defaultValue, isControlled]);
29
+ const resolvedLabels = useMemo(() => ({ ...DEFAULT_LABELS, ...labels }), [labels]);
30
+ const resolvedValue = clampRating(isControlled ? value : internalValue);
31
+ const currentLabel = resolvedLabels[resolvedValue] ?? DEFAULT_LABELS[resolvedValue];
32
+ const labelColor = resolvedValue === 0 ? 'secondary' : 'primary';
33
+ const handlePress = useCallback((nextValue) => {
34
+ if (disabled)
35
+ return;
36
+ if (!isControlled) {
37
+ setInternalValue(nextValue);
38
+ }
39
+ onChange?.(nextValue);
40
+ }, [disabled, isControlled, onChange]);
41
+ styles.useVariants({ disabled });
42
+ return (_jsxs(View, { ...props, accessibilityRole: "radiogroup", accessibilityState: { disabled }, accessibilityLabel: accessibilityLabel ?? currentLabel, style: [styles.container, style], children: [_jsx(View, { style: styles.stars, children: [1, 2, 3, 4, 5].map(starValue => {
43
+ const isFilled = starValue <= resolvedValue;
44
+ const starLabel = resolvedLabels[starValue] ?? DEFAULT_LABELS[starValue];
45
+ return (_jsx(Pressable, { accessibilityRole: "radio", accessibilityState: { selected: resolvedValue === starValue, disabled }, accessibilityLabel: `Rate ${starLabel}`, disabled: disabled, hitSlop: 8, onPress: () => handlePress(starValue), style: styles.star, children: isFilled ? (_jsx(RatingStarFilled, { width: STAR_WIDTH, height: STAR_HEIGHT })) : (_jsx(RatingStarEmpty, { width: STAR_WIDTH, height: STAR_HEIGHT })) }, starValue));
46
+ }) }), !hideLabel ? (_jsx(BodyText, { size: "md", color: labelColor, style: styles.label, children: currentLabel })) : null] }));
47
+ };
48
+ Rating.displayName = 'Rating';
49
+ const styles = StyleSheet.create(theme => ({
50
+ container: {
51
+ alignItems: 'center',
52
+ gap: theme.components.rating.gap,
53
+ variants: {
54
+ disabled: {
55
+ true: {
56
+ opacity: theme.opacity.disabled,
57
+ },
58
+ },
59
+ },
60
+ },
61
+ stars: {
62
+ flexDirection: 'row',
63
+ gap: theme.components.rating.gap,
64
+ },
65
+ star: {
66
+ width: STAR_CONTAINER_SIZE,
67
+ height: STAR_CONTAINER_SIZE,
68
+ alignItems: 'center',
69
+ justifyContent: 'center',
70
+ padding: theme.components.rating.borderWidth,
71
+ },
72
+ label: {
73
+ textAlign: 'center',
74
+ },
75
+ }));
76
+ export default Rating;
@@ -0,0 +1,18 @@
1
+ import type { ViewProps } from 'react-native';
2
+ export type RatingValue = 0 | 1 | 2 | 3 | 4 | 5;
3
+ export type RatingLabels = Partial<Record<RatingValue, string>>;
4
+ export interface RatingProps extends Omit<ViewProps, 'children'> {
5
+ /** Current rating value. */
6
+ value?: RatingValue;
7
+ /** Initial rating value when uncontrolled. */
8
+ defaultValue?: RatingValue;
9
+ /** Called when a star is selected. */
10
+ onChange?: (value: RatingValue) => void;
11
+ /** Disables the rating input. */
12
+ disabled?: boolean;
13
+ /** Override labels for specific rating values. */
14
+ labels?: RatingLabels;
15
+ /** Hide the label text below the stars. */
16
+ hideLabel?: boolean;
17
+ }
18
+ export default RatingProps;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ type RatingStarProps = {
2
+ width?: number;
3
+ height?: number;
4
+ };
5
+ declare const RatingStarEmpty: ({ width, height }: RatingStarProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default RatingStarEmpty;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Path, Svg } from 'react-native-svg';
3
+ import { useTheme } from '../../hooks';
4
+ const STAR_PATH = 'M16 1C16.0831 1 16.1883 1.02107 16.3262 1.09766C16.3896 1.13286 16.4424 1.183 16.4902 1.28711H16.4912L20.1777 9.8916L20.4141 10.4424L21.0117 10.4941L30.5107 11.3096C30.7062 11.3326 30.8 11.3864 30.8467 11.4238C30.915 11.4787 30.9492 11.5325 30.9707 11.6035C31.0029 11.71 31.0094 11.8192 30.9844 11.9463V11.9482C30.98 11.9702 30.9618 12.0389 30.832 12.1514L23.6338 18.2939L23.1689 18.6904L23.3096 19.2852L25.458 28.3926H25.459C25.4832 28.5045 25.4775 28.5714 25.4658 28.6133L25.4531 28.6475C25.4155 28.7291 25.3682 28.7951 25.3076 28.8516L25.2432 28.9043C25.172 28.9558 25.0868 28.9907 24.96 28.999C24.8763 29.0045 24.793 28.9887 24.6865 28.9229L24.6787 28.918L24.6699 28.9131L16.5088 24.0898L16 23.7891L15.4912 24.0898L7.33008 28.9131L7.32129 28.918L7.31348 28.9229C7.20704 28.9887 7.12371 29.0045 7.04004 28.999C6.94495 28.9928 6.87333 28.9713 6.81348 28.9395L6.75684 28.9043C6.68716 28.8537 6.63221 28.7954 6.58789 28.7236L6.54688 28.6475C6.53436 28.6202 6.51869 28.5743 6.52539 28.4902L6.54102 28.3926L8.69043 19.2852L8.83105 18.6904L8.36621 18.2939L1.16699 12.1514C1.03725 12.0389 1.01999 11.9702 1.01563 11.9482V11.9463L1.00195 11.8545C0.994777 11.765 1.00514 11.6834 1.0293 11.6035C1.05077 11.5325 1.08501 11.4787 1.15332 11.4238C1.19992 11.3864 1.2935 11.3327 1.48828 11.3096L10.9883 10.4941L11.5859 10.4424L11.8223 9.8916L15.5088 1.28711C15.5567 1.18274 15.6103 1.1329 15.6738 1.09766C15.8117 1.02107 15.9169 1 16 1Z';
5
+ const RatingStarEmpty = ({ width = 32, height = 30 }) => {
6
+ const theme = useTheme();
7
+ return (_jsx(Svg, { width: width, height: height, viewBox: "0 0 32 30", preserveAspectRatio: "none", children: _jsx(Path, { d: STAR_PATH, strokeWidth: 2, fill: "none", stroke: theme.color.border.subtle }) }));
8
+ };
9
+ export default RatingStarEmpty;
@@ -0,0 +1,6 @@
1
+ type RatingStarProps = {
2
+ width?: number;
3
+ height?: number;
4
+ };
5
+ declare const RatingStarFilled: ({ width, height }: RatingStarProps) => import("react/jsx-runtime").JSX.Element;
6
+ export default RatingStarFilled;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Path, Svg } from 'react-native-svg';
3
+ import { useTheme } from '../../hooks';
4
+ const STAR_PATH = 'M16 1C16.0831 1 16.1883 1.02107 16.3262 1.09766C16.3896 1.13286 16.4424 1.183 16.4902 1.28711H16.4912L20.1777 9.8916L20.4141 10.4424L21.0117 10.4941L30.5107 11.3096C30.7062 11.3326 30.8 11.3864 30.8467 11.4238C30.915 11.4787 30.9492 11.5325 30.9707 11.6035C31.0029 11.71 31.0094 11.8192 30.9844 11.9463V11.9482C30.98 11.9702 30.9618 12.0389 30.832 12.1514L23.6338 18.2939L23.1689 18.6904L23.3096 19.2852L25.458 28.3926H25.459C25.4832 28.5045 25.4775 28.5714 25.4658 28.6133L25.4531 28.6475C25.4155 28.7291 25.3682 28.7951 25.3076 28.8516L25.2432 28.9043C25.172 28.9558 25.0868 28.9907 24.96 28.999C24.8763 29.0045 24.793 28.9887 24.6865 28.9229L24.6787 28.918L24.6699 28.9131L16.5088 24.0898L16 23.7891L15.4912 24.0898L7.33008 28.9131L7.32129 28.918L7.31348 28.9229C7.20704 28.9887 7.12371 29.0045 7.04004 28.999C6.94495 28.9928 6.87333 28.9713 6.81348 28.9395L6.75684 28.9043C6.68716 28.8537 6.63221 28.7954 6.58789 28.7236L6.54688 28.6475C6.53436 28.6202 6.51869 28.5743 6.52539 28.4902L6.54102 28.3926L8.69043 19.2852L8.83105 18.6904L8.36621 18.2939L1.16699 12.1514C1.03725 12.0389 1.01999 11.9702 1.01563 11.9482V11.9463L1.00195 11.8545C0.994777 11.765 1.00514 11.6834 1.0293 11.6035C1.05077 11.5325 1.08501 11.4787 1.15332 11.4238C1.19992 11.3864 1.2935 11.3327 1.48828 11.3096L10.9883 10.4941L11.5859 10.4424L11.8223 9.8916L15.5088 1.28711C15.5567 1.18274 15.6103 1.1329 15.6738 1.09766C15.8117 1.02107 15.9169 1 16 1Z';
5
+ const RatingStarFilled = ({ width = 32, height = 30 }) => {
6
+ const theme = useTheme();
7
+ return (_jsx(Svg, { width: width, height: height, viewBox: "0 0 32 30", preserveAspectRatio: "none", children: _jsx(Path, { d: STAR_PATH, strokeWidth: 2, fill: theme.color.surface.highlight.default, stroke: theme.color.border.strong }) }));
8
+ };
9
+ export default RatingStarFilled;
@@ -0,0 +1,2 @@
1
+ export { default as Rating } from './Rating';
2
+ export type { RatingLabels, RatingProps, RatingValue } from './Rating.props';
@@ -0,0 +1 @@
1
+ export { default as Rating } from './Rating';
@@ -0,0 +1,6 @@
1
+ import type RoundelProps from './Roundel.props';
2
+ declare const Roundel: {
3
+ ({ variant, style, ...props }: RoundelProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default Roundel;
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { CloseSmallIcon, TickSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
3
+ import { View } from 'react-native';
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { Icon } from '../Icon';
6
+ const Roundel = ({ variant = 'success', style, ...props }) => {
7
+ styles.useVariants({ variant });
8
+ const icon = variant === 'error' ? CloseSmallIcon : variant === 'success' ? TickSmallIcon : undefined;
9
+ return (_jsx(View, { ...props, style: [styles.container, style], children: icon ? _jsx(Icon, { as: icon, size: "sm" }) : null }));
10
+ };
11
+ Roundel.displayName = 'Roundel';
12
+ const styles = StyleSheet.create(theme => ({
13
+ container: {
14
+ width: theme.space[300],
15
+ height: theme.space[300],
16
+ borderRadius: theme.components.parts.roundel.borderRadius,
17
+ alignItems: 'center',
18
+ justifyContent: 'center',
19
+ overflow: 'hidden',
20
+ backgroundColor: 'transparent',
21
+ borderWidth: 0,
22
+ borderStyle: 'solid',
23
+ variants: {
24
+ variant: {
25
+ success: {
26
+ backgroundColor: theme.color.feedback.positive.surface.default,
27
+ },
28
+ error: {
29
+ backgroundColor: theme.color.feedback.danger.surface.default,
30
+ },
31
+ pending: {
32
+ borderWidth: theme.components.parts.roundel.pending.borderWidth,
33
+ borderStyle: 'dashed',
34
+ borderColor: theme.color.border.strong,
35
+ },
36
+ },
37
+ },
38
+ },
39
+ }));
40
+ export default Roundel;
@@ -0,0 +1,6 @@
1
+ import type { ViewProps } from 'react-native';
2
+ export interface RoundelProps extends Omit<ViewProps, 'children'> {
3
+ /** Visual variant for the roundel status. */
4
+ variant?: 'success' | 'pending' | 'error';
5
+ }
6
+ export default RoundelProps;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export { default as Roundel } from './Roundel';
2
+ export type { RoundelProps } from './Roundel.props';
@@ -0,0 +1 @@
1
+ export { default as Roundel } from './Roundel';
@@ -0,0 +1,22 @@
1
+ import type { ComponentType } from 'react';
2
+ import type { PressableProps } from 'react-native';
3
+ declare const StepperButton: import("react").ForwardRefExoticComponent<{
4
+ icon: ComponentType;
5
+ disabled?: boolean;
6
+ } & Omit<PressableProps, "children"> & {
7
+ states?: {
8
+ active?: boolean;
9
+ disabled?: boolean;
10
+ };
11
+ } & Omit<import("react-native").PressableProps, "children"> & {
12
+ tabIndex?: 0 | -1 | undefined;
13
+ } & {
14
+ children?: import("react").ReactNode | (({ hovered, pressed, focused, focusVisible, disabled, }: {
15
+ hovered?: boolean | undefined;
16
+ pressed?: boolean | undefined;
17
+ focused?: boolean | undefined;
18
+ focusVisible?: boolean | undefined;
19
+ disabled?: boolean | undefined;
20
+ }) => import("react").ReactNode);
21
+ } & import("react").RefAttributes<unknown>>;
22
+ export default StepperButton;
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createPressable } from '@gluestack-ui/pressable';
3
+ import { Pressable } from 'react-native';
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { Icon } from '../Icon';
6
+ const StepperButtonRoot = ({ icon, disabled, states, ...props }) => {
7
+ const isDisabled = disabled ?? states?.disabled ?? false;
8
+ const isActive = states?.active ?? false;
9
+ styles.useVariants({ active: isActive, disabled: isDisabled });
10
+ return (_jsx(Pressable, { ...props, accessibilityRole: "button", accessibilityState: { disabled: isDisabled, ...props.accessibilityState }, disabled: isDisabled, style: [styles.button, props.style], children: _jsx(Icon, { as: icon, size: "sm", style: styles.icon }) }));
11
+ };
12
+ const StepperButton = createPressable({ Root: StepperButtonRoot });
13
+ StepperButton.displayName = 'StepperButton';
14
+ const styles = StyleSheet.create(theme => ({
15
+ button: {
16
+ width: theme.components.iconButton.md.width,
17
+ height: theme.components.iconButton.md.height,
18
+ alignItems: 'center',
19
+ justifyContent: 'center',
20
+ borderRadius: theme.components.input.borderRadius,
21
+ borderWidth: theme.components.input.borderWidth,
22
+ borderColor: theme.color.border.strong,
23
+ backgroundColor: theme.color.surface.neutral.strong,
24
+ _web: {
25
+ _hover: {
26
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.hover,
27
+ },
28
+ _active: {
29
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
30
+ },
31
+ '_focus-visible': {
32
+ outlineStyle: 'solid',
33
+ outlineWidth: 2,
34
+ outlineColor: theme.color.focus.primary,
35
+ outlineOffset: 2,
36
+ },
37
+ },
38
+ variants: {
39
+ active: {
40
+ true: {
41
+ backgroundColor: theme.color.interactive.neutral.surface.subtle.active,
42
+ },
43
+ },
44
+ disabled: {
45
+ true: {
46
+ opacity: theme.opacity.disabled,
47
+ },
48
+ },
49
+ },
50
+ },
51
+ icon: {
52
+ color: theme.color.icon.primary,
53
+ },
54
+ }));
55
+ export default StepperButton;
@@ -0,0 +1,6 @@
1
+ import StepperInputProps from './StepperInput.props';
2
+ declare const StepperInput: {
3
+ ({ value, defaultValue, onChangeText, onChangeValue, min, max, step, focusInputOnStepPress, validationStatus, disabled, readonly, focused, inBottomSheet, required, label, labelVariant, helperText, helperIcon, validText, invalidText, style, decrementAccessibilityLabel, incrementAccessibilityLabel, onFocus, onBlur, ref, ...props }: StepperInputProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default StepperInput;