@utilitywarehouse/hearth-react-native 0.35.1 → 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
  ```
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@utilitywarehouse/hearth-react-native",
3
- "version": "0.35.1",
3
+ "version": "0.35.2",
4
4
  "description": "Utility Warehouse React Native UI library",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -78,8 +78,8 @@
78
78
  "vitest": "^4.1.7",
79
79
  "@utilitywarehouse/hearth-storybook-utils": "0.0.0",
80
80
  "@utilitywarehouse/hearth-fonts": "^0.1.1",
81
- "@utilitywarehouse/hearth-react-icons": "^0.8.7",
82
- "@utilitywarehouse/hearth-react-native-icons": "^0.8.6",
81
+ "@utilitywarehouse/hearth-react-icons": "^0.9.0",
82
+ "@utilitywarehouse/hearth-react-native-icons": "^0.9.0",
83
83
  "@utilitywarehouse/hearth-svg-assets": "^0.6.5",
84
84
  "@utilitywarehouse/hearth-tokens": "^0.4.3"
85
85
  },
@@ -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
 
@@ -1,110 +1,35 @@
1
1
  # AI Tooling
2
2
 
3
- - [Figma MCP Server](#figma-mcp-server)
3
+ - [Plugin](#plugin)
4
4
  - [llms.txt and markdown docs](#llms-txt-and-markdown-docs)
5
5
  - [Agent Skill](#agent-skill)
6
6
  - [Hearth React Native MCP Server](#hearth-react-native-mcp-server)
7
7
 
8
- The Hearth React Native AI Tools are intended to help your AI agents use Hearth React Native
9
- components to build Utility Warehouse UIs. They provide access to APIs,
10
- component documentation and usage guidelines, so your agent has the context it
11
- needs to build with Hearth correctly.
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
12
 
13
- If you're building UI from a Figma design, start with Figma's own
14
- [Figma MCP Server](#figma-mcp-server), for most people building UI, this is
15
- the first step, since it gives your agent direct access to the design you're
16
- implementing. Hearth's own AI tooling then supplements it for most use cases,
17
- filling in the parts of the Hearth React Native API and usage guidance that
18
- the design alone doesn't capture:
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:
19
21
 
20
22
  - [llms.txt](https://hearth.prod.uw.systems/react-native/llms.txt) and markdown docs
21
23
  - [SKILL.md](https://github.com/utilitywarehouse/hearth/blob/main/packages/react-native/SKILL.md)
22
24
  - [Hearth MCP Server](https://main--68e3ad5c6e80b57678cad6c6.chromatic.com/mcp)
23
25
 
24
- ## Figma MCP Server
25
-
26
- If you're implementing a Figma design, this should be the first tool you reach
27
- for. Figma's [Dev Mode MCP Server](https://developers.figma.com/docs/figma-mcp-server/)
28
- gives your agent direct access to the design file itself, layout, variables,
29
- and generated code for a selected frame or component. This is a Figma-provided
30
- tool, separate from the Hearth React Native MCP Server below.
31
-
32
- ### If you're using Claude
33
-
34
- Utility Warehouse has the [Figma connector](https://claude.com/connectors/figma)
35
- enabled at an organisation level. It already includes the Figma MCP Server's
36
- tools, so if you have it connected there's nothing else to set up — just
37
- prompt Claude with a link to the Figma design you want to build, and it will
38
- pull in the design context itself.
39
-
40
- To check it's connected for you:
41
-
42
- 1. In Claude, go to **Customize > Connectors**.
43
- 2. Find **Figma** and click **Connect**, authenticating with your Figma account if prompted.
44
- 3. In a conversation, open the **+** menu (or type `/`) in the chat box, then **Connectors**, and toggle **Figma** on. Once it's connected, Claude can also bring it into a conversation on its own when a Figma link is relevant, so you don't have to toggle it every time.
45
-
46
- If you're not using Claude, or don't have the connector available, set up the
47
- Figma MCP Server manually instead.
48
-
49
- ### Manual setup
50
-
51
- Figma recommends the **remote server** for most people, as it supports the
52
- broadest set of features. Add an entry to your `mcp.json` file:
53
-
54
- ```json
55
- {
56
- "servers": {
57
- "figma": {
58
- "url": "https://mcp.figma.com/mcp",
59
- "type": "http"
60
- }
61
- }
62
- }
63
- ```
64
-
65
- See Figma's [remote server installation guide](https://developers.figma.com/docs/figma-mcp-server/remote-server-installation/)
66
- for the full setup flow, including the OAuth step required to grant the server
67
- access to your Figma account.
68
-
69
- Alternatively, a **local server** runs from the Figma desktop app itself (Dev
70
- Mode → enable desktop MCP server), which some organisations use instead of the
71
- remote option:
72
-
73
- ```json
74
- {
75
- "servers": {
76
- "figma-desktop": {
77
- "url": "http://127.0.0.1:3845/mcp",
78
- "type": "http"
79
- }
80
- }
81
- }
82
- ```
83
-
84
- See Figma's [local server installation guide](https://developers.figma.com/docs/figma-mcp-server/local-server-installation/)
85
- for prerequisites and setup.
86
-
87
- ### Works with Hearth components
88
-
89
- Hearth publishes [Figma Code Connect](https://help.figma.com/hc/en-us/articles/23920389749655-Code-Connect)
90
- mappings for its React Native components. In practice, this means: when the
91
- Figma frame you're implementing uses a component we've mapped, the Figma MCP
92
- Server returns real Hearth code, for example `<Button variant="emphasis">`,
93
- rather than generic markup reconstructed from layer names and styles. You
94
- don't need to know how these mappings work under the hood, just that a mapped
95
- component gets you code that's ready to use, with little to no rework needed.
96
-
97
- ### Supplement it with Hearth's AI tooling
98
-
99
- The Figma MCP Server tells your agent what design it's looking at, and, via
100
- Code Connect, which Hearth component to use. It doesn't know the rest of the
101
- Hearth React Native API: prop combinations a mapping doesn't cover,
102
- accessibility guidance, or usage conventions.
26
+ ## Plugin
103
27
 
104
- For most use cases, that's exactly what the tools below are for. Set up the
105
- Figma MCP Server alongside the Hearth Agent Skill and/or Hearth MCP Server,
106
- rather than in place of them, so your agent can confirm the full prop API,
107
- correct usage, and any guidance the Figma mapping doesn't capture.
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.
108
33
 
109
34
  ## llms.txt and markdown docs
110
35
 
@@ -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.1
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
@@ -8,7 +8,7 @@
8
8
  - Using Predefined Shadows
9
9
  - Components with Shadow Props
10
10
  - [AI Tooling](llms/docs/ai-tooling.md)
11
- - Figma MCP Server
11
+ - Plugin
12
12
  - llms.txt and markdown docs
13
13
  - Agent Skill
14
14
  - Hearth React Native MCP Server