@utilitywarehouse/hearth-react-native 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.storybook/preview.tsx +1 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-lint.log +16 -16
  4. package/CHANGELOG.md +74 -2
  5. package/build/components/Menu/Menu.d.ts +1 -1
  6. package/build/components/Menu/Menu.js +2 -2
  7. package/build/components/Menu/Menu.props.d.ts +2 -6
  8. package/build/components/Modal/Modal.d.ts +1 -1
  9. package/build/components/Modal/Modal.js +2 -2
  10. package/build/components/Modal/Modal.props.d.ts +1 -0
  11. package/build/components/Modal/Modal.web.d.ts +1 -1
  12. package/build/components/Modal/Modal.web.js +2 -2
  13. package/build/components/Select/Select.d.ts +1 -1
  14. package/build/components/Select/Select.js +9 -10
  15. package/build/components/Select/Select.props.d.ts +16 -0
  16. package/build/components/Toast/ToastItem.js +3 -1
  17. package/docs/changelog.mdx +687 -0
  18. package/package.json +6 -6
  19. package/scripts/copyChangelog.js +50 -0
  20. package/src/components/Checkbox/CheckboxGroup.figma.tsx +21 -1
  21. package/src/components/Menu/Menu.docs.mdx +8 -5
  22. package/src/components/Menu/Menu.figma.tsx +27 -27
  23. package/src/components/Menu/Menu.props.ts +2 -6
  24. package/src/components/Menu/Menu.tsx +3 -6
  25. package/src/components/Menu/MenuItem.figma.tsx +26 -18
  26. package/src/components/Modal/Modal.docs.mdx +22 -21
  27. package/src/components/Modal/Modal.figma.tsx +58 -47
  28. package/src/components/Modal/Modal.props.ts +1 -0
  29. package/src/components/Modal/Modal.stories.tsx +4 -0
  30. package/src/components/Modal/Modal.tsx +2 -1
  31. package/src/components/Modal/Modal.web.tsx +2 -1
  32. package/src/components/PillGroup/Pill.figma.tsx +4 -17
  33. package/src/components/PillGroup/PillGroup.figma.tsx +8 -9
  34. package/src/components/ProgressStepper/ProgressStep.figma.tsx +4 -15
  35. package/src/components/ProgressStepper/ProgressStepper.figma.tsx +9 -16
  36. package/src/components/Radio/Radio.figma.tsx +35 -22
  37. package/src/components/Radio/RadioGroup.figma.tsx +69 -41
  38. package/src/components/Radio/RadioTile.figma.tsx +34 -0
  39. package/src/components/RadioCard/RadioCard.figma.tsx +24 -0
  40. package/src/components/SectionHeader/SectionHeader.figma.tsx +31 -25
  41. package/src/components/Select/Select.docs.mdx +76 -28
  42. package/src/components/Select/Select.figma.tsx +44 -43
  43. package/src/components/Select/Select.props.ts +16 -0
  44. package/src/components/Select/Select.tsx +42 -35
  45. package/src/components/Select/SelectOption.figma.tsx +3 -21
  46. package/src/components/Spinner/Spinner.figma.tsx +12 -25
  47. package/src/components/Switch/Switch.figma.tsx +2 -23
  48. package/src/components/Tabs/Tab.figma.tsx +21 -0
  49. package/src/components/Tabs/Tabs.figma.tsx +18 -27
  50. package/src/components/Textarea/Textarea.figma.tsx +64 -0
  51. package/src/components/Toast/ToastItem.tsx +3 -1
  52. package/src/components/ToggleButtonCard/ToggleButtonCard.figma.tsx +24 -0
  53. package/src/components/VerificationInput/VerificationInput.figma.tsx +53 -0
  54. package/src/components/Radio/RadioTileRoot.figma.tsx +0 -31
@@ -68,6 +68,7 @@ const preview = {
68
68
  order: [
69
69
  'Introduction',
70
70
  'Getting Started',
71
+ 'Changelog',
71
72
  'Styling',
72
73
  'Theme Tokens',
73
74
  'Hooks',
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.19.0 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.20.0 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.19.0 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.20.0 lint /home/runner/work/hearth/hearth/packages/react-native
3
3
  > TIMING=1 eslint .
4
4
 
5
5
 
@@ -31,11 +31,11 @@
31
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
32
 
33
33
  /home/runner/work/hearth/hearth/packages/react-native/src/components/Modal/Modal.tsx
34
- 72:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
35
- 268:5 warning React Hook useCallback has a missing dependency: 'footer'. Either include it or remove the dependency array react-hooks/exhaustive-deps
34
+ 73:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
35
+ 269:5 warning React Hook useCallback has a missing dependency: 'footer'. Either include it or remove the dependency array react-hooks/exhaustive-deps
36
36
 
37
37
  /home/runner/work/hearth/hearth/packages/react-native/src/components/Modal/Modal.web.tsx
38
- 65:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
38
+ 66:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
39
39
 
40
40
  /home/runner/work/hearth/hearth/packages/react-native/src/components/PillGroup/PillGroup.tsx
41
41
  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
@@ -58,15 +58,15 @@
58
58
 
59
59
  ✖ 25 problems (0 errors, 25 warnings)
60
60
 
61
- Rule | Time (ms) | Relative
62
- :------------------------------------------|----------:|--------:
63
- @typescript-eslint/no-unused-vars | 1284.680 | 57.7%
64
- react-hooks/exhaustive-deps | 121.666 | 5.5%
65
- react-hooks/rules-of-hooks | 80.650 | 3.6%
66
- no-global-assign | 65.650 | 2.9%
67
- @typescript-eslint/ban-ts-comment | 55.037 | 2.5%
68
- no-regex-spaces | 46.626 | 2.1%
69
- no-loss-of-precision | 42.743 | 1.9%
70
- no-misleading-character-class | 41.075 | 1.8%
71
- no-unexpected-multiline | 38.229 | 1.7%
72
- @typescript-eslint/no-unsafe-function-type | 27.456 | 1.2%
61
+ Rule | Time (ms) | Relative
62
+ :-----------------------------------------|----------:|--------:
63
+ @typescript-eslint/no-unused-vars | 1584.824 | 64.0%
64
+ react-hooks/exhaustive-deps | 101.118 | 4.1%
65
+ no-global-assign | 70.605 | 2.8%
66
+ react-hooks/rules-of-hooks | 63.862 | 2.6%
67
+ no-misleading-character-class | 49.106 | 2.0%
68
+ @typescript-eslint/ban-ts-comment | 38.021 | 1.5%
69
+ no-unexpected-multiline | 35.859 | 1.4%
70
+ no-loss-of-precision | 32.618 | 1.3%
71
+ @typescript-eslint/triple-slash-reference | 29.048 | 1.2%
72
+ no-useless-escape | 27.538 | 1.1%
package/CHANGELOG.md CHANGED
@@ -1,5 +1,77 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.20.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#898](https://github.com/utilitywarehouse/hearth/pull/898) [`d32a188`](https://github.com/utilitywarehouse/hearth/commit/d32a18840c04222b7b1348133137dc5e56745fe3) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add validation and helper text props to `Select` component
8
+
9
+ The `Select` component now supports built-in validation messages and helper text through new props: `invalidText`, `validText`, `helperText`, and `helperIcon`. This provides a more integrated validation experience without needing to wrap the component in FormField.
10
+
11
+ **Components affected**:
12
+ - `Select`
13
+
14
+ **Developer changes**:
15
+
16
+ You can now add helper text and validation messages directly to Select:
17
+
18
+ ```tsx
19
+ import { Select } from '@utilitywarehouse/hearth-react-native';
20
+
21
+ <Select
22
+ label="Choose an option"
23
+ placeholder="Select an option"
24
+ helperText="This is some helper text for the select component."
25
+ validationStatus="invalid"
26
+ invalidText="Please select a valid option"
27
+ options={[
28
+ { label: 'Option 1', value: '1' },
29
+ { label: 'Option 2', value: '2' },
30
+ { label: 'Option 3', value: '3' },
31
+ ]}
32
+ value={value}
33
+ onValueChange={setValue}
34
+ />;
35
+ ```
36
+
37
+ The component now also supports a `labelVariant` prop to control label styling:
38
+
39
+ ```tsx
40
+ <Select
41
+ label="Choose an option"
42
+ labelVariant="heading"
43
+ // ... other props
44
+ />
45
+ ```
46
+
47
+ These new props work seamlessly alongside the existing FormField wrapper if you prefer that approach. No changes are required to existing Select implementations.
48
+
49
+ ## 0.19.1
50
+
51
+ ### Patch Changes
52
+
53
+ - [#886](https://github.com/utilitywarehouse/hearth/pull/886) [`7a948de`](https://github.com/utilitywarehouse/hearth/commit/7a948dea0d15ce7ca34e4d405e86984213c96196) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Add `loadingHeading` prop to `Modal` component
54
+
55
+ The `Modal` component now supports a `loadingHeading` prop to customise the heading text displayed during loading states. If not provided, it defaults to 'Loading...'.
56
+
57
+ **Components affected**:
58
+ - `Modal`
59
+
60
+ **Developer changes**:
61
+
62
+ No changes required. To customise the loading heading text, use the new `loadingHeading` prop:
63
+
64
+ ```tsx
65
+ <Modal
66
+ loading={isLoading}
67
+ loadingHeading="Processing your request..."
68
+ heading="Confirm Action"
69
+ description="Please wait while we process your request"
70
+ />
71
+ ```
72
+
73
+ - [#888](https://github.com/utilitywarehouse/hearth/pull/888) [`9b3e172`](https://github.com/utilitywarehouse/hearth/commit/9b3e172f9964026f4f3ba140731432ac63550256) Thanks [@jordmccord](https://github.com/jordmccord)! - 🐛 [FIX]: Align `Toast` icon and dismiss button to the top
74
+
3
75
  ## 0.19.0
4
76
 
5
77
  ### Minor Changes
@@ -117,7 +189,7 @@
117
189
 
118
190
  **Developer changes**:
119
191
 
120
- You can now customize `IconButton` colors for service-specific branding:
192
+ You can now customise `IconButton` colors for service-specific branding:
121
193
 
122
194
  ```tsx
123
195
  import { IconButton } from '@utilitywarehouse/hearth-react-native';
@@ -138,7 +210,7 @@
138
210
  - `activeBackgroundColor` - Sets the background color when pressed or in an active state
139
211
  - `shadowColor` - Sets the shadow/elevation color
140
212
 
141
- These overrides work alongside the existing `variant` and `colorScheme` props, allowing you to maintain structural styling while customizing colors for specific branding requirements.
213
+ These overrides work alongside the existing `variant` and `colorScheme` props, allowing you to maintain structural styling while customising colors for specific branding requirements.
142
214
 
143
215
  ### Patch Changes
144
216
 
@@ -1,4 +1,4 @@
1
1
  import type MenuProps from './Menu.props';
2
2
  import type { MenuMethods } from './Menu.props';
3
- declare const Menu: import("react").ForwardRefExoticComponent<MenuProps & import("react").RefAttributes<MenuMethods>>;
3
+ declare const Menu: import("react").ForwardRefExoticComponent<Omit<MenuProps, "ref"> & import("react").RefAttributes<MenuMethods>>;
4
4
  export default Menu;
@@ -4,7 +4,7 @@ import { StyleSheet } from 'react-native-unistyles';
4
4
  import { BodyText } from '../BodyText';
5
5
  import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
6
6
  import { MenuContext } from './Menu.context';
7
- const Menu = forwardRef(({ heading, children, bottomSheetProps }, ref) => {
7
+ const Menu = forwardRef(({ heading, children, ...props }, ref) => {
8
8
  const bottomSheetModalRef = useRef(null);
9
9
  useImperativeHandle(ref, () => ({
10
10
  present: () => bottomSheetModalRef.current?.present(),
@@ -14,7 +14,7 @@ const Menu = forwardRef(({ heading, children, bottomSheetProps }, ref) => {
14
14
  bottomSheetModalRef.current?.dismiss();
15
15
  }, []);
16
16
  const contextValue = useMemo(() => ({ close: handleClose }), [handleClose]);
17
- return (_jsx(BottomSheetModal, { ref: bottomSheetModalRef, ...bottomSheetProps, children: _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, children: _jsxs(MenuContext.Provider, { value: contextValue, children: [heading && (_jsx(BodyText, { size: "md", weight: "semibold", children: heading })), children] }) }) }));
17
+ return (_jsx(BottomSheetModal, { ref: bottomSheetModalRef, ...props, children: _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, children: _jsxs(MenuContext.Provider, { value: contextValue, children: [heading && (_jsx(BodyText, { size: "md", weight: "semibold", children: heading })), children] }) }) }));
18
18
  });
19
19
  Menu.displayName = 'Menu';
20
20
  const styles = StyleSheet.create(theme => ({
@@ -1,10 +1,10 @@
1
- import type { BottomSheetModalProps } from '@gorhom/bottom-sheet';
2
1
  import type { ReactNode } from 'react';
2
+ import { BottomSheetProps } from '../BottomSheet';
3
3
  export interface MenuMethods {
4
4
  present: () => void;
5
5
  dismiss: () => void;
6
6
  }
7
- export interface MenuProps {
7
+ export interface MenuProps extends BottomSheetProps {
8
8
  /**
9
9
  * Heading text displayed at the top of the menu
10
10
  */
@@ -13,9 +13,5 @@ export interface MenuProps {
13
13
  * Menu items to display
14
14
  */
15
15
  children: ReactNode;
16
- /**
17
- * Optional bottom sheet modal props to customise the menu behavior
18
- */
19
- bottomSheetProps?: Partial<BottomSheetModalProps>;
20
16
  }
21
17
  export default MenuProps;
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
3
3
  type Modal<T = any> = BottomSheetModalMethods<T> & {
4
4
  triggerCloseAnimation?: () => void;
5
5
  };
6
- declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;
@@ -13,7 +13,7 @@ import { Button } from '../Button';
13
13
  import { Heading } from '../Heading';
14
14
  import { Spinner } from '../Spinner';
15
15
  import { UnstyledIconButton } from '../UnstyledIconButton';
16
- const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, stickyFooter = true, ...props }) => {
16
+ const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, stickyFooter = true, ...props }) => {
17
17
  const bottomSheetModalRef = useRef(null);
18
18
  const viewRef = useRef(null);
19
19
  const scrollViewRef = useRef(null);
@@ -107,7 +107,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
107
107
  showHandle: props.showHandle,
108
108
  });
109
109
  const footer = (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
110
- const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: "Loading..." })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !stickyFooter && !noButtons ? footer : null] })) }));
110
+ const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, !stickyFooter && !noButtons ? footer : null] })) }));
111
111
  const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [
112
112
  onPressPrimaryButton,
113
113
  primaryButtonText,
@@ -8,6 +8,7 @@ interface ModalProps extends Omit<BottomSheetProps, 'children'> {
8
8
  image?: ReactNode;
9
9
  showCloseButton?: boolean;
10
10
  heading?: string;
11
+ loadingHeading?: string;
11
12
  description?: string;
12
13
  inNavModal?: boolean;
13
14
  fullscreen?: boolean;
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
3
3
  type Modal<T = any> = BottomSheetModalMethods<T> & {
4
4
  triggerCloseAnimation?: () => void;
5
5
  };
6
- declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;
@@ -12,7 +12,7 @@ import { Button } from '../Button';
12
12
  import { Heading } from '../Heading';
13
13
  import { Spinner } from '../Spinner';
14
14
  import { UnstyledIconButton } from '../UnstyledIconButton';
15
- const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, ...props }) => {
15
+ const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, ...props }) => {
16
16
  const bottomSheetModalRef = useRef(null);
17
17
  const viewRef = useRef(null);
18
18
  const scrollViewRef = useRef(null);
@@ -97,7 +97,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
97
97
  bottomSheetModalRef.current?.dismiss();
98
98
  }
99
99
  };
100
- const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: "Loading..." })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, _jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })] })) }));
100
+ const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, description && !image ? _jsx(BodyText, { accessible: true, children: description }) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : null] })] })) : null, children, _jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })] })) }));
101
101
  return inNavModal ? (_jsxs(View, { style: { flex: 1, backgroundColor: theme.color.background.primary }, children: [Platform.OS === 'android' ? (_jsx(Animated.View, { style: [styles.androidContainer, animatedBackgroundStyle], children: _jsx(Animated.View, { style: [styles.pretendContent, animatedPretendContentStyle] }) })) : null, _jsx(Animated.View, { style: [styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle], children: _jsx(View, { style: styles.inNavModalContent, children: content }) })] })) : (_jsx(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, ...props, onChange: handleChange, children: _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, ref: scrollViewRef, children: content }) }));
102
102
  };
103
103
  const styles = StyleSheet.create((theme, rt) => ({
@@ -1,6 +1,6 @@
1
1
  import SelectProps from './Select.props';
2
2
  declare const Select: {
3
- ({ options, value, onValueChange, label, labelVariant, placeholder, disabled, leadingIcon: LeadingIcon, validationStatus, required, children, bottomSheetProps, menuHeading, readonly, emptyText, listProps, searchable, searchPlaceholder, ...rest }: SelectProps): import("react/jsx-runtime").JSX.Element;
3
+ ({ options, value, onValueChange, label, labelVariant, placeholder, disabled, leadingIcon: LeadingIcon, validationStatus, helperText, helperIcon, invalidText, validText, required, children, bottomSheetProps, menuHeading, readonly, emptyText, listProps, searchable, searchPlaceholder, ...rest }: SelectProps): import("react/jsx-runtime").JSX.Element;
4
4
  displayName: string;
5
5
  };
6
6
  export default Select;
@@ -6,13 +6,12 @@ import { StyleSheet } from 'react-native-unistyles';
6
6
  import { BodyText } from '../BodyText';
7
7
  import { BottomSheetFlatList, BottomSheetModal, BottomSheetScrollView, BottomSheetView, } from '../BottomSheet';
8
8
  import { DetailText } from '../DetailText';
9
- import { useFormFieldContext } from '../FormField';
9
+ import { FormField, useFormFieldContext } from '../FormField';
10
10
  import { Icon } from '../Icon';
11
11
  import { Input } from '../Input';
12
- import { Label } from '../Label';
13
12
  import { SelectContext } from './Select.context';
14
13
  import SelectOption from './SelectOption';
15
- const Select = ({ options = [], value, onValueChange, label, labelVariant = 'body', placeholder = 'Select an option', disabled = false, leadingIcon: LeadingIcon, validationStatus = 'initial', required = true, children, bottomSheetProps, menuHeading, readonly = false, emptyText = 'No options available', listProps, searchable = false, searchPlaceholder = 'Search', ...rest }) => {
14
+ const Select = ({ options = [], value, onValueChange, label, labelVariant = 'body', placeholder = 'Select an option', disabled = false, leadingIcon: LeadingIcon, validationStatus = 'initial', helperText, helperIcon, invalidText, validText, required = true, children, bottomSheetProps, menuHeading, readonly = false, emptyText = 'No options available', listProps, searchable = false, searchPlaceholder = 'Search', ...rest }) => {
16
15
  const formFieldContext = useFormFieldContext();
17
16
  const validationStatusFromContext = formFieldContext?.validationStatus ?? validationStatus;
18
17
  const isRequired = formFieldContext?.required ?? required;
@@ -67,13 +66,13 @@ const Select = ({ options = [], value, onValueChange, label, labelVariant = 'bod
67
66
  }, []);
68
67
  const renderSelectOption = useCallback(({ item }) => (_jsx(SelectOption, { label: item.label, value: item.value, disabled: item.disabled, leadingIcon: item.leadingIcon, trailingIcon: item.trailingIcon })), []);
69
68
  const renderEmptyComponent = useCallback(() => (_jsx(BottomSheetView, { style: styles.emptyContainer, children: _jsx(DetailText, { children: emptyText }) })), [emptyText]);
70
- return (_jsxs(View, { ...rest, style: [styles.container, rest.style], children: [!!label && (_jsx(View, { children: _jsxs(Label, { variant: labelVariant, children: [label, !isRequired && _jsx(Label, { variant: labelVariant, children: " (Optional)" })] }) })), _jsxs(Pressable, { onPress: openBottomSheet, disabled: isDisabled || isReadonly, style: ({ pressed }) => [
71
- styles.selectContainer,
72
- styles.pressedContainer(pressed || isOpen),
73
- ], children: [!!LeadingIcon && (_jsx(View, { children: (() => {
74
- const IconAny = Icon;
75
- return _jsx(IconAny, { as: LeadingIcon, style: styles.icon });
76
- })() })), _jsx(View, { style: styles.optionContainer, children: _jsx(BodyText, { numberOfLines: 1, style: styles.placeholderText, children: selectedOption?.label || selectedLabel || placeholder }) }), _jsx(View, { children: _jsx(Icon, { as: ExpandSmallIcon, style: styles.icon }) })] }), _jsx(BottomSheetModal, { ref: bottomSheetModalRef, snapPoints: ['25%', '40%', '80%'], onChange: handleClose, enableDynamicSizing: false, ...bottomSheetProps, children: _jsxs(SelectContext.Provider, { value: {
69
+ return (_jsxs(View, { ...rest, style: [styles.container, rest.style], children: [_jsx(FormField, { label: label, labelVariant: labelVariant, helperText: helperText, helperIcon: helperIcon, validationStatus: validationStatusFromContext, required: isRequired, disabled: isDisabled, readonly: isReadonly, invalidText: invalidText, validText: validText, children: _jsxs(Pressable, { onPress: openBottomSheet, disabled: isDisabled || isReadonly, style: ({ pressed }) => [
70
+ styles.selectContainer,
71
+ styles.pressedContainer(pressed || isOpen),
72
+ ], children: [!!LeadingIcon && (_jsx(View, { children: (() => {
73
+ const IconAny = Icon;
74
+ return _jsx(IconAny, { as: LeadingIcon, style: styles.icon });
75
+ })() })), _jsx(View, { style: styles.optionContainer, children: _jsx(BodyText, { numberOfLines: 1, style: styles.placeholderText, children: selectedOption?.label || selectedLabel || placeholder }) }), _jsx(View, { children: _jsx(Icon, { as: ExpandSmallIcon, style: styles.icon }) })] }) }), _jsx(BottomSheetModal, { ref: bottomSheetModalRef, snapPoints: ['25%', '40%', '80%'], onChange: handleClose, enableDynamicSizing: false, ...bottomSheetProps, children: _jsxs(SelectContext.Provider, { value: {
77
76
  selectedValue: value,
78
77
  onValueChange,
79
78
  close: closeBottomSheet,
@@ -50,6 +50,22 @@ interface SelectProps extends ViewProps {
50
50
  * @default 'body'.
51
51
  */
52
52
  labelVariant?: 'heading' | 'body';
53
+ /**
54
+ * Helper text to show below the select
55
+ */
56
+ helperText?: string;
57
+ /**
58
+ * Optional icon to display alongside the helper text
59
+ */
60
+ helperIcon?: React.ComponentType;
61
+ /**
62
+ * Text to display when validationStatus is 'invalid'
63
+ */
64
+ invalidText?: string;
65
+ /**
66
+ * Text to display when validationStatus is 'valid'
67
+ */
68
+ validText?: string;
53
69
  /**
54
70
  * Placeholder text to show when no value is selected
55
71
  */
@@ -109,7 +109,8 @@ const styles = StyleSheet.create(theme => ({
109
109
  width: 24,
110
110
  height: 24,
111
111
  justifyContent: 'center',
112
- alignItems: 'center',
112
+ alignSelf: 'flex-start',
113
+ alignItems: 'flex-start',
113
114
  flexShrink: 0,
114
115
  },
115
116
  icon: {
@@ -125,6 +126,7 @@ const styles = StyleSheet.create(theme => ({
125
126
  text: { flexShrink: 1 },
126
127
  actions: {
127
128
  flexShrink: 0,
129
+ alignSelf: 'flex-start',
128
130
  },
129
131
  }));
130
132
  export default ToastItem;