@mrmeg/expo-ui 0.1.2 → 0.1.4

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/LLM_USAGE.md ADDED
@@ -0,0 +1,214 @@
1
+ # @mrmeg/expo-ui LLM Usage Guide
2
+
3
+ This file ships in the npm package. In a consumer repo, read it from
4
+ `node_modules/@mrmeg/expo-ui/LLM_USAGE.md` before building app UI.
5
+
6
+ ## First Rule
7
+
8
+ Do not recreate primitives that this package already provides. Import from
9
+ `@mrmeg/expo-ui` and compose the exported components in the app.
10
+
11
+ ## Stable Import Paths
12
+
13
+ ```tsx
14
+ import { Button, StyledText, UIProvider } from "@mrmeg/expo-ui/components";
15
+ import { Button as ButtonDirect } from "@mrmeg/expo-ui/components/Button";
16
+ import { colors, spacing, typography } from "@mrmeg/expo-ui/constants";
17
+ import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
18
+ import { globalUIStore, useThemeStore } from "@mrmeg/expo-ui/state";
19
+ import { configureExpoUiI18n, hapticLight } from "@mrmeg/expo-ui/lib";
20
+ ```
21
+
22
+ The root barrel also exports the public surface:
23
+
24
+ ```tsx
25
+ import { Button, UIProvider, colors, useTheme } from "@mrmeg/expo-ui";
26
+ ```
27
+
28
+ Use only exported package paths: root, `components`, `components/*`,
29
+ `constants`, `constants/*`, `hooks`, `hooks/*`, `state`, and `lib`. Do not
30
+ import from `@mrmeg/expo-ui/dist/*` or from a source checkout path.
31
+
32
+ ## Required App Setup
33
+
34
+ Call `useResources()` once near the Expo app root. Mount `UIProvider` once
35
+ near the root when the app uses package feedback or overlay components.
36
+ `UIProvider` owns the package `Notification`, `StatusBar`, and default
37
+ `@rn-primitives` portal host.
38
+
39
+ ```tsx
40
+ import { ThemeProvider } from "@react-navigation/native";
41
+ import { UIProvider } from "@mrmeg/expo-ui/components";
42
+ import { colors } from "@mrmeg/expo-ui/constants";
43
+ import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
44
+
45
+ export function RootLayout() {
46
+ const { scheme } = useTheme();
47
+ const { loaded } = useResources();
48
+
49
+ if (!loaded) return null;
50
+
51
+ return (
52
+ <ThemeProvider
53
+ value={{
54
+ dark: colors[scheme ?? "light"].dark,
55
+ colors: colors[scheme ?? "light"].navigation,
56
+ fonts: colors[scheme ?? "light"].fonts,
57
+ }}
58
+ >
59
+ <UIProvider>
60
+ {/* App navigation goes here. */}
61
+ </UIProvider>
62
+ </ThemeProvider>
63
+ );
64
+ }
65
+ ```
66
+
67
+ `UIProvider` mounts the default portal host required before using `Dialog`,
68
+ `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`,
69
+ `SelectContent`, or `Tooltip`.
70
+
71
+ i18n is optional. Do not add app-level i18n setup just to use this package.
72
+ Plain children and `text` props work without `i18next` or `react-i18next`.
73
+ `tx` props render the key until the consumer opts in with a package-local
74
+ translator:
75
+
76
+ ```tsx
77
+ import { configureExpoUiI18n } from "@mrmeg/expo-ui/lib";
78
+ import { i18n } from "./i18n";
79
+
80
+ configureExpoUiI18n((key, options) => i18n.t(key, options));
81
+ ```
82
+
83
+ ## Theme And Text Rules
84
+
85
+ - Use `useTheme()` and semantic tokens instead of hardcoded colors.
86
+ - Use `StyledText` or its semantic aliases instead of raw `Text` for app UI.
87
+ - Use `Button.preset`, not `variant`, for buttons.
88
+ - Use `globalUIStore` plus root-mounted `UIProvider` for transient global feedback.
89
+ - Keep app monitoring, auth, API, and domain behavior outside this package.
90
+
91
+ Useful theme tokens include:
92
+
93
+ ```tsx
94
+ theme.colors.background;
95
+ theme.colors.foreground;
96
+ theme.colors.card;
97
+ theme.colors.border;
98
+ theme.colors.primary;
99
+ theme.colors.secondary;
100
+ theme.colors.accent;
101
+ theme.colors.mutedForeground;
102
+ theme.colors.destructive;
103
+ theme.colors.success;
104
+ theme.colors.warning;
105
+ ```
106
+
107
+ Token intent:
108
+
109
+ - `primary`: neutral action color
110
+ - `secondary`: neutral secondary surface
111
+ - `accent`: teal highlight color
112
+
113
+ ## Component Use-Case Index
114
+
115
+ Use this table before creating a new app-local primitive.
116
+
117
+ | Component | Use For | Prefer It Instead Of | Common Example Use Cases |
118
+ |-----------|---------|----------------------|--------------------------|
119
+ | `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` | Multi-section disclosure | Custom FAQ/settings expanders | FAQ lists, grouped settings, help sections |
120
+ | `Alert` | Cross-platform imperative alerts | Direct `window.alert` or duplicated RN/web branching | Confirm destructive actions, native alert dialogs |
121
+ | `AnimatedView` | Entrance and visibility animation | Hand-rolled Reanimated wrappers | Staggered list rows, revealed panels, animated empty states |
122
+ | `Badge` | Short status labels | Custom pill `View` + `Text` | Draft/active states, counts, plan labels, role tags |
123
+ | `BottomSheet` | Mobile-first modal sheets | Custom absolute-position sheets | Action pickers, mobile filters, quick edit forms |
124
+ | `Button` | Commands and CTAs | Pressable plus custom text styling | Submit, save, cancel, delete, navigation CTAs |
125
+ | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` | Framed content groups | Ad hoc bordered panels | List items, pricing plans, settings sections, summaries |
126
+ | `Checkbox` | Boolean selection | Custom checkmark controls | Terms consent, checklist items, multi-select filters |
127
+ | `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | One-off disclosure | Local animated height wrappers | Advanced settings, hidden helper text |
128
+ | `Dialog`, `AlertDialog` | Modal decisions and custom modal content | Custom modal overlays | Confirm delete, edit profile, invite user |
129
+ | `DismissKeyboard` | Tap-away keyboard dismissal | Screen-level keyboard handling | Forms, search screens, sign-in screens |
130
+ | `Drawer` | Side panels and drawer navigation | Custom sliding panels | Filter drawer, app navigation drawer, inspector panel |
131
+ | `DropdownMenu` | Menus and command lists | Homemade popover menus | Row actions, account menu, sort menu |
132
+ | `EmptyState` | No-data or recoverable error regions | One-off empty placeholders | Empty inbox, no search results, failed list load |
133
+ | `ErrorBoundary` | React render error fallback | Unhandled screen crashes | Route-level fallback, feature boundary |
134
+ | `Icon` | Feather or custom icons with theme tokens | Raw vector icons with hardcoded colors | Button accessories, empty-state icons, menu icons |
135
+ | `InputOTP` | Verification code entry | Multiple manually managed text inputs | Email codes, SMS codes, MFA, invite codes |
136
+ | `Label` | Accessible form labels | Plain styled text labels | Required labels, disabled labels, field group labels |
137
+ | `MaxWidthContainer` | Centered responsive width | Per-screen max-width wrappers | Web pages, tablet layouts, settings forms |
138
+ | `Notification` | Global toast surface | Screen-local toast state | Saved/error/sync notifications, bottom-position alerts |
139
+ | `Popover` | Anchored contextual content | Custom anchored views | Inline help, quick previews, contextual controls |
140
+ | `Progress` | Determinate or indeterminate progress | Layout-shifting spinners for progress regions | Upload progress, onboarding completion |
141
+ | `RadioGroup`, `RadioGroupItem` | Mutually exclusive choices | Custom radio rows | Plan interval, visibility choice, survey answer |
142
+ | `Select` | Option menus | Custom dropdowns | Country picker, category selector, status selector |
143
+ | `Separator` | Horizontal or vertical dividers | Border-only spacer views | Menu dividers, section dividers, card dividers |
144
+ | `Skeleton`, `SkeletonText`, `SkeletonAvatar`, `SkeletonCard` | Loading placeholders | Blank space or generic spinners | List loading, profile card loading, dashboard placeholders |
145
+ | `Slider` | Numeric value selection | Custom pan gesture track | Volume, percentage, rating, threshold settings |
146
+ | `StatusBar` | Theme-aware native status bar | Per-screen status-bar duplication | Root layout status styling |
147
+ | `StyledText` and text aliases | Theme-aware typography | Raw `Text` with hardcoded styles | Titles, headings, labels, body copy, captions |
148
+ | `Switch` | Binary settings | Custom toggle switches | Enable notifications, privacy setting, feature toggles |
149
+ | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | In-page tabbed views | Custom segmented/tab controls | Profile sections, report views, settings categories |
150
+ | `TextInput` | Text entry | Raw `TextInput` with repeated label/error code | Email/password, search, numeric input, multiline notes |
151
+ | `Toggle`, `ToggleIcon` | Pressed/unpressed control | Button with local selected styling | Favorite, mute, bold/italic, view mode button |
152
+ | `ToggleGroup`, `ToggleGroupItem`, `ToggleGroupIcon` | Single or multi toggle groups | Custom segmented controls | Alignment, formatting toolbar, filter chips |
153
+ | `Tooltip` | Short hover/focus help | Persistent helper text or custom hover cards | Icon button labels, field hints, disabled action explanations |
154
+
155
+ ## Component Selection Rules
156
+
157
+ - Use `Button` for commands, `Toggle` for one pressed state, `ToggleGroup` for related pressed states, and `Switch` for binary settings.
158
+ - Use `RadioGroup` for small mutually exclusive choices and `Select` for longer option sets.
159
+ - Use `Dialog` for blocking decisions, `Popover` for contextual controls, `Tooltip` for short explanations, and `DropdownMenu` for action lists.
160
+ - Use `Card` for individual repeated or framed items, not as a wrapper around full page sections.
161
+ - Use `EmptyState` for no-data or recoverable error regions.
162
+ - Use `Skeleton` for loading content with stable layout.
163
+ - Use `Progress` for real progress or indeterminate long-running work.
164
+
165
+ ## Minimal Examples
166
+
167
+ ```tsx
168
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@mrmeg/expo-ui/components";
169
+
170
+ <Card variant="outline">
171
+ <CardHeader>
172
+ <CardTitle>Subscription</CardTitle>
173
+ <Badge variant="secondary">Active</Badge>
174
+ </CardHeader>
175
+ <CardContent>
176
+ <Button preset="default" fullWidth>
177
+ Manage billing
178
+ </Button>
179
+ </CardContent>
180
+ </Card>
181
+ ```
182
+
183
+ ```tsx
184
+ import { Button, Switch, TextInput } from "@mrmeg/expo-ui/components";
185
+
186
+ <TextInput
187
+ label="Email"
188
+ placeholder="you@example.com"
189
+ autoCapitalize="none"
190
+ keyboardType="email-address"
191
+ errorText={emailError}
192
+ />
193
+
194
+ <Switch checked={enabled} onCheckedChange={setEnabled} variant="ios" />
195
+
196
+ <Button preset="default" size="lg" fullWidth loading={isSubmitting}>
197
+ Continue
198
+ </Button>
199
+ ```
200
+
201
+ ```tsx
202
+ import { EmptyState, Progress, SkeletonCard } from "@mrmeg/expo-ui/components";
203
+ import { globalUIStore } from "@mrmeg/expo-ui/state";
204
+
205
+ {isLoading ? <SkeletonCard /> : null}
206
+ <Progress value={65} variant="accent" />
207
+ <EmptyState icon="inbox" title="No messages" description="New messages will appear here." />
208
+
209
+ globalUIStore.getState().show({
210
+ type: "success",
211
+ title: "Saved",
212
+ messages: ["Your changes were saved."],
213
+ });
214
+ ```
package/README.md CHANGED
@@ -8,6 +8,14 @@ package, not a generally supported open-source UI library. The package is
8
8
  published as `UNLICENSED`; use outside MrMeg projects requires explicit
9
9
  permission.
10
10
 
11
+ ## For LLMs And Coding Agents
12
+
13
+ When this package is installed from npm, read
14
+ `node_modules/@mrmeg/expo-ui/LLM_USAGE.md` before creating app-local UI
15
+ primitives. That file is shipped in the npm package and gives the short
16
+ component-selection rules, import paths, setup requirements, and examples that
17
+ help agents choose existing package components instead of rebuilding them.
18
+
11
19
  ## Install
12
20
 
13
21
  Install from npm after publishing:
@@ -19,29 +27,30 @@ bun add @mrmeg/expo-ui
19
27
  Consumers must also install the peer dependencies listed in `package.json`.
20
28
  The tested baseline is Expo SDK 55 with React 19.2, React Native 0.83,
21
29
  React Native Web 0.21, Reanimated 4.2, Worklets 0.7, and
22
- `@rn-primitives/*` 1.4. `i18next` and `react-i18next` are runtime peers
23
- because `StyledText` and `Notification` support translated text keys. Start
24
- consumer apps from the same Expo SDK family or update the package and peer
25
- ranges deliberately. Keep npm auth tokens in developer or CI configuration,
26
- not in this repository.
30
+ `@rn-primitives/*` 1.4. `@rn-primitives/portal` is package-managed because
31
+ `UIProvider` mounts the portal host used by package overlays. i18n setup is
32
+ optional; plain text and children render without `i18next` or
33
+ `react-i18next`. Start consumer apps from the same Expo SDK family or update
34
+ the package and peer ranges deliberately. Keep npm auth tokens in developer or
35
+ CI configuration, not in this repository.
27
36
 
28
37
  ## Imports
29
38
 
30
39
  ```tsx
31
- import { Button, StyledText } from "@mrmeg/expo-ui/components";
40
+ import { Button, StyledText, UIProvider } from "@mrmeg/expo-ui/components";
32
41
  import { Button as ButtonDirect } from "@mrmeg/expo-ui/components/Button";
33
42
  import { colors, spacing, typography } from "@mrmeg/expo-ui/constants";
34
43
  import { colors as colorsDirect } from "@mrmeg/expo-ui/constants/colors";
35
44
  import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
36
45
  import { useTheme as useThemeDirect } from "@mrmeg/expo-ui/hooks/useTheme";
37
46
  import { globalUIStore, useThemeStore } from "@mrmeg/expo-ui/state";
38
- import { hapticLight } from "@mrmeg/expo-ui/lib";
47
+ import { configureExpoUiI18n, hapticLight } from "@mrmeg/expo-ui/lib";
39
48
  ```
40
49
 
41
50
  The root barrel also exports the public surface:
42
51
 
43
52
  ```tsx
44
- import { Button, colors, useTheme } from "@mrmeg/expo-ui";
53
+ import { Button, UIProvider, colors, useTheme } from "@mrmeg/expo-ui";
45
54
  ```
46
55
 
47
56
  ## Theme System
@@ -107,6 +116,17 @@ Useful `StyledText` props:
107
116
  - `variant`: `sansSerif`, `serif`
108
117
  - `align`, `tx`, `txOptions`
109
118
 
119
+ `tx` support is opt-in. Without a configured translator, `tx` renders the key
120
+ and `text` renders as provided. Consumers that already use i18n can connect it
121
+ once near app startup:
122
+
123
+ ```tsx
124
+ import { configureExpoUiI18n } from "@mrmeg/expo-ui/lib";
125
+ import { i18n } from "./i18n";
126
+
127
+ configureExpoUiI18n((key, options) => i18n.t(key, options));
128
+ ```
129
+
110
130
  ## Component Guide
111
131
 
112
132
  All components are exported from `@mrmeg/expo-ui/components`; direct imports such as `@mrmeg/expo-ui/components/Button` are supported. Use this table as the first stop before building a new primitive in a consumer app.
@@ -182,7 +202,7 @@ Use `Button.preset`, not `variant`. `default` is the neutral primary action, `se
182
202
 
183
203
  Use `StyledText` or its aliases instead of raw `Text` whenever the text is part of app UI. Use `TextInput` for labeled fields because it already owns label, helper text, error text, clear buttons, password visibility, numeric filtering, and left/right elements.
184
204
 
185
- Mount `PortalHost` once before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, or `Tooltip`. Mount `Notification` once near the root and trigger it from `globalUIStore`.
205
+ Mount `UIProvider` once near the root before using `Dialog`, `AlertDialog`, `BottomSheet`, `Drawer`, `DropdownMenu`, `Popover`, `SelectContent`, `Tooltip`, or package notifications. Trigger transient feedback from `globalUIStore`.
186
206
 
187
207
  Use `Skeleton` components for loading content with stable dimensions, `EmptyState` for no-data/recoverable errors, `Alert` for blocking confirm/alert dialogs, and `Notification` for transient global feedback.
188
208
 
@@ -250,7 +270,7 @@ LLM rules:
250
270
  - Prefer `StyledText` semantic variants over raw `Text`.
251
271
  - Use `useTheme()` and semantic tokens instead of hardcoded colors.
252
272
  - Use `Button.preset`, not `variant`, for buttons.
253
- - Mount `PortalHost` before using overlays, menus, select content, popovers, or tooltips.
273
+ - Mount `UIProvider` before using overlays, menus, select content, popovers, tooltips, or package notifications.
254
274
  - Import from package exports, not `packages/ui/src/*`.
255
275
 
256
276
  ## App Startup
@@ -261,8 +281,7 @@ Call `useResources()` once near the Expo app root before hiding the splash scree
261
281
  import { ThemeProvider } from "@react-navigation/native";
262
282
  import { colors } from "@mrmeg/expo-ui/constants";
263
283
  import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
264
- import { Notification, StatusBar } from "@mrmeg/expo-ui/components";
265
- import { PortalHost } from "@rn-primitives/portal";
284
+ import { UIProvider } from "@mrmeg/expo-ui/components";
266
285
 
267
286
  export default function RootLayout() {
268
287
  const { scheme } = useTheme();
@@ -278,10 +297,9 @@ export default function RootLayout() {
278
297
  fonts: colors[scheme ?? "light"].fonts,
279
298
  }}
280
299
  >
281
- {/* App navigation goes here. */}
282
- <Notification />
283
- <PortalHost />
284
- <StatusBar />
300
+ <UIProvider>
301
+ {/* App navigation goes here. */}
302
+ </UIProvider>
285
303
  </ThemeProvider>
286
304
  );
287
305
  }
@@ -329,13 +347,14 @@ publishing instead:
329
347
  1. In npm package settings for `@mrmeg/expo-ui`, add a trusted publisher:
330
348
  GitHub Actions, owner `mrmeg`, repository `expo-template`, workflow
331
349
  filename `publish-ui.yml`.
332
- 2. In GitHub Actions, run the `Publish UI Package` workflow with `version=patch`
333
- and `ref=dev`.
350
+ 2. Bump `packages/ui/package.json` in a commit and push it to `main`.
334
351
 
335
- The workflow uses npm OIDC, not a checked-in token or local npm login. It bumps
336
- the package version, updates `bun.lock`, runs the package gates, lets npm CLI
337
- use the GitHub Actions OIDC environment, commits the version bump, and publishes
338
- from `packages/ui`.
352
+ The workflow uses npm OIDC, not a checked-in token or local npm login. On push,
353
+ it reads the committed package version, skips cleanly if that version is already
354
+ published, otherwise runs the package gates and publishes from `packages/ui`.
355
+ The workflow can also be run manually with `version=patch` and `ref=main`; manual
356
+ runs bump the package version, update `bun.lock`, run the package gates, commit
357
+ the version bump, and publish.
339
358
 
340
359
  Keep `repository.url` in `package.json` as
341
360
  `git+https://github.com/mrmeg/expo-template.git`. npm trusted publishing checks
@@ -346,9 +365,9 @@ If npm trusted publishing is blocked by package settings, add an npm automation
346
365
  or granular publish token to GitHub Actions secrets as `NPM_TOKEN` and rerun the
347
366
  same workflow. The token is used only for the publish step.
348
367
 
349
- If the workflow fails after the version is already bumped, rerun it with the
350
- exact current package version, for example `version=0.1.2`. Exact-version reruns
351
- do not bump again.
368
+ If the manual workflow fails after the version is already bumped, rerun it with
369
+ the exact current package version, for example `version=0.1.3`. Exact-version
370
+ reruns do not bump again.
352
371
 
353
372
  Manual package checks:
354
373
 
@@ -147,13 +147,13 @@ export function Button(props) {
147
147
  },
148
148
  // Spread array styles from Slot to prevent nested arrays on web
149
149
  ...(Array.isArray(styleOverride) ? styleOverride : [styleOverride]),
150
- ], children: [!!LeftAccessory && !loading && (_jsx(LeftAccessory, { style: styles.leftAccessory, pressableState: state, disabled: isDisabled })), loading && (_jsx(ActivityIndicator, { size: "small", color: textColor, style: styles.loader })), (tx || text) ? (_jsx(StyledText, { style: [
150
+ ], children: [!!LeftAccessory && !loading && (_jsx(LeftAccessory, { style: styles.leftAccessory, pressableState: state, disabled: isDisabled })), loading && (_jsx(ActivityIndicator, { size: "small", color: textColor, style: styles.loader })), (tx || text) ? (_jsx(StyledText, { tx: tx, text: text, txOptions: txOptions, style: [
151
151
  styles.text,
152
152
  state.pressed && styles.pressedText,
153
153
  state.pressed && pressedTextStyleOverride,
154
154
  isDisabled && disabledTextStyleOverride,
155
155
  textStyleOverride,
156
- ], children: tx || text })) : !loading && children ? (
156
+ ] })) : !loading && children ? (
157
157
  // Wrap string children in StyledText to apply TextColorContext
158
158
  typeof children === "string" ? (_jsx(StyledText, { style: [
159
159
  styles.text,
@@ -2,13 +2,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useContext, useEffect, useRef } from "react";
3
3
  import { StyleSheet, View, ActivityIndicator, Pressable, Platform } from "react-native";
4
4
  import Animated, { useSharedValue, useAnimatedStyle, withTiming, runOnJS, useReducedMotion, Easing, } from "react-native-reanimated";
5
- import { useTranslation } from "react-i18next";
6
5
  import { SafeAreaInsetsContext } from "react-native-safe-area-context";
7
6
  import { fontFamilies } from "../constants/fonts.js";
8
7
  import { Icon } from "./Icon.js";
9
8
  import { useTheme } from "../hooks/useTheme.js";
10
9
  import { spacing } from "../constants/spacing.js";
11
10
  import { StyledText } from "./StyledText.js";
11
+ import { translateText } from "../lib/i18n.js";
12
12
  import { globalUIStore } from "../state/globalUIStore.js";
13
13
  /**
14
14
  * Notification
@@ -36,7 +36,6 @@ import { globalUIStore } from "../state/globalUIStore.js";
36
36
  * ```
37
37
  */
38
38
  export const Notification = () => {
39
- const { t } = useTranslation();
40
39
  const { theme, getShadowStyle } = useTheme();
41
40
  const reduceMotion = useReducedMotion();
42
41
  const insets = useContext(SafeAreaInsetsContext);
@@ -140,11 +139,11 @@ export const Notification = () => {
140
139
  return alert.title;
141
140
  switch (alert?.type) {
142
141
  case "error":
143
- return t("notification.error");
142
+ return translateText("notification.error");
144
143
  case "success":
145
- return t("notification.success");
144
+ return translateText("notification.success");
146
145
  case "warning":
147
- return t("notification.warning");
146
+ return translateText("notification.warning");
148
147
  case "info":
149
148
  return "";
150
149
  default:
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import React, { forwardRef } from "react";
3
3
  import { Text as RNText, StyleSheet } from "react-native";
4
- import { useTranslation } from "react-i18next";
5
4
  import { useTheme } from "../hooks/useTheme.js";
6
5
  import { fontFamilies } from "../constants/fonts.js";
6
+ import { translateText } from "../lib/i18n.js";
7
7
  /**
8
8
  * TextClassContext provides className context for nested text components
9
9
  * Used by @rn-primitives to apply consistent styling through the component tree
@@ -75,7 +75,6 @@ const getFontFamilyWeight = (weight) => {
75
75
  export const StyledText = forwardRef((props, ref) => {
76
76
  const { tx, text, txOptions, style, variant = "sansSerif", fontWeight, size, semantic, align, children, ...otherProps } = props;
77
77
  const { theme } = useTheme();
78
- const { t } = useTranslation();
79
78
  // Check if there's a color override from parent context (e.g., Button)
80
79
  const contextColor = React.useContext(TextColorContext);
81
80
  // Use context color if provided, otherwise use theme default
@@ -100,7 +99,7 @@ export const StyledText = forwardRef((props, ref) => {
100
99
  const styleHasFontSize = flatStyle && "fontSize" in flatStyle;
101
100
  const styleHasLineHeight = flatStyle && "lineHeight" in flatStyle;
102
101
  const resolvedLineHeight = styleHasFontSize && !styleHasLineHeight ? undefined : lineHeight;
103
- const i18nText = tx ? String(t(tx, txOptions)) : text;
102
+ const i18nText = translateText(tx, text, txOptions);
104
103
  const content = i18nText || children;
105
104
  return (_jsx(RNText, { ref: ref, style: [
106
105
  {
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+ export interface UIProviderProps {
3
+ children: React.ReactNode;
4
+ /**
5
+ * Mount the package notification renderer for globalUIStore feedback.
6
+ *
7
+ * @default true
8
+ */
9
+ notification?: boolean;
10
+ /**
11
+ * Mount the default @rn-primitives portal host used by package overlays.
12
+ *
13
+ * @default true
14
+ */
15
+ portalHost?: boolean;
16
+ /**
17
+ * Mount the package status bar renderer.
18
+ *
19
+ * @default true
20
+ */
21
+ statusBar?: boolean;
22
+ }
23
+ export declare function UIProvider({ children, notification, portalHost, statusBar, }: UIProviderProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { PortalHost } from "@rn-primitives/portal";
3
+ import { Notification } from "./Notification.js";
4
+ import { StatusBar } from "./StatusBar.js";
5
+ export function UIProvider({ children, notification = true, portalHost = true, statusBar = true, }) {
6
+ return (_jsxs(_Fragment, { children: [children, notification ? _jsx(Notification, {}) : null, portalHost ? _jsx(PortalHost, {}) : null, statusBar ? _jsx(StatusBar, {}) : null] }));
7
+ }
@@ -33,3 +33,4 @@ export * from "./TextInput";
33
33
  export * from "./Toggle";
34
34
  export * from "./ToggleGroup";
35
35
  export * from "./Tooltip";
36
+ export * from "./UIProvider";
@@ -33,3 +33,4 @@ export * from "./TextInput.js";
33
33
  export * from "./Toggle.js";
34
34
  export * from "./ToggleGroup.js";
35
35
  export * from "./Tooltip.js";
36
+ export * from "./UIProvider.js";
@@ -0,0 +1,3 @@
1
+ export type TranslateFn = (key: string, options?: object) => string;
2
+ export declare function configureExpoUiI18n(fn: TranslateFn | null): void;
3
+ export declare function translateText(key?: string, text?: string, options?: object): string | undefined;
@@ -0,0 +1,11 @@
1
+ let translate = null;
2
+ export function configureExpoUiI18n(fn) {
3
+ translate = fn;
4
+ }
5
+ export function translateText(key, text, options) {
6
+ if (text)
7
+ return text;
8
+ if (!key)
9
+ return undefined;
10
+ return translate ? translate(key, options) : key;
11
+ }
@@ -1,2 +1,3 @@
1
1
  export * from "./animations";
2
2
  export * from "./haptics";
3
+ export * from "./i18n";
package/dist/lib/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from "./animations.js";
2
2
  export * from "./haptics.js";
3
+ export * from "./i18n.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrmeg/expo-ui",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
6
6
  "keywords": [
@@ -31,7 +31,8 @@
31
31
  "files": [
32
32
  "dist",
33
33
  "package.json",
34
- "README.md"
34
+ "README.md",
35
+ "LLM_USAGE.md"
35
36
  ],
36
37
  "exports": {
37
38
  ".": {
@@ -77,6 +78,9 @@
77
78
  "build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/fix-ui-package-esm.mjs",
78
79
  "publish:dry-run": "bun pm pack --dry-run"
79
80
  },
81
+ "dependencies": {
82
+ "@rn-primitives/portal": "~1.4.0"
83
+ },
80
84
  "peerDependencies": {
81
85
  "@expo/vector-icons": ">=15.0.0 <16.0.0",
82
86
  "@react-native-async-storage/async-storage": ">=2.2.0 <2.3.0",
@@ -88,7 +92,6 @@
88
92
  "@rn-primitives/dropdown-menu": "~1.4.0",
89
93
  "@rn-primitives/label": "~1.4.0",
90
94
  "@rn-primitives/popover": "~1.4.0",
91
- "@rn-primitives/portal": "~1.4.0",
92
95
  "@rn-primitives/radio-group": "~1.4.0",
93
96
  "@rn-primitives/select": "~1.4.0",
94
97
  "@rn-primitives/separator": "~1.4.0",
@@ -102,9 +105,7 @@
102
105
  "expo": "~55.0.0",
103
106
  "expo-font": "~55.0.0",
104
107
  "expo-haptics": "~55.0.0",
105
- "i18next": ">=25.0.0 <27.0.0",
106
108
  "react": ">=19.2.0 <20.0.0",
107
- "react-i18next": ">=15.0.0 <18.0.0",
108
109
  "react-native": ">=0.83.0 <0.84.0",
109
110
  "react-native-gesture-handler": "~2.30.0",
110
111
  "react-native-reanimated": "~4.2.0",