@mrmeg/expo-ui 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LLM_USAGE.md CHANGED
@@ -37,7 +37,7 @@ near the root when the app uses package feedback or overlay components.
37
37
  `@rn-primitives` portal host.
38
38
 
39
39
  ```tsx
40
- import { ThemeProvider } from "@react-navigation/native";
40
+ import { ThemeProvider } from "expo-router";
41
41
  import { UIProvider } from "@mrmeg/expo-ui/components";
42
42
  import { colors } from "@mrmeg/expo-ui/constants";
43
43
  import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
package/README.md CHANGED
@@ -307,7 +307,7 @@ LLM rules:
307
307
  Call `useResources()` once near the Expo app root before hiding the splash screen:
308
308
 
309
309
  ```tsx
310
- import { ThemeProvider } from "@react-navigation/native";
310
+ import { ThemeProvider } from "expo-router";
311
311
  import { colors } from "@mrmeg/expo-ui/constants";
312
312
  import { useResources, useTheme } from "@mrmeg/expo-ui/hooks";
313
313
  import { UIProvider } from "@mrmeg/expo-ui/components";
@@ -1,5 +1,5 @@
1
1
  import React, { ComponentType } from "react";
2
- import { PressableProps, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ import { PressableProps, PressableStateCallbackType, DimensionValue, StyleProp, TextStyle, ViewStyle } from "react-native";
3
3
  import { TextProps } from "./StyledText";
4
4
  /**
5
5
  * Button variants
@@ -9,9 +9,19 @@ type Presets = "default" | "outline" | "ghost" | "link" | "destructive" | "secon
9
9
  * Button size variants
10
10
  */
11
11
  export type ButtonSize = "sm" | "md" | "lg";
12
- export type ButtonAccessoryStyle = Pick<ViewStyle, "margin" | "marginHorizontal" | "marginVertical" | "marginTop" | "marginRight" | "marginBottom" | "marginLeft" | "marginStart" | "marginEnd">;
12
+ export type ButtonAccessoryStyle = {
13
+ margin?: DimensionValue;
14
+ marginHorizontal?: DimensionValue;
15
+ marginVertical?: DimensionValue;
16
+ marginTop?: DimensionValue;
17
+ marginRight?: DimensionValue;
18
+ marginBottom?: DimensionValue;
19
+ marginLeft?: DimensionValue;
20
+ marginStart?: DimensionValue;
21
+ marginEnd?: DimensionValue;
22
+ };
13
23
  export interface ButtonAccessoryProps {
14
- style: StyleProp<ButtonAccessoryStyle>;
24
+ style: ButtonAccessoryStyle;
15
25
  pressableState: PressableStateCallbackType;
16
26
  disabled?: boolean;
17
27
  }
@@ -47,9 +47,24 @@ export function useTheme() {
47
47
  const userTheme = useThemeStore((s) => s.userTheme);
48
48
  const systemTheme = useThemeStore((s) => s.systemTheme);
49
49
  const setTheme = useThemeStore((s) => s.setTheme);
50
+ const colorOverrides = useThemeStore((s) => s.colorOverrides);
50
51
  // Determine which theme to use (user preference or system)
51
52
  const effectiveScheme = resolveThemePreference(userTheme, systemTheme);
52
- const theme = colors[effectiveScheme];
53
+ const base = colors[effectiveScheme];
54
+ // Layer any app-injected palette over the package defaults for the active
55
+ // scheme. When no override is present we return the base theme *by reference*
56
+ // so memoization and identity checks downstream stay stable (and the package
57
+ // behaves exactly as it did before overrides existed).
58
+ const override = colorOverrides[effectiveScheme];
59
+ const theme = useMemo(() => {
60
+ if (!override || Object.keys(override).length === 0) {
61
+ return base;
62
+ }
63
+ return {
64
+ ...base,
65
+ colors: { ...base.colors, ...override },
66
+ };
67
+ }, [base, override]);
53
68
  // Sync theme to DOM so CSS in +html.tsx follows the app's runtime theme
54
69
  useEffect(() => {
55
70
  if (Platform.OS === "web" && typeof document !== "undefined") {
@@ -1,10 +1,40 @@
1
+ import type { ThemeColors } from "../constants/colors";
1
2
  export type ThemePreference = "system" | "light" | "dark";
2
3
  export type ResolvedTheme = "light" | "dark";
4
+ /**
5
+ * Per-scheme color overrides a host app can inject to brand the package.
6
+ *
7
+ * The package ships a neutral default palette (see `constants/colors.ts`).
8
+ * A consuming app almost always has its own brand palette; without a way to
9
+ * push it in, package components (Badge, Button, inputs, …) render with the
10
+ * package's colors while app-authored siblings render with the app's — the
11
+ * two disagree on what e.g. `primary` means, producing collisions such as
12
+ * white text on a white badge. `setColors` lets the app forward its palette
13
+ * once so every package component resolves against the same source of truth.
14
+ *
15
+ * Each scheme is `Partial<ThemeColors>`: only the keys provided are overridden,
16
+ * so an app can re-skin `primary`/`accent` while inheriting neutral defaults.
17
+ */
18
+ export type ColorOverrides = {
19
+ light?: Partial<ThemeColors>;
20
+ dark?: Partial<ThemeColors>;
21
+ };
3
22
  export type ThemeStore = {
4
23
  userTheme: ThemePreference;
5
24
  systemTheme: ResolvedTheme;
25
+ /**
26
+ * App-injected palette overrides, applied by `useTheme` on top of the
27
+ * package defaults. Empty by default — zero override means the package
28
+ * behaves exactly as before this field existed (fully backward compatible).
29
+ */
30
+ colorOverrides: ColorOverrides;
6
31
  setTheme: (theme: ThemePreference) => void;
7
32
  setSystemTheme: (theme: ResolvedTheme) => void;
33
+ /**
34
+ * Replace the active color overrides. Pass `{}` (or omit both schemes) to
35
+ * clear overrides and fall back to the package defaults.
36
+ */
37
+ setColors: (overrides: ColorOverrides) => void;
8
38
  loadTheme: () => void;
9
39
  };
10
40
  export declare function resolveThemePreference(userTheme: ThemePreference, systemTheme: ResolvedTheme): ResolvedTheme;
@@ -16,6 +16,12 @@ export const useThemeStore = create((set) => ({
16
16
  // Always start with "light" so SSR and the first client render agree.
17
17
  // Real values are populated by `syncThemeFromEnvironment()` after mount.
18
18
  systemTheme: "light",
19
+ // No overrides by default: the package renders with its built-in palette
20
+ // until a host app calls `setColors`.
21
+ colorOverrides: {},
22
+ setColors: (overrides) => {
23
+ set({ colorOverrides: overrides ?? {} });
24
+ },
19
25
  setTheme: (theme) => {
20
26
  set({
21
27
  userTheme: theme,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrmeg/expo-ui",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "Reusable Expo and React Native UI primitives for MrMeg projects.",
6
6
  "keywords": [