@utilitywarehouse/hearth-react-native 0.35.0 → 0.35.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.
package/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hearth-react-native
3
- description: Use when building any UI component or screen with the Hearth React Native library. Use this implicitly whenever building any UI component or screen in a React Native app that has Hearth React Native installed Hearth React Native is the default. Do not wait for an explicit mention of "Hearth React Native"if it's UI work in an app with Hearth React Native, use this skill. Triggers when creating components, implementing Figma designs, adding UI features, or writing any React Native code in the app.
3
+ description: MANDATORY prerequisite you MUST load this skill BEFORE writing or editing any file that imports from `@utilitywarehouse/hearth-react-native` or `@utilitywarehouse/hearth-react-native-icons`, BEFORE adapting Figma `get_design_context` reference output into a React Native screen in a Hearth app, and BEFORE delegating research about a Hearth component's props or usage to a subagent. NEVER substitute node_modules type definitions or a grep of existing usage for this skill's own component docs that is how this skill gets silently bypassed. This skill IS the "target project's conventions" that figma-design-to-code hands off to; loading that skill does NOT satisfy this requirement. Do not wait for an explicit mention of "Hearth React Native" any React Native UI work in an app with the library installed requires this skill first.
4
4
  ---
5
5
 
6
6
  # Building with Hearth React Native
@@ -9,6 +9,31 @@ You are acting as a React Native engineer. Your job is to turn requirements —
9
9
  either Figma designs or written requirements — into UI React Native code using
10
10
  Hearth — Utility Warehouse's Design Systems libraries.
11
11
 
12
+ ## MANDATORY — when you must load this skill
13
+
14
+ Load this skill — do not skip it — at each of these moments:
15
+
16
+ - **Before writing or editing any file that imports from
17
+ `@utilitywarehouse/hearth-react-native` or
18
+ `@utilitywarehouse/hearth-react-native-icons`.** If the file touches either
19
+ package, this skill MUST be loaded first, no exceptions.
20
+ - **Before adapting Figma `get_design_context` reference output into this
21
+ codebase.** `figma-design-to-code` hands off with "adapt the reference code
22
+ to the target project's conventions" without saying what those conventions
23
+ are. This skill IS that answer. Loading `figma-design-to-code` does NOT
24
+ satisfy this requirement — load both, this one before you touch the
25
+ reference code.
26
+ - **Before delegating research about a Hearth component's props, variants, or
27
+ usage to a subagent.** Reading `node_modules` type definitions or grepping
28
+ existing usage in the app is NOT a substitute for this skill's own component
29
+ docs (see "Before you implement" below). Spawning a research subagent to
30
+ answer a question this skill already answers is how this skill gets
31
+ silently bypassed — load this skill, or pass its instructions into the
32
+ subagent's prompt, before delegating.
33
+
34
+ Treat each moment above as a hard gate, not a vague "this is UI work" prompt
35
+ to get around to eventually.
36
+
12
37
  ## Package imports
13
38
 
14
39
  | Purpose | Package |
@@ -106,6 +131,8 @@ pairing with someone, ask for it here too.
106
131
 
107
132
  ## Use layout components
108
133
 
134
+ (Source of truth: [`public/llms/docs/layout-components.md`](public/llms/docs/layout-components.md))
135
+
109
136
  Use layout components to structure and space content. Do not add margin to
110
137
  individual UI elements to achieve spacing — keep spacing decisions at the layout
111
138
  level so UI components stay composable.
@@ -172,6 +199,8 @@ alongside their own layout-specific props (`direction`, `align`, `justify`,
172
199
 
173
200
  ## Styling: Unistyles, not `StyleSheet` from `react-native`
174
201
 
202
+ (Source of truth: [`public/llms/docs/styling.md`](public/llms/docs/styling.md))
203
+
175
204
  Custom styles use `react-native-unistyles`'s `StyleSheet.create(theme => ({...}))`,
176
205
  re-exported from the package. Only reach for this when utility props and
177
206
  existing component variants don't cover the need.
@@ -235,6 +264,8 @@ const styles = StyleSheet.create(theme => ({
235
264
 
236
265
  ## Responsive breakpoints
237
266
 
267
+ (Source of truth: [`public/llms/docs/styling.md`](public/llms/docs/styling.md#responsive-design-with-breakpoints))
268
+
238
269
  Breakpoints are configured once (via Unistyles' `StyleSheet.configure`) and
239
270
  cascade **mobile-first**: a value applies from that breakpoint upward until
240
271
  overridden by a larger one. See the
@@ -264,6 +295,8 @@ const styles = StyleSheet.create(theme => ({
264
295
 
265
296
  ## Required app-level providers
266
297
 
298
+ (Source of truth: [`public/llms/docs/getting-started.md`](public/llms/docs/getting-started.md))
299
+
267
300
  Hearth React Native components rely on these wrapping the app root:
268
301
 
269
302
  - `GestureHandlerRootView` (from `react-native-gesture-handler`) — required for
@@ -299,11 +332,13 @@ applies before reaching for `react-native-safe-area-context` directly:
299
332
  `SafeAreaView` applies the `top` edge. Set `useSafeAreaInsets={false}` on
300
333
  `BottomSheetModalProvider` only if the parent screen already handles
301
334
  safe-area insets itself — don't look for the prop on the individual
302
- components.
335
+ components. (Source of truth:
336
+ [`public/llms/components/modal.md`](public/llms/components/modal.md))
303
337
  - **`NavModal`** (used when a screen is already presented by React Navigation)
304
338
  reads safe-area insets directly from the Unistyles runtime
305
339
  (`rt.insets.top`/`rt.insets.bottom` inside `StyleSheet.create((theme, rt) =>
306
- ...)`) rather than rendering a `SafeAreaView`.
340
+ ...)`) rather than rendering a `SafeAreaView`. (Source of truth:
341
+ [`public/llms/components/nav-modal.md`](public/llms/components/nav-modal.md))
307
342
 
308
343
  Don't wrap Hearth components in your own `SafeAreaView` on top of these —
309
344
  check `BottomSheetModalProvider`'s `useSafeAreaInsets` prop (or `NavModal`'s
@@ -344,6 +379,8 @@ Compound components share state via a `<Component>Context` (check for a
344
379
 
345
380
  ### Themed images that adapt to light/dark mode
346
381
 
382
+ (Source of truth: [`public/llms/docs/dark-mode-best-practice.md`](public/llms/docs/dark-mode-best-practice.md))
383
+
347
384
  ```tsx
348
385
  <ThemedImage light={require('./logo-light.png')} dark={require('./logo-dark.png')} />
349
386
  ```
@@ -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").BodyTextProps>;
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").BodyTextProps, "ref"> & import("react").RefAttributes<import("../BodyText").BodyTextProps>>;
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").BodyTextProps, "ref"> & import("react").RefAttributes<import("../BodyText").BodyTextProps>>;
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").BodyTextProps): 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").BodyTextProps): 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;
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
3
3
  type Modal<T = any> = BottomSheetModalMethods<T> & {
4
4
  triggerCloseAnimation?: () => void;
5
5
  };
6
- declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, loadingDescription, fullscreen, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, loadingDescription, fullscreen, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter, testID, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;
@@ -12,7 +12,7 @@ import { Button } from '../Button';
12
12
  import { Heading } from '../Heading';
13
13
  import { Spinner } from '../Spinner';
14
14
  import { UnstyledIconButton } from '../UnstyledIconButton';
15
- const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', loadingDescription, fullscreen = false, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, ...props }) => {
15
+ const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', loadingDescription, fullscreen = false, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, stickyFooter = true, testID, ...props }) => {
16
16
  const theme = useTheme();
17
17
  const bottomSheetModalRef = useRef(null);
18
18
  const viewRef = useRef(null);
@@ -90,7 +90,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
90
90
  secondaryButtonProps,
91
91
  secondaryButtonText,
92
92
  ]);
93
- const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined, accessibilityHint: Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading }), loadingDescription ? _jsx(BodyText, { textAlign: "center", children: loadingDescription }) : null] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, hasDescription && !image ? (descriptionIsText ? (_jsx(BodyText, { accessible: true, children: description })) : (description)) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, hasDescription ? (descriptionIsText ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : (description)) : null] })] })) : null, children, !stickyFooter && shouldShowFooter ? (_jsx(View, { style: footerStyle, children: footerContent })) : null] })) }));
93
+ const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, testID: testID, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? (loadingHeading ?? 'Loading') : undefined, accessibilityHint: Platform.OS === 'android' && loadingDescription ? loadingDescription : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading }), loadingDescription ? _jsx(BodyText, { textAlign: "center", children: loadingDescription }) : null] })) : (_jsxs(View, { style: styles.container, testID: testID, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, hasDescription && !image ? (descriptionIsText ? (_jsx(BodyText, { accessible: true, children: description })) : (description)) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, hasDescription ? (descriptionIsText ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : (description)) : null] })] })) : null, children, !stickyFooter && shouldShowFooter ? (_jsx(View, { style: footerStyle, children: footerContent })) : null] })) }));
94
94
  const renderFooter = useCallback((bottomSheetFooterProps) => (_jsx(BottomSheetFooter, { ...bottomSheetFooterProps, children: _jsx(View, { onLayout: handleStickyFooterLayout, style: [styles.footerWrap, footerStyle], children: footerContent }) })), [footerContent, footerStyle, handleStickyFooterLayout]);
95
95
  return (_jsxs(_Fragment, { children: [stickyFooter && shouldShowFooter && stickyFooterHeight === 0 ? (_jsx(View, { accessible: false, importantForAccessibility: "no-hide-descendants", pointerEvents: "none", style: styles.footerMeasurementContainer, children: _jsx(View, { onLayout: handleStickyFooterLayout, style: [styles.footerWrap, footerStyle], children: footerContent }) })) : null, _jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && shouldShowFooter ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: [
96
96
  styles.scrollView,
@@ -3,6 +3,7 @@ import { StyleProp, ViewProps, ViewStyle } from 'react-native';
3
3
  import { ButtonWithoutChildrenProps } from '../Button/Button.props';
4
4
  import { UnstyledIconButtonProps } from '../UnstyledIconButton';
5
5
  export interface ModalCommonBaseProps {
6
+ testID?: string;
6
7
  loading?: boolean;
7
8
  image?: ReactNode;
8
9
  showCloseButton?: boolean;
@@ -3,5 +3,5 @@ import ModalProps from './Modal.props';
3
3
  type Modal<T = any> = BottomSheetModalMethods<T> & {
4
4
  triggerCloseAnimation?: () => void;
5
5
  };
6
- declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
6
+ declare const Modal: ({ ref, children, heading, description, showCloseButton, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress, closeOnSecondaryButtonPress, loading, loadingHeading, fullscreen, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, testID, ...props }: ModalProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export default Modal;
@@ -9,7 +9,7 @@ import { Button } from '../Button';
9
9
  import { Heading } from '../Heading';
10
10
  import { Spinner } from '../Spinner';
11
11
  import { UnstyledIconButton } from '../UnstyledIconButton';
12
- const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, ...props }) => {
12
+ const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, footer, footerStyle, primaryButtonProps, secondaryButtonProps, closeButtonProps, testID, ...props }) => {
13
13
  const bottomSheetModalRef = useRef(null);
14
14
  const viewRef = useRef(null);
15
15
  const scrollViewRef = useRef(null);
@@ -62,7 +62,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
62
62
  const descriptionIsText = typeof description === 'string' || typeof description === 'number';
63
63
  const hasDescription = description !== undefined && description !== null;
64
64
  const footerContent = footer ?? (_jsxs(View, { style: styles.footer, children: [hasPrimaryButton ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, hasSecondaryButton ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
65
- const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, hasDescription && !image ? (descriptionIsText ? (_jsx(BodyText, { accessible: true, children: description })) : (description)) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, hasDescription ? (descriptionIsText ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : (description)) : null] })] })) : null, children, hasFooter ? _jsx(View, { style: footerStyle, children: footerContent }) : null] })) }));
65
+ const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, testID: testID, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg" }), _jsx(Heading, { size: "lg", textAlign: "center", children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, testID: testID, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, children: heading })) : null, hasDescription && !image ? (descriptionIsText ? (_jsx(BodyText, { accessible: true, children: description })) : (description)) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, children: heading })) : null, hasDescription ? (descriptionIsText ? (_jsx(BodyText, { textAlign: "center", accessible: true, children: description })) : (description)) : null] })] })) : null, children, hasFooter ? _jsx(View, { style: footerStyle, children: footerContent }) : null] })) }));
66
66
  return (_jsx(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, ...props, onChange: handleChange, children: _jsx(BottomSheetScrollView, { contentContainerStyle: styles.container, ref: scrollViewRef, children: content }) }));
67
67
  };
68
68
  const styles = StyleSheet.create(theme => ({
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "@utilitywarehouse/hearth-react-native",
3
- "version": "0.35.0",
3
+ "version": "0.35.2",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/utilitywarehouse/hearth.git"
8
+ "url": "git+https://github.com/utilitywarehouse/hearth.git",
9
+ "directory": "packages/react-native"
9
10
  },
10
11
  "main": "build/index.js",
11
12
  "types": "build/index.d.ts",
@@ -77,10 +78,10 @@
77
78
  "vitest": "^4.1.7",
78
79
  "@utilitywarehouse/hearth-storybook-utils": "0.0.0",
79
80
  "@utilitywarehouse/hearth-fonts": "^0.1.1",
80
- "@utilitywarehouse/hearth-react-icons": "^0.8.6",
81
- "@utilitywarehouse/hearth-react-native-icons": "^0.8.5",
81
+ "@utilitywarehouse/hearth-react-icons": "^0.9.0",
82
+ "@utilitywarehouse/hearth-react-native-icons": "^0.9.0",
82
83
  "@utilitywarehouse/hearth-svg-assets": "^0.6.5",
83
- "@utilitywarehouse/hearth-tokens": "^0.4.2"
84
+ "@utilitywarehouse/hearth-tokens": "^0.4.3"
84
85
  },
85
86
  "peerDependencies": {
86
87
  "@gorhom/bottom-sheet": ">=5.0.0",
@@ -104,6 +105,7 @@
104
105
  "lint": "TIMING=1 eslint .",
105
106
  "build": "tsc",
106
107
  "watch": "tsc --watch",
108
+ "typecheck": "tsc --noEmit",
107
109
  "figma:create": "figma connect create",
108
110
  "figma:publish": "figma connect publish",
109
111
  "test": "vitest run --config vitest.unit.config.ts",
@@ -106,6 +106,7 @@ The Modal component extends the `BottomSheetModal` component and accepts all of
106
106
  | `footerStyle` | `StyleProp<ViewStyle>` | Styles applied to the footer container, useful for sticky footer shadows or custom spacing | - |
107
107
  | `closeButtonProps` | `Omit<UnstyledIconButtonProps, 'children'>` | Additional props to pass to the close button | - |
108
108
  | `fullscreen` | `boolean` | Whether the modal should take up the full screen height | `false` |
109
+ | `testID` | `string` | Applied to the modal content container, useful for locating the modal in e2e tests | - |
109
110
 
110
111
  When `footer` is provided, the primary and secondary button props are not available. Build your footer actions directly inside the custom footer content instead.
111
112
 
@@ -0,0 +1,113 @@
1
+ # AI Tooling
2
+
3
+ - [Plugin](#plugin)
4
+ - [llms.txt and markdown docs](#llms-txt-and-markdown-docs)
5
+ - [Agent Skill](#agent-skill)
6
+ - [Hearth React Native MCP Server](#hearth-react-native-mcp-server)
7
+
8
+ The Hearth React Native AI Tools are intended to help your AI agents use Hearth
9
+ React Native components to build Utility Warehouse UIs. They provide access to
10
+ APIs, component documentation and usage guidelines, so your agent has the
11
+ context it needs to build with Hearth correctly.
12
+
13
+ If you're building UI from a design in Figma, start with Figma's MCP Server.
14
+ This should be the first step, since it gives your agent direct access to the
15
+ design you're implementing. You can read more information about setting this up
16
+ in the root AI Toolkit documentation.
17
+
18
+ Hearth's own AI tooling then supplements it for most use cases, filling in the
19
+ parts of the Hearth React Native API and usage guidance that the design alone
20
+ doesn't capture:
21
+
22
+ - [llms.txt](https://hearth.prod.uw.systems/react-native/llms.txt) and markdown docs
23
+ - [SKILL.md](https://github.com/utilitywarehouse/hearth/blob/main/packages/react-native/SKILL.md)
24
+ - [Hearth MCP Server](https://main--68e3ad5c6e80b57678cad6c6.chromatic.com/mcp)
25
+
26
+ ## Plugin
27
+
28
+ The Hearth AI Toolkit plugin is the recommended way to set up your agent with
29
+ the Hearth React Native AI Tools. It will automatically add the skill and MCP
30
+ server to your agent configuration, and ensure that your agent has access to
31
+ the latest Hearth React Native documentation and usage guidance. How to install
32
+ and use the plugin is documented in the root AI Toolkit documentation.
33
+
34
+ ## llms.txt and markdown docs
35
+
36
+ The [llms.txt](https://hearth.prod.uw.systems/react-native/llms.txt) file is a list of
37
+ all Hearth React Native documentation, and each documentation page has a corresponding
38
+ markdown, file for use by AI agents.
39
+
40
+ This documentation is also included in the Hearth React Native package, so your agent
41
+ can access it locally if you have the package installed. The easiest way to do
42
+ this is via the Hearth React Native Agent Skill.
43
+
44
+ ## Agent Skill
45
+
46
+ The [Hearth React Native Agent Skill](https://github.com/utilitywarehouse/hearth/blob/main/packages/react-native/SKILL.md)
47
+ is a skill for your AI agent to use when building any React Native UI.
48
+
49
+ This skill is focused on usage of the Hearth React Native component library, and
50
+ doesn't attempt to cover any other aspects of building a React application, or
51
+ local custom components.
52
+
53
+ The skill is packaged with the Hearth React Native package, so your agent can access
54
+ it locally if you have the package installed. To ensure your agent has access
55
+ to the skill, you can add an entry to your local `AGENTS.md` or `CLAUDE.md` file.
56
+
57
+ You can do this by using the `init-ai` tool:
58
+
59
+ ```console
60
+ npx @utilitywarehouse/hearth-react-native init-ai
61
+ ```
62
+
63
+ This will add an entry to your `AGENTS.md` or `CLAUDE.md` file pointing to the
64
+ skill file.
65
+
66
+ Alternatively, you can add an entry manually to your `AGENTS.md` or `CLAUDE.md`
67
+ file like this:
68
+
69
+ ```markdown
70
+ <!-- @utilitywarehouse/hearth-react — AI skill for component library usage guidance -->
71
+
72
+ @node_modules/@utilitywarehouse/hearth-react-native/SKILL.md
73
+ ```
74
+
75
+ or if you are using a monorepo with the Hearth React Native package in a subdirectory,
76
+ you can add an entry like this:
77
+
78
+ ```markdown
79
+ <!-- @utilitywarehouse/hearth-react-native — AI skill for component library usage guidance -->
80
+
81
+ @../../node_modules/@utilitywarehouse/hearth-react-native/SKILL.md
82
+ ```
83
+
84
+ We recommend having `v0.34.5` or later of the Hearth React Native package installed,
85
+ as this version includes the latest updates to the skill.
86
+
87
+ ## Hearth React Native MCP Server
88
+
89
+ The remote [Hearth React Native MCP Server](https://main--68e3ad5c6e80b57678cad6c6.chromatic.com/mcp),
90
+ built on the [Storybook MCP Server](https://storybook.js.org/docs/ai/mcp/overview#docs),
91
+ provides tools for agents to use Hearth React Native documentation and components.
92
+
93
+ It is referenced in the Hearth React Native Agent Skill, so your agent can access it
94
+ remotely if you have the skill installed, and the MCP server configured in your
95
+ agent.
96
+
97
+ To configure your agent to use the MCP server, you can add an entry to your
98
+ configuration file like this:
99
+
100
+ ```json
101
+ {
102
+ "servers": {
103
+ "hearth-react-native": {
104
+ "url": "https://main--68e3ad5c6e80b57678cad6c6.chromatic.com/mcp",
105
+ "type": "http"
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ This is an example for adding to the VS Code `mcp.json` file, but the
112
+ configuration format may vary depending on your agent. We are in the process of
113
+ adding support for the MCP Server to the organisation's Claude configuration.
@@ -1,7 +1,7 @@
1
1
  # Hearth React Native
2
2
 
3
3
  React Native component library for building UIs at Utility Warehouse.<br/>
4
- Current version: v0.35.0
4
+ Current version: v0.35.2
5
5
 
6
6
  Hearth React Native is a comprehensive design system library for React Native, providing reusable components, consistent styling, and tools to streamline UI development. It is built to enhance productivity and maintain design consistency across projects.
7
7
 
package/public/llms.txt CHANGED
@@ -7,6 +7,11 @@
7
7
  - [Adding Shadows](llms/docs/adding-shadows.md)
8
8
  - Using Predefined Shadows
9
9
  - Components with Shadow Props
10
+ - [AI Tooling](llms/docs/ai-tooling.md)
11
+ - Plugin
12
+ - llms.txt and markdown docs
13
+ - Agent Skill
14
+ - Hearth React Native MCP Server
10
15
  - [All Components](llms/docs/all-components.md)
11
16
  - [Dark Mode Best Practice](llms/docs/dark-mode-best-practice.md)
12
17
  - Setting Up Dark Mode