@utilitywarehouse/hearth-react-native 0.8.1 → 0.8.2

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 (43) hide show
  1. package/.storybook/preview.tsx +1 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-lint.log +1 -1
  4. package/CHANGELOG.md +16 -0
  5. package/build/components/Banner/Banner.js +27 -7
  6. package/build/components/Banner/Banner.props.d.ts +4 -9
  7. package/build/components/Card/Card.props.d.ts +1 -0
  8. package/build/components/Card/CardRoot.d.ts +1 -1
  9. package/build/components/Card/CardRoot.js +28 -1
  10. package/build/components/HighlightBanner/HighlightBanner.props.d.ts +1 -1
  11. package/build/components/List/List.js +1 -1
  12. package/build/components/List/ListAction/ListActionTrailingIcon.js +2 -2
  13. package/build/components/RadioCard/RadioCardGroup.context.d.ts +12 -0
  14. package/build/components/RadioCard/RadioCardGroup.context.js +3 -0
  15. package/build/components/RadioCard/RadioCardGroup.js +15 -10
  16. package/build/components/RadioCard/RadioCardLabel.d.ts +1 -1
  17. package/build/components/RadioCard/RadioCardLabel.js +7 -1
  18. package/build/components/RadioCard/RadioCardRoot.js +13 -0
  19. package/build/core/themes.d.ts +40 -0
  20. package/build/core/themes.js +20 -0
  21. package/docs/adding-shadows.mdx +43 -0
  22. package/package.json +2 -2
  23. package/src/components/Banner/Banner.docs.mdx +1 -1
  24. package/src/components/Banner/Banner.props.ts +4 -9
  25. package/src/components/Banner/Banner.stories.tsx +16 -0
  26. package/src/components/Banner/Banner.tsx +46 -31
  27. package/src/components/Card/Card.docs.mdx +20 -1
  28. package/src/components/Card/Card.props.ts +9 -0
  29. package/src/components/Card/Card.stories.tsx +39 -0
  30. package/src/components/Card/CardRoot.tsx +29 -0
  31. package/src/components/Checkbox/CheckboxGroup.stories.tsx +19 -1
  32. package/src/components/HighlightBanner/HighlightBanner.docs.mdx +1 -1
  33. package/src/components/HighlightBanner/HighlightBanner.props.ts +1 -0
  34. package/src/components/HighlightBanner/HighlightBanner.stories.tsx +15 -1
  35. package/src/components/List/List.tsx +5 -3
  36. package/src/components/List/ListAction/ListActionTrailingIcon.tsx +2 -2
  37. package/src/components/Radio/RadioGroup.stories.tsx +18 -0
  38. package/src/components/RadioCard/RadioCardGroup.context.ts +16 -0
  39. package/src/components/RadioCard/RadioCardGroup.stories.tsx +24 -0
  40. package/src/components/RadioCard/RadioCardGroup.tsx +28 -19
  41. package/src/components/RadioCard/RadioCardLabel.tsx +12 -1
  42. package/src/components/RadioCard/RadioCardRoot.tsx +15 -0
  43. package/src/core/themes.ts +20 -0
@@ -72,6 +72,7 @@ const preview = {
72
72
  'Theme Tokens',
73
73
  'Hooks',
74
74
  'Layout Components',
75
+ 'Guides',
75
76
  'All Components',
76
77
  'Primitives',
77
78
  'Typography',
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.8.1 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.8.2 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.8.1 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.8.2 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.8.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#652](https://github.com/utilitywarehouse/hearth/pull/652) [`5119703`](https://github.com/utilitywarehouse/hearth/commit/5119703a31f663cc8c0a8bb2c6ba5b10f9bf72d6) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes `RadioCard` label wrapping issue
8
+
9
+ - [#648](https://github.com/utilitywarehouse/hearth/pull/648) [`c90ea5c`](https://github.com/utilitywarehouse/hearth/commit/c90ea5ce165f27ccfc9774ec58ac642d02b349d1) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes `ListAction` icon size
10
+
11
+ - [#648](https://github.com/utilitywarehouse/hearth/pull/648) [`c90ea5c`](https://github.com/utilitywarehouse/hearth/commit/c90ea5ce165f27ccfc9774ec58ac642d02b349d1) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes `Banner` text gap size
12
+
13
+ - [#651](https://github.com/utilitywarehouse/hearth/pull/651) [`4ee77b7`](https://github.com/utilitywarehouse/hearth/commit/4ee77b75e58ac4abbeba375ecb367d8899c7f1a0) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes `Banner` image and height issue
14
+
15
+ - [#649](https://github.com/utilitywarehouse/hearth/pull/649) [`7374535`](https://github.com/utilitywarehouse/hearth/commit/737453598ad5e885b35b5fcd2e4c4ccff4910c43) Thanks [@jordmccord](https://github.com/jordmccord)! - Fixes `List` to only show `Card` container if it has children
16
+
17
+ - [#648](https://github.com/utilitywarehouse/hearth/pull/648) [`c90ea5c`](https://github.com/utilitywarehouse/hearth/commit/c90ea5ce165f27ccfc9774ec58ac642d02b349d1) Thanks [@jordmccord](https://github.com/jordmccord)! - Adds `shadowColor` prop to `Card` and exposes helpers in theme
18
+
3
19
  ## 0.8.1
4
20
 
5
21
  ### Patch Changes
@@ -16,10 +16,10 @@ const Banner = ({ icon, iconContainerVariant = 'subtle', iconContainerSize = 'md
16
16
  return (_jsx(IconContainer, { icon: icon, variant: iconContainerVariant, size: iconContainerSize, color: iconContainerColor, style: styles.media }));
17
17
  }
18
18
  if (illustration) {
19
- return (_jsx(ThemedImage, { light: illustration.light, dark: illustration.dark, style: styles.image, accessible: true, accessibilityLabel: heading }));
19
+ return (_jsx(ThemedImage, { ...illustration, resizeMode: "cover", style: [styles.media, styles.imageWrapper, illustration.style] }));
20
20
  }
21
21
  if (image) {
22
- return (_jsx(View, { style: [styles.media, styles.imageWrapper], children: _jsx(ThemedImage, { light: image.light, dark: image.dark, style: styles.image, accessible: true, accessibilityLabel: heading }) }));
22
+ return (_jsx(View, { style: [styles.media, styles.imageWrapper], children: _jsx(ThemedImage, { ...image, style: [styles.image, image.style] }) }));
23
23
  }
24
24
  return null;
25
25
  };
@@ -32,7 +32,7 @@ const Banner = ({ icon, iconContainerVariant = 'subtle', iconContainerSize = 'md
32
32
  }
33
33
  return null;
34
34
  };
35
- const content = (_jsxs(View, { style: styles.container, children: [renderIconOrImage(), _jsxs(View, { style: styles.contentContainer, children: [_jsxs(View, { style: styles.textContainer, children: [_jsx(Heading, { size: "sm", style: styles.heading, textAlign: hasIllustration && direction === 'vertical' ? 'center' : 'left', children: heading }), _jsx(BodyText, { size: "md", style: styles.description, textAlign: hasIllustration && direction === 'vertical' ? 'center' : 'left', children: description }), renderAction()] }), onPress && (_jsx(UnstyledIconButton, { icon: ChevronRightSmallIcon, size: "sm", onPress: onPress, style: styles.chevron })), onClose && (_jsx(UnstyledIconButton, { icon: CloseSmallIcon, size: "sm", onPress: onClose, style: styles.closeButton, accessibilityLabel: "Close banner" }))] })] }));
35
+ const content = (_jsxs(View, { style: styles.container, children: [renderIconOrImage(), _jsxs(View, { style: styles.contentContainer, children: [_jsxs(View, { style: styles.contentTextContainer, children: [_jsxs(View, { style: styles.textContainer, children: [_jsx(Heading, { size: "sm", style: styles.heading, textAlign: hasIllustration && direction === 'vertical' ? 'center' : 'left', children: heading }), _jsx(BodyText, { size: "md", style: styles.description, textAlign: hasIllustration && direction === 'vertical' ? 'center' : 'left', children: description })] }), renderAction()] }), onPress && (_jsx(UnstyledIconButton, { icon: ChevronRightSmallIcon, size: "sm", onPress: onPress, style: styles.chevron })), onClose && (_jsx(UnstyledIconButton, { icon: CloseSmallIcon, size: "sm", onPress: onClose, style: styles.closeButton, accessibilityLabel: "Close banner" }))] })] }));
36
36
  if (onPress) {
37
37
  return (_jsx(Card, { variant: variant, style: [styles.card, style], ...props, children: _jsx(Pressable, { onPress: onPress, accessibilityRole: "button", style: styles.pressable, children: content }) }));
38
38
  }
@@ -92,6 +92,7 @@ const styles = StyleSheet.create(theme => ({
92
92
  },
93
93
  },
94
94
  imageWrapper: {
95
+ flexDirection: 'row',
95
96
  variants: {
96
97
  direction: {
97
98
  horizontal: {},
@@ -103,8 +104,8 @@ const styles = StyleSheet.create(theme => ({
103
104
  },
104
105
  image: {
105
106
  borderRadius: theme.borderRadius.md,
106
- borderWidth: theme.borderWidth[1],
107
107
  borderColor: theme.color.border.strong,
108
+ borderWidth: theme.borderWidth[1],
108
109
  variants: {
109
110
  direction: {
110
111
  horizontal: { width: 160, height: 95 },
@@ -116,15 +117,34 @@ const styles = StyleSheet.create(theme => ({
116
117
  },
117
118
  },
118
119
  contentContainer: {
119
- flex: 1,
120
- flexDirection: 'row',
121
120
  alignItems: 'flex-start',
122
121
  justifyContent: 'space-between',
123
122
  gap: theme.space.lg,
123
+ variants: {
124
+ direction: {
125
+ horizontal: {
126
+ flex: 1,
127
+ flexDirection: 'row',
128
+ },
129
+ vertical: {
130
+ flexDirection: 'column',
131
+ },
132
+ },
133
+ },
124
134
  },
125
135
  textContainer: {
126
- flex: 1,
136
+ gap: theme.space.sm,
137
+ },
138
+ contentTextContainer: {
127
139
  gap: theme.space.lg,
140
+ variants: {
141
+ direction: {
142
+ horizontal: {
143
+ flex: 1,
144
+ },
145
+ vertical: {},
146
+ },
147
+ },
128
148
  },
129
149
  heading: {
130
150
  compoundVariants: [
@@ -1,6 +1,7 @@
1
1
  import type { ComponentType, ReactElement } from 'react';
2
- import type { ImageSourcePropType } from 'react-native';
2
+ import { ImageProps } from 'react-native';
3
3
  import type CardProps from '../Card/Card.props';
4
+ import { ThemedImageProps } from '../ThemedImage';
4
5
  export type BannerDirection = 'horizontal' | 'vertical';
5
6
  export interface BannerProps extends Omit<CardProps, 'noPadding' | 'variant' | 'colorScheme' | 'space' | 'gap' | 'rowGap' | 'columnGap' | 'flexDirection' | 'flexWrap' | 'alignItems' | 'justifyContent'> {
6
7
  /**
@@ -27,18 +28,12 @@ export interface BannerProps extends Omit<CardProps, 'noPadding' | 'variant' | '
27
28
  * Illustration to display in the banner
28
29
  * Mutually exclusive with icon and image
29
30
  */
30
- illustration?: {
31
- light: ImageSourcePropType | ReactElement | ComponentType;
32
- dark: ImageSourcePropType | ReactElement | ComponentType;
33
- };
31
+ illustration?: ThemedImageProps & ImageProps;
34
32
  /**
35
33
  * Image to display in the banner
36
34
  * Mutually exclusive with icon and illustration
37
35
  */
38
- image?: {
39
- light: ImageSourcePropType | ReactElement | ComponentType;
40
- dark: ImageSourcePropType | ReactElement | ComponentType;
41
- };
36
+ image?: ThemedImageProps & ImageProps;
42
37
  /**
43
38
  * Heading text
44
39
  */
@@ -3,6 +3,7 @@ import { SpaceValue, SpacingValues } from '../../types';
3
3
  interface CardProps extends PressableProps {
4
4
  variant?: 'emphasis' | 'subtle';
5
5
  colorScheme?: 'neutralStrong' | 'neutralSubtle' | 'brand' | 'energy' | 'broadband' | 'mobile' | 'insurance' | 'cashback' | 'pig';
6
+ shadowColor?: 'functional' | 'brand' | 'energy' | 'broadband' | 'mobile' | 'insurance' | 'cashback' | 'pig';
6
7
  noPadding?: boolean;
7
8
  disabled?: boolean;
8
9
  space?: SpacingValues;
@@ -1,6 +1,6 @@
1
1
  import CardProps from './Card.props';
2
2
  declare const Card: {
3
- ({ children, variant, colorScheme, noPadding, style, states, space, disabled, onPress, ...rest }: CardProps & {
3
+ ({ children, variant, colorScheme, shadowColor, noPadding, style, states, space, disabled, onPress, ...rest }: CardProps & {
4
4
  states?: {
5
5
  active?: boolean;
6
6
  disabled?: boolean;
@@ -83,7 +83,7 @@ const collectChildActionHandlers = (children) => React.Children.toArray(children
83
83
  }
84
84
  return handlers;
85
85
  }, []);
86
- const Card = ({ children, variant = 'subtle', colorScheme = 'neutralStrong', noPadding = false, style, states, space, disabled = false, onPress, ...rest }) => {
86
+ const Card = ({ children, variant = 'subtle', colorScheme = 'neutralStrong', shadowColor, noPadding = false, style, states, space, disabled = false, onPress, ...rest }) => {
87
87
  const { active } = states || { active: false };
88
88
  const childActionHandlers = collectChildActionHandlers(children);
89
89
  const hasActions = checkForComponentType(children, 'CardAction');
@@ -123,6 +123,7 @@ const Card = ({ children, variant = 'subtle', colorScheme = 'neutralStrong', noP
123
123
  showPressed,
124
124
  disabled,
125
125
  space: hasActions || hasContent ? 'none' : space,
126
+ shadowColor,
126
127
  });
127
128
  const renderChildren = () => {
128
129
  // Default: render children as-is
@@ -190,6 +191,32 @@ const styles = StyleSheet.create(theme => ({
190
191
  borderWidth: theme.components.card.brand.borderWidth,
191
192
  },
192
193
  },
194
+ shadowColor: {
195
+ functional: {
196
+ boxShadow: theme.helpers.shadow.functional,
197
+ },
198
+ brand: {
199
+ boxShadow: theme.helpers.shadow.brand,
200
+ },
201
+ energy: {
202
+ boxShadow: theme.helpers.shadow.energy,
203
+ },
204
+ broadband: {
205
+ boxShadow: theme.helpers.shadow.broadband,
206
+ },
207
+ mobile: {
208
+ boxShadow: theme.helpers.shadow.mobile,
209
+ },
210
+ insurance: {
211
+ boxShadow: theme.helpers.shadow.insurance,
212
+ },
213
+ cashback: {
214
+ boxShadow: theme.helpers.shadow.cashback,
215
+ },
216
+ pig: {
217
+ boxShadow: theme.helpers.shadow.pig,
218
+ },
219
+ },
193
220
  noPadding: {
194
221
  true: {
195
222
  padding: theme.components.card.mobile.paddingNone,
@@ -1,7 +1,7 @@
1
1
  import { ReactElement } from 'react';
2
2
  import { ImageProps } from 'react-native';
3
3
  import CardProps from '../Card/Card.props';
4
- interface HighlightBannerProps extends Omit<CardProps, 'noPadding' | 'variant' | 'space' | 'gap' | 'rowGap' | 'columnGap' | 'flexDirection' | 'flexWrap' | 'alignItems' | 'justifyContent'> {
4
+ interface HighlightBannerProps extends Omit<CardProps, 'noPadding' | 'variant' | 'space' | 'gap' | 'rowGap' | 'columnGap' | 'flexDirection' | 'flexWrap' | 'alignItems' | 'justifyContent' | 'colorScheme'> {
5
5
  heading?: string;
6
6
  headingColor?: 'pig' | 'energy' | 'broadband' | 'mobile' | 'insurance' | 'cashback' | 'highlight';
7
7
  variant?: 'emphasis' | 'subtle';
@@ -44,7 +44,7 @@ const List = ({ children, heading, helperText, headerTrailingContent, ...props }
44
44
  const updatedChildren = markFirstListItem(children);
45
45
  const value = useMemo(() => ({ loading, disabled, container }), [loading, disabled, container]);
46
46
  styles.useVariants({ disabled });
47
- return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...props, style: [styles.container, props.style], children: [heading ? (_jsx(SectionHeader, { heading: heading, helperText: helperText, trailingContent: headerTrailingContent })) : null, container === 'none' ? (_jsx(View, { children: updatedChildren })) : (_jsx(Card, { ...containerToCard, noPadding: true, style: styles.card, children: _jsx(_Fragment, { children: updatedChildren }) }))] }) }));
47
+ return (_jsx(ListContext.Provider, { value: value, children: _jsxs(View, { ...props, style: [styles.container, props.style], children: [heading ? (_jsx(SectionHeader, { heading: heading, helperText: helperText, trailingContent: headerTrailingContent })) : null, container === 'none' ? (_jsx(View, { children: updatedChildren })) : (React.Children.count(updatedChildren) > 0 && (_jsx(Card, { ...containerToCard, noPadding: true, style: styles.card, children: _jsx(_Fragment, { children: updatedChildren }) })))] }) }));
48
48
  };
49
49
  List.displayName = 'List';
50
50
  const styles = StyleSheet.create(theme => ({
@@ -12,8 +12,8 @@ ListActionTrailingIcon.displayName = 'ListActionTrailingIcon';
12
12
  const styles = StyleSheet.create(theme => ({
13
13
  icon: {
14
14
  color: theme.color.icon.primary,
15
- width: 24,
16
- height: 24,
15
+ minWidth: 20,
16
+ minHeight: 20,
17
17
  },
18
18
  }));
19
19
  export default ListActionTrailingIcon;
@@ -0,0 +1,12 @@
1
+ export declare const RadioCardGroupContext: import("react").Context<{
2
+ flexDirection?: "row" | "column" | "row-reverse" | "column-reverse";
3
+ flexWrap?: "nowrap" | "wrap" | "wrap-reverse";
4
+ justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
5
+ alignItems?: "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
6
+ }>;
7
+ export declare const useRadioCardGroupContext: () => {
8
+ flexDirection?: "row" | "column" | "row-reverse" | "column-reverse";
9
+ flexWrap?: "nowrap" | "wrap" | "wrap-reverse";
10
+ justifyContent?: "flex-start" | "flex-end" | "center" | "space-between" | "space-around" | "space-evenly";
11
+ alignItems?: "flex-start" | "flex-end" | "center" | "stretch" | "baseline";
12
+ };
@@ -0,0 +1,3 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const RadioCardGroupContext = createContext({});
3
+ export const useRadioCardGroupContext = () => useContext(RadioCardGroupContext);
@@ -1,18 +1,23 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
2
3
  import { View } from 'react-native';
3
4
  import { StyleSheet } from 'react-native-unistyles';
4
5
  import { Grid } from '../Grid';
6
+ import { RadioCardGroupContext } from './RadioCardGroup.context';
5
7
  const RadioCardGroup = ({ children, gap = '200', style, flexDirection = 'row', flexWrap, justifyContent, alignItems, columns, ...props }) => {
6
- return columns ? (_jsx(Grid, { ...props, gap: gap, columns: columns, style: style, children: children })) : (_jsx(View, { ...props, style: [
7
- styles.containerGap(gap),
8
- {
9
- flexDirection,
10
- flexWrap,
11
- justifyContent,
12
- alignItems,
13
- },
14
- style,
15
- ], children: children }));
8
+ const context = useMemo(() => {
9
+ return { flexDirection, flexWrap, justifyContent, alignItems };
10
+ }, [flexDirection, flexWrap, justifyContent, alignItems]);
11
+ return columns ? (_jsx(RadioCardGroupContext.Provider, { value: context, children: _jsx(Grid, { ...props, gap: gap, columns: columns, style: style, children: children }) })) : (_jsx(RadioCardGroupContext.Provider, { value: context, children: _jsx(View, { ...props, style: [
12
+ styles.containerGap(gap),
13
+ {
14
+ flexDirection,
15
+ flexWrap,
16
+ justifyContent,
17
+ alignItems,
18
+ },
19
+ style,
20
+ ], children: children }) }));
16
21
  };
17
22
  const styles = StyleSheet.create(theme => ({
18
23
  containerGap: (gap) => ({
@@ -1,6 +1,6 @@
1
1
  import LabelProps from '../Label/Label.props';
2
2
  declare const RadioCardLabel: {
3
- ({ children, ...props }: LabelProps): import("react/jsx-runtime").JSX.Element;
3
+ ({ children, style, ...props }: LabelProps): import("react/jsx-runtime").JSX.Element;
4
4
  displayName: string;
5
5
  };
6
6
  export default RadioCardLabel;
@@ -1,5 +1,11 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { StyleSheet } from 'react-native-unistyles';
2
3
  import { Label } from '../Label';
3
- const RadioCardLabel = ({ children, ...props }) => _jsx(Label, { ...props, children: children });
4
+ const RadioCardLabel = ({ children, style, ...props }) => (_jsx(Label, { ...props, style: [styles.label, style], children: children }));
5
+ const styles = StyleSheet.create({
6
+ label: {
7
+ flexShrink: 1,
8
+ },
9
+ });
4
10
  RadioCardLabel.displayName = 'RadioCardLabel';
5
11
  export default RadioCardLabel;
@@ -3,14 +3,17 @@ import { useMemo } from 'react';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
  import { Pressable } from 'react-native';
5
5
  import { RadioCardContext } from './RadioCard.context';
6
+ import { useRadioCardGroupContext } from './RadioCardGroup.context';
6
7
  const RadioCardRoot = ({ children, style, states, ...props }) => {
7
8
  const { checked, active } = states ?? {};
9
+ const { flexDirection } = useRadioCardGroupContext() ?? {};
8
10
  const value = useMemo(() => ({
9
11
  checked,
10
12
  active,
11
13
  }), [checked, active]);
12
14
  styles.useVariants({
13
15
  selected: checked,
16
+ flexDirection,
14
17
  });
15
18
  return (_jsx(RadioCardContext.Provider, { value: value, children: _jsx(Pressable, { ...props, style: [styles.container, style], children: children }) }));
16
19
  };
@@ -37,6 +40,16 @@ const styles = StyleSheet.create(theme => ({
37
40
  margin: -theme.components.card.selectable.borderWidthSelected / 2,
38
41
  },
39
42
  },
43
+ flexDirection: {
44
+ row: {},
45
+ column: {
46
+ width: '100%',
47
+ },
48
+ 'row-reverse': {},
49
+ 'column-reverse': {
50
+ width: '100%',
51
+ },
52
+ },
40
53
  },
41
54
  _web: {
42
55
  '_focus-visible': {
@@ -2,6 +2,16 @@ import { DimensionValue } from 'react-native';
2
2
  import { components } from '../tokens';
3
3
  export declare const lightTheme: {
4
4
  readonly helpers: {
5
+ shadow: {
6
+ functional: string;
7
+ brand: string;
8
+ energy: string;
9
+ broadband: string;
10
+ mobile: string;
11
+ insurance: string;
12
+ cashback: string;
13
+ pig: string;
14
+ };
5
15
  focusVisible: {
6
16
  outlineStyle: string;
7
17
  outlineWidth: number;
@@ -1352,6 +1362,16 @@ export declare const lightTheme: {
1352
1362
  };
1353
1363
  export declare const darkTheme: {
1354
1364
  readonly helpers: {
1365
+ shadow: {
1366
+ functional: string;
1367
+ brand: string;
1368
+ energy: string;
1369
+ broadband: string;
1370
+ mobile: string;
1371
+ insurance: string;
1372
+ cashback: string;
1373
+ pig: string;
1374
+ };
1355
1375
  focusVisible: {
1356
1376
  outlineStyle: string;
1357
1377
  outlineWidth: number;
@@ -2714,6 +2734,16 @@ export declare const darkTheme: {
2714
2734
  export declare const themes: {
2715
2735
  readonly light: {
2716
2736
  readonly helpers: {
2737
+ shadow: {
2738
+ functional: string;
2739
+ brand: string;
2740
+ energy: string;
2741
+ broadband: string;
2742
+ mobile: string;
2743
+ insurance: string;
2744
+ cashback: string;
2745
+ pig: string;
2746
+ };
2717
2747
  focusVisible: {
2718
2748
  outlineStyle: string;
2719
2749
  outlineWidth: number;
@@ -4064,6 +4094,16 @@ export declare const themes: {
4064
4094
  };
4065
4095
  readonly dark: {
4066
4096
  readonly helpers: {
4097
+ shadow: {
4098
+ functional: string;
4099
+ brand: string;
4100
+ energy: string;
4101
+ broadband: string;
4102
+ mobile: string;
4103
+ insurance: string;
4104
+ cashback: string;
4105
+ pig: string;
4106
+ };
4067
4107
  focusVisible: {
4068
4108
  outlineStyle: string;
4069
4109
  outlineWidth: number;
@@ -254,6 +254,16 @@ const shared = {
254
254
  };
255
255
  const lightHelpers = {
256
256
  ...shared.helpers,
257
+ shadow: {
258
+ functional: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.default}`,
259
+ brand: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.brand}`,
260
+ energy: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.energy}`,
261
+ broadband: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.broadband}`,
262
+ mobile: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.mobile}`,
263
+ insurance: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.insurance}`,
264
+ cashback: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.cashback}`,
265
+ pig: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.pig}`,
266
+ },
257
267
  focusVisible: {
258
268
  outlineStyle: 'solid',
259
269
  outlineWidth: 2,
@@ -286,6 +296,16 @@ export const lightTheme = {
286
296
  };
287
297
  const darkHelpers = {
288
298
  ...shared.helpers,
299
+ shadow: {
300
+ functional: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.default}`,
301
+ brand: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.brand}`,
302
+ energy: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.energy}`,
303
+ broadband: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.broadband}`,
304
+ mobile: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.mobile}`,
305
+ insurance: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.insurance}`,
306
+ cashback: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.cashback}`,
307
+ pig: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.pig}`,
308
+ },
289
309
  focusVisible: {
290
310
  outlineStyle: 'solid',
291
311
  outlineWidth: 2,
@@ -0,0 +1,43 @@
1
+ import { Meta } from '@storybook/addon-docs/blocks';
2
+ import { BackToTopButton, NextPrevPage } from './components';
3
+
4
+ <Meta title="Guides / Adding Shadows" />
5
+ <BackToTopButton />
6
+
7
+ # Adding Shadows
8
+
9
+ You can add shadows to your components in Hearth React Native using the Unistyles styling system. Shadows help create depth and visual hierarchy in your UI.
10
+
11
+ - [Using Predefined Shadows](#using-predefined-shadows)
12
+ - [Components with Shadow Props](#components-with-shadow-props)
13
+
14
+ ## Using Predefined Shadows
15
+
16
+ Hearth provides a set of predefined shadow styles that you can easily apply to your components. These shadows are defined in the theme and can be accessed via the `theme.helpers.shadow` object.
17
+
18
+ ```tsx
19
+ import { View } from 'react-native';
20
+ import { StyleSheet, Input } from '@utilitywarehouse/hearth-react-native';
21
+
22
+ const styles = StyleSheet.create(theme => ({
23
+ input: {
24
+ boxShadow: theme.helpers.shadow.functional, // Apply a predefined shadow
25
+ },
26
+ }));
27
+
28
+ const MyComponent = () => (
29
+ <View>
30
+ <Input placeholder="Input with shadow" style={styles.input} />
31
+ </View>
32
+ );
33
+ ```
34
+
35
+ ## Components with Shadow Props
36
+
37
+ Some Hearth components, like `Card`, allow you to specify shadow colors directly via props. You can use the `shadowColor` prop to set the shadow color based on the theme.
38
+
39
+ ```tsx
40
+ import { Card } from '@utilitywarehouse/hearth-react-native';
41
+
42
+ const MyComponent = () => <Card shadowColor="brand">{/* Card content */}</Card>;
43
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilitywarehouse/hearth-react-native",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -56,10 +56,10 @@
56
56
  "vite": "^7.1.3",
57
57
  "vite-plugin-svgr": "^4.5.0",
58
58
  "vitest": "^3.2.4",
59
+ "@utilitywarehouse/hearth-fonts": "^0.0.4",
59
60
  "@utilitywarehouse/hearth-react-icons": "^0.7.3",
60
61
  "@utilitywarehouse/hearth-react-native-icons": "^0.7.3",
61
62
  "@utilitywarehouse/hearth-svg-assets": "^0.2.0",
62
- "@utilitywarehouse/hearth-fonts": "^0.0.4",
63
63
  "@utilitywarehouse/hearth-tokens": "^0.1.3"
64
64
  },
65
65
  "peerDependencies": {
@@ -24,7 +24,7 @@ The `Banner` component is a versatile card-based component for displaying inform
24
24
  - [With Link](#with-link)
25
25
  - [Pressable](#pressable)
26
26
  - [With Close Button](#with-close-button)
27
- - [Vertical Layout](#vertical-layout)
27
+ - [Vertical Layout](#vertical-layout-1)
28
28
  - [Color Schemes](#color-schemes)
29
29
  - [Complex Examples](#complex-examples)
30
30
 
@@ -1,6 +1,7 @@
1
1
  import type { ComponentType, ReactElement } from 'react';
2
- import type { ImageSourcePropType } from 'react-native';
2
+ import { ImageProps } from 'react-native';
3
3
  import type CardProps from '../Card/Card.props';
4
+ import { ThemedImageProps } from '../ThemedImage';
4
5
 
5
6
  export type BannerDirection = 'horizontal' | 'vertical';
6
7
 
@@ -50,18 +51,12 @@ export interface BannerProps
50
51
  * Illustration to display in the banner
51
52
  * Mutually exclusive with icon and image
52
53
  */
53
- illustration?: {
54
- light: ImageSourcePropType | ReactElement | ComponentType;
55
- dark: ImageSourcePropType | ReactElement | ComponentType;
56
- };
54
+ illustration?: ThemedImageProps & ImageProps;
57
55
  /**
58
56
  * Image to display in the banner
59
57
  * Mutually exclusive with icon and illustration
60
58
  */
61
- image?: {
62
- light: ImageSourcePropType | ReactElement | ComponentType;
63
- dark: ImageSourcePropType | ReactElement | ComponentType;
64
- };
59
+ image?: ThemedImageProps & ImageProps;
65
60
  /**
66
61
  * Heading text
67
62
  */
@@ -54,6 +54,20 @@ const meta = {
54
54
  description: 'Icon container size',
55
55
  options: ['sm', 'md', 'lg'],
56
56
  },
57
+ shadowColor: {
58
+ control: 'select',
59
+ description: 'The shadow color of the banner',
60
+ options: [
61
+ 'functional',
62
+ 'brand',
63
+ 'energy',
64
+ 'broadband',
65
+ 'mobile',
66
+ 'insurance',
67
+ 'cashback',
68
+ 'pig',
69
+ ],
70
+ },
57
71
  },
58
72
  args: {
59
73
  heading: 'Welcome to Banner',
@@ -310,6 +324,7 @@ export const VerticalLayout: Story = {
310
324
  direction="vertical"
311
325
  />
312
326
  <Banner
327
+ variant="emphasis"
313
328
  image={{
314
329
  light: {
315
330
  uri: 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=200&q=80',
@@ -320,6 +335,7 @@ export const VerticalLayout: Story = {
320
335
  }}
321
336
  heading="Featured Content"
322
337
  description="Discover amazing content curated just for you."
338
+ shadowColor="brand"
323
339
  direction="vertical"
324
340
  button={
325
341
  <Button size="sm" onPress={() => console.log('Learn More pressed')}>
@@ -46,24 +46,16 @@ const Banner = ({
46
46
  if (illustration) {
47
47
  return (
48
48
  <ThemedImage
49
- light={illustration.light}
50
- dark={illustration.dark}
51
- style={styles.image}
52
- accessible
53
- accessibilityLabel={heading}
49
+ {...illustration}
50
+ resizeMode="cover"
51
+ style={[styles.media, styles.imageWrapper, illustration.style]}
54
52
  />
55
53
  );
56
54
  }
57
55
  if (image) {
58
56
  return (
59
57
  <View style={[styles.media, styles.imageWrapper]}>
60
- <ThemedImage
61
- light={image.light}
62
- dark={image.dark}
63
- style={styles.image}
64
- accessible
65
- accessibilityLabel={heading}
66
- />
58
+ <ThemedImage {...image} style={[styles.image, image.style]} />
67
59
  </View>
68
60
  );
69
61
  }
@@ -84,21 +76,24 @@ const Banner = ({
84
76
  <View style={styles.container}>
85
77
  {renderIconOrImage()}
86
78
  <View style={styles.contentContainer}>
87
- <View style={styles.textContainer}>
88
- <Heading
89
- size="sm"
90
- style={styles.heading}
91
- textAlign={hasIllustration && direction === 'vertical' ? 'center' : 'left'}
92
- >
93
- {heading}
94
- </Heading>
95
- <BodyText
96
- size="md"
97
- style={styles.description}
98
- textAlign={hasIllustration && direction === 'vertical' ? 'center' : 'left'}
99
- >
100
- {description}
101
- </BodyText>
79
+ <View style={styles.contentTextContainer}>
80
+ <View style={styles.textContainer}>
81
+ <Heading
82
+ size="sm"
83
+ style={styles.heading}
84
+ textAlign={hasIllustration && direction === 'vertical' ? 'center' : 'left'}
85
+ >
86
+ {heading}
87
+ </Heading>
88
+ <BodyText
89
+ size="md"
90
+ style={styles.description}
91
+ textAlign={hasIllustration && direction === 'vertical' ? 'center' : 'left'}
92
+ >
93
+ {description}
94
+ </BodyText>
95
+ </View>
96
+
102
97
  {renderAction()}
103
98
  </View>
104
99
  {onPress && (
@@ -194,6 +189,7 @@ const styles = StyleSheet.create(theme => ({
194
189
  },
195
190
  },
196
191
  imageWrapper: {
192
+ flexDirection: 'row',
197
193
  variants: {
198
194
  direction: {
199
195
  horizontal: {},
@@ -205,8 +201,8 @@ const styles = StyleSheet.create(theme => ({
205
201
  },
206
202
  image: {
207
203
  borderRadius: theme.borderRadius.md,
208
- borderWidth: theme.borderWidth[1],
209
204
  borderColor: theme.color.border.strong,
205
+ borderWidth: theme.borderWidth[1],
210
206
  variants: {
211
207
  direction: {
212
208
  horizontal: { width: 160, height: 95 },
@@ -218,15 +214,34 @@ const styles = StyleSheet.create(theme => ({
218
214
  },
219
215
  },
220
216
  contentContainer: {
221
- flex: 1,
222
- flexDirection: 'row',
223
217
  alignItems: 'flex-start',
224
218
  justifyContent: 'space-between',
225
219
  gap: theme.space.lg,
220
+ variants: {
221
+ direction: {
222
+ horizontal: {
223
+ flex: 1,
224
+ flexDirection: 'row',
225
+ },
226
+ vertical: {
227
+ flexDirection: 'column',
228
+ },
229
+ },
230
+ },
226
231
  },
227
232
  textContainer: {
228
- flex: 1,
233
+ gap: theme.space.sm,
234
+ },
235
+ contentTextContainer: {
229
236
  gap: theme.space.lg,
237
+ variants: {
238
+ direction: {
239
+ horizontal: {
240
+ flex: 1,
241
+ },
242
+ vertical: {},
243
+ },
244
+ },
230
245
  },
231
246
  heading: {
232
247
  compoundVariants: [
@@ -22,6 +22,7 @@ A Card component serves as a visual container that groups related content and ac
22
22
  - [Variants](#variants)
23
23
  - [Examples](#examples)
24
24
  - [Interactive](#interactive)
25
+ - [With Shadow](#with-shadow)
25
26
  - [With `CardAction`](#with-cardaction)
26
27
  - [`CardAction` Playground](#cardaction-playground)
27
28
  - [`CardAction` With Badge](#cardaction-with-badge)
@@ -64,7 +65,8 @@ const MyComponent = () => (
64
65
  | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | ----------------- |
65
66
  | variant | `'subtle' \| 'emphasis' ` | The variant of the card. | `'subtle'` |
66
67
  | colorScheme | `'neutralStrong' \| 'neutralSubtle' \| 'purple' \| 'energy'` <br /> `'broadband' \| 'mobile' \| 'insurance' \| 'cashback' \|` <br /> `'pig'` | The color scheme of the card. | `'neutralStrong'` |
67
- | noPadding | `boolean` | Wether or not the card has padding. | `false` |
68
+ | shadowColor | `'functional' \| 'brand' \| 'energy' \| 'broadband' \| 'mobile' `<br /> `'insurance' \| 'cashback' \| 'pig'` | The shadow color of the card. | `-` |
69
+ | noPadding | `boolean` | Whether or not the card has padding. | `false` |
68
70
  | selected | `boolean` | Whether the card is selected. | `false` |
69
71
  | onPress | `() => void` | Callback function to be called. | `-` |
70
72
  | disabled | `boolean` | Whether the card is disabled. | `false` |
@@ -146,6 +148,23 @@ const MyComponent = () => (
146
148
  );
147
149
  ```
148
150
 
151
+ ### With Shadow
152
+
153
+ You can add shadow to the `Card` component by using the `shadowColor` prop. This prop accepts various semantic color options to match your design needs.
154
+
155
+ <Canvas of={Stories.WithShadow} />
156
+
157
+ ```jsx
158
+ import { Card, BodyText, Heading } from '@utilitywarehouse/hearth-react-native';
159
+
160
+ const MyComponent = () => (
161
+ <Card shadowColor="functional" variant="emphasis">
162
+ <Heading size="lg">Card with Shadow</Heading>
163
+ <BodyText>This card has a shadow applied using the shadowColor prop.</BodyText>
164
+ </Card>
165
+ );
166
+ ```
167
+
149
168
  ### With `CardAction`
150
169
 
151
170
  You can use the `CardAction` component within a `Card` to create actionable items with consistent styling.
@@ -13,6 +13,15 @@ interface CardProps extends PressableProps {
13
13
  | 'insurance'
14
14
  | 'cashback'
15
15
  | 'pig';
16
+ shadowColor?:
17
+ | 'functional'
18
+ | 'brand'
19
+ | 'energy'
20
+ | 'broadband'
21
+ | 'mobile'
22
+ | 'insurance'
23
+ | 'cashback'
24
+ | 'pig';
16
25
  noPadding?: boolean;
17
26
  disabled?: boolean;
18
27
  space?: SpacingValues;
@@ -38,6 +38,20 @@ const meta = {
38
38
  ],
39
39
  description: 'Use this value to set the Card color scheme.',
40
40
  },
41
+ shadowColor: {
42
+ control: 'select',
43
+ options: [
44
+ 'functional',
45
+ 'brand',
46
+ 'energy',
47
+ 'broadband',
48
+ 'mobile',
49
+ 'insurance',
50
+ 'cashback',
51
+ 'pig',
52
+ ],
53
+ description: 'Use this value to set the Card shadow color.',
54
+ },
41
55
  },
42
56
  args: {
43
57
  children: 'This is a card',
@@ -208,6 +222,31 @@ export const Variants: Story = {
208
222
  },
209
223
  };
210
224
 
225
+ export const WithShadow: Story = {
226
+ args: {
227
+ shadowColor: 'functional',
228
+ },
229
+ parameters: {
230
+ controls: { exclude: ['variant'] },
231
+ },
232
+ render: ({ children, ...props }) => {
233
+ return (
234
+ <Flex space="lg">
235
+ <VariantTitle title="Subtle - White - Shadow">
236
+ <Card {...props} variant="subtle">
237
+ <BodyText>{children as string}</BodyText>
238
+ </Card>
239
+ </VariantTitle>
240
+ <VariantTitle title="Emphasis - White - Shadow">
241
+ <Card {...props} variant="emphasis">
242
+ <BodyText>{children as string}</BodyText>
243
+ </Card>
244
+ </VariantTitle>
245
+ </Flex>
246
+ );
247
+ },
248
+ };
249
+
211
250
  export const Interactive: Story = {
212
251
  parameters: {
213
252
  controls: { exclude: ['variant', 'colorScheme'] },
@@ -104,12 +104,14 @@ const Card = ({
104
104
  children,
105
105
  variant = 'subtle',
106
106
  colorScheme = 'neutralStrong',
107
+ shadowColor,
107
108
  noPadding = false,
108
109
  style,
109
110
  states,
110
111
  space,
111
112
  disabled = false,
112
113
  onPress,
114
+
113
115
  ...rest
114
116
  }: CardProps & { states?: { active?: boolean; disabled?: boolean } }) => {
115
117
  const { active } = states || { active: false };
@@ -165,6 +167,7 @@ const Card = ({
165
167
  showPressed,
166
168
  disabled,
167
169
  space: hasActions || hasContent ? 'none' : space,
170
+ shadowColor,
168
171
  });
169
172
 
170
173
  const renderChildren = () => {
@@ -257,6 +260,32 @@ const styles = StyleSheet.create(theme => ({
257
260
  borderWidth: theme.components.card.brand.borderWidth,
258
261
  },
259
262
  },
263
+ shadowColor: {
264
+ functional: {
265
+ boxShadow: theme.helpers.shadow.functional,
266
+ },
267
+ brand: {
268
+ boxShadow: theme.helpers.shadow.brand,
269
+ },
270
+ energy: {
271
+ boxShadow: theme.helpers.shadow.energy,
272
+ },
273
+ broadband: {
274
+ boxShadow: theme.helpers.shadow.broadband,
275
+ },
276
+ mobile: {
277
+ boxShadow: theme.helpers.shadow.mobile,
278
+ },
279
+ insurance: {
280
+ boxShadow: theme.helpers.shadow.insurance,
281
+ },
282
+ cashback: {
283
+ boxShadow: theme.helpers.shadow.cashback,
284
+ },
285
+ pig: {
286
+ boxShadow: theme.helpers.shadow.pig,
287
+ },
288
+ },
260
289
  noPadding: {
261
290
  true: {
262
291
  padding: theme.components.card.mobile.paddingNone,
@@ -1,5 +1,5 @@
1
- import { Checkbox, CheckboxGroup } from '.';
2
1
  import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Checkbox, CheckboxGroup } from '.';
3
3
 
4
4
  const meta = {
5
5
  title: 'Stories / CheckboxGroup',
@@ -80,3 +80,21 @@ export const Playground: Story = {
80
80
  </CheckboxGroup>
81
81
  ),
82
82
  };
83
+
84
+ export const LongContent: Story = {
85
+ args: {
86
+ type: 'tile',
87
+ },
88
+ render: args => (
89
+ <CheckboxGroup {...args}>
90
+ <Checkbox aria-label="Label 1" label="Option 1" value="Option 1" nativeID="Checkbox-1" />
91
+ <Checkbox
92
+ aria-label="Label 2"
93
+ label="Option 2 with a very long content that should wrap into multiple lines to test the layout of the checkbox component in such scenarios."
94
+ value="Option 2"
95
+ nativeID="Checkbox-2"
96
+ />
97
+ <Checkbox aria-label="Label 3" label="Option 3" value="Option 3" nativeID="Checkbox-3" />
98
+ </CheckboxGroup>
99
+ ),
100
+ };
@@ -79,7 +79,7 @@ const MyComponent = () => (
79
79
  | button | `ReactElement` | Optional Button component displayed below description | `-` |
80
80
  | variant | `'emphasis' \| 'subtle'` | Visual style variant with strong or subtle borders | `'emphasis'` |
81
81
 
82
- The component also accepts all standard Card props except `noPadding`, `space`, `gap`, `rowGap`, `columnGap`, `flexDirection`, `flexWrap`, `alignItems`, and `justifyContent`.
82
+ The component also accepts all standard Card props except `noPadding`, `colorScheme`, `space`, `gap`, `rowGap`, `columnGap`, `flexDirection`, `flexWrap`, `alignItems`, and `justifyContent`.
83
83
 
84
84
  ## Variants
85
85
 
@@ -15,6 +15,7 @@ interface HighlightBannerProps
15
15
  | 'flexWrap'
16
16
  | 'alignItems'
17
17
  | 'justifyContent'
18
+ | 'colorScheme'
18
19
  > {
19
20
  heading?: string;
20
21
  headingColor?: 'pig' | 'energy' | 'broadband' | 'mobile' | 'insurance' | 'cashback' | 'highlight';
@@ -1,4 +1,4 @@
1
- import { Meta, StoryObj } from '@storybook/react-vite';
1
+ import { Meta, StoryObj } from '@storybook/react-native';
2
2
  import { View } from 'react-native';
3
3
  import { Button } from '../Button';
4
4
  import { Flex } from '../Flex';
@@ -30,6 +30,20 @@ const meta = {
30
30
  description: 'The variant style of the HighlightBanner',
31
31
  options: ['emphasis', 'subtle'],
32
32
  },
33
+ shadowColor: {
34
+ control: 'select',
35
+ description: 'The shadow color of the card',
36
+ options: [
37
+ 'functional',
38
+ 'brand',
39
+ 'energy',
40
+ 'broadband',
41
+ 'mobile',
42
+ 'insurance',
43
+ 'cashback',
44
+ 'pig',
45
+ ],
46
+ },
33
47
  },
34
48
  args: {
35
49
  heading: 'Featured Content',
@@ -69,9 +69,11 @@ const List = ({ children, heading, helperText, headerTrailingContent, ...props }
69
69
  {container === 'none' ? (
70
70
  <View>{updatedChildren}</View>
71
71
  ) : (
72
- <Card {...containerToCard} noPadding style={styles.card}>
73
- <>{updatedChildren}</>
74
- </Card>
72
+ React.Children.count(updatedChildren) > 0 && (
73
+ <Card {...containerToCard} noPadding style={styles.card}>
74
+ <>{updatedChildren}</>
75
+ </Card>
76
+ )
75
77
  )}
76
78
  </View>
77
79
  </ListContext.Provider>
@@ -25,8 +25,8 @@ ListActionTrailingIcon.displayName = 'ListActionTrailingIcon';
25
25
  const styles = StyleSheet.create(theme => ({
26
26
  icon: {
27
27
  color: theme.color.icon.primary,
28
- width: 24,
29
- height: 24,
28
+ minWidth: 20,
29
+ minHeight: 20,
30
30
  },
31
31
  }));
32
32
 
@@ -105,3 +105,21 @@ export const WithGrid: Story = {
105
105
  </RadioGroup>
106
106
  ),
107
107
  };
108
+
109
+ export const LongContent: Story = {
110
+ args: {
111
+ type: 'tile',
112
+ },
113
+ render: args => (
114
+ <RadioGroup {...args}>
115
+ <Radio aria-label="Label 1" label="Option 1" value="Option 1" nativeID="Radio-1" />
116
+ <Radio
117
+ aria-label="Label 2"
118
+ label="Option 2 with a very long content that spans multiple lines to test text wrapping"
119
+ value="Option 2"
120
+ nativeID="Radio-2"
121
+ />
122
+ <Radio aria-label="Label 3" label="Option 3" value="Option 3" nativeID="Radio-3" />
123
+ </RadioGroup>
124
+ ),
125
+ };
@@ -0,0 +1,16 @@
1
+ import { createContext, useContext } from 'react';
2
+
3
+ export const RadioCardGroupContext = createContext<{
4
+ flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
5
+ flexWrap?: 'nowrap' | 'wrap' | 'wrap-reverse';
6
+ justifyContent?:
7
+ | 'flex-start'
8
+ | 'flex-end'
9
+ | 'center'
10
+ | 'space-between'
11
+ | 'space-around'
12
+ | 'space-evenly';
13
+ alignItems?: 'flex-start' | 'flex-end' | 'center' | 'stretch' | 'baseline';
14
+ }>({});
15
+
16
+ export const useRadioCardGroupContext = () => useContext(RadioCardGroupContext);
@@ -68,3 +68,27 @@ export const Playground: Story = {
68
68
  </RadioCardGroup>
69
69
  ),
70
70
  };
71
+
72
+ export const LongContent: Story = {
73
+ args: {
74
+ flexDirection: 'column',
75
+ },
76
+ render: args => (
77
+ <RadioCardGroup {...args}>
78
+ <RadioCard aria-label="Label 1" label="Option 1" value="Option 1" nativeID="RadioCard-1">
79
+ <BodyText>Additional content</BodyText>
80
+ </RadioCard>
81
+ <RadioCard
82
+ aria-label="Label 2"
83
+ label="Option 2 with a very long content that should wrap into multiple lines to test the layout of the RadioCard component in such scenarios."
84
+ value="Option 2"
85
+ nativeID="RadioCard-2"
86
+ >
87
+ <BodyText>Additional content</BodyText>
88
+ </RadioCard>
89
+ <RadioCard aria-label="Label 3" label="Option 3" value="Option 3" nativeID="RadioCard-3">
90
+ <BodyText>Additional content</BodyText>
91
+ </RadioCard>
92
+ </RadioCardGroup>
93
+ ),
94
+ };
@@ -1,7 +1,9 @@
1
- import RadioCardGroupProps from './RadioCardGroup.props';
1
+ import { useMemo } from 'react';
2
2
  import { View } from 'react-native';
3
3
  import { StyleSheet } from 'react-native-unistyles';
4
4
  import { Grid } from '../Grid';
5
+ import { RadioCardGroupContext } from './RadioCardGroup.context';
6
+ import RadioCardGroupProps from './RadioCardGroup.props';
5
7
 
6
8
  const RadioCardGroup = ({
7
9
  children,
@@ -14,26 +16,33 @@ const RadioCardGroup = ({
14
16
  columns,
15
17
  ...props
16
18
  }: RadioCardGroupProps) => {
19
+ const context = useMemo(() => {
20
+ return { flexDirection, flexWrap, justifyContent, alignItems };
21
+ }, [flexDirection, flexWrap, justifyContent, alignItems]);
17
22
  return columns ? (
18
- <Grid {...props} gap={gap} columns={columns} style={style}>
19
- {children as any}
20
- </Grid>
23
+ <RadioCardGroupContext.Provider value={context}>
24
+ <Grid {...props} gap={gap} columns={columns} style={style}>
25
+ {children as any}
26
+ </Grid>
27
+ </RadioCardGroupContext.Provider>
21
28
  ) : (
22
- <View
23
- {...props}
24
- style={[
25
- styles.containerGap(gap),
26
- {
27
- flexDirection,
28
- flexWrap,
29
- justifyContent,
30
- alignItems,
31
- },
32
- style,
33
- ]}
34
- >
35
- {children}
36
- </View>
29
+ <RadioCardGroupContext.Provider value={context}>
30
+ <View
31
+ {...props}
32
+ style={[
33
+ styles.containerGap(gap),
34
+ {
35
+ flexDirection,
36
+ flexWrap,
37
+ justifyContent,
38
+ alignItems,
39
+ },
40
+ style,
41
+ ]}
42
+ >
43
+ {children}
44
+ </View>
45
+ </RadioCardGroupContext.Provider>
37
46
  );
38
47
  };
39
48
 
@@ -1,7 +1,18 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
1
2
  import { Label } from '../Label';
2
3
  import LabelProps from '../Label/Label.props';
3
4
 
4
- const RadioCardLabel = ({ children, ...props }: LabelProps) => <Label {...props}>{children}</Label>;
5
+ const RadioCardLabel = ({ children, style, ...props }: LabelProps) => (
6
+ <Label {...props} style={[styles.label, style]}>
7
+ {children}
8
+ </Label>
9
+ );
10
+
11
+ const styles = StyleSheet.create({
12
+ label: {
13
+ flexShrink: 1,
14
+ },
15
+ });
5
16
 
6
17
  RadioCardLabel.displayName = 'RadioCardLabel';
7
18
 
@@ -4,6 +4,7 @@ import { StyleSheet } from 'react-native-unistyles';
4
4
  import { Pressable, ViewStyle } from 'react-native';
5
5
  import { RadioCardContext } from './RadioCard.context';
6
6
  import type RadioCardProps from './RadioCard.props';
7
+ import { useRadioCardGroupContext } from './RadioCardGroup.context';
7
8
 
8
9
  const RadioCardRoot = ({
9
10
  children,
@@ -13,6 +14,8 @@ const RadioCardRoot = ({
13
14
  }: RadioCardProps & { states?: { disabled?: boolean; checked?: boolean; active?: boolean } }) => {
14
15
  const { checked, active } = states ?? {};
15
16
 
17
+ const { flexDirection } = useRadioCardGroupContext() ?? {};
18
+
16
19
  const value = useMemo(
17
20
  () => ({
18
21
  checked,
@@ -23,6 +26,7 @@ const RadioCardRoot = ({
23
26
 
24
27
  styles.useVariants({
25
28
  selected: checked,
29
+ flexDirection,
26
30
  });
27
31
 
28
32
  return (
@@ -39,6 +43,7 @@ RadioCardRoot.displayName = 'RadioCardRoot';
39
43
  const styles = StyleSheet.create(theme => ({
40
44
  container: {
41
45
  flexDirection: 'column',
46
+
42
47
  gap: theme.components.card.selectable.gap,
43
48
  borderRadius: theme.components.card.borderRadius,
44
49
  backgroundColor: theme.color.surface.neutral.strong,
@@ -58,6 +63,16 @@ const styles = StyleSheet.create(theme => ({
58
63
  margin: -theme.components.card.selectable.borderWidthSelected / 2,
59
64
  },
60
65
  },
66
+ flexDirection: {
67
+ row: {},
68
+ column: {
69
+ width: '100%',
70
+ },
71
+ 'row-reverse': {},
72
+ 'column-reverse': {
73
+ width: '100%',
74
+ },
75
+ },
61
76
  },
62
77
  _web: {
63
78
  '_focus-visible': {
@@ -269,6 +269,16 @@ const shared = {
269
269
 
270
270
  const lightHelpers = {
271
271
  ...shared.helpers,
272
+ shadow: {
273
+ functional: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.default}`,
274
+ brand: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.brand}`,
275
+ energy: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.energy}`,
276
+ broadband: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.broadband}`,
277
+ mobile: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.mobile}`,
278
+ insurance: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.insurance}`,
279
+ cashback: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.cashback}`,
280
+ pig: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${light.shadow.pig}`,
281
+ },
272
282
  focusVisible: {
273
283
  outlineStyle: 'solid',
274
284
  outlineWidth: 2,
@@ -303,6 +313,16 @@ export const lightTheme = {
303
313
 
304
314
  const darkHelpers = {
305
315
  ...shared.helpers,
316
+ shadow: {
317
+ functional: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.default}`,
318
+ brand: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.brand}`,
319
+ energy: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.energy}`,
320
+ broadband: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.broadband}`,
321
+ mobile: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.mobile}`,
322
+ insurance: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.insurance}`,
323
+ cashback: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.cashback}`,
324
+ pig: `${shadow.mobile.md.x}px ${shadow.mobile.md.y}px ${shadow.mobile.md.spread}px ${dark.shadow.pig}`,
325
+ },
306
326
  focusVisible: {
307
327
  outlineStyle: 'solid',
308
328
  outlineWidth: 2,