@mrmeg/expo-ui 0.1.0 → 0.1.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/README.md +275 -3
- package/dist/components/Accordion.js +4 -4
- package/dist/components/AnimatedView.js +1 -1
- package/dist/components/Badge.js +3 -3
- package/dist/components/BottomSheet.js +4 -4
- package/dist/components/Button.js +31 -15
- package/dist/components/Card.js +4 -4
- package/dist/components/Checkbox.js +5 -5
- package/dist/components/Collapsible.js +3 -3
- package/dist/components/Dialog.js +6 -6
- package/dist/components/Drawer.js +4 -4
- package/dist/components/DropdownMenu.js +5 -5
- package/dist/components/EmptyState.js +5 -5
- package/dist/components/ErrorBoundary.d.ts +4 -0
- package/dist/components/ErrorBoundary.js +1 -9
- package/dist/components/Icon.js +1 -1
- package/dist/components/InputOTP.js +4 -4
- package/dist/components/Label.js +4 -4
- package/dist/components/MaxWidthContainer.js +1 -1
- package/dist/components/Notification.js +6 -6
- package/dist/components/Popover.js +5 -5
- package/dist/components/Progress.js +2 -2
- package/dist/components/RadioGroup.js +4 -4
- package/dist/components/Select.js +6 -6
- package/dist/components/Separator.js +2 -2
- package/dist/components/Skeleton.js +3 -3
- package/dist/components/Slider.js +4 -4
- package/dist/components/StatusBar.js +1 -1
- package/dist/components/StyledText.js +2 -2
- package/dist/components/Switch.js +6 -6
- package/dist/components/Tabs.js +4 -4
- package/dist/components/TextInput.js +7 -7
- package/dist/components/Toggle.js +5 -5
- package/dist/components/ToggleGroup.js +4 -4
- package/dist/components/Tooltip.js +5 -5
- package/dist/components/index.js +35 -35
- package/dist/constants/colors.js +5 -5
- package/dist/constants/fonts.js +75 -71
- package/dist/constants/index.js +3 -3
- package/dist/hooks/index.js +6 -6
- package/dist/hooks/useScalePress.js +1 -1
- package/dist/hooks/useTheme.js +3 -3
- package/dist/index.js +5 -5
- package/dist/lib/index.d.ts +0 -1
- package/dist/lib/index.js +2 -3
- package/dist/state/index.js +2 -2
- package/package.json +63 -34
- package/dist/lib/sentry.d.ts +0 -16
- package/dist/lib/sentry.js +0 -55
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Reusable Expo and React Native UI primitives shared by the template and consumer apps. The package does not ship font files; web consumers load Lato from Google Fonts and native consumers use platform sans-serif fallbacks.
|
|
4
4
|
|
|
5
|
+
This package is public for installability, reuse across MrMeg projects, and
|
|
6
|
+
discoverability. It is a personal reusable Expo / React Native design-system
|
|
7
|
+
package, not a generally supported open-source UI library. The package is
|
|
8
|
+
published as `UNLICENSED`; use outside MrMeg projects requires explicit
|
|
9
|
+
permission.
|
|
10
|
+
|
|
5
11
|
## Install
|
|
6
12
|
|
|
7
13
|
Install from npm after publishing:
|
|
@@ -10,7 +16,14 @@ Install from npm after publishing:
|
|
|
10
16
|
bun add @mrmeg/expo-ui
|
|
11
17
|
```
|
|
12
18
|
|
|
13
|
-
Consumers must also install the peer dependencies listed in `package.json`.
|
|
19
|
+
Consumers must also install the peer dependencies listed in `package.json`.
|
|
20
|
+
The tested baseline is Expo SDK 55 with React 19.2, React Native 0.83,
|
|
21
|
+
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.
|
|
14
27
|
|
|
15
28
|
## Imports
|
|
16
29
|
|
|
@@ -18,9 +31,11 @@ Consumers must also install the peer dependencies listed in `package.json`. Keep
|
|
|
18
31
|
import { Button, StyledText } from "@mrmeg/expo-ui/components";
|
|
19
32
|
import { Button as ButtonDirect } from "@mrmeg/expo-ui/components/Button";
|
|
20
33
|
import { colors, spacing, typography } from "@mrmeg/expo-ui/constants";
|
|
34
|
+
import { colors as colorsDirect } from "@mrmeg/expo-ui/constants/colors";
|
|
21
35
|
import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
|
|
36
|
+
import { useTheme as useThemeDirect } from "@mrmeg/expo-ui/hooks/useTheme";
|
|
22
37
|
import { globalUIStore, useThemeStore } from "@mrmeg/expo-ui/state";
|
|
23
|
-
import { hapticLight
|
|
38
|
+
import { hapticLight } from "@mrmeg/expo-ui/lib";
|
|
24
39
|
```
|
|
25
40
|
|
|
26
41
|
The root barrel also exports the public surface:
|
|
@@ -29,6 +44,215 @@ The root barrel also exports the public surface:
|
|
|
29
44
|
import { Button, colors, useTheme } from "@mrmeg/expo-ui";
|
|
30
45
|
```
|
|
31
46
|
|
|
47
|
+
## Theme System
|
|
48
|
+
|
|
49
|
+
Use `useTheme()` for theme-aware app styles:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { StyleSheet, View } from "react-native";
|
|
53
|
+
import { StyledText } from "@mrmeg/expo-ui/components";
|
|
54
|
+
import { spacing } from "@mrmeg/expo-ui/constants";
|
|
55
|
+
import { useTheme } from "@mrmeg/expo-ui/hooks";
|
|
56
|
+
|
|
57
|
+
export function Panel() {
|
|
58
|
+
const { theme, getShadowStyle } = useTheme();
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<View
|
|
62
|
+
style={[
|
|
63
|
+
styles.panel,
|
|
64
|
+
{
|
|
65
|
+
backgroundColor: theme.colors.card,
|
|
66
|
+
borderColor: theme.colors.border,
|
|
67
|
+
},
|
|
68
|
+
getShadowStyle("subtle"),
|
|
69
|
+
]}
|
|
70
|
+
>
|
|
71
|
+
<StyledText semantic="heading">Theme-aware panel</StyledText>
|
|
72
|
+
<StyledText semantic="body" style={{ color: theme.colors.mutedForeground }}>
|
|
73
|
+
Uses package tokens instead of hardcoded colors.
|
|
74
|
+
</StyledText>
|
|
75
|
+
</View>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const styles = StyleSheet.create({
|
|
80
|
+
panel: {
|
|
81
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
82
|
+
borderRadius: spacing.radiusLg,
|
|
83
|
+
padding: spacing.cardPadding,
|
|
84
|
+
gap: spacing.sm,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`useTheme()` returns the active `theme`, resolved `scheme`, persisted `currentTheme`, `setTheme`, `toggleTheme`, shadow helpers, contrast helpers, and `withAlpha`. Use semantic tokens such as `theme.colors.background`, `foreground`, `card`, `border`, `primary`, `secondary`, `accent`, `mutedForeground`, `destructive`, `success`, and `warning`. `primary` is the neutral action color, `secondary` is a neutral secondary surface, and `accent` is the teal highlight color.
|
|
90
|
+
|
|
91
|
+
Use `StyledText` for theme-aware text:
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
import { BodyText, CaptionText, HeadingText, StyledText } from "@mrmeg/expo-ui/components";
|
|
95
|
+
|
|
96
|
+
<HeadingText>Settings</HeadingText>
|
|
97
|
+
<BodyText>Manage your account.</BodyText>
|
|
98
|
+
<CaptionText>Changes sync automatically.</CaptionText>
|
|
99
|
+
<StyledText semantic="label" tx="common.email" />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Useful `StyledText` props:
|
|
103
|
+
|
|
104
|
+
- `semantic`: `title`, `heading`, `subheading`, `body`, `caption`, `label`
|
|
105
|
+
- `size`: `xs`, `sm`, `base`, `body`, `lg`, `xl`, `xxl`, `display`
|
|
106
|
+
- `fontWeight`: `light`, `regular`, `medium`, `semibold`, `bold`
|
|
107
|
+
- `variant`: `sansSerif`, `serif`
|
|
108
|
+
- `align`, `tx`, `txOptions`
|
|
109
|
+
|
|
110
|
+
## Component Guide
|
|
111
|
+
|
|
112
|
+
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.
|
|
113
|
+
|
|
114
|
+
### LLM Component Use-Case Index
|
|
115
|
+
|
|
116
|
+
| Component | Use For | Prefer It Instead Of | Common Example Use Cases |
|
|
117
|
+
|-----------|---------|----------------------|--------------------------|
|
|
118
|
+
| `Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` | Multi-section disclosure | Custom FAQ/settings expanders | FAQ lists, grouped settings, help sections, dense detail pages |
|
|
119
|
+
| `Alert` | Cross-platform imperative alerts | Direct `window.alert` or duplicated RN/web branching | Confirm destructive actions, native alert dialogs, simple blocking messages |
|
|
120
|
+
| `AnimatedView` | Entrance and visibility animation | Hand-rolled Reanimated wrappers | Staggered list rows, revealed panels, animated empty states |
|
|
121
|
+
| `Badge` | Short status labels | Custom pill `View` + `Text` | Draft/active states, counts, plan labels, role tags |
|
|
122
|
+
| `BottomSheet` | Mobile-first modal sheets | Custom absolute-position sheets | Action pickers, mobile filters, quick edit forms, contextual details |
|
|
123
|
+
| `Button` | Commands and CTAs | Pressable plus custom text styling | Submit, save, cancel, delete, navigation CTAs, icon-accessory buttons |
|
|
124
|
+
| `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` | Framed content groups | Ad hoc bordered panels | List items, pricing plans, settings sections, summaries, dashboards |
|
|
125
|
+
| `Checkbox` | Boolean selection | Custom checkmark controls | Terms consent, checklist items, multi-select filters, notification opt-ins |
|
|
126
|
+
| `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | One-off disclosure | Local animated height wrappers | Advanced settings, hidden helper text, optional details |
|
|
127
|
+
| `Dialog`, `AlertDialog` | Modal decisions and custom modal content | Custom modal overlays | Confirm delete, edit profile, invite user, blocking warnings |
|
|
128
|
+
| `DismissKeyboard` | Tap-away keyboard dismissal | Screen-level keyboard handling | Forms, search screens, sign-in screens |
|
|
129
|
+
| `Drawer` | Side panels and drawer navigation | Custom sliding panels | Filter drawer, app navigation drawer, inspector panel |
|
|
130
|
+
| `DropdownMenu` | Menus and command lists | Homemade popover menus | Row actions, account menu, sort menu, checkbox/radio menu groups |
|
|
131
|
+
| `EmptyState` | No-data or recoverable error regions | One-off empty placeholders | Empty inbox, no search results, missing permissions, failed list load |
|
|
132
|
+
| `ErrorBoundary` | React render error fallback | Unhandled screen crashes | Route-level fallback, feature boundary, recoverable widget crashes |
|
|
133
|
+
| `Icon` | Feather or custom icons with theme tokens | Raw vector icons with hardcoded colors | Button accessories, empty-state icons, menu icons, status glyphs |
|
|
134
|
+
| `InputOTP` | Verification code entry | Multiple manually managed text inputs | Email codes, SMS codes, MFA, invite codes |
|
|
135
|
+
| `Label` | Accessible form labels | Plain styled text labels | Required labels, disabled labels, field group labels |
|
|
136
|
+
| `MaxWidthContainer` | Centered responsive width | Per-screen max-width wrappers | Web pages, tablet layouts, settings forms, auth panels |
|
|
137
|
+
| `Notification` | Global toast surface | Screen-local toast state | Saved/error/sync notifications, loading toast, bottom-position alerts |
|
|
138
|
+
| `Popover` | Anchored contextual content | Custom anchored views | Inline help, quick previews, contextual controls, small forms |
|
|
139
|
+
| `Progress` | Determinate or indeterminate progress | Layout-shifting spinners for progress regions | Upload progress, onboarding completion, long-running task state |
|
|
140
|
+
| `RadioGroup`, `RadioGroupItem` | Mutually exclusive choices | Custom radio rows | Plan interval, visibility choice, survey answer, preference setting |
|
|
141
|
+
| `Select` | Option menus | Custom dropdowns | Country picker, category selector, status selector, compact form choice |
|
|
142
|
+
| `Separator` | Horizontal or vertical dividers | Border-only spacer views | Menu dividers, section dividers, card dividers |
|
|
143
|
+
| `Skeleton`, `SkeletonText`, `SkeletonAvatar`, `SkeletonCard` | Loading placeholders | Blank space or generic spinners | List loading, profile card loading, dashboard placeholders |
|
|
144
|
+
| `Slider` | Numeric value selection | Custom pan gesture track | Volume, percentage, rating, threshold, range-like settings |
|
|
145
|
+
| `StatusBar` | Theme-aware native status bar | Per-screen status-bar duplication | Root layout status styling, dark/light mode updates |
|
|
146
|
+
| `StyledText` and text aliases | Theme-aware typography | Raw `Text` with hardcoded styles | Titles, headings, labels, body copy, captions, translated text |
|
|
147
|
+
| `Switch` | Binary settings | Custom toggle switches | Enable notifications, privacy setting, feature toggles |
|
|
148
|
+
| `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | In-page tabbed views | Custom segmented/tab controls | Profile sections, report views, settings categories |
|
|
149
|
+
| `TextInput` | Text entry | Raw `TextInput` with repeated label/error code | Email/password, search, numeric input, multiline notes |
|
|
150
|
+
| `Toggle`, `ToggleIcon` | Pressed/unpressed control | Button with local selected styling | Favorite, mute, bold/italic, view mode button |
|
|
151
|
+
| `ToggleGroup`, `ToggleGroupItem`, `ToggleGroupIcon` | Single or multi toggle groups | Custom segmented controls | Alignment, formatting toolbar, filter chips, view mode switcher |
|
|
152
|
+
| `Tooltip` | Short hover/focus help | Persistent helper text or custom hover cards | Icon button labels, field hints, disabled action explanations |
|
|
153
|
+
|
|
154
|
+
### Compound Parts And Aliases
|
|
155
|
+
|
|
156
|
+
Most compound components support both direct named imports and dot notation on the root component. Prefer the named exports when code completion or LLM context benefits from explicit names.
|
|
157
|
+
|
|
158
|
+
| Root | Exported Parts |
|
|
159
|
+
|------|----------------|
|
|
160
|
+
| `Accordion` | `AccordionItem`, `AccordionTrigger`, `AccordionContent` |
|
|
161
|
+
| `AlertDialog` | `AlertDialogTrigger`, `AlertDialogContent`, `AlertDialogTitle`, `AlertDialogDescription`, `AlertDialogAction`, `AlertDialogCancel` |
|
|
162
|
+
| `BottomSheet` | `BottomSheetTrigger`, `BottomSheetContent`, `BottomSheetHandle`, `BottomSheetHeader`, `BottomSheetBody`, `BottomSheetFooter`, `BottomSheetClose` |
|
|
163
|
+
| `Card` | `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` |
|
|
164
|
+
| `Collapsible` | `CollapsibleTrigger`, `CollapsibleContent` |
|
|
165
|
+
| `Dialog` | `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogFooter`, `DialogTitle`, `DialogDescription`, `DialogClose` |
|
|
166
|
+
| `Drawer` | `DrawerTrigger`, `DrawerContent`, `DrawerHeader`, `DrawerBody`, `DrawerFooter`, `DrawerClose` |
|
|
167
|
+
| `DropdownMenu` | `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuGroup`, `DropdownMenuItem`, `DropdownMenuCheckboxItem`, `DropdownMenuRadioGroup`, `DropdownMenuRadioItem`, `DropdownMenuLabel`, `DropdownMenuSeparator`, `DropdownMenuShortcut`, `DropdownMenuPortal`, `DropdownMenuSub`, `DropdownMenuSubTrigger`, `DropdownMenuSubContent` |
|
|
168
|
+
| `Popover` | `PopoverTrigger`, `PopoverContent`, `PopoverHeader`, `PopoverBody`, `PopoverFooter` |
|
|
169
|
+
| `RadioGroup` | `RadioGroupItem` |
|
|
170
|
+
| `Select` | `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, `SelectSeparator` |
|
|
171
|
+
| `Skeleton` | `SkeletonText`, `SkeletonAvatar`, `SkeletonCard` |
|
|
172
|
+
| `Tabs` | `TabsList`, `TabsTrigger`, `TabsContent` |
|
|
173
|
+
| `Toggle` | `ToggleIcon` |
|
|
174
|
+
| `ToggleGroup` | `ToggleGroupItem`, `ToggleGroupIcon` |
|
|
175
|
+
| `Tooltip` | `TooltipTrigger`, `TooltipContent`, `TooltipBody` |
|
|
176
|
+
|
|
177
|
+
Text aliases are exported for common semantic typography: `SerifText`, `SansSerifText`, `SerifBoldText`, `SansSerifBoldText`, `DisplayText`, `TitleText`, `HeadingText`, `SubheadingText`, `BodyText`, `CaptionText`, and `LabelText`. `TextClassContext` and `TextColorContext` are advanced context exports used by package controls to pass nested text styling.
|
|
178
|
+
|
|
179
|
+
### Common Patterns
|
|
180
|
+
|
|
181
|
+
Use `Button.preset`, not `variant`. `default` is the neutral primary action, `secondary` is a neutral secondary surface, `outline` is for lower-emphasis actions, `ghost` is for compact toolbars, `link` is for text-like commands, and `destructive` is for dangerous actions.
|
|
182
|
+
|
|
183
|
+
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
|
+
|
|
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`.
|
|
186
|
+
|
|
187
|
+
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
|
+
|
|
189
|
+
Use `Toggle` for one pressed state, `ToggleGroup` for a related set of pressed states, `Switch` for binary settings, `RadioGroup` for small mutually exclusive choices, and `Select` for longer option sets.
|
|
190
|
+
|
|
191
|
+
### Quick Examples
|
|
192
|
+
|
|
193
|
+
Card, badge, and CTA:
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@mrmeg/expo-ui/components";
|
|
197
|
+
|
|
198
|
+
<Card variant="outline">
|
|
199
|
+
<CardHeader>
|
|
200
|
+
<CardTitle>Subscription</CardTitle>
|
|
201
|
+
<Badge variant="secondary">Active</Badge>
|
|
202
|
+
</CardHeader>
|
|
203
|
+
<CardContent>
|
|
204
|
+
<Button preset="default" fullWidth>
|
|
205
|
+
Manage billing
|
|
206
|
+
</Button>
|
|
207
|
+
</CardContent>
|
|
208
|
+
</Card>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Form controls:
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { Button, TextInput, Switch } from "@mrmeg/expo-ui/components";
|
|
215
|
+
|
|
216
|
+
<TextInput
|
|
217
|
+
label="Email"
|
|
218
|
+
placeholder="you@example.com"
|
|
219
|
+
autoCapitalize="none"
|
|
220
|
+
keyboardType="email-address"
|
|
221
|
+
errorText={emailError}
|
|
222
|
+
/>
|
|
223
|
+
|
|
224
|
+
<Switch checked={enabled} onCheckedChange={setEnabled} variant="ios" />
|
|
225
|
+
|
|
226
|
+
<Button preset="default" size="lg" fullWidth loading={isSubmitting}>
|
|
227
|
+
Continue
|
|
228
|
+
</Button>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Feedback:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { EmptyState, Progress, SkeletonCard } from "@mrmeg/expo-ui/components";
|
|
235
|
+
import { globalUIStore } from "@mrmeg/expo-ui/state";
|
|
236
|
+
|
|
237
|
+
{isLoading ? <SkeletonCard /> : null}
|
|
238
|
+
<Progress value={65} variant="accent" />
|
|
239
|
+
<EmptyState icon="inbox" title="No messages" description="New messages will appear here." />
|
|
240
|
+
|
|
241
|
+
globalUIStore.getState().show({
|
|
242
|
+
type: "success",
|
|
243
|
+
title: "Saved",
|
|
244
|
+
messages: ["Your changes were saved."],
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
LLM rules:
|
|
249
|
+
|
|
250
|
+
- Prefer `StyledText` semantic variants over raw `Text`.
|
|
251
|
+
- Use `useTheme()` and semantic tokens instead of hardcoded colors.
|
|
252
|
+
- Use `Button.preset`, not `variant`, for buttons.
|
|
253
|
+
- Mount `PortalHost` before using overlays, menus, select content, popovers, or tooltips.
|
|
254
|
+
- Import from package exports, not `packages/ui/src/*`.
|
|
255
|
+
|
|
32
256
|
## App Startup
|
|
33
257
|
|
|
34
258
|
Call `useResources()` once near the Expo app root before hiding the splash screen:
|
|
@@ -83,7 +307,50 @@ On native, the package uses platform sans-serif fallbacks. `useResources()` stil
|
|
|
83
307
|
|
|
84
308
|
## Package Checks
|
|
85
309
|
|
|
86
|
-
|
|
310
|
+
For a one-command release from the repo root, use:
|
|
311
|
+
|
|
312
|
+
```sh
|
|
313
|
+
bun run ui:release -- --patch --publish
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Replace `--patch` with `--minor`, `--major`, or an exact version such as `0.2.0`.
|
|
317
|
+
The command updates `packages/ui/package.json` and `bun.lock`, runs all package
|
|
318
|
+
gates, then publishes with `npm publish --access public` only when `--publish`
|
|
319
|
+
is present. Without `--publish`, it performs the same version bump and gates as
|
|
320
|
+
a dry run.
|
|
321
|
+
|
|
322
|
+
The release command requires a clean working tree by default. Commit current
|
|
323
|
+
changes first, or pass `--allow-dirty` when you intentionally want to release
|
|
324
|
+
from uncommitted local changes.
|
|
325
|
+
|
|
326
|
+
If npm login email is unavailable, publish through GitHub Actions trusted
|
|
327
|
+
publishing instead:
|
|
328
|
+
|
|
329
|
+
1. In npm package settings for `@mrmeg/expo-ui`, add a trusted publisher:
|
|
330
|
+
GitHub Actions, owner `mrmeg`, repository `expo-template`, workflow
|
|
331
|
+
filename `publish-ui.yml`.
|
|
332
|
+
2. In GitHub Actions, run the `Publish UI Package` workflow with `version=patch`
|
|
333
|
+
and `ref=dev`.
|
|
334
|
+
|
|
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`.
|
|
339
|
+
|
|
340
|
+
Keep `repository.url` in `package.json` as
|
|
341
|
+
`git+https://github.com/mrmeg/expo-template.git`. npm trusted publishing checks
|
|
342
|
+
that metadata during publish, and publish-time URL normalization can block OIDC
|
|
343
|
+
auth.
|
|
344
|
+
|
|
345
|
+
If npm trusted publishing is blocked by package settings, add an npm automation
|
|
346
|
+
or granular publish token to GitHub Actions secrets as `NPM_TOKEN` and rerun the
|
|
347
|
+
same workflow. The token is used only for the publish step.
|
|
348
|
+
|
|
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.
|
|
352
|
+
|
|
353
|
+
Manual package checks:
|
|
87
354
|
|
|
88
355
|
```sh
|
|
89
356
|
bun run ui:typecheck
|
|
@@ -94,3 +361,8 @@ bun run ui:consumer-smoke
|
|
|
94
361
|
```
|
|
95
362
|
|
|
96
363
|
`bun run ui:pack` runs a dry pack so the published file list and package size can be inspected before release.
|
|
364
|
+
`bun run ui:consumer-smoke` installs the packed tarball into a clean fixture,
|
|
365
|
+
checks the documented export-map files, type-checks all public package
|
|
366
|
+
entrypoints, verifies that `@mrmeg/expo-ui/constants` can be imported by
|
|
367
|
+
plain Node ESM for token inspection, and runs an Expo SDK 55 iOS export
|
|
368
|
+
against the packed package without a custom Metro config.
|
|
@@ -2,10 +2,10 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
3
|
import { Platform, Pressable, View } from "react-native";
|
|
4
4
|
import Animated, { useSharedValue, useAnimatedStyle, withTiming, useReducedMotion, } from "react-native-reanimated";
|
|
5
|
-
import { Icon } from "./Icon";
|
|
6
|
-
import { TextClassContext } from "./StyledText";
|
|
7
|
-
import { useTheme } from "../hooks/useTheme";
|
|
8
|
-
import { spacing } from "../constants/spacing";
|
|
5
|
+
import { Icon } from "./Icon.js";
|
|
6
|
+
import { TextClassContext } from "./StyledText.js";
|
|
7
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
8
|
+
import { spacing } from "../constants/spacing.js";
|
|
9
9
|
import * as AccordionPrimitive from "@rn-primitives/accordion";
|
|
10
10
|
function normalizeSingleValue(value) {
|
|
11
11
|
return value ?? "";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import Animated from "react-native-reanimated";
|
|
3
|
-
import { useStaggeredEntrance } from "../hooks/useStaggeredEntrance";
|
|
3
|
+
import { useStaggeredEntrance } from "../hooks/useStaggeredEntrance.js";
|
|
4
4
|
/**
|
|
5
5
|
* Cross-Platform Animated View Component
|
|
6
6
|
* Uses Reanimated for smooth 60fps animations on all platforms
|
package/dist/components/Badge.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { View, StyleSheet } from "react-native";
|
|
4
|
-
import { useTheme } from "../hooks/useTheme";
|
|
5
|
-
import { spacing } from "../constants/spacing";
|
|
6
|
-
import { StyledText } from "./StyledText";
|
|
4
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
5
|
+
import { spacing } from "../constants/spacing.js";
|
|
6
|
+
import { StyledText } from "./StyledText.js";
|
|
7
7
|
/**
|
|
8
8
|
* Badge Component
|
|
9
9
|
*
|
|
@@ -4,10 +4,10 @@ import { View, Pressable, Animated, StyleSheet, Platform, Dimensions, PanRespond
|
|
|
4
4
|
import { Portal } from "@rn-primitives/portal";
|
|
5
5
|
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
|
|
6
6
|
import { Pressable as SlotPressable } from "@rn-primitives/slot";
|
|
7
|
-
import { useTheme } from "../hooks/useTheme";
|
|
8
|
-
import { spacing } from "../constants/spacing";
|
|
9
|
-
import { shouldUseNativeDriver } from "../lib/animations";
|
|
10
|
-
import { TextColorContext, TextClassContext } from "./StyledText";
|
|
7
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
8
|
+
import { spacing } from "../constants/spacing.js";
|
|
9
|
+
import { shouldUseNativeDriver } from "../lib/animations.js";
|
|
10
|
+
import { TextColorContext, TextClassContext } from "./StyledText.js";
|
|
11
11
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
12
12
|
/**
|
|
13
13
|
* BottomSheet Component with Sub-components
|
|
@@ -2,12 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { Pressable, StyleSheet, View, Platform, ActivityIndicator, } from "react-native";
|
|
4
4
|
import Animated from "react-native-reanimated";
|
|
5
|
-
import { spacing } from "../constants/spacing";
|
|
6
|
-
import { StyledText, TextColorContext } from "./StyledText";
|
|
7
|
-
import { fontFamilies } from "../constants/fonts";
|
|
8
|
-
import { palette } from "../constants/colors";
|
|
9
|
-
import { useTheme } from "../hooks/useTheme";
|
|
10
|
-
import { useScalePress } from "../hooks/useScalePress";
|
|
5
|
+
import { spacing } from "../constants/spacing.js";
|
|
6
|
+
import { StyledText, TextColorContext } from "./StyledText.js";
|
|
7
|
+
import { fontFamilies } from "../constants/fonts.js";
|
|
8
|
+
import { palette } from "../constants/colors.js";
|
|
9
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
10
|
+
import { useScalePress } from "../hooks/useScalePress.js";
|
|
11
11
|
const SIZE_CONFIGS = {
|
|
12
12
|
sm: {
|
|
13
13
|
paddingVertical: spacing.xs,
|
|
@@ -64,7 +64,7 @@ const SIZE_CONFIGS = {
|
|
|
64
64
|
* ```
|
|
65
65
|
*/
|
|
66
66
|
export function Button(props) {
|
|
67
|
-
const { tx, text, txOptions, style: styleOverride, pressedStyle: pressedStyleOverride, textStyle: textStyleOverride, pressedTextStyle: pressedTextStyleOverride, disabledTextStyle: disabledTextStyleOverride, children, RightAccessory, LeftAccessory, disabled, disabledStyle: disabledStyleOverride, withShadow = false, preset = "default", size = "md", loading = false, fullWidth = false, ...rest } = props;
|
|
67
|
+
const { tx, text, txOptions, style: styleOverride, pressedStyle: pressedStyleOverride, textStyle: textStyleOverride, pressedTextStyle: pressedTextStyleOverride, disabledTextStyle: disabledTextStyleOverride, children, RightAccessory, LeftAccessory, disabled, disabledStyle: disabledStyleOverride, withShadow = false, preset = "default", size = "md", loading = false, fullWidth = false, onFocus, onBlur, onPressIn, onPressOut, ...rest } = props;
|
|
68
68
|
const { theme, getContrastingColor, getShadowStyle } = useTheme();
|
|
69
69
|
const styles = createStyles(theme, size);
|
|
70
70
|
const shadowStyle = getShadowStyle("base");
|
|
@@ -79,10 +79,10 @@ export function Button(props) {
|
|
|
79
79
|
backgroundColor = customBgColor;
|
|
80
80
|
}
|
|
81
81
|
else if (preset === "default") {
|
|
82
|
-
backgroundColor = theme.colors.
|
|
82
|
+
backgroundColor = theme.colors.primary;
|
|
83
83
|
}
|
|
84
84
|
else if (preset === "secondary") {
|
|
85
|
-
backgroundColor = theme.colors.
|
|
85
|
+
backgroundColor = theme.colors.secondary;
|
|
86
86
|
}
|
|
87
87
|
else if (preset === "destructive") {
|
|
88
88
|
backgroundColor = theme.colors.destructive;
|
|
@@ -91,7 +91,7 @@ export function Button(props) {
|
|
|
91
91
|
backgroundColor = "transparent";
|
|
92
92
|
}
|
|
93
93
|
else {
|
|
94
|
-
backgroundColor = theme.colors.
|
|
94
|
+
backgroundColor = theme.colors.primary;
|
|
95
95
|
}
|
|
96
96
|
// Determine text color per preset for readable contrast
|
|
97
97
|
const textColor = preset === "outline"
|
|
@@ -101,9 +101,9 @@ export function Button(props) {
|
|
|
101
101
|
: preset === "link"
|
|
102
102
|
? theme.colors.accent
|
|
103
103
|
: preset === "default"
|
|
104
|
-
? theme.colors.
|
|
104
|
+
? theme.colors.primaryForeground
|
|
105
105
|
: preset === "secondary"
|
|
106
|
-
? theme.colors.
|
|
106
|
+
? theme.colors.secondaryForeground
|
|
107
107
|
: getContrastingColor(backgroundColor, palette.white, palette.black);
|
|
108
108
|
const [focused, setFocused] = useState(false);
|
|
109
109
|
const isDisabled = disabled || loading;
|
|
@@ -112,7 +112,23 @@ export function Button(props) {
|
|
|
112
112
|
haptic: false,
|
|
113
113
|
scaleTo: preset === "link" ? 1 : 0.97,
|
|
114
114
|
});
|
|
115
|
-
|
|
115
|
+
const handleFocus = (event) => {
|
|
116
|
+
setFocused(true);
|
|
117
|
+
onFocus?.(event);
|
|
118
|
+
};
|
|
119
|
+
const handleBlur = (event) => {
|
|
120
|
+
setFocused(false);
|
|
121
|
+
onBlur?.(event);
|
|
122
|
+
};
|
|
123
|
+
const handlePressIn = (event) => {
|
|
124
|
+
pressHandlers.onPressIn();
|
|
125
|
+
onPressIn?.(event);
|
|
126
|
+
};
|
|
127
|
+
const handlePressOut = (event) => {
|
|
128
|
+
pressHandlers.onPressOut();
|
|
129
|
+
onPressOut?.(event);
|
|
130
|
+
};
|
|
131
|
+
return (_jsx(TextColorContext.Provider, { value: textColor, children: _jsx(Pressable, { accessibilityRole: "button", accessibilityState: { disabled: !!isDisabled, busy: loading }, ...rest, onPressIn: handlePressIn, onPressOut: handlePressOut, onFocus: handleFocus, onBlur: handleBlur, style: { alignSelf: fullWidth ? "stretch" : flattenedStyle?.alignSelf ?? "flex-start" }, disabled: isDisabled, children: (state) => (_jsx(Animated.View, { style: scaleStyle, children: _jsxs(View, { style: [
|
|
116
132
|
styles.button,
|
|
117
133
|
preset === "default" && styles.buttonDefault,
|
|
118
134
|
preset === "outline" && styles.buttonOutline,
|
|
@@ -162,10 +178,10 @@ const createStyles = (theme, size) => {
|
|
|
162
178
|
...(Platform.OS === "web" && { cursor: "pointer" }),
|
|
163
179
|
},
|
|
164
180
|
buttonDefault: {
|
|
165
|
-
backgroundColor: theme.colors.
|
|
181
|
+
backgroundColor: theme.colors.primary,
|
|
166
182
|
},
|
|
167
183
|
buttonSecondary: {
|
|
168
|
-
backgroundColor: theme.colors.
|
|
184
|
+
backgroundColor: theme.colors.secondary,
|
|
169
185
|
},
|
|
170
186
|
buttonDestructive: {
|
|
171
187
|
backgroundColor: theme.colors.destructive,
|
package/dist/components/Card.js
CHANGED
|
@@ -2,10 +2,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { createContext, useContext } from "react";
|
|
3
3
|
import { View, Pressable, StyleSheet, Platform } from "react-native";
|
|
4
4
|
import Animated from "react-native-reanimated";
|
|
5
|
-
import { StyledText } from "./StyledText";
|
|
6
|
-
import { useTheme } from "../hooks/useTheme";
|
|
7
|
-
import { useScalePress } from "../hooks/useScalePress";
|
|
8
|
-
import { spacing } from "../constants/spacing";
|
|
5
|
+
import { StyledText } from "./StyledText.js";
|
|
6
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
7
|
+
import { useScalePress } from "../hooks/useScalePress.js";
|
|
8
|
+
import { spacing } from "../constants/spacing.js";
|
|
9
9
|
/**
|
|
10
10
|
* Card Component
|
|
11
11
|
*
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { View, StyleSheet, Pressable } from "react-native";
|
|
3
3
|
import Animated, { useSharedValue, useAnimatedStyle, withTiming, useReducedMotion, } from "react-native-reanimated";
|
|
4
|
-
import { Icon } from "./Icon";
|
|
5
|
-
import { StyledText } from "./StyledText";
|
|
6
|
-
import { useTheme } from "../hooks/useTheme";
|
|
7
|
-
import { spacing } from "../constants/spacing";
|
|
8
|
-
import { hapticLight } from "../lib/haptics";
|
|
4
|
+
import { Icon } from "./Icon.js";
|
|
5
|
+
import { StyledText } from "./StyledText.js";
|
|
6
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
7
|
+
import { spacing } from "../constants/spacing.js";
|
|
8
|
+
import { hapticLight } from "../lib/haptics.js";
|
|
9
9
|
import * as CheckboxPrimitive from "@rn-primitives/checkbox";
|
|
10
10
|
const DEFAULT_HIT_SLOP = 8;
|
|
11
11
|
const SIZE_CONFIGS = {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Animated, Platform, StyleSheet, View } from "react-native";
|
|
4
|
-
import { TextClassContext } from "./StyledText";
|
|
5
|
-
import { spacing } from "../constants/spacing";
|
|
6
|
-
import { useTheme } from "../hooks/useTheme";
|
|
4
|
+
import { TextClassContext } from "./StyledText.js";
|
|
5
|
+
import { spacing } from "../constants/spacing.js";
|
|
6
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
7
7
|
import * as CollapsiblePrimitive from "@rn-primitives/collapsible";
|
|
8
8
|
function Collapsible({ children, ...props }) {
|
|
9
9
|
return (_jsx(CollapsiblePrimitive.Root, { ...props, asChild: Platform.OS !== "web", children: _jsx(View, { children: children }) }));
|
|
@@ -4,12 +4,12 @@ import { Platform, StyleSheet, View } from "react-native";
|
|
|
4
4
|
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
|
|
5
5
|
import * as DialogPrimitive from "@rn-primitives/dialog";
|
|
6
6
|
import * as AlertDialogPrimitive from "@rn-primitives/alert-dialog";
|
|
7
|
-
import { AnimatedView } from "./AnimatedView";
|
|
8
|
-
import { TextClassContext, TextColorContext } from "./StyledText";
|
|
9
|
-
import { StyledText } from "./StyledText";
|
|
10
|
-
import { useTheme } from "../hooks/useTheme";
|
|
11
|
-
import { spacing } from "../constants/spacing";
|
|
12
|
-
import { palette } from "../constants/colors";
|
|
7
|
+
import { AnimatedView } from "./AnimatedView.js";
|
|
8
|
+
import { TextClassContext, TextColorContext } from "./StyledText.js";
|
|
9
|
+
import { StyledText } from "./StyledText.js";
|
|
10
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
11
|
+
import { spacing } from "../constants/spacing.js";
|
|
12
|
+
import { palette } from "../constants/colors.js";
|
|
13
13
|
const FullWindowOverlay = Platform.OS === "ios" ? RNFullWindowOverlay : React.Fragment;
|
|
14
14
|
// ============================================================================
|
|
15
15
|
// Dialog
|
|
@@ -4,10 +4,10 @@ import { View, Pressable, Animated, StyleSheet, Platform, Dimensions, PanRespond
|
|
|
4
4
|
import { Portal } from "@rn-primitives/portal";
|
|
5
5
|
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
|
|
6
6
|
import { Pressable as SlotPressable } from "@rn-primitives/slot";
|
|
7
|
-
import { useTheme } from "../hooks/useTheme";
|
|
8
|
-
import { shouldUseNativeDriver } from "../lib/animations";
|
|
9
|
-
import { spacing } from "../constants/spacing";
|
|
10
|
-
import { TextColorContext, TextClassContext } from "./StyledText";
|
|
7
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
8
|
+
import { shouldUseNativeDriver } from "../lib/animations.js";
|
|
9
|
+
import { spacing } from "../constants/spacing.js";
|
|
10
|
+
import { TextColorContext, TextClassContext } from "./StyledText.js";
|
|
11
11
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
12
12
|
/**
|
|
13
13
|
* Drawer Component with Sub-components
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
import { Platform, StyleSheet, Text, View } from "react-native";
|
|
4
|
-
import { Icon } from "./Icon";
|
|
5
|
-
import { AnimatedView } from "./AnimatedView";
|
|
6
|
-
import { TextClassContext } from "./StyledText";
|
|
7
|
-
import { useTheme } from "../hooks/useTheme";
|
|
8
|
-
import { spacing } from "../constants/spacing";
|
|
4
|
+
import { Icon } from "./Icon.js";
|
|
5
|
+
import { AnimatedView } from "./AnimatedView.js";
|
|
6
|
+
import { TextClassContext } from "./StyledText.js";
|
|
7
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
8
|
+
import { spacing } from "../constants/spacing.js";
|
|
9
9
|
import * as DropdownMenuPrimitive from "@rn-primitives/dropdown-menu";
|
|
10
10
|
import { FullWindowOverlay as RNFullWindowOverlay } from "react-native-screens";
|
|
11
11
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { View, StyleSheet } from "react-native";
|
|
3
|
-
import { SansSerifBoldText, SansSerifText } from "./StyledText";
|
|
4
|
-
import { Button } from "./Button";
|
|
5
|
-
import { Icon } from "./Icon";
|
|
6
|
-
import { useTheme } from "../hooks/useTheme";
|
|
7
|
-
import { spacing } from "../constants/spacing";
|
|
3
|
+
import { SansSerifBoldText, SansSerifText } from "./StyledText.js";
|
|
4
|
+
import { Button } from "./Button.js";
|
|
5
|
+
import { Icon } from "./Icon.js";
|
|
6
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
7
|
+
import { spacing } from "../constants/spacing.js";
|
|
8
8
|
/**
|
|
9
9
|
* EmptyState Component
|
|
10
10
|
*
|
|
@@ -20,6 +20,10 @@ interface ErrorBoundaryProps {
|
|
|
20
20
|
errorInfo: ErrorInfo | null;
|
|
21
21
|
resetError: () => void;
|
|
22
22
|
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Optional app-owned error reporter.
|
|
25
|
+
*/
|
|
26
|
+
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
|
23
27
|
}
|
|
24
28
|
interface ErrorBoundaryState {
|
|
25
29
|
error: Error | null;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Component } from "react";
|
|
3
|
-
import { captureException } from "../lib/sentry";
|
|
4
3
|
/**
|
|
5
4
|
* ErrorBoundary catches JavaScript errors anywhere in the child component tree,
|
|
6
5
|
* logs those errors, and displays a fallback UI.
|
|
@@ -49,14 +48,7 @@ export class ErrorBoundary extends Component {
|
|
|
49
48
|
console.error("ErrorBoundary caught an error:", error);
|
|
50
49
|
console.error("Error info:", errorInfo);
|
|
51
50
|
}
|
|
52
|
-
|
|
53
|
-
captureException(error, {
|
|
54
|
-
contexts: {
|
|
55
|
-
react: {
|
|
56
|
-
componentStack: errorInfo?.componentStack ?? undefined,
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
});
|
|
51
|
+
this.props.onError?.(error, errorInfo);
|
|
60
52
|
}
|
|
61
53
|
render() {
|
|
62
54
|
const { children, FallbackComponent } = this.props;
|
package/dist/components/Icon.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { View } from "react-native";
|
|
3
|
-
import { useTheme } from "../hooks/useTheme";
|
|
3
|
+
import { useTheme } from "../hooks/useTheme.js";
|
|
4
4
|
import Feather from "@expo/vector-icons/Feather";
|
|
5
5
|
const THEME_COLOR_KEYS = [
|
|
6
6
|
"primary", "primaryForeground", "secondary", "muted",
|