react-native-terra-ui 0.10.1 → 0.11.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/CHANGELOG.md +29 -0
- package/README.md +8 -4
- package/lib/module/components/button/Button.js +21 -13
- package/lib/module/components/button/Button.js.map +1 -1
- package/lib/module/components/button/index.js.map +1 -1
- package/lib/module/components/divider/Divider.js +127 -0
- package/lib/module/components/divider/Divider.js.map +1 -0
- package/lib/module/components/divider/index.js +4 -0
- package/lib/module/components/divider/index.js.map +1 -0
- package/lib/module/components/index.js +2 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/components/pressable/index.js +4 -0
- package/lib/module/components/pressable/index.js.map +1 -0
- package/lib/module/components/pressable/variants/PressableScale/index.js +110 -0
- package/lib/module/components/pressable/variants/PressableScale/index.js.map +1 -0
- package/lib/module/context/ThemeProvider.js +5 -5
- package/lib/module/context/ThemeProvider.js.map +1 -1
- package/lib/module/theme/define-config.js +5 -5
- package/lib/module/theme/define-config.js.map +1 -1
- package/lib/module/theme/errors.js +1 -1
- package/lib/module/theme/index.js +2 -2
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/registry.js +15 -21
- package/lib/module/theme/registry.js.map +1 -1
- package/lib/module/theme/resolve-config.js +68 -68
- package/lib/module/theme/resolve-config.js.map +1 -1
- package/lib/module/theme/runtime.js +11 -11
- package/lib/module/theme/runtime.js.map +1 -1
- package/lib/module/theme/selection-store.js +7 -7
- package/lib/module/theme/selection-store.js.map +1 -1
- package/lib/module/theme/unistyles-adapter.js +1 -1
- package/lib/module/utils/accent-utils.js +24 -24
- package/lib/module/utils/accent-utils.js.map +1 -1
- package/lib/typescript/src/components/button/Button.d.ts +7 -0
- package/lib/typescript/src/components/button/Button.d.ts.map +1 -1
- package/lib/typescript/src/components/button/index.d.ts +1 -1
- package/lib/typescript/src/components/button/index.d.ts.map +1 -1
- package/lib/typescript/src/components/divider/Divider.d.ts +49 -0
- package/lib/typescript/src/components/divider/Divider.d.ts.map +1 -0
- package/lib/typescript/src/components/divider/index.d.ts +3 -0
- package/lib/typescript/src/components/divider/index.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +2 -0
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/components/pressable/index.d.ts +3 -0
- package/lib/typescript/src/components/pressable/index.d.ts.map +1 -0
- package/lib/typescript/src/components/pressable/variants/PressableScale/index.d.ts +47 -0
- package/lib/typescript/src/components/pressable/variants/PressableScale/index.d.ts.map +1 -0
- package/lib/typescript/src/context/ThemeProvider.d.ts +7 -7
- package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/theme/define-config.d.ts +11 -8
- package/lib/typescript/src/theme/define-config.d.ts.map +1 -1
- package/lib/typescript/src/theme/errors.d.ts +1 -1
- package/lib/typescript/src/theme/index.d.ts +3 -3
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/registry.d.ts +5 -5
- package/lib/typescript/src/theme/registry.d.ts.map +1 -1
- package/lib/typescript/src/theme/resolve-config.d.ts +25 -25
- package/lib/typescript/src/theme/resolve-config.d.ts.map +1 -1
- package/lib/typescript/src/theme/runtime.d.ts +5 -5
- package/lib/typescript/src/theme/runtime.d.ts.map +1 -1
- package/lib/typescript/src/theme/selection-store.d.ts +5 -5
- package/lib/typescript/src/theme/selection-store.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +20 -47
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/lib/typescript/src/theme/unistyles-adapter.d.ts +1 -1
- package/lib/typescript/src/utils/accent-utils.d.ts +6 -7
- package/lib/typescript/src/utils/accent-utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/button/Button.tsx +32 -13
- package/src/components/button/index.ts +1 -0
- package/src/components/divider/Divider.tsx +164 -0
- package/src/components/divider/index.ts +7 -0
- package/src/components/index.ts +2 -0
- package/src/components/pressable/index.ts +5 -0
- package/src/components/pressable/variants/PressableScale/index.tsx +166 -0
- package/src/context/ThemeProvider.tsx +10 -10
- package/src/theme/define-config.ts +12 -7
- package/src/theme/errors.ts +1 -1
- package/src/theme/index.ts +6 -8
- package/src/theme/registry.ts +15 -23
- package/src/theme/resolve-config.ts +88 -95
- package/src/theme/runtime.ts +11 -11
- package/src/theme/selection-store.ts +9 -9
- package/src/theme/types.ts +21 -47
- package/src/theme/unistyles-adapter.ts +1 -1
- package/src/utils/accent-utils.ts +25 -25
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { type ComponentRef, forwardRef, useCallback, useEffect, useState } from "react";
|
|
2
|
+
import type {
|
|
3
|
+
GestureResponderEvent,
|
|
4
|
+
PressableProps,
|
|
5
|
+
PressableStateCallbackType,
|
|
6
|
+
StyleProp,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
} from "react-native";
|
|
9
|
+
import { Pressable } from "react-native";
|
|
10
|
+
|
|
11
|
+
import Animated, {
|
|
12
|
+
Easing,
|
|
13
|
+
interpolate,
|
|
14
|
+
useAnimatedStyle,
|
|
15
|
+
useSharedValue,
|
|
16
|
+
type WithSpringConfig,
|
|
17
|
+
type WithTimingConfig,
|
|
18
|
+
withSpring,
|
|
19
|
+
withTiming,
|
|
20
|
+
} from "react-native-reanimated";
|
|
21
|
+
import { StyleSheet } from "react-native-unistyles";
|
|
22
|
+
|
|
23
|
+
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
|
24
|
+
|
|
25
|
+
/** A 250ms ease that reads as instant on press-in but never snaps back on release. */
|
|
26
|
+
const DEFAULT_TIMING: WithTimingConfig = {
|
|
27
|
+
duration: 250,
|
|
28
|
+
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type PressableScaleAnimationConfig =
|
|
32
|
+
| { type: "timing"; config?: WithTimingConfig }
|
|
33
|
+
| { type: "spring"; config?: WithSpringConfig };
|
|
34
|
+
|
|
35
|
+
export interface PressableScaleProps extends Omit<PressableProps, "style" | "disabled"> {
|
|
36
|
+
/** Scale held at rest. Defaults to `1`. */
|
|
37
|
+
baseScale?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Scale held while pressed. Values below `baseScale` shrink, above it grow.
|
|
40
|
+
* Defaults to `0.96`.
|
|
41
|
+
*/
|
|
42
|
+
minScale?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Opacity held while pressed, animated on the same curve as the scale.
|
|
45
|
+
* Defaults to `undefined` — opacity is left untouched so `style` keeps
|
|
46
|
+
* ownership of it.
|
|
47
|
+
*/
|
|
48
|
+
pressedOpacity?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Drives the press animation. Defaults to `{ type: 'timing' }` with a 250ms
|
|
51
|
+
* ease; pass `{ type: 'spring' }` for a springy release.
|
|
52
|
+
*/
|
|
53
|
+
animationConfig?: PressableScaleAnimationConfig;
|
|
54
|
+
/** Blocks presses and holds the resting scale. Defaults to `false`. */
|
|
55
|
+
isDisabled?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Styles for the pressable itself. The function form receives `pressed`
|
|
58
|
+
* only — `hovered` and `focused` are not tracked, because the animated
|
|
59
|
+
* style has to be merged outside of `Pressable`'s own callback.
|
|
60
|
+
*/
|
|
61
|
+
style?: StyleProp<ViewStyle> | ((state: PressableStateCallbackType) => StyleProp<ViewStyle>);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A `Pressable` that animates a scale (and optionally an opacity) between its
|
|
66
|
+
* resting and pressed states, driven by a single shared `progress` value on the
|
|
67
|
+
* UI thread.
|
|
68
|
+
*
|
|
69
|
+
* Use it anywhere a press deserves physical feedback — cards, list rows, custom
|
|
70
|
+
* controls. `Button` renders one internally via its `animation` prop.
|
|
71
|
+
*/
|
|
72
|
+
export const PressableScale = forwardRef<ComponentRef<typeof Pressable>, PressableScaleProps>(
|
|
73
|
+
function PressableScale(
|
|
74
|
+
{
|
|
75
|
+
baseScale = 1,
|
|
76
|
+
minScale = 0.96,
|
|
77
|
+
pressedOpacity,
|
|
78
|
+
animationConfig,
|
|
79
|
+
isDisabled = false,
|
|
80
|
+
style,
|
|
81
|
+
onPressIn,
|
|
82
|
+
onPressOut,
|
|
83
|
+
...rest
|
|
84
|
+
},
|
|
85
|
+
ref
|
|
86
|
+
) {
|
|
87
|
+
const progress = useSharedValue(0);
|
|
88
|
+
|
|
89
|
+
// Only the function form of `style` needs a re-render to resolve; tracking
|
|
90
|
+
// `pressed` unconditionally would re-render every consumer on every touch.
|
|
91
|
+
const isStyleFunction = typeof style === "function";
|
|
92
|
+
const [pressed, setPressed] = useState(false);
|
|
93
|
+
|
|
94
|
+
const animateTo = useCallback(
|
|
95
|
+
(to: number) => {
|
|
96
|
+
if (animationConfig?.type === "spring") {
|
|
97
|
+
progress.value = withSpring(to, animationConfig.config);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
progress.value = withTiming(to, { ...DEFAULT_TIMING, ...animationConfig?.config });
|
|
101
|
+
},
|
|
102
|
+
[animationConfig, progress]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const handlePressIn = useCallback(
|
|
106
|
+
(event: GestureResponderEvent) => {
|
|
107
|
+
animateTo(1);
|
|
108
|
+
if (isStyleFunction) setPressed(true);
|
|
109
|
+
onPressIn?.(event);
|
|
110
|
+
},
|
|
111
|
+
[animateTo, isStyleFunction, onPressIn]
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const handlePressOut = useCallback(
|
|
115
|
+
(event: GestureResponderEvent) => {
|
|
116
|
+
animateTo(0);
|
|
117
|
+
if (isStyleFunction) setPressed(false);
|
|
118
|
+
onPressOut?.(event);
|
|
119
|
+
},
|
|
120
|
+
[animateTo, isStyleFunction, onPressOut]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// A press that disables its own pressable — `onPress` flipping a button into
|
|
124
|
+
// a loading state — never gets an `onPressOut`, so release it here instead
|
|
125
|
+
// of leaving it stuck at the pressed scale.
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!isDisabled) return;
|
|
128
|
+
animateTo(0);
|
|
129
|
+
setPressed(false);
|
|
130
|
+
}, [isDisabled, animateTo]);
|
|
131
|
+
|
|
132
|
+
const animatedStyle = useAnimatedStyle(() => {
|
|
133
|
+
const scale = interpolate(progress.value, [0, 1], [baseScale, minScale]);
|
|
134
|
+
if (pressedOpacity === undefined) {
|
|
135
|
+
return { transform: [{ scale }] };
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
transform: [{ scale }],
|
|
139
|
+
opacity: interpolate(progress.value, [0, 1], [1, pressedOpacity]),
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const pressableState: PressableStateCallbackType = { pressed };
|
|
144
|
+
|
|
145
|
+
return (
|
|
146
|
+
<AnimatedPressable
|
|
147
|
+
ref={ref}
|
|
148
|
+
disabled={isDisabled}
|
|
149
|
+
{...rest}
|
|
150
|
+
onPressIn={handlePressIn}
|
|
151
|
+
onPressOut={handlePressOut}
|
|
152
|
+
// The animated style goes first so anything the caller sets — a disabled
|
|
153
|
+
// opacity, say — still wins.
|
|
154
|
+
style={[styles.root, animatedStyle, isStyleFunction ? style(pressableState) : style]}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
161
|
+
//
|
|
162
|
+
// PressableScale has no style of its own — this stylesheet exists so the
|
|
163
|
+
// Unistyles Babel plugin swaps the `Pressable` imported above for its
|
|
164
|
+
// Unistyles-aware counterpart. Without it a themed style handed in through
|
|
165
|
+
// `style` freezes at whichever theme was active when it was created.
|
|
166
|
+
const styles = StyleSheet.create(() => ({ root: {} }));
|
|
@@ -5,9 +5,9 @@ import { useUnistyles } from "react-native-unistyles";
|
|
|
5
5
|
|
|
6
6
|
import { bootstrapTerraUI } from "../theme/registry";
|
|
7
7
|
import {
|
|
8
|
-
applyAccent,
|
|
9
8
|
applyScheme,
|
|
10
9
|
getThemeSelection,
|
|
10
|
+
setTheme,
|
|
11
11
|
setThemeMode,
|
|
12
12
|
subscribeThemeSelection,
|
|
13
13
|
} from "../theme/runtime";
|
|
@@ -41,13 +41,13 @@ export interface UseThemeResult {
|
|
|
41
41
|
scheme: Scheme;
|
|
42
42
|
/** Pin a scheme (stops following the system). Same as `setMode(scheme)`. */
|
|
43
43
|
setScheme: (scheme: Scheme) => void;
|
|
44
|
-
/** Applied
|
|
45
|
-
|
|
44
|
+
/** Applied theme name, or `undefined` for the base themes. */
|
|
45
|
+
themeName: string | undefined;
|
|
46
46
|
/**
|
|
47
|
-
* Apply a registered
|
|
48
|
-
* name warns in development and leaves the
|
|
47
|
+
* Apply a registered theme; `null` restores the base themes. An unknown
|
|
48
|
+
* name warns in development and leaves the theme unchanged.
|
|
49
49
|
*/
|
|
50
|
-
|
|
50
|
+
setTheme: (name: string | null) => void;
|
|
51
51
|
/**
|
|
52
52
|
* How the active scheme is chosen: `system` follows the OS appearance,
|
|
53
53
|
* `light` / `dark` pin a scheme. A system appearance change updates
|
|
@@ -59,8 +59,8 @@ export interface UseThemeResult {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Access the active theme and switch scheme / mode /
|
|
63
|
-
* comes from the runtime store, so changes made imperatively (`
|
|
62
|
+
* Access the active theme and switch scheme / mode / theme. Selection state
|
|
63
|
+
* comes from the runtime store, so changes made imperatively (`setTheme`,
|
|
64
64
|
* `setThemeMode`) and through this hook are observed by every consumer.
|
|
65
65
|
*/
|
|
66
66
|
export function useTheme(): UseThemeResult {
|
|
@@ -71,8 +71,8 @@ export function useTheme(): UseThemeResult {
|
|
|
71
71
|
theme,
|
|
72
72
|
scheme: (rt.themeName ?? "light") as Scheme,
|
|
73
73
|
setScheme: applyScheme,
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
themeName: selection.themeName,
|
|
75
|
+
setTheme,
|
|
76
76
|
mode: selection.mode,
|
|
77
77
|
setMode: setThemeMode,
|
|
78
78
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TerraConfig } from "./types";
|
|
2
2
|
|
|
3
|
-
/** The literal
|
|
4
|
-
type
|
|
3
|
+
/** The literal theme names declared in `T.themes`, or `never` when none are declared. */
|
|
4
|
+
type DeclaredThemeNames<T> = T extends { themes: infer A }
|
|
5
5
|
? A extends Record<string, unknown>
|
|
6
6
|
? keyof A & string
|
|
7
7
|
: never
|
|
@@ -9,19 +9,24 @@ type DeclaredAccentNames<T> = T extends { accents: infer A }
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Identity helper for authoring a {@link TerraConfig} with contextual typing.
|
|
12
|
-
* Has no side effects. With literal `
|
|
13
|
-
* them; a dynamic `Record<string,
|
|
12
|
+
* Has no side effects. With literal `themes`, `defaultTheme` must name one of
|
|
13
|
+
* them; a dynamic `Record<string, NamedTheme>` keeps `defaultTheme` as `string`
|
|
14
14
|
* and defers to runtime validation.
|
|
15
15
|
*
|
|
16
16
|
* @example
|
|
17
17
|
* const config = defineTerraConfig({
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* themes: { indigo: { light: "#4f46e5", dark: "#818cf8" } },
|
|
19
|
+
* defaultTheme: "indigo",
|
|
20
20
|
* });
|
|
21
21
|
* configureTerraUI(config);
|
|
22
22
|
*/
|
|
23
23
|
export function defineTerraConfig<const T extends TerraConfig>(
|
|
24
|
-
config: T & {
|
|
24
|
+
config: T & {
|
|
25
|
+
defaultTheme?: DeclaredThemeNames<T>;
|
|
26
|
+
schemes?: never;
|
|
27
|
+
accents?: never;
|
|
28
|
+
defaultAccent?: never;
|
|
29
|
+
}
|
|
25
30
|
): T {
|
|
26
31
|
return config;
|
|
27
32
|
}
|
package/src/theme/errors.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thrown for invalid pure configuration input — before any registry, selection,
|
|
3
3
|
* or native state is touched. `path` points at the offending config entry, e.g.
|
|
4
|
-
* `
|
|
4
|
+
* `themes.indigo.dark.seed` or `defaultTheme`.
|
|
5
5
|
*/
|
|
6
6
|
export class TerraConfigError extends Error {
|
|
7
7
|
readonly path: string;
|
package/src/theme/index.ts
CHANGED
|
@@ -33,23 +33,23 @@ export { defineTerraConfig } from "./define-config";
|
|
|
33
33
|
export { TerraConfigError } from "./errors";
|
|
34
34
|
export {
|
|
35
35
|
configureTerraUI,
|
|
36
|
-
getAccentNames,
|
|
37
36
|
getDefaultRadius,
|
|
38
37
|
getIcon,
|
|
39
38
|
getImageComponent,
|
|
40
39
|
getIsConfigured,
|
|
41
40
|
getRequiredIcon,
|
|
42
41
|
getSurfaceElevation,
|
|
42
|
+
getThemeNames,
|
|
43
43
|
resolveTheme,
|
|
44
44
|
} from "./registry";
|
|
45
45
|
export { createTerraThemes } from "./resolve-config";
|
|
46
46
|
export type { ThemeSelection } from "./runtime";
|
|
47
47
|
export {
|
|
48
|
-
applyAccent,
|
|
49
48
|
applyScheme,
|
|
50
|
-
|
|
49
|
+
getCurrentTheme,
|
|
51
50
|
getThemeMode,
|
|
52
51
|
getThemeSelection,
|
|
52
|
+
setTheme,
|
|
53
53
|
setThemeMode,
|
|
54
54
|
} from "./runtime";
|
|
55
55
|
export type { ResolvedScreenMargin } from "./screen-margin";
|
|
@@ -60,12 +60,7 @@ export { resolveScreenPadding } from "./screen-padding";
|
|
|
60
60
|
export { defaultDarkTheme, defaultLightTheme } from "./theme";
|
|
61
61
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
62
62
|
export type {
|
|
63
|
-
Accent,
|
|
64
63
|
AccentColorToken,
|
|
65
|
-
AccentOverride,
|
|
66
|
-
AccentSchemeInput,
|
|
67
|
-
AccentSeed,
|
|
68
|
-
AccentShorthand,
|
|
69
64
|
ActionColorToken,
|
|
70
65
|
BackgroundColorToken,
|
|
71
66
|
BorderColorToken,
|
|
@@ -81,6 +76,7 @@ export type {
|
|
|
81
76
|
ElevationStyle,
|
|
82
77
|
FontWeightToken,
|
|
83
78
|
LayoutTokens,
|
|
79
|
+
NamedTheme,
|
|
84
80
|
NeutralColorToken,
|
|
85
81
|
OpacityTokens,
|
|
86
82
|
RadiusComponent,
|
|
@@ -110,6 +106,8 @@ export type {
|
|
|
110
106
|
ThemeColor,
|
|
111
107
|
ThemeMode,
|
|
112
108
|
ThemeRadiusOverride,
|
|
109
|
+
ThemeSchemeInput,
|
|
110
|
+
ThemeSeed,
|
|
113
111
|
ThemeSpacingOverride,
|
|
114
112
|
TypeStyle,
|
|
115
113
|
Typography,
|
package/src/theme/registry.ts
CHANGED
|
@@ -23,7 +23,6 @@ import type {
|
|
|
23
23
|
TerraImageComponent,
|
|
24
24
|
TerraSemanticIconName,
|
|
25
25
|
TerraTheme,
|
|
26
|
-
TerraThemeInput,
|
|
27
26
|
ThemeMode,
|
|
28
27
|
} from "./types";
|
|
29
28
|
import { applyMode, configureUnistyles, hasConfiguredUnistyles, updateThemes } from "./unistyles-adapter";
|
|
@@ -81,8 +80,8 @@ let installation: "unconfigured" | "fallback" | "app" = "unconfigured";
|
|
|
81
80
|
|
|
82
81
|
export const getIsConfigured = (): boolean => installation !== "unconfigured";
|
|
83
82
|
|
|
84
|
-
/** Names of the registered
|
|
85
|
-
export const
|
|
83
|
+
/** Names of the registered themes, in declaration order. Returns a fresh array. */
|
|
84
|
+
export const getThemeNames = (): string[] => [...getInstalledConfig().themeNames];
|
|
86
85
|
|
|
87
86
|
export const getImageComponent = (): TerraImageComponent => settings.imageComponent;
|
|
88
87
|
|
|
@@ -107,22 +106,22 @@ export const getSurfaceElevation = (): ElevationKey => settings.surfaceElevation
|
|
|
107
106
|
|
|
108
107
|
/**
|
|
109
108
|
* Resolves one scheme's theme from the installed configuration with the named
|
|
110
|
-
*
|
|
111
|
-
* not the configured `
|
|
109
|
+
* theme selected. Omitting `name` selects the base theme (defaults + shared —
|
|
110
|
+
* not the configured `defaultTheme`). An unknown name warns in development
|
|
112
111
|
* and falls back to the base theme. Pure with respect to runtime state: it
|
|
113
112
|
* never changes the applied selection.
|
|
114
113
|
*/
|
|
115
|
-
export function resolveTheme(scheme: Scheme,
|
|
114
|
+
export function resolveTheme(scheme: Scheme, name?: string): TerraTheme {
|
|
116
115
|
const config = getInstalledConfig();
|
|
117
|
-
let
|
|
118
|
-
if (
|
|
119
|
-
if (config.
|
|
120
|
-
|
|
116
|
+
let themeName: string | null = null;
|
|
117
|
+
if (name !== undefined) {
|
|
118
|
+
if (config.themeNames.includes(name)) {
|
|
119
|
+
themeName = name;
|
|
121
120
|
} else if (__DEV__) {
|
|
122
|
-
console.warn(`[react-native-terra-ui] Unknown
|
|
121
|
+
console.warn(`[react-native-terra-ui] Unknown theme "${name}"; resolving the base theme.`);
|
|
123
122
|
}
|
|
124
123
|
}
|
|
125
|
-
return resolveThemeScheme(config, scheme,
|
|
124
|
+
return resolveThemeScheme(config, scheme, themeName).theme;
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
@@ -189,22 +188,15 @@ function deriveMode(config: TerraConfig): ThemeMode {
|
|
|
189
188
|
return "system";
|
|
190
189
|
}
|
|
191
190
|
|
|
192
|
-
const pickThemeInput = (config: TerraConfig): TerraThemeInput => ({
|
|
193
|
-
shared: config.shared,
|
|
194
|
-
schemes: config.schemes,
|
|
195
|
-
accents: config.accents,
|
|
196
|
-
defaultAccent: config.defaultAccent,
|
|
197
|
-
});
|
|
198
|
-
|
|
199
191
|
function applyConfig(config: TerraConfig, kind: "fallback" | "app"): void {
|
|
200
192
|
// (a) Pure: validate and build everything. Throws leave ALL state untouched.
|
|
201
|
-
const themeConfig = normalizeThemeInput(
|
|
193
|
+
const themeConfig = normalizeThemeInput(config);
|
|
202
194
|
const mode = deriveMode(config);
|
|
203
|
-
const { themes, diagnostics } = resolveThemes(themeConfig, themeConfig.
|
|
195
|
+
const { themes, diagnostics } = resolveThemes(themeConfig, themeConfig.defaultTheme);
|
|
204
196
|
const nextSettings = buildSettings(config);
|
|
205
197
|
|
|
206
198
|
// (b) Native. `StyleSheet.configure` runs once per app; after that the
|
|
207
|
-
// rebuilt themes reach the runtime the same way
|
|
199
|
+
// rebuilt themes reach the runtime the same way a theme change does, so an
|
|
208
200
|
// app's config is never stranded behind the fallback's defaults.
|
|
209
201
|
if (hasConfiguredUnistyles()) {
|
|
210
202
|
updateThemes(themes);
|
|
@@ -217,7 +209,7 @@ function applyConfig(config: TerraConfig, kind: "fallback" | "app"): void {
|
|
|
217
209
|
installation = kind;
|
|
218
210
|
settings = nextSettings;
|
|
219
211
|
setInstalledConfig(themeConfig);
|
|
220
|
-
publishThemeSelection({
|
|
212
|
+
publishThemeSelection({ themeName: themeConfig.defaultTheme ?? undefined, mode });
|
|
221
213
|
|
|
222
214
|
warnContrastDiagnostics(diagnostics);
|
|
223
215
|
}
|