@graineai/inapp-react-native 0.36.0 → 0.37.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/dist/client.d.ts +2 -0
- package/dist/design.d.ts +5 -0
- package/dist/design.js +50 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/react-native/design-math.d.ts +13 -0
- package/dist/react-native/design-math.js +85 -0
- package/dist/react-native/fab.js +57 -5
- package/dist/react-native/index.d.ts +6 -1
- package/dist/react-native/index.js +18 -2
- package/dist/react-native/loader.js +23 -7
- package/dist/react-native/ui.js +139 -6
- package/dist/react-native/use-design.d.ts +14 -0
- package/dist/react-native/use-design.js +72 -0
- package/dist/react-native/widget.js +171 -61
- package/dist/theme.d.ts +206 -0
- package/dist/theme.js +1999 -0
- package/package.json +1 -1
package/dist/react-native/ui.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { Image, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, useColorScheme, View, } from "react-native";
|
|
3
|
+
import { Animated, Image, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, useColorScheme, View, } from "react-native";
|
|
4
4
|
import { useKeyboardLift } from "./keyboard.js";
|
|
5
5
|
import { useDraggablePlacement } from "./placement.js";
|
|
6
6
|
import { useGraineAgent } from "./index.js";
|
|
7
7
|
import { GraineLoader } from "./loader.js";
|
|
8
8
|
import { resolveNativeAppearance } from "./appearance.js";
|
|
9
|
+
import { useDesign } from "./use-design.js";
|
|
10
|
+
import { letterSpacing, lineHeight, onAccentFor, shadowStyle, softFor, surfaceFill } from "./design-math.js";
|
|
11
|
+
import { normaliseHex, readableOn } from "../theme.js";
|
|
9
12
|
const DARK = {
|
|
10
13
|
surface: "#17161A",
|
|
11
14
|
border: "rgba(255,255,255,0.09)",
|
|
@@ -31,14 +34,17 @@ export function GraineAgentBar({ accent: accentProp, name: nameProp, avatarUrl:
|
|
|
31
34
|
accent: "#318CE7",
|
|
32
35
|
heading: "Assistant",
|
|
33
36
|
});
|
|
34
|
-
const
|
|
37
|
+
const look = useDesign(scheme);
|
|
38
|
+
const accent = accentProp ?? (look.classic ? dashboard.accent : look.tokens.colors.accent);
|
|
35
39
|
const name = nameProp ?? dashboard.heading;
|
|
36
40
|
const avatarUrl = avatarProp ?? dashboard.logoUrl ?? undefined;
|
|
37
41
|
const deviceScheme = useColorScheme();
|
|
38
42
|
const styles = useMemo(() => {
|
|
43
|
+
if (!look.classic)
|
|
44
|
+
return makeDesignStyles(look, accent, themeProp);
|
|
39
45
|
const base = (scheme === "auto" ? deviceScheme === "light" : scheme === "light") ? LIGHT : DARK;
|
|
40
46
|
return makeStyles(themeProp ? { ...base, ...themeProp } : base);
|
|
41
|
-
}, [scheme, deviceScheme, themeProp]);
|
|
47
|
+
}, [scheme, deviceScheme, themeProp, look, accent]);
|
|
42
48
|
const [expanded, setExpanded] = useState(surface === "chat");
|
|
43
49
|
const keyboardLift = useKeyboardLift();
|
|
44
50
|
const baseBottom = launcherInset.bottom ?? bottomInset;
|
|
@@ -51,6 +57,21 @@ export function GraineAgentBar({ accent: accentProp, name: nameProp, avatarUrl:
|
|
|
51
57
|
const [captions, setCaptions] = useState(captionsOn);
|
|
52
58
|
const [draft, setDraft] = useState("");
|
|
53
59
|
const scrollRef = useRef(null);
|
|
60
|
+
const panelIn = useRef(new Animated.Value(1)).current;
|
|
61
|
+
const lookRef = useRef(look);
|
|
62
|
+
lookRef.current = look;
|
|
63
|
+
const still = look.classic || look.durationMs <= 0;
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (!expanded || still) {
|
|
66
|
+
panelIn.setValue(1);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
panelIn.setValue(0);
|
|
70
|
+
const { durationMs, easing } = lookRef.current;
|
|
71
|
+
const a = Animated.timing(panelIn, { toValue: 1, duration: durationMs, easing, useNativeDriver: true });
|
|
72
|
+
a.start();
|
|
73
|
+
return () => a.stop();
|
|
74
|
+
}, [expanded, still, panelIn]);
|
|
54
75
|
const last = messages[messages.length - 1];
|
|
55
76
|
const waiting = connecting;
|
|
56
77
|
const waitingMoment = "connecting";
|
|
@@ -89,13 +110,16 @@ export function GraineAgentBar({ accent: accentProp, name: nameProp, avatarUrl:
|
|
|
89
110
|
right: launcherInset.right ?? 12,
|
|
90
111
|
left: launcherInset.left ?? 12,
|
|
91
112
|
},
|
|
92
|
-
], children: [expanded && (_jsxs(
|
|
113
|
+
], children: [expanded && (_jsxs(PanelFrame, { animated: !look.classic, style: look.classic ? styles.panel : [styles.panel, {
|
|
114
|
+
opacity: panelIn,
|
|
115
|
+
transform: [{ translateY: panelIn.interpolate({ inputRange: [0, 1], outputRange: [8, 0] }) }],
|
|
116
|
+
}], children: [look.classic ? null : (_jsx(View, { style: styles.header, children: _jsx(Text, { numberOfLines: 1, style: styles.headerTitle, children: name }) })), _jsxs(ScrollView, { ref: scrollRef, style: [styles.transcript, keyboardLift ? styles.transcriptLifted : null], contentContainerStyle: styles.transcriptInner, showsVerticalScrollIndicator: false, children: [messages.length === 0 && (_jsx(Text, { style: styles.empty, children: isChat
|
|
93
117
|
? "I can see what you\u2019re working on \u2014 type a question, or I\u2019ll step in if you get stuck."
|
|
94
118
|
: "I can see what you\u2019re working on \u2014 ask me anything, or I\u2019ll step in if you get stuck." })), messages.map((m, i) => (_jsx(View, { style: [
|
|
95
119
|
styles.bubble,
|
|
96
|
-
m.role === "agent" ? styles.agentBubble : [styles.customerBubble, { backgroundColor: accent }],
|
|
120
|
+
m.role === "agent" ? styles.agentBubble : [styles.customerBubble, look.classic ? { backgroundColor: accent } : null],
|
|
97
121
|
], children: _jsx(Text, { style: m.role === "agent" ? styles.agentText : styles.customerText, children: m.text }) }, i)))] }), _jsx(View, { children: _jsxs(View, { style: styles.composer, children: [_jsx(TextInput, { style: styles.input, value: draft, onChangeText: setDraft, placeholder: "Type a message", placeholderTextColor: styles.placeholder.color, onSubmitEditing: submit, returnKeyType: "send", blurOnSubmit: false }), _jsx(Pressable, { onPress: submit, disabled: !draft.trim(), accessibilityLabel: "Send", style: [styles.send, { backgroundColor: accent, opacity: draft.trim() ? 1 : 0.4 }], children: _jsx(Text, { style: styles.sendLabel, children: "\u2191" }) })] }) })] })), captions && !isChat && caption?.text ? (_jsx(View, { style: styles.captionWrap, children: _jsx(Text, { numberOfLines: 3, style: [styles.caption, caption.live && styles.captionLive], children: caption.text }) })) : null, _jsxs(View, { style: styles.bar, children: [avatarUrl ? (_jsx(Image, { source: { uri: avatarUrl }, style: styles.avatarImage, accessibilityIgnoresInvertColors: true })) : (_jsx(View, { style: [styles.avatar, { backgroundColor: accent }], children: _jsx(Text, { style: styles.avatarGlyph, children: "\u2726" }) })), _jsxs(Pressable, { onPress: () => { if (!drag.dragging)
|
|
98
|
-
setExpanded((v) => !v); }, style: { flex: 1 }, accessibilityLabel: expanded ? "Collapse" : "Expand", children: [_jsx(Text, { style: styles.name, children: name }), waiting ? (_jsx(GraineLoader, { moment: waitingMoment, size: 15 })) : (_jsx(Text, { style: styles.status, children: status }))] }), canSwitch ? (_jsx(Pressable, { onPress: () => setSurface(isChat ? "voice" : "chat"), hitSlop: 8, accessibilityRole: "button", accessibilityLabel: isChat ? "Switch to a call" : "Switch to chat", style: styles.round, children: _jsx(Text, { style: styles.glyph, children: isChat ? "📞" : "💬" }) })) : null, waiting ? null : (_jsxs(_Fragment, { children: [isChat ? null : (_jsxs(_Fragment, { children: [_jsx(Pressable, { onPress: () => setCaptions((v) => !v), hitSlop: 8, accessibilityLabel: captions ? "Hide captions" : "Show captions", style: [styles.round, captions && { backgroundColor: accent }], children: _jsx(Text, { style: [styles.glyph, captions && styles.glyphOnAccent], children: "CC" }) }), _jsx(Pressable, { onPress: () => setMuted(!muted), disabled: !connected, hitSlop: 8, accessibilityLabel: muted ? "Unmute" : "Mute", style: [styles.round, muted && styles.muted, !connected && { opacity: 0.4 }], children: _jsx(Text, { style: [styles.glyph, muted && styles.glyphOnAccent], children: muted ? "🔇" : "🎙" }) })] })), _jsx(Pressable, { onPress: () => setExpanded((v) => !v), hitSlop: 8, accessibilityLabel: expanded ? "Collapse" : "Expand", style: styles.round, children: _jsx(Text, { style: styles.chevron, children: expanded ? "⌄" : "⌃" }) })] }))] })] }));
|
|
122
|
+
setExpanded((v) => !v); }, style: { flex: 1 }, accessibilityLabel: expanded ? "Collapse" : "Expand", children: [_jsx(Text, { style: styles.name, children: name }), waiting ? (_jsx(GraineLoader, { moment: waitingMoment, size: 15 })) : (_jsx(Text, { style: styles.status, children: status }))] }), canSwitch ? (_jsx(Pressable, { onPress: () => setSurface(isChat ? "voice" : "chat"), hitSlop: 8, accessibilityRole: "button", accessibilityLabel: isChat ? "Switch to a call" : "Switch to chat", style: styles.round, children: _jsx(Text, { style: styles.glyph, children: isChat ? "📞" : "💬" }) })) : null, waiting ? null : (_jsxs(_Fragment, { children: [isChat ? null : (_jsxs(_Fragment, { children: [_jsx(Pressable, { onPress: () => setCaptions((v) => !v), hitSlop: 8, accessibilityLabel: captions ? "Hide captions" : "Show captions", style: [styles.round, captions && { backgroundColor: accent }], children: _jsx(Text, { style: [styles.glyph, captions && styles.glyphOnAccent], children: "CC" }) }), _jsx(Pressable, { onPress: () => setMuted(!muted), disabled: !connected, hitSlop: 8, accessibilityLabel: muted ? "Unmute" : "Mute", style: [styles.round, !look.classic && styles.mic, muted && styles.muted, !connected && { opacity: 0.4 }], children: _jsx(Text, { style: [styles.glyph, !look.classic && styles.glyphOnAccent, muted && (look.classic ? styles.glyphOnAccent : styles.glyphOnDanger)], children: muted ? "🔇" : "🎙" }) })] })), _jsx(Pressable, { onPress: () => setExpanded((v) => !v), hitSlop: 8, accessibilityLabel: expanded ? "Collapse" : "Expand", style: styles.round, children: _jsx(Text, { style: styles.chevron, children: expanded ? "⌄" : "⌃" }) })] }))] })] }));
|
|
99
123
|
}
|
|
100
124
|
export const GraineLauncher = GraineAgentBar;
|
|
101
125
|
const makeStyles = (t) => StyleSheet.create({
|
|
@@ -148,3 +172,112 @@ const makeStyles = (t) => StyleSheet.create({
|
|
|
148
172
|
},
|
|
149
173
|
chevron: { color: t.text, fontSize: 16, lineHeight: 18 },
|
|
150
174
|
});
|
|
175
|
+
function PanelFrame({ animated, style, children }) {
|
|
176
|
+
return animated ? _jsx(Animated.View, { style: style, children: children }) : _jsx(View, { style: style, children: children });
|
|
177
|
+
}
|
|
178
|
+
function makeDesignStyles(look, accent, over) {
|
|
179
|
+
const t = look.tokens;
|
|
180
|
+
const c = t.colors;
|
|
181
|
+
const f = t.font;
|
|
182
|
+
const r = t.shape.radius;
|
|
183
|
+
const bw = t.shape.borderWidth;
|
|
184
|
+
const pad = t.spacing.cardPadding;
|
|
185
|
+
const surface = over?.surface ?? surfaceFill(c.surface, t);
|
|
186
|
+
const border = over?.border ?? c.border;
|
|
187
|
+
const text = over?.text ?? c.text;
|
|
188
|
+
const sub = over?.sub ?? c.textMuted;
|
|
189
|
+
const chip = over?.chip ?? softFor(accent, t);
|
|
190
|
+
const onAccent = over?.onAccent ?? onAccentFor(accent, t);
|
|
191
|
+
const danger = over?.danger ?? c.danger;
|
|
192
|
+
const bodyFont = look.font(f.body);
|
|
193
|
+
const headingFont = look.font(f.heading);
|
|
194
|
+
const body = {
|
|
195
|
+
fontSize: f.scale.body,
|
|
196
|
+
lineHeight: lineHeight(f.scale.body, f.lineHeight.body),
|
|
197
|
+
fontWeight: String(f.weights.body),
|
|
198
|
+
letterSpacing: letterSpacing(f.scale.body, f.letterSpacing.body),
|
|
199
|
+
fontFamily: bodyFont,
|
|
200
|
+
};
|
|
201
|
+
const mode = t.bubbles === "outline" || t.bubbles === "minimal" ? t.bubbles : "filled";
|
|
202
|
+
const followsAccent = normaliseHex(c.userBubble) === normaliseHex(c.accent);
|
|
203
|
+
const ownAccent = normaliseHex(accent) === normaliseHex(c.accent);
|
|
204
|
+
const userBubble = followsAccent ? accent : c.userBubble;
|
|
205
|
+
const userText = followsAccent
|
|
206
|
+
? (over?.onAccent ?? (ownAccent ? c.userBubbleText : readableOn(accent, [c.userBubbleText, "#FFFFFF", "#101318"])))
|
|
207
|
+
: c.userBubbleText;
|
|
208
|
+
const agentBubble = mode === "filled" ? { backgroundColor: over?.chip ?? c.agentBubble }
|
|
209
|
+
: mode === "outline" ? { backgroundColor: "transparent", borderWidth: bw, borderColor: border }
|
|
210
|
+
: { backgroundColor: "transparent", paddingHorizontal: 0, paddingVertical: 2, borderRadius: 0 };
|
|
211
|
+
const customerBubble = mode === "filled" ? { backgroundColor: userBubble }
|
|
212
|
+
: mode === "outline" ? { backgroundColor: "transparent", borderWidth: bw, borderColor: accent }
|
|
213
|
+
: { backgroundColor: softFor(accent, t) };
|
|
214
|
+
return StyleSheet.create({
|
|
215
|
+
root: { position: "absolute", left: 12, right: 12, zIndex: 900 },
|
|
216
|
+
panel: {
|
|
217
|
+
backgroundColor: c.background, borderRadius: r.xl, borderWidth: bw, borderColor: border,
|
|
218
|
+
marginBottom: 8, overflow: "hidden",
|
|
219
|
+
},
|
|
220
|
+
header: {
|
|
221
|
+
backgroundColor: surface, paddingHorizontal: pad, paddingVertical: Math.round(pad * 0.75),
|
|
222
|
+
borderBottomWidth: bw, borderBottomColor: border,
|
|
223
|
+
},
|
|
224
|
+
headerTitle: {
|
|
225
|
+
color: text, fontFamily: headingFont, fontSize: f.scale.title, fontWeight: String(f.weights.title),
|
|
226
|
+
letterSpacing: letterSpacing(f.scale.title, f.letterSpacing.title), lineHeight: lineHeight(f.scale.title, f.lineHeight.title),
|
|
227
|
+
},
|
|
228
|
+
transcript: { maxHeight: 240 },
|
|
229
|
+
transcriptLifted: { maxHeight: 150 },
|
|
230
|
+
transcriptInner: { padding: pad, gap: Math.max(4, Math.round(t.spacing.gap * 0.8)) },
|
|
231
|
+
empty: { color: sub, ...body },
|
|
232
|
+
bubble: {
|
|
233
|
+
maxWidth: "88%", paddingHorizontal: pad, paddingVertical: Math.round(pad * 0.75), borderRadius: r.lg,
|
|
234
|
+
},
|
|
235
|
+
agentBubble: { alignSelf: "flex-start", borderBottomLeftRadius: r.sm, ...agentBubble },
|
|
236
|
+
customerBubble: { alignSelf: "flex-end", borderBottomRightRadius: r.sm, ...customerBubble },
|
|
237
|
+
agentText: { color: mode === "filled" ? (over?.text ?? c.agentBubbleText) : text, ...body },
|
|
238
|
+
customerText: { color: mode === "filled" ? userText : text, ...body },
|
|
239
|
+
composer: {
|
|
240
|
+
flexDirection: "row", alignItems: "center", gap: 8,
|
|
241
|
+
paddingHorizontal: Math.round((pad * 6) / 7), paddingBottom: Math.round((pad * 6) / 7),
|
|
242
|
+
},
|
|
243
|
+
input: {
|
|
244
|
+
flex: 1, backgroundColor: c.surfaceRaised, borderWidth: bw, borderColor: border, borderRadius: r.xl,
|
|
245
|
+
paddingHorizontal: 14, paddingVertical: Platform.OS === "ios" ? 10 : 7, color: text,
|
|
246
|
+
fontSize: f.scale.body, fontFamily: bodyFont,
|
|
247
|
+
},
|
|
248
|
+
send: { width: 38, height: 38, borderRadius: 19, alignItems: "center", justifyContent: "center" },
|
|
249
|
+
sendLabel: { color: onAccent, fontSize: 17, lineHeight: 20 },
|
|
250
|
+
bar: {
|
|
251
|
+
flexDirection: "row", alignItems: "center", gap: 11,
|
|
252
|
+
backgroundColor: surface, borderRadius: r.xl, borderWidth: bw, borderColor: border,
|
|
253
|
+
paddingHorizontal: 8, paddingVertical: 8,
|
|
254
|
+
...shadowStyle(t.elevation.floating),
|
|
255
|
+
},
|
|
256
|
+
avatar: { width: 38, height: 38, borderRadius: 19, alignItems: "center", justifyContent: "center" },
|
|
257
|
+
avatarImage: { width: 38, height: 38, borderRadius: 19, backgroundColor: chip },
|
|
258
|
+
captionWrap: {
|
|
259
|
+
backgroundColor: surface, borderRadius: r.lg, borderWidth: bw, borderColor: border,
|
|
260
|
+
paddingHorizontal: pad, paddingVertical: Math.round(pad * 0.8), marginBottom: 8,
|
|
261
|
+
...shadowStyle(t.elevation.card),
|
|
262
|
+
},
|
|
263
|
+
caption: { color: text, ...body, fontWeight: String(f.weights.strong) },
|
|
264
|
+
captionLive: { opacity: 0.8 },
|
|
265
|
+
glyph: { color: text, fontSize: f.scale.caption + 1, fontWeight: String(f.weights.strong) },
|
|
266
|
+
glyphOnAccent: { color: onAccent },
|
|
267
|
+
glyphOnDanger: { color: "#FFFFFF" },
|
|
268
|
+
placeholder: { color: sub },
|
|
269
|
+
mic: { backgroundColor: accent },
|
|
270
|
+
muted: { backgroundColor: danger },
|
|
271
|
+
avatarGlyph: { color: onAccent, fontSize: 17, lineHeight: 20 },
|
|
272
|
+
name: {
|
|
273
|
+
color: text, fontSize: f.scale.body, fontWeight: String(f.weights.title), fontFamily: headingFont,
|
|
274
|
+
letterSpacing: letterSpacing(f.scale.body, f.letterSpacing.title),
|
|
275
|
+
},
|
|
276
|
+
status: { color: sub, fontSize: f.scale.caption, marginTop: 1, fontFamily: bodyFont },
|
|
277
|
+
round: {
|
|
278
|
+
width: 38, height: 38, borderRadius: 19, alignItems: "center", justifyContent: "center",
|
|
279
|
+
backgroundColor: chip,
|
|
280
|
+
},
|
|
281
|
+
chevron: { color: text, fontSize: 16, lineHeight: 18 },
|
|
282
|
+
});
|
|
283
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type MotionEasing, type ResolvedDesign, type ThemeTokens } from "../theme.js";
|
|
2
|
+
export declare function useReduceMotion(): boolean;
|
|
3
|
+
export declare function easingOf(easing: MotionEasing | string, bounce: number): (t: number) => number;
|
|
4
|
+
export interface DesignLook {
|
|
5
|
+
design: ResolvedDesign;
|
|
6
|
+
tokens: ThemeTokens;
|
|
7
|
+
classic: boolean;
|
|
8
|
+
font: (family: string) => string | undefined;
|
|
9
|
+
durationMs: number;
|
|
10
|
+
reduceMotion: boolean;
|
|
11
|
+
easing: (t: number) => number;
|
|
12
|
+
}
|
|
13
|
+
export declare function useDesign(scheme?: "light" | "dark" | "auto"): DesignLook;
|
|
14
|
+
export declare function useGraineTheme(): ThemeTokens;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useContext, useEffect, useMemo, useState } from "react";
|
|
2
|
+
import { AccessibilityInfo, Easing, useColorScheme } from "react-native";
|
|
3
|
+
import { Ctx } from "./index.js";
|
|
4
|
+
import { sessionDesign } from "../design.js";
|
|
5
|
+
import { pickTokens } from "../theme.js";
|
|
6
|
+
import { fontFamilyFor, isClassicLook } from "./design-math.js";
|
|
7
|
+
const DEFAULT_DESIGN = sessionDesign(null);
|
|
8
|
+
const classicSeen = new WeakMap();
|
|
9
|
+
function classic(design) {
|
|
10
|
+
let v = classicSeen.get(design);
|
|
11
|
+
if (v === undefined) {
|
|
12
|
+
v = isClassicLook(design);
|
|
13
|
+
classicSeen.set(design, v);
|
|
14
|
+
}
|
|
15
|
+
return v;
|
|
16
|
+
}
|
|
17
|
+
export function useReduceMotion() {
|
|
18
|
+
const [reduce, setReduce] = useState(false);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
let live = true;
|
|
21
|
+
const info = AccessibilityInfo;
|
|
22
|
+
try {
|
|
23
|
+
info?.isReduceMotionEnabled?.()?.then?.((v) => { if (live)
|
|
24
|
+
setReduce(!!v); }, () => { });
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
let sub = null;
|
|
28
|
+
try {
|
|
29
|
+
sub = info?.addEventListener?.("reduceMotionChanged", (v) => setReduce(!!v));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
sub = null;
|
|
33
|
+
}
|
|
34
|
+
return () => { live = false; try {
|
|
35
|
+
sub?.remove?.();
|
|
36
|
+
}
|
|
37
|
+
catch { } };
|
|
38
|
+
}, []);
|
|
39
|
+
return reduce;
|
|
40
|
+
}
|
|
41
|
+
export function easingOf(easing, bounce) {
|
|
42
|
+
if (easing === "linear" || easing === "none")
|
|
43
|
+
return Easing.linear;
|
|
44
|
+
if (easing === "emphasized")
|
|
45
|
+
return Easing.bezier(0.05, 0.7, 0.1, 1);
|
|
46
|
+
if (easing === "spring")
|
|
47
|
+
return Easing.out(Easing.back(Math.max(0.5, bounce * 4)));
|
|
48
|
+
return Easing.bezier(0.2, 0, 0, 1);
|
|
49
|
+
}
|
|
50
|
+
export function useDesign(scheme) {
|
|
51
|
+
const ctx = useContext(Ctx);
|
|
52
|
+
const device = useColorScheme();
|
|
53
|
+
const reduce = useReduceMotion();
|
|
54
|
+
const design = ctx?.design ?? DEFAULT_DESIGN;
|
|
55
|
+
const fonts = ctx?.fonts ?? null;
|
|
56
|
+
return useMemo(() => {
|
|
57
|
+
const tokens = scheme === "light" ? design.light : scheme === "dark" ? design.dark : pickTokens(design, device);
|
|
58
|
+
const durationMs = reduce || tokens.motion.easing === "none" ? 0 : Math.max(0, tokens.motion.durationMs);
|
|
59
|
+
return {
|
|
60
|
+
design,
|
|
61
|
+
tokens,
|
|
62
|
+
classic: classic(design),
|
|
63
|
+
font: (family) => fontFamilyFor(family, fonts),
|
|
64
|
+
durationMs,
|
|
65
|
+
reduceMotion: reduce,
|
|
66
|
+
easing: easingOf(tokens.motion.easing, tokens.motion.bounce),
|
|
67
|
+
};
|
|
68
|
+
}, [design, fonts, device, reduce, scheme]);
|
|
69
|
+
}
|
|
70
|
+
export function useGraineTheme() {
|
|
71
|
+
return useDesign().tokens;
|
|
72
|
+
}
|