@utilitywarehouse/hearth-react-native 0.13.0 → 0.14.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 (52) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/CHANGELOG.md +16 -0
  4. package/build/components/BodyText/index.d.ts +1 -0
  5. package/build/components/Checkbox/CheckboxGroup.context.d.ts +2 -0
  6. package/build/components/Checkbox/CheckboxGroup.js +5 -5
  7. package/build/components/Checkbox/CheckboxTextContent.js +0 -1
  8. package/build/components/Checkbox/CheckboxTileRoot.js +9 -1
  9. package/build/components/FormField/FormField.d.ts +5 -5
  10. package/build/components/List/List.context.d.ts +2 -0
  11. package/build/components/List/List.context.js +2 -0
  12. package/build/components/List/List.js +19 -15
  13. package/build/components/List/ListAction/ListAction.js +3 -2
  14. package/build/components/List/ListAction/ListAction.props.d.ts +0 -1
  15. package/build/components/List/ListItem/ListItem.props.d.ts +4 -1
  16. package/build/components/List/ListItem/ListItemHeading.d.ts +13 -0
  17. package/build/components/List/ListItem/ListItemHeading.js +12 -0
  18. package/build/components/List/ListItem/ListItemHelperText.d.ts +2 -2
  19. package/build/components/List/ListItem/ListItemRoot.d.ts +1 -1
  20. package/build/components/List/ListItem/ListItemRoot.js +7 -6
  21. package/build/components/List/ListItem/index.d.ts +1 -1
  22. package/build/components/List/ListItem/index.js +1 -1
  23. package/build/components/Radio/RadioGroup.context.d.ts +2 -0
  24. package/build/components/Radio/RadioGroup.js +1 -1
  25. package/build/components/Radio/RadioTextContent.js +0 -1
  26. package/build/components/Radio/RadioTileRoot.js +9 -1
  27. package/package.json +2 -2
  28. package/src/components/BodyText/index.ts +1 -0
  29. package/src/components/Checkbox/CheckboxGroup.context.ts +1 -0
  30. package/src/components/Checkbox/CheckboxGroup.tsx +7 -8
  31. package/src/components/Checkbox/CheckboxTextContent.tsx +0 -1
  32. package/src/components/Checkbox/CheckboxTileRoot.tsx +9 -1
  33. package/src/components/List/List.context.ts +4 -0
  34. package/src/components/List/List.docs.mdx +30 -24
  35. package/src/components/List/List.stories.tsx +26 -1
  36. package/src/components/List/List.tsx +26 -19
  37. package/src/components/List/ListAction/ListAction.props.ts +0 -1
  38. package/src/components/List/ListAction/ListAction.tsx +3 -2
  39. package/src/components/List/ListItem/ListItem.props.ts +4 -1
  40. package/src/components/List/ListItem/ListItemHeading.tsx +20 -0
  41. package/src/components/List/ListItem/ListItemHelperText.tsx +2 -3
  42. package/src/components/List/ListItem/ListItemRoot.tsx +11 -6
  43. package/src/components/List/ListItem/index.ts +1 -1
  44. package/src/components/Modal/Modal.docs.mdx +23 -20
  45. package/src/components/Radio/Radio.stories.tsx +24 -0
  46. package/src/components/Radio/RadioGroup.context.ts +1 -0
  47. package/src/components/Radio/RadioGroup.tsx +2 -2
  48. package/src/components/Radio/RadioTextContent.tsx +0 -1
  49. package/src/components/Radio/RadioTileRoot.tsx +9 -1
  50. package/build/components/List/ListItem/ListItemText.d.ts +0 -6
  51. package/build/components/List/ListItem/ListItemText.js +0 -7
  52. package/src/components/List/ListItem/ListItemText.tsx +0 -14
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.13.0 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.14.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.13.0 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.14.0 lint /home/runner/work/hearth/hearth/packages/react-native
3
3
  > TIMING=1 eslint --max-warnings 0
4
4
 
5
5
  Rule | Time (ms) | Relative
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#741](https://github.com/utilitywarehouse/hearth/pull/741) [`acf081a`](https://github.com/utilitywarehouse/hearth/commit/acf081a8e10722ea1e9106f8111f5d9548815646) Thanks [@jordmccord](https://github.com/jordmccord)! - Renames `ListItemText` to `ListItemHeading` (`ListItemText` is deprecated and will be removed in a later release)
8
+
9
+ ### Patch Changes
10
+
11
+ - [#741](https://github.com/utilitywarehouse/hearth/pull/741) [`acf081a`](https://github.com/utilitywarehouse/hearth/commit/acf081a8e10722ea1e9106f8111f5d9548815646) Thanks [@jordmccord](https://github.com/jordmccord)! - Adds `truncateHeading` and `truncateHelperText` props to `ListItem`
12
+
13
+ - [#740](https://github.com/utilitywarehouse/hearth/pull/740) [`16f1ce0`](https://github.com/utilitywarehouse/hearth/commit/16f1ce073d62c4e72693e7e07233a7498c0d0602) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes custom `ListItem` first child border issue
14
+
15
+ - [#737](https://github.com/utilitywarehouse/hearth/pull/737) [`85f76db`](https://github.com/utilitywarehouse/hearth/commit/85f76dbbf7c90db96d7e89e5f5e353a772dd84c2) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes `Radio` and `Checkbox` Groups row direction issue
16
+
17
+ - [#741](https://github.com/utilitywarehouse/hearth/pull/741) [`acf081a`](https://github.com/utilitywarehouse/hearth/commit/acf081a8e10722ea1e9106f8111f5d9548815646) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes `ListItemText` and `ListItemHelperText` prop types
18
+
3
19
  ## 0.13.0
4
20
 
5
21
  ### Minor Changes
@@ -1 +1,2 @@
1
1
  export { default as BodyText } from './BodyText';
2
+ export type { default as BodyTextProps } from './BodyText.props';
@@ -2,9 +2,11 @@ export declare const CheckboxGroupContext: import("react").Context<{
2
2
  disabled?: boolean;
3
3
  validationStatus?: "valid" | "invalid" | "initial";
4
4
  type?: "default" | "tile";
5
+ direction?: "column" | "row";
5
6
  }>;
6
7
  export declare const useCheckboxGroupContext: () => {
7
8
  disabled?: boolean;
8
9
  validationStatus?: "valid" | "invalid" | "initial";
9
10
  type?: "default" | "tile";
11
+ direction?: "column" | "row";
10
12
  };
@@ -1,14 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useMemo } from 'react';
3
- import { CheckboxGroup as CheckboxGroupComponent } from './Checkbox';
4
- import { CheckboxGroupContext } from './CheckboxGroup.context';
5
3
  import { View } from 'react-native';
6
4
  import { StyleSheet } from 'react-native-unistyles';
7
- import CheckboxGroupTextContent from './CheckboxGroupTextContent';
8
- import { Label } from '../Label';
9
5
  import { Helper } from '../Helper';
6
+ import { Label } from '../Label';
7
+ import { CheckboxGroup as CheckboxGroupComponent } from './Checkbox';
8
+ import { CheckboxGroupContext } from './CheckboxGroup.context';
9
+ import CheckboxGroupTextContent from './CheckboxGroupTextContent';
10
10
  const CheckboxGroup = ({ children, disabled, readonly, validationStatus, label, helperText, invalidText, validText, showValidationIcon = true, helperIcon, type, direction = 'column', gap, ...props }) => {
11
- const value = useMemo(() => ({ disabled, validationStatus, type }), [disabled, validationStatus, type]);
11
+ const value = useMemo(() => ({ disabled, validationStatus, type, direction }), [disabled, validationStatus, type, direction]);
12
12
  const showHeader = !!label || !!helperText || !!invalidText || !!validText;
13
13
  const childrenArray = React.Children.toArray(children);
14
14
  const childIsCard = type === 'tile' ||
@@ -6,7 +6,6 @@ const CheckboxTextContent = ({ children, style, ...props }) => {
6
6
  };
7
7
  const styles = StyleSheet.create({
8
8
  content: {
9
- flex: 1,
10
9
  flexShrink: 1,
11
10
  },
12
11
  });
@@ -2,10 +2,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { View } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
  import { useCheckboxContext } from './Checkbox.context';
5
+ import { useCheckboxGroupContext } from './CheckboxGroup.context';
5
6
  const CheckboxTileRoot = ({ children }) => {
7
+ const { direction } = useCheckboxGroupContext();
6
8
  const { checked } = useCheckboxContext();
7
9
  styles.useVariants({
8
10
  checked,
11
+ direction,
9
12
  });
10
13
  return _jsx(View, { style: styles.container, children: children });
11
14
  };
@@ -13,7 +16,6 @@ const styles = StyleSheet.create(theme => ({
13
16
  container: {
14
17
  flexDirection: 'row',
15
18
  justifyContent: 'flex-start',
16
- flex: 1,
17
19
  alignSelf: 'stretch',
18
20
  gap: theme.components.checkbox.gap,
19
21
  padding: theme.components.checkbox.tile.padding,
@@ -29,6 +31,12 @@ const styles = StyleSheet.create(theme => ({
29
31
  margin: -theme.components.checkbox.tile.borderWidthSelected / 2,
30
32
  },
31
33
  },
34
+ direction: {
35
+ row: {},
36
+ column: {
37
+ flex: 1,
38
+ },
39
+ },
32
40
  },
33
41
  },
34
42
  }));
@@ -1,24 +1,24 @@
1
1
  import { View } from 'react-native';
2
2
  import FormFieldProps from './FormField.props';
3
- export declare const FormFieldComponent: import("@gluestack-ui/form-control/lib/types").IFormControlComponentType<import("react-native").ViewProps, Omit<import("../Helper/Helper.props").default, "validationStatus">, Omit<import("../Helper/Helper.props").default, "validationStatus">, Omit<import("..").IconProps, "as">, unknown, Omit<import("../Label/Label.props").default, "disabled">, unknown, Omit<import("../Helper/Helper.props").default, "validationStatus">, import("../BodyText/BodyText.props").default>;
3
+ export declare const FormFieldComponent: import("@gluestack-ui/form-control/lib/types").IFormControlComponentType<import("react-native").ViewProps, Omit<import("../Helper/Helper.props").default, "validationStatus">, Omit<import("../Helper/Helper.props").default, "validationStatus">, Omit<import("..").IconProps, "as">, unknown, Omit<import("../Label/Label.props").default, "disabled">, unknown, Omit<import("../Helper/Helper.props").default, "validationStatus">, import("..").BodyTextProps>;
4
4
  export declare const FormFieldLabel: import("react").ForwardRefExoticComponent<import("react").RefAttributes<unknown>> & {
5
5
  Text: import("react").ForwardRefExoticComponent<Omit<Omit<import("../Label/Label.props").default, "disabled">, "ref"> & import("react").RefAttributes<Omit<import("../Label/Label.props").default, "disabled">>>;
6
6
  };
7
7
  export declare const FormFieldLabelText: import("react").ForwardRefExoticComponent<Omit<Omit<import("../Label/Label.props").default, "disabled">, "ref"> & import("react").RefAttributes<Omit<import("../Label/Label.props").default, "disabled">>>;
8
8
  export declare const FormFieldHelper: import("react").ForwardRefExoticComponent<Omit<import("../Helper/Helper.props").default, "validationStatus"> & import("react").RefAttributes<Omit<import("../Helper/Helper.props").default, "validationStatus">>> & {
9
- Text: import("react").ForwardRefExoticComponent<Omit<import("../BodyText/BodyText.props").default, "ref"> & import("react").RefAttributes<import("../BodyText/BodyText.props").default>>;
9
+ Text: import("react").ForwardRefExoticComponent<Omit<import("..").BodyTextProps, "ref"> & import("react").RefAttributes<import("..").BodyTextProps>>;
10
10
  };
11
- export declare const FormFieldHelperText: import("react").ForwardRefExoticComponent<Omit<import("../BodyText/BodyText.props").default, "ref"> & import("react").RefAttributes<import("../BodyText/BodyText.props").default>>;
11
+ export declare const FormFieldHelperText: import("react").ForwardRefExoticComponent<Omit<import("..").BodyTextProps, "ref"> & import("react").RefAttributes<import("..").BodyTextProps>>;
12
12
  export declare const FormFieldHelperIcon: {
13
13
  (props: import("..").IconProps): import("react/jsx-runtime").JSX.Element;
14
14
  displayName: string;
15
15
  };
16
16
  export declare const FormFieldValidText: {
17
- (props: import("../BodyText/BodyText.props").default): import("react/jsx-runtime").JSX.Element;
17
+ (props: import("..").BodyTextProps): import("react/jsx-runtime").JSX.Element;
18
18
  displayName: string;
19
19
  };
20
20
  export declare const FormFieldInvalidText: {
21
- (props: import("../BodyText/BodyText.props").default): import("react/jsx-runtime").JSX.Element;
21
+ (props: import("..").BodyTextProps): import("react/jsx-runtime").JSX.Element;
22
22
  displayName: string;
23
23
  };
24
24
  export declare const FormFieldTextContent: typeof View;
@@ -9,3 +9,5 @@ export declare const useListContext: () => {
9
9
  disabled?: ListProps["disabled"];
10
10
  container?: ListProps["container"];
11
11
  };
12
+ export declare const ListFirstItemContext: import("react").Context<boolean>;
13
+ export declare const useListFirstItemContext: () => boolean;
@@ -4,3 +4,5 @@ export const useListContext = () => {
4
4
  const context = useContext(ListContext);
5
5
  return context;
6
6
  };
7
+ export const ListFirstItemContext = createContext(false);
8
+ export const useListFirstItemContext = () => useContext(ListFirstItemContext);
@@ -4,7 +4,8 @@ import { View } from 'react-native';
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
5
  import { Card } from '../Card';
6
6
  import { SectionHeader } from '../SectionHeader';
7
- import { ListContext } from './List.context';
7
+ import { ListContext, ListFirstItemContext } from './List.context';
8
+ import { ListAction } from './ListAction';
8
9
  import { ListItem } from './ListItem';
9
10
  const markFirstListItem = (children) => {
10
11
  let found = false;
@@ -12,21 +13,24 @@ const markFirstListItem = (children) => {
12
13
  return React.Children.map(children, (child) => {
13
14
  if (!React.isValidElement(child))
14
15
  return child;
15
- // Check if the current element is the ListItem and hasn't been marked yet
16
- if (!found && child.type === ListItem) {
16
+ // Check if the current element is the ListItem or ListAction and hasn't been marked yet
17
+ if (!found) {
18
+ if (child.type === ListItem || child.type === ListAction) {
19
+ found = true;
20
+ return (_jsx(ListFirstItemContext.Provider, { value: true, children: child }));
21
+ }
22
+ // If the child has nested children, process them recursively
23
+ if (React.isValidElement(child) &&
24
+ child.props &&
25
+ typeof child.props === 'object' &&
26
+ child.props !== null &&
27
+ 'children' in child.props &&
28
+ child.props.children) {
29
+ const clonedChildren = recursiveClone(child.props.children);
30
+ return React.cloneElement(child, { children: clonedChildren });
31
+ }
17
32
  found = true;
18
- // Cast the additional prop to match ListItemProps
19
- return React.cloneElement(child, { isFirst: true });
20
- }
21
- // If the child has nested children, process them recursively
22
- if (React.isValidElement(child) &&
23
- child.props &&
24
- typeof child.props === 'object' &&
25
- child.props !== null &&
26
- 'children' in child.props &&
27
- child.props.children) {
28
- const clonedChildren = recursiveClone(child.props.children);
29
- return React.cloneElement(child, { children: clonedChildren });
33
+ return _jsx(ListFirstItemContext.Provider, { value: true, children: child });
30
34
  }
31
35
  return child;
32
36
  });
@@ -3,7 +3,7 @@ import { createPressable } from '@gluestack-ui/pressable';
3
3
  import { ChevronRightSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
4
4
  import { Pressable } from 'react-native';
5
5
  import { StyleSheet } from 'react-native-unistyles';
6
- import { useListContext } from '../List.context';
6
+ import { useListContext, useListFirstItemContext } from '../List.context';
7
7
  import ListActionContent from './ListActionContent';
8
8
  import ListActionText from './ListActionText';
9
9
  import ListActionTrailingContent from './ListActionTrailingContent';
@@ -11,6 +11,7 @@ import ListActionTrailingIcon from './ListActionTrailingIcon';
11
11
  const ListActionRoot = ({ heading, disabled, variant = 'subtle', ...props }) => {
12
12
  const { onPress } = props;
13
13
  const listContext = useListContext();
14
+ const isFirstContext = useListFirstItemContext();
14
15
  const { active } = props.states || { active: false };
15
16
  const getListContainer = () => {
16
17
  if (listContext?.container?.includes('subtle')) {
@@ -29,7 +30,7 @@ const ListActionRoot = ({ heading, disabled, variant = 'subtle', ...props }) =>
29
30
  disabled: isDisabled,
30
31
  active,
31
32
  showDisabled: !listContext?.disabled && disabled,
32
- isFirstChild: props.isFirst,
33
+ isFirstChild: isFirstContext,
33
34
  container: listContext?.container,
34
35
  });
35
36
  return (_jsxs(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: isDisabled || !onPress, children: [_jsx(ListActionContent, { children: _jsx(ListActionText, { children: heading }) }), _jsx(ListActionTrailingContent, { style: styles.centeredTrailingIcon, children: _jsx(ListActionTrailingIcon, { as: ChevronRightSmallIcon }) })] }));
@@ -3,6 +3,5 @@ interface ListActionProps extends Omit<PressableProps, 'children'> {
3
3
  heading: string;
4
4
  disabled?: boolean;
5
5
  variant?: 'subtle' | 'emphasis';
6
- isFirst?: boolean;
7
6
  }
8
7
  export default ListActionProps;
@@ -4,7 +4,6 @@ interface ListItemBaseProps extends Omit<PressableProps, 'children'> {
4
4
  loading?: boolean;
5
5
  disabled?: boolean;
6
6
  variant?: 'subtle' | 'emphasis';
7
- isFirst?: boolean;
8
7
  }
9
8
  export interface ListItemWithChildren extends ListItemBaseProps {
10
9
  children: ViewProps['children'];
@@ -15,6 +14,8 @@ export interface ListItemWithChildren extends ListItemBaseProps {
15
14
  numericValue?: never;
16
15
  badge?: never;
17
16
  badgePosition?: never;
17
+ truncateHeading?: never;
18
+ truncateHelperText?: never;
18
19
  }
19
20
  export interface ListItemWithoutChildren extends ListItemBaseProps {
20
21
  children?: never;
@@ -25,6 +26,8 @@ export interface ListItemWithoutChildren extends ListItemBaseProps {
25
26
  numericValue?: string | number;
26
27
  badge?: ReactNode;
27
28
  badgePosition?: 'top' | 'bottom';
29
+ truncateHeading?: boolean;
30
+ truncateHelperText?: boolean;
28
31
  }
29
32
  type ListItemProps = ListItemWithChildren | ListItemWithoutChildren;
30
33
  export default ListItemProps;
@@ -0,0 +1,13 @@
1
+ import { BodyTextProps } from '../../BodyText';
2
+ declare const ListItemHeading: {
3
+ ({ children, ...props }: BodyTextProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ /**
7
+ * @deprecated Use `ListItemHeading` instead.
8
+ */
9
+ export declare const ListItemText: {
10
+ ({ children, ...props }: BodyTextProps): import("react/jsx-runtime").JSX.Element;
11
+ displayName: string;
12
+ };
13
+ export default ListItemHeading;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { BodyText } from '../../BodyText';
3
+ const ListItemHeading = ({ children, ...props }) => {
4
+ return (_jsx(BodyText, { size: "lg", ...props, children: children }));
5
+ };
6
+ ListItemHeading.displayName = 'ListItemHeading';
7
+ /**
8
+ * @deprecated Use `ListItemHeading` instead.
9
+ */
10
+ export const ListItemText = ListItemHeading;
11
+ ListItemText.displayName = 'ListItemText';
12
+ export default ListItemHeading;
@@ -1,6 +1,6 @@
1
- import { TextProps } from 'react-native';
1
+ import { BodyTextProps } from '../../BodyText';
2
2
  declare const ListItemHelperText: {
3
- ({ children, ...props }: TextProps): import("react/jsx-runtime").JSX.Element;
3
+ ({ children, ...props }: BodyTextProps): import("react/jsx-runtime").JSX.Element;
4
4
  displayName: string;
5
5
  };
6
6
  export default ListItemHelperText;
@@ -1,6 +1,6 @@
1
1
  import type ListItemProps from './ListItem.props';
2
2
  declare const ListItemRoot: {
3
- ({ heading, helperText, leadingContent, trailingContent, disabled, loading, children, states, variant, badge, badgePosition, numericValue, ...props }: ListItemProps & {
3
+ ({ heading, helperText, leadingContent, trailingContent, disabled, loading, children, states, variant, badge, badgePosition, numericValue, truncateHeading, truncateHelperText, ...props }: ListItemProps & {
4
4
  states?: {
5
5
  active?: boolean;
6
6
  disabled?: boolean;
@@ -5,17 +5,18 @@ import { Pressable } from 'react-native';
5
5
  import { StyleSheet } from 'react-native-unistyles';
6
6
  import { DetailText } from '../../DetailText';
7
7
  import { Skeleton } from '../../Skeleton';
8
- import { useListContext } from '../List.context';
8
+ import { useListContext, useListFirstItemContext } from '../List.context';
9
9
  import { ListItemContext } from './ListItem.context';
10
10
  import ListItemContent from './ListItemContent';
11
+ import ListItemHeading from './ListItemHeading';
11
12
  import ListItemHelperText from './ListItemHelperText';
12
13
  import ListItemLeadingContent from './ListItemLeadingContent';
13
- import ListItemText from './ListItemText';
14
14
  import ListItemTrailingContent from './ListItemTrailingContent';
15
15
  import ListItemTrailingIcon from './ListItemTrailingIcon';
16
- const ListItemRoot = ({ heading, helperText, leadingContent, trailingContent, disabled, loading, children, states, variant = 'subtle', badge, badgePosition = 'bottom', numericValue, ...props }) => {
16
+ const ListItemRoot = ({ heading, helperText, leadingContent, trailingContent, disabled, loading, children, states, variant = 'subtle', badge, badgePosition = 'bottom', numericValue, truncateHeading = false, truncateHelperText = false, ...props }) => {
17
17
  const { onPress } = props;
18
18
  const listContext = useListContext();
19
+ const isFirstContext = useListFirstItemContext();
19
20
  const { active } = states || { active: false };
20
21
  const getListContainer = () => {
21
22
  if (listContext?.container?.includes('subtle')) {
@@ -37,8 +38,8 @@ const ListItemRoot = ({ heading, helperText, leadingContent, trailingContent, di
37
38
  showPressed,
38
39
  active,
39
40
  disabled: isDisabled || isLoading,
40
- showDisabled: !listContext?.disabled && disabled,
41
- isFirstChild: props.isFirst,
41
+ showDisabled: !listContext.disabled && disabled,
42
+ isFirstChild: isFirstContext,
42
43
  container: listContext?.container,
43
44
  });
44
45
  const value = useMemo(() => {
@@ -52,7 +53,7 @@ const ListItemRoot = ({ heading, helperText, leadingContent, trailingContent, di
52
53
  if (loading || listContext?.loading) {
53
54
  return (_jsxs(Pressable, { ...props, testID: loadingTestID, style: [styles.container, props.style], disabled: isDisabled, children: [leadingContent ? _jsx(Skeleton, { width: 24, height: 24 }) : null, _jsxs(ListItemContent, { children: [_jsx(Skeleton, { width: "80%", height: 20 }), _jsx(Skeleton, { width: "100%", height: 16 })] }), onPress || trailingContent ? _jsx(Skeleton, { width: 24, height: 24 }) : null] }));
54
55
  }
55
- return (_jsx(ListItemContext.Provider, { value: value, children: _jsx(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: isDisabled, accessibilityRole: onPress ? 'button' : undefined, children: children ? (children) : (_jsxs(_Fragment, { children: [leadingContent ? (_jsx(ListItemLeadingContent, { children: leadingContent })) : null, _jsxs(ListItemContent, { children: [badgePosition === 'top' && badge ? badge : null, _jsx(ListItemText, { children: heading }), helperText ? _jsx(ListItemHelperText, { children: helperText }) : null, badgePosition === 'bottom' && badge ? badge : null] }), !!numericValue && _jsx(DetailText, { size: "lg", children: numericValue }), trailingContent ? (_jsx(ListItemTrailingContent, { children: trailingContent })) : onPress ? (_jsx(ListItemTrailingContent, { style: styles.centeredTrailingIcon, children: _jsx(ListItemTrailingIcon, { as: ChevronRightSmallIcon }) })) : null] })) }) }));
56
+ return (_jsx(ListItemContext.Provider, { value: value, children: _jsx(Pressable, { ...props, testID: testID, style: [styles.container, props.style], disabled: isDisabled, accessibilityRole: onPress ? 'button' : undefined, children: children ? (children) : (_jsxs(_Fragment, { children: [leadingContent ? (_jsx(ListItemLeadingContent, { children: leadingContent })) : null, _jsxs(ListItemContent, { children: [badgePosition === 'top' && badge ? badge : null, _jsx(ListItemHeading, { truncated: truncateHeading, children: heading }), helperText ? (_jsx(ListItemHelperText, { truncated: truncateHelperText, children: helperText })) : null, badgePosition === 'bottom' && badge ? badge : null] }), !!numericValue && _jsx(DetailText, { size: "lg", children: numericValue }), trailingContent ? (_jsx(ListItemTrailingContent, { children: trailingContent })) : onPress ? (_jsx(ListItemTrailingContent, { style: styles.centeredTrailingIcon, children: _jsx(ListItemTrailingIcon, { as: ChevronRightSmallIcon }) })) : null] })) }) }));
56
57
  };
57
58
  ListItemRoot.displayName = 'ListItemRoot';
58
59
  const styles = StyleSheet.create(theme => ({
@@ -1,9 +1,9 @@
1
1
  export { default as ListItem } from './ListItem';
2
2
  export type { default as ListItemProps } from './ListItem.props';
3
3
  export { default as ListItemContent } from './ListItemContent';
4
+ export { default as ListItemHeading, ListItemText } from './ListItemHeading';
4
5
  export { default as ListItemHelperText } from './ListItemHelperText';
5
6
  export { default as ListItemIcon } from './ListItemIcon';
6
7
  export { default as ListItemLeadingContent } from './ListItemLeadingContent';
7
- export { default as ListItemText } from './ListItemText';
8
8
  export { default as ListItemTrailingContent } from './ListItemTrailingContent';
9
9
  export { default as ListItemTrailingIcon } from './ListItemTrailingIcon';
@@ -1,8 +1,8 @@
1
1
  export { default as ListItem } from './ListItem';
2
2
  export { default as ListItemContent } from './ListItemContent';
3
+ export { default as ListItemHeading, ListItemText } from './ListItemHeading';
3
4
  export { default as ListItemHelperText } from './ListItemHelperText';
4
5
  export { default as ListItemIcon } from './ListItemIcon';
5
6
  export { default as ListItemLeadingContent } from './ListItemLeadingContent';
6
- export { default as ListItemText } from './ListItemText';
7
7
  export { default as ListItemTrailingContent } from './ListItemTrailingContent';
8
8
  export { default as ListItemTrailingIcon } from './ListItemTrailingIcon';
@@ -2,9 +2,11 @@ export declare const RadioGroupContext: import("react").Context<{
2
2
  disabled?: boolean;
3
3
  validationStatus?: "valid" | "invalid" | "initial";
4
4
  type?: "default" | "tile";
5
+ direction?: "column" | "row";
5
6
  }>;
6
7
  export declare const useRadioGroupContext: () => {
7
8
  disabled?: boolean;
8
9
  validationStatus?: "valid" | "invalid" | "initial";
9
10
  type?: "default" | "tile";
11
+ direction?: "column" | "row";
10
12
  };
@@ -8,7 +8,7 @@ import { RadioGroup as RadioGroupComponent } from './Radio';
8
8
  import { RadioGroupContext } from './RadioGroup.context';
9
9
  import RadioGroupTextContent from './RadioGroupTextContent';
10
10
  const RadioGroup = ({ children, disabled, readonly, validationStatus, label, helperText, invalidText, validText, showValidationIcon = true, helperIcon, type, direction = 'column', gap, ...props }) => {
11
- const value = useMemo(() => ({ disabled, validationStatus, type }), [disabled, validationStatus, type]);
11
+ const value = useMemo(() => ({ disabled, validationStatus, type, direction }), [disabled, validationStatus, type, direction]);
12
12
  const showHeader = !!label || !!helperText || !!invalidText || !!validText;
13
13
  const childrenArray = React.Children.toArray(children);
14
14
  const childIsCard = type === 'tile' ||
@@ -6,7 +6,6 @@ const RadioTextContent = ({ children, style, ...props }) => {
6
6
  };
7
7
  const styles = StyleSheet.create({
8
8
  content: {
9
- flex: 1,
10
9
  flexShrink: 1,
11
10
  },
12
11
  });
@@ -2,10 +2,13 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { View } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
  import { useRadioContext } from './Radio.context';
5
+ import { useRadioGroupContext } from './RadioGroup.context';
5
6
  const RadioTileRoot = ({ children }) => {
7
+ const { direction } = useRadioGroupContext();
6
8
  const { checked } = useRadioContext();
7
9
  styles.useVariants({
8
10
  checked,
11
+ direction,
9
12
  });
10
13
  return _jsx(View, { style: styles.container, children: children });
11
14
  };
@@ -13,7 +16,6 @@ const styles = StyleSheet.create(theme => ({
13
16
  container: {
14
17
  flexDirection: 'row',
15
18
  justifyContent: 'flex-start',
16
- flex: 1,
17
19
  alignSelf: 'stretch',
18
20
  gap: theme.components.radio.gap,
19
21
  padding: theme.components.radio.tile.padding,
@@ -29,6 +31,12 @@ const styles = StyleSheet.create(theme => ({
29
31
  margin: -theme.components.radio.tile.borderWidthSelected / 2,
30
32
  },
31
33
  },
34
+ direction: {
35
+ row: {},
36
+ column: {
37
+ flex: 1,
38
+ },
39
+ },
32
40
  },
33
41
  },
34
42
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilitywarehouse/hearth-react-native",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -56,8 +56,8 @@
56
56
  "vite-plugin-svgr": "^4.5.0",
57
57
  "vitest": "^3.2.4",
58
58
  "@utilitywarehouse/hearth-fonts": "^0.0.4",
59
- "@utilitywarehouse/hearth-react-native-icons": "^0.7.4",
60
59
  "@utilitywarehouse/hearth-react-icons": "^0.7.4",
60
+ "@utilitywarehouse/hearth-react-native-icons": "^0.7.4",
61
61
  "@utilitywarehouse/hearth-svg-assets": "^0.2.0",
62
62
  "@utilitywarehouse/hearth-tokens": "^0.2.2"
63
63
  },
@@ -1 +1,2 @@
1
1
  export { default as BodyText } from './BodyText';
2
+ export type { default as BodyTextProps } from './BodyText.props';
@@ -4,6 +4,7 @@ export const CheckboxGroupContext = createContext<{
4
4
  disabled?: boolean;
5
5
  validationStatus?: 'valid' | 'invalid' | 'initial';
6
6
  type?: 'default' | 'tile';
7
+ direction?: 'column' | 'row';
7
8
  }>({});
8
9
 
9
10
  export const useCheckboxGroupContext = () => useContext(CheckboxGroupContext);
@@ -1,13 +1,12 @@
1
-
2
1
  import React, { useMemo } from 'react';
3
- import { CheckboxGroup as CheckboxGroupComponent } from './Checkbox';
4
- import CheckboxGroupProps from './CheckboxGroup.props';
5
- import { CheckboxGroupContext } from './CheckboxGroup.context';
6
2
  import { View } from 'react-native';
7
3
  import { StyleSheet } from 'react-native-unistyles';
8
- import CheckboxGroupTextContent from './CheckboxGroupTextContent';
9
- import { Label } from '../Label';
10
4
  import { Helper } from '../Helper';
5
+ import { Label } from '../Label';
6
+ import { CheckboxGroup as CheckboxGroupComponent } from './Checkbox';
7
+ import { CheckboxGroupContext } from './CheckboxGroup.context';
8
+ import CheckboxGroupProps from './CheckboxGroup.props';
9
+ import CheckboxGroupTextContent from './CheckboxGroupTextContent';
11
10
 
12
11
  const CheckboxGroup = ({
13
12
  children,
@@ -26,8 +25,8 @@ const CheckboxGroup = ({
26
25
  ...props
27
26
  }: CheckboxGroupProps) => {
28
27
  const value = useMemo(
29
- () => ({ disabled, validationStatus, type }),
30
- [disabled, validationStatus, type]
28
+ () => ({ disabled, validationStatus, type, direction }),
29
+ [disabled, validationStatus, type, direction]
31
30
  );
32
31
  const showHeader = !!label || !!helperText || !!invalidText || !!validText;
33
32
  const childrenArray = React.Children.toArray(children as any);
@@ -12,7 +12,6 @@ const CheckboxTextContent = ({ children, style, ...props }: FlexProps) => {
12
12
 
13
13
  const styles = StyleSheet.create({
14
14
  content: {
15
- flex: 1,
16
15
  flexShrink: 1,
17
16
  },
18
17
  });
@@ -1,11 +1,14 @@
1
1
  import { View, ViewProps } from 'react-native';
2
2
  import { StyleSheet } from 'react-native-unistyles';
3
3
  import { useCheckboxContext } from './Checkbox.context';
4
+ import { useCheckboxGroupContext } from './CheckboxGroup.context';
4
5
 
5
6
  const CheckboxTileRoot = ({ children }: { children: ViewProps['children'] }) => {
7
+ const { direction } = useCheckboxGroupContext();
6
8
  const { checked } = useCheckboxContext();
7
9
  styles.useVariants({
8
10
  checked,
11
+ direction,
9
12
  });
10
13
  return <View style={styles.container}>{children}</View>;
11
14
  };
@@ -14,7 +17,6 @@ const styles = StyleSheet.create(theme => ({
14
17
  container: {
15
18
  flexDirection: 'row',
16
19
  justifyContent: 'flex-start',
17
- flex: 1,
18
20
  alignSelf: 'stretch',
19
21
  gap: theme.components.checkbox.gap,
20
22
  padding: theme.components.checkbox.tile.padding,
@@ -30,6 +32,12 @@ const styles = StyleSheet.create(theme => ({
30
32
  margin: -theme.components.checkbox.tile.borderWidthSelected / 2,
31
33
  },
32
34
  },
35
+ direction: {
36
+ row: {},
37
+ column: {
38
+ flex: 1,
39
+ },
40
+ },
33
41
  },
34
42
  },
35
43
  }));
@@ -12,3 +12,7 @@ export const useListContext = () => {
12
12
 
13
13
  return context;
14
14
  };
15
+
16
+ export const ListFirstItemContext = createContext<boolean>(false);
17
+
18
+ export const useListFirstItemContext = () => useContext(ListFirstItemContext);
@@ -20,10 +20,10 @@ import {
20
20
  ListAction,
21
21
  ListItem,
22
22
  ListItemContent,
23
+ ListItemHeading,
23
24
  ListItemHelperText,
24
25
  ListItemIcon,
25
26
  ListItemLeadingContent,
26
- ListItemText,
27
27
  ListItemTrailingContent,
28
28
  ListItemTrailingIcon,
29
29
  SectionHeader,
@@ -105,19 +105,21 @@ const MyComponent = () => (
105
105
 
106
106
  ### `ListItem`
107
107
 
108
- | Name | Type | Default | Description |
109
- | --------------- | ------------------------ | -------- | --------------------------------------------------------------- |
110
- | heading | `string` | | The text to display in the list item. |
111
- | onPress | `() => void` | | A callback function to be called when the list item is pressed. |
112
- | helperText | `string` | | The supporting text to display in the list item. |
113
- | leadingContent | `ReactNode` | | The leading content to display in the list item. |
114
- | trailingContent | `ReactNode` | | The trailing content to display in the list item. |
115
- | variant | `'subtle' \| 'emphasis'` | | The variant style of the list item. |
116
- | disabled | `boolean` | `false` | Whether to disable the list item. |
117
- | loading | `boolean` | `false` | Whether to show the list item in loading state. |
118
- | badge | `ReactNode` | | The badge component to display in the list item. |
119
- | badgePosition | `'top' \| 'bottom'` | `bottom` | Position of the badge in the list item. |
120
- | numericValue | `string \| number` | | A numeric value to display on the right side of the item. |
108
+ | Name | Type | Default | Description |
109
+ | ------------------ | ------------------------ | -------- | --------------------------------------------------------------- |
110
+ | heading | `string` | | The text to display in the list item. |
111
+ | onPress | `() => void` | | A callback function to be called when the list item is pressed. |
112
+ | helperText | `string` | | The supporting text to display in the list item. |
113
+ | leadingContent | `ReactNode` | | The leading content to display in the list item. |
114
+ | trailingContent | `ReactNode` | | The trailing content to display in the list item. |
115
+ | variant | `'subtle' \| 'emphasis'` | | The variant style of the list item. |
116
+ | disabled | `boolean` | `false` | Whether to disable the list item. |
117
+ | loading | `boolean` | `false` | Whether to show the list item in loading state. |
118
+ | badge | `ReactNode` | | The badge component to display in the list item. |
119
+ | badgePosition | `'top' \| 'bottom'` | `bottom` | Position of the badge in the list item. |
120
+ | numericValue | `string \| number` | | A numeric value to display on the right side of the item. |
121
+ | truncateHeading | `boolean` | `false` | Whether to truncate the heading text if it overflows. |
122
+ | truncateHelperText | `boolean` | `false` | Whether to truncate the helper text if it overflows. |
121
123
 
122
124
  ### `ListAction`
123
125
 
@@ -137,10 +139,14 @@ const MyComponent = () => (
137
139
 
138
140
  #### - `ListItemContent`
139
141
 
140
- #### - `ListItemText`
142
+ #### - `ListItemHeading`
143
+
144
+ Has all props of the [`BodyText` component](?path=/docs/typography-body-text--docs).
141
145
 
142
146
  #### - `ListItemHelperText`
143
147
 
148
+ Has all props of the [`BodyText` component](?path=/docs/typography-body-text--docs).
149
+
144
150
  #### - `ListItemTrailingContent`
145
151
 
146
152
  #### - `ListItemTrailingIcon`
@@ -478,7 +484,7 @@ If you need to use the `<List>` component in a more advanced way, you can use th
478
484
  <ListItemIcon as={BillMediumIcon} />
479
485
  </ListItemLeadingContent>
480
486
  <ListItemContent>
481
- <ListItemText>Bills</ListItemText>
487
+ <ListItemHeading>Bills</ListItemHeading>
482
488
  <ListItemHelperText>View your bills</ListItemHelperText>
483
489
  </ListItemContent>
484
490
  <ListItemTrailingContent>
@@ -490,7 +496,7 @@ If you need to use the `<List>` component in a more advanced way, you can use th
490
496
  <ListItemIcon as={PaymentMediumIcon} />
491
497
  </ListItemLeadingContent>
492
498
  <ListItemContent>
493
- <ListItemText>Payments</ListItemText>
499
+ <ListItemHeading>Payments</ListItemHeading>
494
500
  <ListItemHelperText>Make a payment</ListItemHelperText>
495
501
  </ListItemContent>
496
502
  <ListItemTrailingContent>
@@ -502,7 +508,7 @@ If you need to use the `<List>` component in a more advanced way, you can use th
502
508
  <ListItemIcon as={HomeMediumIcon} />
503
509
  </ListItemLeadingContent>
504
510
  <ListItemContent>
505
- <ListItemText>Moving home</ListItemText>
511
+ <ListItemHeading>Moving home</ListItemHeading>
506
512
  <ListItemHelperText>Tell us if your moving</ListItemHelperText>
507
513
  </ListItemContent>
508
514
  <ListItemTrailingContent>
@@ -514,7 +520,7 @@ If you need to use the `<List>` component in a more advanced way, you can use th
514
520
  <ListItemIcon as={UserMediumIcon} />
515
521
  </ListItemLeadingContent>
516
522
  <ListItemContent>
517
- <ListItemText>Refer a friend</ListItemText>
523
+ <ListItemHeading>Refer a friend</ListItemHeading>
518
524
  <ListItemHelperText>Get rewarded with a friend</ListItemHelperText>
519
525
  </ListItemContent>
520
526
  <ListItemTrailingContent>
@@ -532,7 +538,7 @@ import {
532
538
  ListItemIcon,
533
539
  ListItemLeadingContent,
534
540
  ListItemHelperText,
535
- ListItemText,
541
+ ListItemHeading,
536
542
  ListItemTrailingContent,
537
543
  ListItemTrailingIcon,
538
544
  ListItemContent,
@@ -553,7 +559,7 @@ const MyComponent = () => (
553
559
  <ListItemIcon as={BillMediumIcon} />
554
560
  </ListItemLeadingContent>
555
561
  <ListItemContent>
556
- <ListItemText>Bills</ListItemText>
562
+ <ListItemHeading>Bills</ListItemHeading>
557
563
  <ListItemHelperText>View your bills</ListItemHelperText>
558
564
  </ListItemContent>
559
565
  <ListItemTrailingContent>
@@ -565,7 +571,7 @@ const MyComponent = () => (
565
571
  <ListItemIcon as={PaymentMediumIcon} />
566
572
  </ListItemLeadingContent>
567
573
  <ListItemContent>
568
- <ListItemText>Payments</ListItemText>
574
+ <ListItemHeading>Payments</ListItemHeading>
569
575
  <ListItemHelperText>Make a payment</ListItemHelperText>
570
576
  </ListItemContent>
571
577
  <ListItemTrailingContent>
@@ -577,7 +583,7 @@ const MyComponent = () => (
577
583
  <ListItemIcon as={HomeMediumIcon} />
578
584
  </ListItemLeadingContent>
579
585
  <ListItemContent>
580
- <ListItemText>Moving home</ListItemText>
586
+ <ListItemHeading>Moving home</ListItemHeading>
581
587
  <ListItemHelperText>Tell us if your moving</ListItemHelperText>
582
588
  </ListItemContent>
583
589
  <ListItemTrailingContent>
@@ -589,7 +595,7 @@ const MyComponent = () => (
589
595
  <ListItemIcon as={UserMediumIcon} />
590
596
  </ListItemLeadingContent>
591
597
  <ListItemContent>
592
- <ListItemText>Refer a friend</ListItemText>
598
+ <ListItemHeading>Refer a friend</ListItemHeading>
593
599
  <ListItemHelperText>Get rewarded with a friend</ListItemHelperText>
594
600
  </ListItemContent>
595
601
  <ListItemTrailingContent>
@@ -212,6 +212,30 @@ export const WithSectionHeader: Story = {
212
212
  ),
213
213
  };
214
214
 
215
+ const CustomListItem = () => (
216
+ <ListItem
217
+ heading="Custom List Item"
218
+ helperText="This is a custom list item component"
219
+ leadingContent={<ListItemIcon as={SettingsMediumIcon} />}
220
+ trailingContent={<ListItemTrailingIcon as={ChevronRightSmallIcon} />}
221
+ onPress={() => console.log('Custom List Item pressed')}
222
+ />
223
+ );
224
+
225
+ export const WithCustomListItemComponent: Story = {
226
+ parameters: {
227
+ controls: { include: [] },
228
+ },
229
+ render: () => (
230
+ <List container="subtleWarmWhite">
231
+ <CustomListItem />
232
+ <CustomListItem />
233
+ <CustomListItem />
234
+ <ListAction heading="Contact support" onPress={() => console.log('Contact pressed')} />
235
+ </List>
236
+ ),
237
+ };
238
+
215
239
  export const WithListAction: Story = {
216
240
  parameters: {
217
241
  controls: { include: [] },
@@ -219,7 +243,8 @@ export const WithListAction: Story = {
219
243
  render: () => (
220
244
  <List container="subtleWarmWhite">
221
245
  <ListItem
222
- heading="Upgrade your plan"
246
+ heading="Upgrade your plan this is really long text to test wrapping"
247
+ truncateHeading={true}
223
248
  helperText="Get more features with a premium plan"
224
249
  onPress={() => console.log('Upgrade pressed')}
225
250
  />
@@ -3,9 +3,10 @@ import { View, ViewProps } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
  import { Card } from '../Card';
5
5
  import { SectionHeader } from '../SectionHeader';
6
- import { ListContext } from './List.context';
6
+ import { ListContext, ListFirstItemContext } from './List.context';
7
7
  import type ListProps from './List.props';
8
- import { ListItem, ListItemProps } from './ListItem';
8
+ import { ListAction } from './ListAction';
9
+ import { ListItem } from './ListItem';
9
10
 
10
11
  const markFirstListItem = (children: ReactNode): ViewProps['children'] => {
11
12
  let found = false;
@@ -14,24 +15,30 @@ const markFirstListItem = (children: ReactNode): ViewProps['children'] => {
14
15
  return React.Children.map(children, (child: ReactNode): ReactNode => {
15
16
  if (!React.isValidElement(child)) return child;
16
17
 
17
- // Check if the current element is the ListItem and hasn't been marked yet
18
- if (!found && child.type === ListItem) {
19
- found = true;
20
- // Cast the additional prop to match ListItemProps
21
- return React.cloneElement(child, { isFirst: true } as Partial<ListItemProps>);
22
- }
18
+ // Check if the current element is the ListItem or ListAction and hasn't been marked yet
19
+ if (!found) {
20
+ if (child.type === ListItem || child.type === ListAction) {
21
+ found = true;
22
+ return (
23
+ <ListFirstItemContext.Provider value={true}>{child}</ListFirstItemContext.Provider>
24
+ );
25
+ }
23
26
 
24
- // If the child has nested children, process them recursively
25
- if (
26
- React.isValidElement(child) &&
27
- child.props &&
28
- typeof child.props === 'object' &&
29
- child.props !== null &&
30
- 'children' in child.props &&
31
- child.props.children
32
- ) {
33
- const clonedChildren = recursiveClone((child.props as any).children);
34
- return React.cloneElement(child, { children: clonedChildren } as any);
27
+ // If the child has nested children, process them recursively
28
+ if (
29
+ React.isValidElement(child) &&
30
+ child.props &&
31
+ typeof child.props === 'object' &&
32
+ child.props !== null &&
33
+ 'children' in child.props &&
34
+ child.props.children
35
+ ) {
36
+ const clonedChildren = recursiveClone((child.props as any).children);
37
+ return React.cloneElement(child, { children: clonedChildren } as any);
38
+ }
39
+
40
+ found = true;
41
+ return <ListFirstItemContext.Provider value={true}>{child}</ListFirstItemContext.Provider>;
35
42
  }
36
43
 
37
44
  return child;
@@ -4,7 +4,6 @@ interface ListActionProps extends Omit<PressableProps, 'children'> {
4
4
  heading: string;
5
5
  disabled?: boolean;
6
6
  variant?: 'subtle' | 'emphasis';
7
- isFirst?: boolean;
8
7
  }
9
8
 
10
9
  export default ListActionProps;
@@ -2,7 +2,7 @@ import { createPressable } from '@gluestack-ui/pressable';
2
2
  import { ChevronRightSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
3
3
  import { Pressable, ViewStyle } from 'react-native';
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
- import { useListContext } from '../List.context';
5
+ import { useListContext, useListFirstItemContext } from '../List.context';
6
6
  import type ListActionProps from './ListAction.props';
7
7
  import ListActionContent from './ListActionContent';
8
8
  import ListActionText from './ListActionText';
@@ -17,6 +17,7 @@ const ListActionRoot = ({
17
17
  }: ListActionProps & { states?: { active?: boolean; disabled?: boolean } }) => {
18
18
  const { onPress } = props;
19
19
  const listContext = useListContext();
20
+ const isFirstContext = useListFirstItemContext();
20
21
 
21
22
  const { active } = props.states || { active: false };
22
23
 
@@ -40,7 +41,7 @@ const ListActionRoot = ({
40
41
  disabled: isDisabled,
41
42
  active,
42
43
  showDisabled: !listContext?.disabled && disabled,
43
- isFirstChild: props.isFirst,
44
+ isFirstChild: isFirstContext,
44
45
  container: listContext?.container,
45
46
  });
46
47
 
@@ -5,7 +5,6 @@ interface ListItemBaseProps extends Omit<PressableProps, 'children'> {
5
5
  loading?: boolean;
6
6
  disabled?: boolean;
7
7
  variant?: 'subtle' | 'emphasis';
8
- isFirst?: boolean;
9
8
  }
10
9
 
11
10
  export interface ListItemWithChildren extends ListItemBaseProps {
@@ -17,6 +16,8 @@ export interface ListItemWithChildren extends ListItemBaseProps {
17
16
  numericValue?: never;
18
17
  badge?: never;
19
18
  badgePosition?: never;
19
+ truncateHeading?: never;
20
+ truncateHelperText?: never;
20
21
  }
21
22
 
22
23
  export interface ListItemWithoutChildren extends ListItemBaseProps {
@@ -28,6 +29,8 @@ export interface ListItemWithoutChildren extends ListItemBaseProps {
28
29
  numericValue?: string | number;
29
30
  badge?: ReactNode;
30
31
  badgePosition?: 'top' | 'bottom';
32
+ truncateHeading?: boolean;
33
+ truncateHelperText?: boolean;
31
34
  }
32
35
 
33
36
  type ListItemProps = ListItemWithChildren | ListItemWithoutChildren;
@@ -0,0 +1,20 @@
1
+ import { BodyText, BodyTextProps } from '../../BodyText';
2
+
3
+ const ListItemHeading = ({ children, ...props }: BodyTextProps) => {
4
+ return (
5
+ <BodyText size="lg" {...props}>
6
+ {children}
7
+ </BodyText>
8
+ );
9
+ };
10
+
11
+ ListItemHeading.displayName = 'ListItemHeading';
12
+
13
+ /**
14
+ * @deprecated Use `ListItemHeading` instead.
15
+ */
16
+ export const ListItemText = ListItemHeading;
17
+
18
+ ListItemText.displayName = 'ListItemText';
19
+
20
+ export default ListItemHeading;
@@ -1,8 +1,7 @@
1
- import { TextProps } from 'react-native';
2
1
  import { StyleSheet } from 'react-native-unistyles';
3
- import { BodyText } from '../../BodyText';
2
+ import { BodyText, BodyTextProps } from '../../BodyText';
4
3
 
5
- const ListItemHelperText = ({ children, ...props }: TextProps) => {
4
+ const ListItemHelperText = ({ children, ...props }: BodyTextProps) => {
6
5
  return (
7
6
  <BodyText size="md" {...props} style={[styles.text, props.style]}>
8
7
  {children}
@@ -4,13 +4,13 @@ import { Pressable, ViewStyle } from 'react-native';
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
5
  import { DetailText } from '../../DetailText';
6
6
  import { Skeleton } from '../../Skeleton';
7
- import { useListContext } from '../List.context';
7
+ import { useListContext, useListFirstItemContext } from '../List.context';
8
8
  import { IListItemContext, ListItemContext } from './ListItem.context';
9
9
  import type ListItemProps from './ListItem.props';
10
10
  import ListItemContent from './ListItemContent';
11
+ import ListItemHeading from './ListItemHeading';
11
12
  import ListItemHelperText from './ListItemHelperText';
12
13
  import ListItemLeadingContent from './ListItemLeadingContent';
13
- import ListItemText from './ListItemText';
14
14
  import ListItemTrailingContent from './ListItemTrailingContent';
15
15
  import ListItemTrailingIcon from './ListItemTrailingIcon';
16
16
 
@@ -27,10 +27,13 @@ const ListItemRoot = ({
27
27
  badge,
28
28
  badgePosition = 'bottom',
29
29
  numericValue,
30
+ truncateHeading = false,
31
+ truncateHelperText = false,
30
32
  ...props
31
33
  }: ListItemProps & { states?: { active?: boolean; disabled?: boolean } }) => {
32
34
  const { onPress } = props;
33
35
  const listContext = useListContext();
36
+ const isFirstContext = useListFirstItemContext();
34
37
  const { active } = states || { active: false };
35
38
 
36
39
  const getListContainer = (): ListItemProps['variant'] => {
@@ -56,8 +59,8 @@ const ListItemRoot = ({
56
59
  showPressed,
57
60
  active,
58
61
  disabled: isDisabled || isLoading,
59
- showDisabled: !listContext?.disabled && disabled,
60
- isFirstChild: props.isFirst,
62
+ showDisabled: !listContext.disabled && disabled,
63
+ isFirstChild: isFirstContext,
61
64
  container: listContext?.container,
62
65
  });
63
66
 
@@ -106,8 +109,10 @@ const ListItemRoot = ({
106
109
  ) : null}
107
110
  <ListItemContent>
108
111
  {badgePosition === 'top' && badge ? badge : null}
109
- <ListItemText>{heading}</ListItemText>
110
- {helperText ? <ListItemHelperText>{helperText}</ListItemHelperText> : null}
112
+ <ListItemHeading truncated={truncateHeading}>{heading}</ListItemHeading>
113
+ {helperText ? (
114
+ <ListItemHelperText truncated={truncateHelperText}>{helperText}</ListItemHelperText>
115
+ ) : null}
111
116
  {badgePosition === 'bottom' && badge ? badge : null}
112
117
  </ListItemContent>
113
118
  {!!numericValue && <DetailText size="lg">{numericValue}</DetailText>}
@@ -1,9 +1,9 @@
1
1
  export { default as ListItem } from './ListItem';
2
2
  export type { default as ListItemProps } from './ListItem.props';
3
3
  export { default as ListItemContent } from './ListItemContent';
4
+ export { default as ListItemHeading, ListItemText } from './ListItemHeading';
4
5
  export { default as ListItemHelperText } from './ListItemHelperText';
5
6
  export { default as ListItemIcon } from './ListItemIcon';
6
7
  export { default as ListItemLeadingContent } from './ListItemLeadingContent';
7
- export { default as ListItemText } from './ListItemText';
8
8
  export { default as ListItemTrailingContent } from './ListItemTrailingContent';
9
9
  export { default as ListItemTrailingIcon } from './ListItemTrailingIcon';
@@ -87,26 +87,29 @@ const MyComponent = () => {
87
87
 
88
88
  The Modal component extends the `BottomSheetModal` component and accepts all of its props, plus the following additional props:
89
89
 
90
- | Property | Type | Description | Default |
91
- | ----------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | ------- |
92
- | `heading` | `string` | The heading text displayed at the top of the modal | - |
93
- | `description` | `string` | The description text displayed below the heading | - |
94
- | `showCloseButton` | `boolean` | Whether to show the close button in the top-right corner | `true` |
95
- | `primaryButtonText` | `string` | Text for the primary action button | - |
96
- | `secondaryButtonText` | `string` | Text for the secondary action button | - |
97
- | `onPressPrimaryButton` | `() => void` | Callback function called when the primary button is pressed | - |
98
- | `onPressSecondaryButton` | `() => void` | Callback function called when the secondary button is pressed | - |
99
- | `onPressCloseButton` | `() => void` | Callback function called when the close button is pressed | - |
100
- | `closeOnPrimaryButtonPress` | `boolean` | Whether to automatically close the modal when the primary button is pressed | `true` |
101
- | `closeOnSecondaryButtonPress` | `boolean` | Whether to automatically close the modal when the secondary button is pressed | `true` |
102
- | `loading` | `boolean` | Whether to show a loading state with spinner | `false` |
103
- | `image` | `ImageProps` | Image to display in the modal (shows as centered content with text below) | - |
104
- | `children` | `ReactNode` | Custom content to display in the modal body | - |
105
- | `primaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the primary button (colorScheme defaults to 'highlight', variant to 'solid') | - |
106
- | `secondaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the secondary button (colorScheme defaults to 'functional', variant to 'outline') | - |
107
- | `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
108
- | `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
109
- | `inNavModal` | `boolean` | Renders the modal correctly when used inside a navigation modal | `false` |
90
+ | Property | Type | Description | Default |
91
+ | ----------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ------- |
92
+ | `heading` | `string` | The heading text displayed at the top of the modal | - |
93
+ | `description` | `string` | The description text displayed below the heading | - |
94
+ | `showCloseButton` | `boolean` | Whether to show the close button in the top-right corner | `true` |
95
+ | `primaryButtonText` | `string` | Text for the primary action button | - |
96
+ | `secondaryButtonText` | `string` | Text for the secondary action button | - |
97
+ | `onPressPrimaryButton` | `() => void` | Callback function called when the primary button is pressed | - |
98
+ | `onPressSecondaryButton` | `() => void` | Callback function called when the secondary button is pressed | - |
99
+ | `onPressCloseButton` | `() => void` | Callback function called when the close button is pressed | - |
100
+ | `closeOnPrimaryButtonPress` | `boolean` | Whether to automatically close the modal when the primary button is pressed | `true` |
101
+ | `closeOnSecondaryButtonPress` | `boolean` | Whether to automatically close the modal when the secondary button is pressed | `true` |
102
+ | `onChange` | `(index: number, position: number, `<br />` type: number) => void` | Callback function called when the modal's position changes \* | - |
103
+ | `loading` | `boolean` | Whether to show a loading state with spinner | `false` |
104
+ | `image` | `ImageProps` | Image to display in the modal (shows as centered content with text below) | - |
105
+ | `children` | `ReactNode` | Custom content to display in the modal body | - |
106
+ | `primaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the primary button (colorScheme defaults to 'highlight', variant to 'solid') | - |
107
+ | `secondaryButtonProps` | `Omit<ButtonWithoutChildrenProps, 'children'>` | Additional props to pass to the secondary button (colorScheme defaults to 'functional', variant to 'outline') | - |
108
+ | `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
109
+ | `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
110
+ | `inNavModal` | `boolean` | Renders the modal correctly when used inside a navigation modal | `false` |
111
+
112
+ \* use this to detect if the modal has been opened or closed, index 0 indicates open state and -1 indicates closed state
110
113
 
111
114
  ### `ModalImage` Props
112
115
 
@@ -6,6 +6,7 @@ import bankLogo from '../../../docs/assets/bank-logo.png';
6
6
  import bankLogo1 from '../../../docs/assets/bank-logo1.png';
7
7
  import { VariantTitle } from '../../../docs/components';
8
8
  import { Flex } from '../Flex';
9
+ import { FormField } from '../FormField';
9
10
 
10
11
  const meta = {
11
12
  title: 'Stories / Radio',
@@ -206,3 +207,26 @@ export const Variants: Story = {
206
207
  );
207
208
  },
208
209
  };
210
+
211
+ export const WithFormFieldExample: Story = () => (
212
+ <>
213
+ <FormField
214
+ label="Account type"
215
+ helperText="Is this account used for personal or business purposes?"
216
+ >
217
+ <RadioGroup type="tile">
218
+ <Radio label="Personal" value="Personal" />
219
+ <Radio label="Business" value="Business" />
220
+ </RadioGroup>
221
+ </FormField>
222
+ <RadioGroup
223
+ direction="row"
224
+ label="Account type"
225
+ helperText="Is this account used for personal or business purposes?"
226
+ type="tile"
227
+ >
228
+ <Radio label="Personal" value="Personal" />
229
+ <Radio label="Business" value="Business" />
230
+ </RadioGroup>
231
+ </>
232
+ );
@@ -4,6 +4,7 @@ export const RadioGroupContext = createContext<{
4
4
  disabled?: boolean;
5
5
  validationStatus?: 'valid' | 'invalid' | 'initial';
6
6
  type?: 'default' | 'tile';
7
+ direction?: 'column' | 'row';
7
8
  }>({});
8
9
 
9
10
  export const useRadioGroupContext = () => useContext(RadioGroupContext);
@@ -25,8 +25,8 @@ const RadioGroup = ({
25
25
  ...props
26
26
  }: RadioGroupProps) => {
27
27
  const value = useMemo(
28
- () => ({ disabled, validationStatus, type }),
29
- [disabled, validationStatus, type]
28
+ () => ({ disabled, validationStatus, type, direction }),
29
+ [disabled, validationStatus, type, direction]
30
30
  );
31
31
  const showHeader = !!label || !!helperText || !!invalidText || !!validText;
32
32
  const childrenArray = React.Children.toArray(children as any);
@@ -12,7 +12,6 @@ const RadioTextContent = ({ children, style, ...props }: FlexProps) => {
12
12
 
13
13
  const styles = StyleSheet.create({
14
14
  content: {
15
- flex: 1,
16
15
  flexShrink: 1,
17
16
  },
18
17
  });
@@ -1,11 +1,14 @@
1
1
  import { View, ViewProps } from 'react-native';
2
2
  import { StyleSheet } from 'react-native-unistyles';
3
3
  import { useRadioContext } from './Radio.context';
4
+ import { useRadioGroupContext } from './RadioGroup.context';
4
5
 
5
6
  const RadioTileRoot = ({ children }: { children: ViewProps['children'] }) => {
7
+ const { direction } = useRadioGroupContext();
6
8
  const { checked } = useRadioContext();
7
9
  styles.useVariants({
8
10
  checked,
11
+ direction,
9
12
  });
10
13
  return <View style={styles.container}>{children}</View>;
11
14
  };
@@ -14,7 +17,6 @@ const styles = StyleSheet.create(theme => ({
14
17
  container: {
15
18
  flexDirection: 'row',
16
19
  justifyContent: 'flex-start',
17
- flex: 1,
18
20
  alignSelf: 'stretch',
19
21
  gap: theme.components.radio.gap,
20
22
  padding: theme.components.radio.tile.padding,
@@ -30,6 +32,12 @@ const styles = StyleSheet.create(theme => ({
30
32
  margin: -theme.components.radio.tile.borderWidthSelected / 2,
31
33
  },
32
34
  },
35
+ direction: {
36
+ row: {},
37
+ column: {
38
+ flex: 1,
39
+ },
40
+ },
33
41
  },
34
42
  },
35
43
  }));
@@ -1,6 +0,0 @@
1
- import { TextProps } from 'react-native';
2
- declare const ListItemText: {
3
- ({ children, ...props }: TextProps): import("react/jsx-runtime").JSX.Element;
4
- displayName: string;
5
- };
6
- export default ListItemText;
@@ -1,7 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { BodyText } from '../../BodyText';
3
- const ListItemText = ({ children, ...props }) => {
4
- return (_jsx(BodyText, { size: "lg", ...props, children: children }));
5
- };
6
- ListItemText.displayName = 'ListItemText';
7
- export default ListItemText;
@@ -1,14 +0,0 @@
1
- import { TextProps } from 'react-native';
2
- import { BodyText } from '../../BodyText';
3
-
4
- const ListItemText = ({ children, ...props }: TextProps) => {
5
- return (
6
- <BodyText size="lg" {...props}>
7
- {children}
8
- </BodyText>
9
- );
10
- };
11
-
12
- ListItemText.displayName = 'ListItemText';
13
-
14
- export default ListItemText;