@utilitywarehouse/hearth-react-native 0.16.1 → 0.17.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 (73) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-lint.log +14 -14
  3. package/CHANGELOG.md +156 -0
  4. package/build/components/Card/CardAction/CardActionRoot.js +12 -2
  5. package/build/components/Card/CardActions.context.d.ts +6 -0
  6. package/build/components/Card/CardActions.context.js +5 -0
  7. package/build/components/Card/CardActions.d.ts +7 -0
  8. package/build/components/Card/CardActions.js +29 -0
  9. package/build/components/Card/CardRoot.js +16 -104
  10. package/build/components/Card/helpers.d.ts +8 -0
  11. package/build/components/Card/helpers.js +146 -0
  12. package/build/components/Card/index.d.ts +2 -0
  13. package/build/components/Card/index.js +2 -0
  14. package/build/components/DateInput/DateInput.d.ts +1 -1
  15. package/build/components/DateInput/DateInput.js +2 -3
  16. package/build/components/DateInput/DateInput.props.d.ts +22 -1
  17. package/build/components/DateInput/DateInputSegment.d.ts +2 -16
  18. package/build/components/DateInput/DateInputSegment.js +3 -6
  19. package/build/components/ExpandableCard/ExpandableCardGroup.d.ts +1 -1
  20. package/build/components/ExpandableCard/ExpandableCardGroup.js +2 -2
  21. package/build/components/ExpandableCard/ExpandableCardGroup.props.d.ts +4 -0
  22. package/build/components/Input/Input.js +4 -3
  23. package/build/components/Input/Input.props.d.ts +9 -0
  24. package/build/components/List/List.context.d.ts +4 -2
  25. package/build/components/List/List.context.js +0 -2
  26. package/build/components/List/List.d.ts +1 -1
  27. package/build/components/List/List.js +25 -38
  28. package/build/components/List/List.props.d.ts +1 -0
  29. package/build/components/List/ListAction/ListAction.js +24 -7
  30. package/build/components/List/ListAction/ListAction.props.d.ts +1 -0
  31. package/build/components/List/ListItem/ListItemRoot.js +12 -4
  32. package/build/utils/isThemedImageProps.d.ts +1 -1
  33. package/package.json +2 -2
  34. package/src/components/Card/Card.docs.mdx +224 -66
  35. package/src/components/Card/Card.stories.tsx +29 -25
  36. package/src/components/Card/CardAction/CardAction.stories.tsx +239 -93
  37. package/src/components/Card/CardAction/CardActionRoot.tsx +15 -2
  38. package/src/components/Card/CardActions.context.ts +12 -0
  39. package/src/components/Card/CardActions.tsx +40 -0
  40. package/src/components/Card/CardRoot.tsx +27 -132
  41. package/src/components/Card/helpers.tsx +195 -0
  42. package/src/components/Card/index.ts +2 -0
  43. package/src/components/DateInput/DateInput.docs.mdx +47 -29
  44. package/src/components/DateInput/DateInput.props.ts +32 -1
  45. package/src/components/DateInput/DateInput.stories.tsx +10 -0
  46. package/src/components/DateInput/DateInput.tsx +12 -1
  47. package/src/components/DateInput/DateInputSegment.tsx +8 -23
  48. package/src/components/ExpandableCard/ExpandableCard.figma.tsx +33 -38
  49. package/src/components/ExpandableCard/ExpandableCardGroup.figma.tsx +34 -17
  50. package/src/components/ExpandableCard/ExpandableCardGroup.props.ts +5 -0
  51. package/src/components/ExpandableCard/ExpandableCardGroup.tsx +2 -0
  52. package/src/components/HighlightBanner/HighlightBanner.figma.tsx +46 -0
  53. package/src/components/IconButton/IconButton.figma.tsx +20 -30
  54. package/src/components/IconContainer/IconContainer.figma.tsx +7 -13
  55. package/src/components/IndicatorIconButton/IndicatorIconButton.figma.tsx +16 -0
  56. package/src/components/Input/Input.docs.mdx +55 -15
  57. package/src/components/Input/Input.figma.tsx +106 -40
  58. package/src/components/Input/Input.props.ts +9 -0
  59. package/src/components/Input/Input.tsx +21 -0
  60. package/src/components/Link/Link.figma.tsx +31 -38
  61. package/src/components/List/List.context.ts +2 -4
  62. package/src/components/List/List.docs.mdx +10 -5
  63. package/src/components/List/List.figma.tsx +42 -28
  64. package/src/components/List/List.props.ts +1 -0
  65. package/src/components/List/List.stories.tsx +43 -0
  66. package/src/components/List/List.tsx +38 -51
  67. package/src/components/List/ListAction/ListAction.figma.tsx +5 -13
  68. package/src/components/List/ListAction/ListAction.props.ts +1 -0
  69. package/src/components/List/ListAction/ListAction.tsx +40 -10
  70. package/src/components/List/ListItem/ListItem.figma.tsx +43 -27
  71. package/src/components/List/ListItem/ListItemRoot.tsx +15 -4
  72. package/src/utils/isThemedImageProps.ts +1 -1
  73. package/src/components/InlineLink/InlineLink.figma.tsx +0 -33
@@ -1,4 +1,4 @@
1
1
 
2
- > @utilitywarehouse/hearth-react-native@0.16.1 build /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.17.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.16.1 lint /home/runner/work/hearth/hearth/packages/react-native
2
+ > @utilitywarehouse/hearth-react-native@0.17.0 lint /home/runner/work/hearth/hearth/packages/react-native
3
3
  > TIMING=1 eslint .
4
4
 
5
5
 
@@ -28,7 +28,7 @@
28
28
  52:6 warning React Hook useCallback has a missing dependency: 'containerHeight'. Either include it or remove the dependency array. Outer scope values like 'styles' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
29
29
 
30
30
  /home/runner/work/hearth/hearth/packages/react-native/src/components/Input/Input.tsx
31
- 75:8 warning React Hook useEffect has a missing dependency: 'formFieldContext'. Either include it or remove the dependency array react-hooks/exhaustive-deps
31
+ 78:8 warning React Hook useEffect has a missing dependency: 'formFieldContext'. Either include it or remove the dependency array react-hooks/exhaustive-deps
32
32
 
33
33
  /home/runner/work/hearth/hearth/packages/react-native/src/components/Modal/Modal.tsx
34
34
  72:6 warning React Hook useCallback has an unnecessary dependency: 'Platform.OS'. Either exclude it or remove the dependency array. Outer scope values like 'Platform.OS' aren't valid dependencies because mutating them doesn't re-render the component react-hooks/exhaustive-deps
@@ -55,15 +55,15 @@
55
55
 
56
56
  ✖ 24 problems (0 errors, 24 warnings)
57
57
 
58
- Rule | Time (ms) | Relative
59
- :-----------------------------------------|----------:|--------:
60
- @typescript-eslint/no-unused-vars | 1453.557 | 63.1%
61
- react-hooks/exhaustive-deps | 117.512 | 5.1%
62
- no-global-assign | 117.462 | 5.1%
63
- react-hooks/rules-of-hooks | 75.475 | 3.3%
64
- no-unexpected-multiline | 45.705 | 2.0%
65
- @typescript-eslint/ban-ts-comment | 35.030 | 1.5%
66
- @typescript-eslint/triple-slash-reference | 30.235 | 1.3%
67
- no-misleading-character-class | 27.369 | 1.2%
68
- no-loss-of-precision | 26.374 | 1.1%
69
- no-useless-escape | 25.969 | 1.1%
58
+ Rule | Time (ms) | Relative
59
+ :-------------------------------------------------|----------:|--------:
60
+ @typescript-eslint/no-unused-vars | 1454.377 | 60.3%
61
+ react-hooks/rules-of-hooks | 109.992 | 4.6%
62
+ react-hooks/exhaustive-deps | 91.815 | 3.8%
63
+ no-global-assign | 66.528 | 2.8%
64
+ no-loss-of-precision | 57.391 | 2.4%
65
+ @typescript-eslint/ban-ts-comment | 54.505 | 2.3%
66
+ no-misleading-character-class | 45.527 | 1.9%
67
+ no-unexpected-multiline | 39.063 | 1.6%
68
+ no-regex-spaces | 29.197 | 1.2%
69
+ @typescript-eslint/no-unnecessary-type-constraint | 28.583 | 1.2%
package/CHANGELOG.md CHANGED
@@ -1,5 +1,161 @@
1
1
  # @utilitywarehouse/hearth-react-native
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#867](https://github.com/utilitywarehouse/hearth/pull/867) [`9a15eb8`](https://github.com/utilitywarehouse/hearth/commit/9a15eb8c659aa541988da6f28f6c50261f3557f9) Thanks [@jordmccord](https://github.com/jordmccord)! - 💔 [BREAKING CHANGE]: Require `CardActions` wrapper for `CardAction` groups.
8
+
9
+ `Card` now only treats actions as such when they are wrapped in `CardActions`. This removes wrapper heuristics and makes action grouping explicit while keeping automatic content wrapping.
10
+
11
+ **Components affected**:
12
+ - `Card`
13
+ - `CardActions`
14
+ - `CardAction`
15
+
16
+ **Developer changes**:
17
+
18
+ Wrap all `CardAction` items in `CardActions`:
19
+
20
+ ```diff
21
+ - <Card>
22
+ - <CardAction heading="Action 1" onPress={() => {}} />
23
+ - <CardAction heading="Action 2" onPress={() => {}} />
24
+ - </Card>
25
+ + <Card>
26
+ + <CardActions>
27
+ + <CardAction heading="Action 1" onPress={() => {}} />
28
+ + <CardAction heading="Action 2" onPress={() => {}} />
29
+ + </CardActions>
30
+ + </Card>
31
+ ```
32
+
33
+ - [#860](https://github.com/utilitywarehouse/hearth/pull/860) [`ec44a9d`](https://github.com/utilitywarehouse/hearth/commit/ec44a9d3d7a2d95ab69b6e4c461104402d82659d) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `prefix` and `suffix` props to `Input` component
34
+
35
+ The `Input` component now supports `prefix` and `suffix` props, allowing you to display text or custom content before and after the input field. This is useful for adding units, currency symbols, or other contextual information.
36
+
37
+ **Components affected**:
38
+ - `Input`
39
+
40
+ **Developer changes**:
41
+
42
+ Use the `prefix` and `suffix` props to add content before or after the input:
43
+
44
+ ```tsx
45
+ <Input label="Amount" prefix="£" suffix="GBP" placeholder="0.00" />
46
+ ```
47
+
48
+ You can also pass custom React nodes:
49
+
50
+ ```tsx
51
+ <Input label="Email" prefix={<CustomIcon />} suffix={<BodyText>@example.com</BodyText>} />
52
+ ```
53
+
54
+ **Note**: The `prefix` and `suffix` props are not available on `password` and `search` input types, as these have specific UI patterns.
55
+
56
+ - [#862](https://github.com/utilitywarehouse/hearth/pull/862) [`654552e`](https://github.com/utilitywarehouse/hearth/commit/654552e33d56e4b2b2ba8fb783b7f9a7c57ba212) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `loading` prop to `ListAction` component
57
+
58
+ The `ListAction` component now supports a `loading` prop that displays a skeleton loading state while content is being fetched. This provides better user feedback during asynchronous operations.
59
+
60
+ **Components affected**:
61
+ - `ListAction`
62
+
63
+ **Developer changes**:
64
+
65
+ Use the `loading` prop to show a loading state:
66
+
67
+ ```tsx
68
+ <List>
69
+ <ListItem heading="Account details" />
70
+ <ListAction heading="View transactions" loading={isLoading} onPress={handlePress} />
71
+ </List>
72
+ ```
73
+
74
+ When `loading` is true, the action will display skeleton placeholders instead of the heading and icon.
75
+
76
+ - [#862](https://github.com/utilitywarehouse/hearth/pull/862) [`654552e`](https://github.com/utilitywarehouse/hearth/commit/654552e33d56e4b2b2ba8fb783b7f9a7c57ba212) Thanks [@jordmccord](https://github.com/jordmccord)! - 🌟 [FEATURE]: Add `invalidText` prop to `List` component
77
+
78
+ The `List` component now supports an `invalidText` prop for displaying validation error messages in the section header. This provides consistent validation feedback across list-based forms and grouped content.
79
+
80
+ **Components affected**:
81
+ - `List`
82
+
83
+ **Developer changes**:
84
+
85
+ Use the `invalidText` prop to display validation errors:
86
+
87
+ ```tsx
88
+ <List
89
+ heading="Payment methods"
90
+ helperText="Select at least one payment method"
91
+ invalidText="You must select a payment method"
92
+ >
93
+ <ListItem heading="Credit card" />
94
+ <ListItem heading="Direct debit" />
95
+ </List>
96
+ ```
97
+
98
+ ### Patch Changes
99
+
100
+ - [#845](https://github.com/utilitywarehouse/hearth/pull/845) [`9c034f9`](https://github.com/utilitywarehouse/hearth/commit/9c034f98f9d6aa4596a45296d02e01703ef1c762) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Add `invalidText` prop to `ExpandableCardGroup`
101
+
102
+ The `ExpandableCardGroup` component now supports an `invalidText` prop that displays validation text below the helper text when the group is in an invalid state.
103
+
104
+ **Components affected**:
105
+ - `ExpandableCardGroup`
106
+
107
+ **Developer changes**:
108
+
109
+ No changes required. If you want to display validation text, you can now use the `invalidText` prop:
110
+
111
+ ```tsx
112
+ <ExpandableCardGroup
113
+ heading="Select an option"
114
+ helperText="Choose one of the options below"
115
+ invalidText="Please select at least one option"
116
+ >
117
+ {/* ExpandableCard components */}
118
+ </ExpandableCardGroup>
119
+ ```
120
+
121
+ - [#867](https://github.com/utilitywarehouse/hearth/pull/867) [`9a15eb8`](https://github.com/utilitywarehouse/hearth/commit/9a15eb8c659aa541988da6f28f6c50261f3557f9) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Improve first-item border detection in `Card` and `List` components
122
+
123
+ The `Card` and `List` components now use a more reliable method to detect and style the first rendered `CardAction`, `ListItem`, or `ListAction`. This fixes edge cases where wrapper components that conditionally render `null` would previously interfere with first-item border removal.
124
+
125
+ **Components affected**:
126
+ - `Card` / `CardAction`
127
+ - `List` / `ListItem` / `ListAction`
128
+
129
+ **Developer changes**:
130
+
131
+ No changes required. The improvement is automatic and maintains the same visual behavior. Components that wrap card actions or list items will now work correctly even when some wrappers return `null` conditionally.
132
+
133
+ **Note**: The `useCardFirstActionContext` hook has been removed as it was an internal implementation detail.
134
+
135
+ ## 0.16.2
136
+
137
+ ### Patch Changes
138
+
139
+ - [#837](https://github.com/utilitywarehouse/hearth/pull/837) [`126657c`](https://github.com/utilitywarehouse/hearth/commit/126657cd96008625f9573ed5cc1588709c00f7da) Thanks [@jordmccord](https://github.com/jordmccord)! - 💅 [ENHANCEMENT]: Add style customisation props to `DateInput` component
140
+
141
+ The `DateInput` component now supports three new style props for customising the appearance of date segments: `inputContainerStyle`, `inputStyle`, and `inputLabelStyle`. This allows for greater flexibility when integrating DateInput into different layouts.
142
+
143
+ **Components affected**:
144
+ - `DateInput`
145
+
146
+ **Developer changes**:
147
+
148
+ No changes required. To customise the appearance of date input segments, use the new style props:
149
+
150
+ ```tsx
151
+ <DateInput
152
+ label="Custom date input"
153
+ inputContainerStyle={{ maxWidth: 'auto' }}
154
+ inputStyle={{ fontSize: 16 }}
155
+ inputLabelStyle={{ fontWeight: 'bold' }}
156
+ />
157
+ ```
158
+
3
159
  ## 0.16.1
4
160
 
5
161
  ### Patch Changes
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { ChevronRightSmallIcon } from '@utilitywarehouse/hearth-react-native-icons';
3
- import { useMemo } from 'react';
3
+ import { useId, useLayoutEffect, useMemo } from 'react';
4
4
  import { Pressable, View } from 'react-native';
5
5
  import { StyleSheet } from 'react-native-unistyles';
6
6
  import { IconContainer } from '../../IconContainer';
7
7
  import { Skeleton } from '../../Skeleton';
8
8
  import { useCardContext } from '../Card.context';
9
+ import { useCardActionsContext } from '../CardActions.context';
9
10
  import { CardActionContext } from './CardAction.context';
10
11
  import CardActionContent from './CardActionContent';
11
12
  import CardActionHelperText from './CardActionHelperText';
@@ -23,7 +24,16 @@ const CardActionRoot = ({ heading, helperText, leadingContent, trailingContent,
23
24
  const testID = props.testID || 'card-action';
24
25
  const loadingTestID = isLoading ? `${testID}-loading` : testID;
25
26
  const { variant, hasOnlyActions } = useCardContext();
26
- const isFirst = props.isFirst;
27
+ const actionId = useId();
28
+ const actionsContext = useCardActionsContext();
29
+ useLayoutEffect(() => {
30
+ if (!actionsContext) {
31
+ return;
32
+ }
33
+ return actionsContext.registerAction(actionId);
34
+ }, [actionId, actionsContext]);
35
+ const isFirstFromContext = actionsContext?.firstActionId === actionId;
36
+ const isFirst = props.isFirst ?? isFirstFromContext;
27
37
  styles.useVariants({
28
38
  showPressed,
29
39
  active,
@@ -0,0 +1,6 @@
1
+ export interface CardActionsContextValue {
2
+ firstActionId?: string;
3
+ registerAction: (id: string) => () => void;
4
+ }
5
+ export declare const CardActionsContext: import("react").Context<CardActionsContextValue | null>;
6
+ export declare const useCardActionsContext: () => CardActionsContextValue | null;
@@ -0,0 +1,5 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const CardActionsContext = createContext(null);
3
+ export const useCardActionsContext = () => {
4
+ return useContext(CardActionsContext);
5
+ };
@@ -0,0 +1,7 @@
1
+ import { PropsWithChildren } from 'react';
2
+ import { ViewProps } from 'react-native';
3
+ declare const CardActions: {
4
+ ({ children, style, ...props }: PropsWithChildren<ViewProps>): import("react/jsx-runtime").JSX.Element;
5
+ displayName: string;
6
+ };
7
+ export default CardActions;
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCallback, useRef, useState } from 'react';
3
+ import { View } from 'react-native';
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { CardActionsContext } from './CardActions.context';
6
+ const CardActions = ({ children, style, ...props }) => {
7
+ const orderRef = useRef([]);
8
+ const [firstActionId, setFirstActionId] = useState(undefined);
9
+ const registerAction = useCallback((id) => {
10
+ if (!orderRef.current.includes(id)) {
11
+ orderRef.current.push(id);
12
+ }
13
+ const nextFirst = orderRef.current[0];
14
+ setFirstActionId(prev => (prev === nextFirst ? prev : nextFirst));
15
+ return () => {
16
+ orderRef.current = orderRef.current.filter(currentId => currentId !== id);
17
+ const nextFirst = orderRef.current[0];
18
+ setFirstActionId(prev => (prev === nextFirst ? prev : nextFirst));
19
+ };
20
+ }, []);
21
+ return (_jsx(CardActionsContext.Provider, { value: { firstActionId, registerAction }, children: _jsx(View, { ...props, style: [styles.container, style], children: children }) }));
22
+ };
23
+ CardActions.displayName = 'CardActions';
24
+ const styles = StyleSheet.create({
25
+ container: {
26
+ width: '100%',
27
+ },
28
+ });
29
+ export default CardActions;
@@ -1,93 +1,17 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useMemo } from 'react';
2
+ import { useMemo } from 'react';
3
3
  import { Pressable } from 'react-native';
4
4
  import { StyleSheet } from 'react-native-unistyles';
5
5
  import { useStyleProps } from '../../hooks';
6
6
  import { CardContext } from './Card.context';
7
+ import CardActions from './CardActions';
7
8
  import CardContent from './CardContent';
8
- // Helper to check if children contain specific component types
9
- const checkForComponentType = (children, displayName) => {
10
- return React.Children.toArray(children).some(child => {
11
- if (React.isValidElement(child)) {
12
- // @ts-expect-error - type
13
- if (child.type.displayName === displayName) {
14
- return true;
15
- }
16
- const childProps = child.props;
17
- if (childProps.children) {
18
- return checkForComponentType(childProps.children, displayName);
19
- }
20
- }
21
- return false;
22
- });
23
- };
24
- // Helper to filter out specific component types from children
25
- const filterChildren = (children, excludeDisplayName) => {
26
- return React.Children.map(children, child => {
27
- if (React.isValidElement(child)) {
28
- // @ts-expect-error - type
29
- if (child.type.displayName === excludeDisplayName) {
30
- return null;
31
- }
32
- const childProps = child.props;
33
- if (childProps.children) {
34
- return React.cloneElement(child, {
35
- ...childProps,
36
- children: filterChildren(childProps.children, excludeDisplayName),
37
- });
38
- }
39
- }
40
- return child;
41
- });
42
- };
43
- // Helper to extract specific component types from children
44
- const extractChildren = (children, includeDisplayName, markFirst = false) => {
45
- let isFirstFound = false;
46
- return React.Children.map(children, child => {
47
- if (React.isValidElement(child)) {
48
- // @ts-expect-error - type
49
- if (child.type.displayName === includeDisplayName) {
50
- const isFirst = markFirst && !isFirstFound;
51
- if (isFirst) {
52
- isFirstFound = true;
53
- }
54
- return markFirst
55
- ? React.cloneElement(child, { ...(child.props || {}), isFirst })
56
- : child;
57
- }
58
- const childProps = child.props;
59
- if (childProps.children) {
60
- return extractChildren(childProps.children, includeDisplayName, markFirst);
61
- }
62
- }
63
- return null;
64
- });
65
- };
66
- // Helper that recursively collects onPress or other defined handlers from descendants
67
- const collectChildActionHandlers = (children) => React.Children.toArray(children).reduce((handlers, child) => {
68
- if (React.isValidElement(child)) {
69
- const childProps = child.props;
70
- // @ts-expect-error - type
71
- if (child.type.displayName === 'CardPressHandler') {
72
- const actionChildren = React.Children.toArray(childProps.children);
73
- const handlerToInherit = childProps['handlerToInherit'] || 'onPress';
74
- const firstChild = actionChildren[0];
75
- if (React.isValidElement(firstChild) &&
76
- typeof firstChild.props[handlerToInherit] === 'function') {
77
- handlers.push(firstChild.props[handlerToInherit]);
78
- }
79
- }
80
- if (childProps.children) {
81
- handlers.push(...collectChildActionHandlers(childProps.children));
82
- }
83
- }
84
- return handlers;
85
- }, []);
9
+ import { checkForComponentType, collectChildActionHandlers, extractCardActions, filterChildren, hasOnlyPotentialActions, } from './helpers';
86
10
  const Card = ({ children, variant = 'subtle', colorScheme = 'neutralStrong', shadowColor, noPadding = false, style, states, space, disabled = false, onPress, ...rest }) => {
87
11
  const { active } = states || { active: false };
88
12
  const childActionHandlers = collectChildActionHandlers(children);
89
- const hasActions = checkForComponentType(children, 'CardAction');
90
- const hasContent = checkForComponentType(children, 'CardContent');
13
+ const hasActions = checkForComponentType(children, CardActions);
14
+ const hasContent = checkForComponentType(children, CardContent);
91
15
  // Extract style props using our custom hook
92
16
  const { computedStyles, remainingProps } = useStyleProps(rest);
93
17
  const handlePress = (e) => {
@@ -98,14 +22,9 @@ const Card = ({ children, variant = 'subtle', colorScheme = 'neutralStrong', sha
98
22
  };
99
23
  const inheritChildAction = childActionHandlers.length > 0;
100
24
  const showPressed = inheritChildAction || !!onPress;
101
- const filteredChildren = !hasContent && hasActions ? filterChildren(children, 'CardAction') : null;
102
- // Check if there's any content besides CardActions
103
- const hasOnlyActions = hasActions &&
104
- !hasContent &&
105
- React.Children.toArray(filteredChildren).filter(child => child != null).length === 0;
106
- const filteredCardActions = !hasContent && hasActions
107
- ? extractChildren(children, 'CardAction', hasOnlyActions)
108
- : null;
25
+ // Check if all children are action groups (CardActions)
26
+ const potentiallyOnlyActions = hasOnlyPotentialActions(children, CardActions);
27
+ const hasOnlyActions = potentiallyOnlyActions && !hasContent;
109
28
  const context = useMemo(() => ({
110
29
  pressed: showPressed && active,
111
30
  noPadding,
@@ -118,29 +37,22 @@ const Card = ({ children, variant = 'subtle', colorScheme = 'neutralStrong', sha
118
37
  styles.useVariants({
119
38
  variant,
120
39
  colorScheme,
121
- noPadding: noPadding || hasActions || hasContent,
40
+ noPadding: noPadding || hasActions || hasContent || hasOnlyActions,
122
41
  active,
123
42
  showPressed,
124
43
  disabled,
125
- space: hasActions || hasContent ? 'none' : space,
44
+ space: hasActions || hasContent || hasOnlyActions ? 'none' : space,
126
45
  shadowColor,
127
46
  });
128
47
  const renderChildren = () => {
129
- // Default: render children as-is
130
- if (hasContent || !hasActions) {
48
+ // Explicit CardContent used - render as-is or Card has only actions (or potential action wrappers) - render children directly
49
+ if (hasContent || hasOnlyActions || !hasActions) {
131
50
  return children;
132
51
  }
133
- // Card has actions but no explicit CardContent
134
- if (hasOnlyActions) {
135
- // Only CardActions, no other content - render actions directly
136
- return filteredCardActions;
137
- }
138
- if (filteredChildren) {
139
- // Has both actions and other content - wrap content and render actions below
140
- return (_jsxs(_Fragment, { children: [_jsx(CardContent, { children: filteredChildren }), filteredCardActions] }));
141
- }
142
- // Fallback
143
- return children;
52
+ // Has both actions and other content - wrap non-action content and render actions separately
53
+ const filteredNonActionChildren = filterChildren(children, CardActions);
54
+ const cardActions = extractCardActions(children, CardActions);
55
+ return (_jsxs(_Fragment, { children: [_jsx(CardContent, { children: filteredNonActionChildren }), cardActions] }));
144
56
  };
145
57
  return (_jsx(CardContext.Provider, { value: context, children: _jsx(Pressable, { ...remainingProps, disabled: disabled, style: [styles.card, computedStyles, style], onPress: handlePress, accessible: showPressed, importantForAccessibility: showPressed ? 'yes' : 'no', children: renderChildren() }) }));
146
58
  };
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { GestureResponderEvent } from 'react-native';
3
+ export declare const checkForComponentType: (children: React.ReactNode, componentType: React.ComponentType<any>) => boolean;
4
+ export declare const hasContentInChildren: (child: React.ReactNode, actionType: React.ComponentType<any>) => boolean;
5
+ export declare const hasOnlyPotentialActions: (children: React.ReactNode, actionType: React.ComponentType<any>) => boolean;
6
+ export declare const filterChildren: (children: React.ReactNode, excludeComponentType: React.ComponentType<any>) => React.ReactNode;
7
+ export declare const extractCardActions: (children: React.ReactNode, actionType: React.ComponentType<any>) => React.ReactNode;
8
+ export declare const collectChildActionHandlers: (children: React.ReactNode) => Array<(e: GestureResponderEvent) => void>;
@@ -0,0 +1,146 @@
1
+ import React from 'react';
2
+ const getInheritableHandler = (child, handlerToInherit) => {
3
+ if (!React.isValidElement(child)) {
4
+ return null;
5
+ }
6
+ const childProps = child.props;
7
+ const isDisabled = !!childProps.disabled || !!childProps.loading;
8
+ if (isDisabled || typeof childProps[handlerToInherit] !== 'function') {
9
+ return null;
10
+ }
11
+ return childProps[handlerToInherit];
12
+ };
13
+ // Helper to check if children contain specific component types
14
+ export const checkForComponentType = (children, componentType) => {
15
+ return React.Children.toArray(children).some(child => {
16
+ if (React.isValidElement(child)) {
17
+ if (child.type === componentType) {
18
+ return true;
19
+ }
20
+ const childProps = child.props;
21
+ if (childProps.children) {
22
+ return checkForComponentType(childProps.children, componentType);
23
+ }
24
+ }
25
+ return false;
26
+ });
27
+ };
28
+ // Check if a component has "content" in its children (not just actions)
29
+ export const hasContentInChildren = (child, actionType) => {
30
+ if (!React.isValidElement(child)) {
31
+ return false;
32
+ }
33
+ const childProps = child.props;
34
+ if (!childProps?.children) {
35
+ return false;
36
+ }
37
+ // Check if children contain anything other than the action type
38
+ const childrenArray = React.Children.toArray(childProps.children);
39
+ return childrenArray.some(c => {
40
+ if (!React.isValidElement(c)) {
41
+ // Text, numbers, etc - this is content
42
+ return c != null;
43
+ }
44
+ return c.type !== actionType;
45
+ });
46
+ };
47
+ // Check if all children are CardActions groups
48
+ export const hasOnlyPotentialActions = (children, actionType) => {
49
+ const childArray = React.Children.toArray(children);
50
+ if (childArray.length === 0) {
51
+ return false;
52
+ }
53
+ let hasActionCandidate = false;
54
+ for (const child of childArray) {
55
+ if (!React.isValidElement(child)) {
56
+ if (child != null) {
57
+ return false;
58
+ }
59
+ continue;
60
+ }
61
+ if (child.type === actionType) {
62
+ hasActionCandidate = true;
63
+ continue;
64
+ }
65
+ if (checkForComponentType(child, actionType)) {
66
+ hasActionCandidate = true;
67
+ continue;
68
+ }
69
+ if (typeof child.type === 'string') {
70
+ return false;
71
+ }
72
+ if (hasContentInChildren(child, actionType)) {
73
+ return false;
74
+ }
75
+ return false;
76
+ }
77
+ return hasActionCandidate || checkForComponentType(children, actionType);
78
+ };
79
+ // Helper to filter out specific component types from children
80
+ export const filterChildren = (children, excludeComponentType) => {
81
+ return React.Children.map(children, child => {
82
+ if (React.isValidElement(child)) {
83
+ if (child.type === excludeComponentType) {
84
+ return null;
85
+ }
86
+ // Check if this child contains the excludeComponentType
87
+ if (checkForComponentType(child, excludeComponentType)) {
88
+ return null; // This child or its descendants contain the action
89
+ }
90
+ const childProps = child.props;
91
+ if (childProps.children) {
92
+ const filteredChildren = filterChildren(childProps.children, excludeComponentType);
93
+ // Only preserve wrapper if it has non-null children
94
+ const hasContent = React.Children.toArray(filteredChildren).some(c => c != null);
95
+ if (!hasContent) {
96
+ return null;
97
+ }
98
+ return React.cloneElement(child, {
99
+ ...childProps,
100
+ children: filteredChildren,
101
+ });
102
+ }
103
+ }
104
+ return child;
105
+ });
106
+ };
107
+ // Helper to extract only CardActions (preserving wrapper components)
108
+ export const extractCardActions = (children, actionType) => {
109
+ const recursiveExtract = (children) => {
110
+ return React.Children.map(children, child => {
111
+ if (!React.isValidElement(child))
112
+ return null;
113
+ // Direct action
114
+ if (child.type === actionType) {
115
+ return child;
116
+ }
117
+ // If this child contains a CardActions wrapper in its tree, keep it
118
+ if (checkForComponentType(child, actionType) && !hasContentInChildren(child, actionType)) {
119
+ return child;
120
+ }
121
+ return null;
122
+ });
123
+ };
124
+ return recursiveExtract(children);
125
+ };
126
+ // Helper that recursively collects onPress or other defined handlers from descendants
127
+ export const collectChildActionHandlers = (children) => React.Children.toArray(children).reduce((handlers, child) => {
128
+ if (React.isValidElement(child)) {
129
+ const childProps = child.props;
130
+ // Check using displayName as CardPressHandler might not be directly importable
131
+ // @ts-expect-error - type
132
+ if (child.type?.displayName === 'CardPressHandler') {
133
+ const actionChildren = React.Children.toArray(childProps.children);
134
+ const handlerToInherit = childProps['handlerToInherit'] || 'onPress';
135
+ const firstChild = actionChildren[0];
136
+ const handler = getInheritableHandler(firstChild, handlerToInherit);
137
+ if (handler) {
138
+ handlers.push(handler);
139
+ }
140
+ }
141
+ if (childProps.children) {
142
+ handlers.push(...collectChildActionHandlers(childProps.children));
143
+ }
144
+ }
145
+ return handlers;
146
+ }, []);
@@ -1,5 +1,7 @@
1
1
  export { default as Card } from './Card';
2
2
  export { useCardContext } from './Card.context';
3
3
  export * from './CardAction';
4
+ export { default as CardActions } from './CardActions';
5
+ export { default as CardContent } from './CardContent';
4
6
  export { default as CardPressHandler } from './CardPressHandler';
5
7
  export { useCardPressHandlerContext } from './CardPressHandler.context';
@@ -1,5 +1,7 @@
1
1
  export { default as Card } from './Card';
2
2
  export { useCardContext } from './Card.context';
3
3
  export * from './CardAction';
4
+ export { default as CardActions } from './CardActions';
5
+ export { default as CardContent } from './CardContent';
4
6
  export { default as CardPressHandler } from './CardPressHandler';
5
7
  export { useCardPressHandlerContext } from './CardPressHandler.context';
@@ -1,6 +1,6 @@
1
1
  import type { DateInputProps } from './DateInput.props';
2
2
  declare const DateInput: {
3
- ({ label, helperText, helperIcon, validationStatus, validText, invalidText, disabled, readonly, required, hideDay, hideMonth, hideYear, dayPlaceholder, monthPlaceholder, yearPlaceholder, dayValue, monthValue, yearValue, onDayChange, onMonthChange, onYearChange, onDayFocus, onMonthFocus, onYearFocus, onDayBlur, onMonthBlur, onYearBlur, ...props }: DateInputProps): import("react/jsx-runtime").JSX.Element;
3
+ ({ label, helperText, helperIcon, validationStatus, validText, invalidText, disabled, readonly, required, hideDay, hideMonth, hideYear, dayPlaceholder, monthPlaceholder, yearPlaceholder, dayValue, monthValue, yearValue, onDayChange, onMonthChange, onYearChange, onDayFocus, onMonthFocus, onYearFocus, onDayBlur, onMonthBlur, onYearBlur, inputLabelStyle, inputContainerStyle, inputStyle, ...props }: DateInputProps): import("react/jsx-runtime").JSX.Element;
4
4
  displayName: string;
5
5
  };
6
6
  export default DateInput;