@vvedantb/native-ui 0.13.0 → 0.17.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/README.md +40 -19
- package/package.json +36 -3
- package/src/BottomSheetContainer.tsx +2 -2
- package/src/BulkActionBar.tsx +240 -105
- package/src/Button.tsx +11 -7
- package/src/ConfirmDialog.tsx +25 -35
- package/src/Dialog.tsx +47 -35
- package/src/DividedList.tsx +11 -7
- package/src/DropdownMenu.tsx +22 -8
- package/src/FloatingSurface.tsx +5 -2
- package/src/FloatingTabBar.tsx +13 -22
- package/src/HeaderIconButton.tsx +4 -3
- package/src/Input.tsx +2 -2
- package/src/LoadingState.tsx +132 -0
- package/src/PageTitle.tsx +9 -2
- package/src/Pager.tsx +62 -0
- package/src/PasswordInput.tsx +1 -1
- package/src/PillTabs.tsx +144 -34
- package/src/Progress.tsx +9 -7
- package/src/Select.tsx +4 -4
- package/src/Sheet.tsx +29 -22
- package/src/SheetRow.tsx +27 -9
- package/src/Skeleton.tsx +91 -9
- package/src/SurfaceRow.tsx +111 -0
- package/src/SwipeableRow.tsx +43 -0
- package/src/Switch.tsx +6 -4
- package/src/Tabs.tsx +68 -13
- package/src/Toast.tsx +107 -65
- package/src/ai/activity-step-detail.tsx +1 -3
- package/src/ai/activity-step-label.ts +981 -0
- package/src/ai/activity-step-types.ts +107 -0
- package/src/ai/activity-tasks-utils.ts +199 -0
- package/src/ai/activity-timeline.ts +45 -0
- package/src/ai/attachments.tsx +3 -1
- package/src/ai/chat-text.ts +13 -7
- package/src/ai/chat-thread.tsx +34 -27
- package/src/ai/choice-question.tsx +154 -35
- package/src/ai/code-block.tsx +189 -49
- package/src/ai/collapsible.tsx +19 -13
- package/src/ai/conversation.tsx +390 -57
- package/src/ai/icon-button.tsx +41 -6
- package/src/ai/index.ts +38 -1
- package/src/ai/message-action-sheet.tsx +4 -1
- package/src/ai/message.tsx +60 -31
- package/src/ai/prompt-input-menu.tsx +197 -0
- package/src/ai/prompt-input.tsx +301 -75
- package/src/ai/shimmer.tsx +4 -6
- package/src/ai/trace.tsx +54 -17
- package/src/ai/unified-diff.tsx +182 -0
- package/src/auth/GoogleSignInButton.tsx +6 -3
- package/src/auth/SignInScreen.tsx +11 -2
- package/src/brand/ProviderIcon.tsx +136 -0
- package/src/brand/index.ts +2 -0
- package/src/configureTheme.ts +39 -11
- package/src/controlStyles.ts +46 -30
- package/src/entries/gesture-handler.ts +4 -0
- package/src/entries/keyboard-controller.ts +4 -0
- package/src/entries/markdown.ts +1 -1
- package/src/entries/primitives.ts +24 -3
- package/src/entries/theme.ts +2 -1
- package/src/entries/video.ts +4 -0
- package/src/entries/webview.ts +6 -0
- package/src/floating.ts +25 -4
- package/src/haptics.ts +30 -4
- package/src/highlight.ts +302 -1
- package/src/index.ts +27 -5
- package/src/loadingGrid.ts +75 -0
- package/src/markdown/Markdown.tsx +234 -32
- package/src/markdown/MarkdownTable.tsx +188 -0
- package/src/markdown/tableText.ts +58 -0
- package/src/marketing/UpgradeToProCard.tsx +4 -3
- package/src/media/ImageGallery.tsx +224 -0
- package/src/media/MediaCard.tsx +100 -0
- package/src/media/imageGrid.ts +51 -0
- package/src/motion.ts +33 -0
- package/src/pagerSwipe.ts +102 -0
- package/src/palette.ts +8 -1
- package/src/primitives/button.tsx +4 -1
- package/src/primitives/keyboard-controller.tsx +106 -0
- package/src/primitives/keyboard-parts.tsx +104 -0
- package/src/primitives/overlay.tsx +133 -45
- package/src/primitives/pager-gesture.tsx +131 -0
- package/src/primitives/sheet-drag.tsx +305 -0
- package/src/primitives/sheet-gesture.tsx +181 -0
- package/src/primitives/swipe-row-gesture.tsx +290 -0
- package/src/primitives/toast-drag-gesture.tsx +92 -0
- package/src/primitives/web-click-guard.ts +50 -0
- package/src/relativeTime.ts +94 -0
- package/src/router/RouterStackPageHeader.tsx +2 -2
- package/src/settings/SettingsEmptyState.tsx +20 -10
- package/src/settings/SettingsField.tsx +11 -5
- package/src/settings/SettingsSection.tsx +8 -5
- package/src/settings/SettingsToggleRow.tsx +55 -22
- package/src/settings/relaxedSizeStyle.ts +11 -0
- package/src/sheetDrag.ts +67 -0
- package/src/swipeRow.ts +169 -0
- package/src/tabBarRoutes.ts +18 -0
- package/src/tabIndicator.ts +71 -0
- package/src/tailwind-preset.cjs +13 -6
- package/src/tailwind-preset.d.cts +5 -0
- package/src/theme.tsx +6 -2
- package/src/toastDrag.ts +58 -0
- package/src/typography.ts +20 -3
- package/src/useScheme.ts +10 -1
- package/src/video/InlineVideo.tsx +91 -0
- package/src/webview/HtmlView.tsx +91 -0
- package/src/webview/HtmlView.web.tsx +21 -0
- package/src/webview/htmlView.ts +45 -0
package/src/ai/trace.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { createContext, useContext, type ComponentType, type ReactNode } from "r
|
|
|
2
2
|
import { Pressable, Text, View, type ViewProps } from "react-native";
|
|
3
3
|
import type { IconProps } from "@tabler/icons-react-native";
|
|
4
4
|
import { cn } from "../cn";
|
|
5
|
-
import { sizeStyle
|
|
5
|
+
import { sizeStyle } from "../controlStyles";
|
|
6
6
|
import { palette } from "../palette";
|
|
7
7
|
import { useLargerText } from "../theme";
|
|
8
8
|
import { tokens } from "../tokens";
|
|
@@ -40,9 +40,13 @@ function useTraceColor() {
|
|
|
40
40
|
return useContext(TraceLineColorContext) ?? tokens.color.muted[scheme];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* A column of trace lines (web `flex w-full flex-col gap-2`). Each line is 28
|
|
45
|
+
* high (its `py-1` around the 20 text) with no gap, so the rows keep the web
|
|
46
|
+
* 28 pitch and each is its whole tap area, without slop over its neighbours.
|
|
47
|
+
*/
|
|
44
48
|
export function AgentTrace({ className, ...props }: ViewProps & { className?: string }) {
|
|
45
|
-
return <View className={cn("w-full min-w-0
|
|
49
|
+
return <View className={cn("w-full min-w-0", className)} {...props} />;
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
export type AgentTraceLineProps = ViewProps & {
|
|
@@ -50,13 +54,13 @@ export type AgentTraceLineProps = ViewProps & {
|
|
|
50
54
|
className?: string;
|
|
51
55
|
};
|
|
52
56
|
|
|
53
|
-
/** One line: a row of icon, title and description. `pending` fades it to 60%, `error` turns it the danger colour. */
|
|
57
|
+
/** One line, 28 high (`py-1`): a row of icon, title and description. `pending` fades it to 60%, `error` turns it the danger colour. */
|
|
54
58
|
export function AgentTraceLine({ status, className, style, ...props }: AgentTraceLineProps) {
|
|
55
59
|
const scheme = useScheme();
|
|
56
60
|
return (
|
|
57
61
|
<TraceLineColorContext.Provider value={traceLineColor(status, scheme)}>
|
|
58
62
|
<View
|
|
59
|
-
className={cn("min-w-0 max-w-full shrink flex-row items-center gap-2", className)}
|
|
63
|
+
className={cn("min-w-0 max-w-full shrink flex-row items-center gap-2 py-1", className)}
|
|
60
64
|
style={[status === "pending" ? { opacity: 0.6 } : null, style]}
|
|
61
65
|
{...props}
|
|
62
66
|
/>
|
|
@@ -149,7 +153,17 @@ export type AgentTraceCollapsibleTriggerProps = CollapsibleTriggerProps & {
|
|
|
149
153
|
trailing?: ReactNode;
|
|
150
154
|
};
|
|
151
155
|
|
|
152
|
-
/**
|
|
156
|
+
/** No slop: the 28 high line is the whole target, as trace lines stack with no gap. */
|
|
157
|
+
const LINE_SLOP = { top: 0, bottom: 0, left: 0, right: 0 } as const;
|
|
158
|
+
|
|
159
|
+
/** The chevron's slop: 4 back toward the line, 14 on past it (a 34 wide target), none above or below. */
|
|
160
|
+
const CHEVRON_SLOP = { top: 0, bottom: 0, left: 4, right: 14 } as const;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The line as a toggle, with a chevron after it that turns down when open.
|
|
164
|
+
* The trigger stretches to the line's 28 height and takes no slop above or
|
|
165
|
+
* below, so a tap under one line never opens the next.
|
|
166
|
+
*/
|
|
153
167
|
export function AgentTraceCollapsibleTrigger({
|
|
154
168
|
children,
|
|
155
169
|
trailing,
|
|
@@ -166,6 +180,7 @@ export function AgentTraceCollapsibleTrigger({
|
|
|
166
180
|
if (trailing === undefined) {
|
|
167
181
|
return (
|
|
168
182
|
<CollapsibleTrigger
|
|
183
|
+
hitSlop={LINE_SLOP}
|
|
169
184
|
className={cn("max-w-full flex-row items-center gap-1 self-start", className)}
|
|
170
185
|
{...props}
|
|
171
186
|
>
|
|
@@ -176,7 +191,7 @@ export function AgentTraceCollapsibleTrigger({
|
|
|
176
191
|
}
|
|
177
192
|
return (
|
|
178
193
|
<View className={cn("max-w-full flex-row items-center gap-2 self-start", className)}>
|
|
179
|
-
<CollapsibleTrigger className="min-w-0 shrink flex-row items-center" {...props}>
|
|
194
|
+
<CollapsibleTrigger hitSlop={LINE_SLOP} className="min-w-0 shrink flex-row items-center self-stretch" {...props}>
|
|
180
195
|
{children}
|
|
181
196
|
</CollapsibleTrigger>
|
|
182
197
|
{trailing}
|
|
@@ -184,8 +199,9 @@ export function AgentTraceCollapsibleTrigger({
|
|
|
184
199
|
<Pressable
|
|
185
200
|
accessible={false}
|
|
186
201
|
importantForAccessibility="no-hide-descendants"
|
|
187
|
-
hitSlop={
|
|
202
|
+
hitSlop={CHEVRON_SLOP}
|
|
188
203
|
onPress={() => setOpen(!open)}
|
|
204
|
+
className="self-stretch justify-center"
|
|
189
205
|
>
|
|
190
206
|
{chevron}
|
|
191
207
|
</Pressable>
|
|
@@ -210,7 +226,7 @@ export function AgentTraceCollapsibleContent({
|
|
|
210
226
|
return (
|
|
211
227
|
<CollapsibleContent>
|
|
212
228
|
<View
|
|
213
|
-
className={cn("mb-
|
|
229
|
+
className={cn("mb-2 mt-1 min-w-0 pl-4 pr-1", className)}
|
|
214
230
|
style={[
|
|
215
231
|
{
|
|
216
232
|
marginLeft: iconSize / 2 - 1,
|
|
@@ -234,31 +250,52 @@ export function AgentTraceCollapsibleContent({
|
|
|
234
250
|
}
|
|
235
251
|
|
|
236
252
|
export type AgentTraceFoldProps = Omit<CollapsibleProps, "children"> & {
|
|
237
|
-
/** How long the turn ran, in seconds. The title reads "Worked for 1m 5s". */
|
|
238
|
-
seconds: number;
|
|
239
253
|
/** The settled turn's steps, shown in an `AgentTrace` when open. */
|
|
240
254
|
children: ReactNode;
|
|
241
|
-
}
|
|
255
|
+
} & (
|
|
256
|
+
| {
|
|
257
|
+
/** How long the turn ran, in seconds. The title reads "Worked for 1m 5s" (`formatElapsed`). */
|
|
258
|
+
seconds: number;
|
|
259
|
+
duration?: never;
|
|
260
|
+
}
|
|
261
|
+
| {
|
|
262
|
+
/**
|
|
263
|
+
* The duration already formatted, as the web `ActivityTasks` `duration`:
|
|
264
|
+
* the title reads "Worked for " and this. For an app's own formatter.
|
|
265
|
+
*/
|
|
266
|
+
duration: string;
|
|
267
|
+
seconds?: never;
|
|
268
|
+
}
|
|
269
|
+
);
|
|
242
270
|
|
|
243
271
|
/**
|
|
244
272
|
* A settled turn folded behind one line, as the web `ActivityTasks` in its
|
|
245
273
|
* folded (Codex-style) mode: "Worked for …" over a hairline
|
|
246
|
-
* (`border-b border-border pb-1.5
|
|
274
|
+
* 6 below the text (web `border-b border-border pb-1.5`: the line's own 4 and
|
|
275
|
+
* 2 more), closed by default, and the steps in an
|
|
247
276
|
* `AgentTrace` 8 below when open.
|
|
248
277
|
*/
|
|
249
|
-
export function AgentTraceFold({
|
|
278
|
+
export function AgentTraceFold({
|
|
279
|
+
seconds,
|
|
280
|
+
duration,
|
|
281
|
+
children,
|
|
282
|
+
defaultOpen = false,
|
|
283
|
+
className,
|
|
284
|
+
...props
|
|
285
|
+
}: AgentTraceFoldProps) {
|
|
250
286
|
const scheme = useScheme();
|
|
287
|
+
const elapsed = duration ?? formatElapsed(seconds ?? 0);
|
|
251
288
|
return (
|
|
252
289
|
<Collapsible className={cn("w-full min-w-0", className)} defaultOpen={defaultOpen} {...props}>
|
|
253
|
-
<View style={{ borderBottomWidth: 1, borderBottomColor: tokens.color.border[scheme], paddingBottom:
|
|
290
|
+
<View style={{ borderBottomWidth: 1, borderBottomColor: tokens.color.border[scheme], paddingBottom: 2 }}>
|
|
254
291
|
<AgentTraceCollapsibleTrigger>
|
|
255
292
|
<AgentTraceLine>
|
|
256
|
-
<AgentTraceTitle>{`Worked for ${
|
|
293
|
+
<AgentTraceTitle>{`Worked for ${elapsed}`}</AgentTraceTitle>
|
|
257
294
|
</AgentTraceLine>
|
|
258
295
|
</AgentTraceCollapsibleTrigger>
|
|
259
296
|
</View>
|
|
260
297
|
<CollapsibleContent>
|
|
261
|
-
<AgentTrace className="mt-
|
|
298
|
+
<AgentTrace className="mt-1">{children}</AgentTrace>
|
|
262
299
|
</CollapsibleContent>
|
|
263
300
|
</Collapsible>
|
|
264
301
|
);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { Pressable, ScrollView, Text, View, type TextStyle } from "react-native";
|
|
2
|
+
import { sizeStyle, textStyle } from "../controlStyles";
|
|
3
|
+
import { palette, withAlpha } from "../palette";
|
|
4
|
+
import { tokens } from "../tokens";
|
|
5
|
+
import { useLargerText } from "../theme";
|
|
6
|
+
import { useScheme } from "../useScheme";
|
|
7
|
+
import { MONO_FONT, chatTextSize } from "./chat-text";
|
|
8
|
+
|
|
9
|
+
/** One row of a unified diff: a hunk separator, or a line with its old and new line numbers. */
|
|
10
|
+
export type UnifiedDiffLine =
|
|
11
|
+
| { readonly kind: "hunk"; readonly text: string }
|
|
12
|
+
| {
|
|
13
|
+
readonly kind: "added" | "removed" | "context";
|
|
14
|
+
/** The line's content, without its `+`/`-`/space marker. */
|
|
15
|
+
readonly text: string;
|
|
16
|
+
/** Null for an added line. */
|
|
17
|
+
readonly oldLine: number | null;
|
|
18
|
+
/** Null for a removed line. */
|
|
19
|
+
readonly newLine: number | null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The web Code tab type (`--diffs-font-size: 14px`, `--diffs-line-height: 22px`),
|
|
24
|
+
* the gutter padding and the width of one gutter digit.
|
|
25
|
+
*/
|
|
26
|
+
export const UNIFIED_DIFF = { fontSize: 14, lineHeight: 22, gutterPadding: 8, digitWidth: 8.5 } as const;
|
|
27
|
+
|
|
28
|
+
/** Larger text steps the code up as `chatTextSize("small")` does (`text-lg`), keeping the 22/14 leading. */
|
|
29
|
+
const LARGER_FONT_SIZE = 18;
|
|
30
|
+
|
|
31
|
+
/** Web `bg-emerald-500/10` and `text-emerald-700` (dark `text-emerald-300`): edit and diff added lines. */
|
|
32
|
+
export const EMERALD = { fill: "#10b981", light: "#047857", dark: "#6ee7b7" } as const;
|
|
33
|
+
|
|
34
|
+
/** Web `text-emerald-600` (dark `text-emerald-400`), the `+n` of a diff stat. */
|
|
35
|
+
const ADDED_TEXT = { light: "#059669", dark: "#34d399" } as const;
|
|
36
|
+
|
|
37
|
+
const SIGN = { added: "+", removed: "-", context: " " } as const;
|
|
38
|
+
|
|
39
|
+
const TABULAR: TextStyle = { fontVariant: ["tabular-nums"] };
|
|
40
|
+
|
|
41
|
+
export type UnifiedDiffProps = {
|
|
42
|
+
lines: ReadonlyArray<UnifiedDiffLine>;
|
|
43
|
+
/**
|
|
44
|
+
* Makes each code row (not a hunk header) a button: a tap calls this with
|
|
45
|
+
* the row's index in `lines`. The code is then not selectable, so a tap
|
|
46
|
+
* always lands.
|
|
47
|
+
*/
|
|
48
|
+
onLinePress?: (index: number) => void;
|
|
49
|
+
/** What a row's button does, for a screen reader (`accessibilityHint`). */
|
|
50
|
+
linePressHint?: string;
|
|
51
|
+
/** Indexes in `lines` of the rows to mark, such as those with a comment: their gutter fills. */
|
|
52
|
+
markedLines?: ReadonlySet<number>;
|
|
53
|
+
className?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* One file's diff in one column, as the web Code tab draws it in unified view
|
|
58
|
+
* (`@pierre/diffs`): a line number gutter (the old number on a removed line,
|
|
59
|
+
* the new one otherwise), the `+`/`-` marker, and the line in mono 14 / 22.
|
|
60
|
+
* Added lines tint green and removed lines red, each at 10%; a hunk header is
|
|
61
|
+
* a muted row on the secondary surface. Long lines do not wrap: the whole
|
|
62
|
+
* diff scrolls sideways, and every row spans the widest line. With
|
|
63
|
+
* `onLinePress` a code row is a button (`Line N`); `markedLines` fills the
|
|
64
|
+
* gutter of the rows it names at 12% of the primary colour.
|
|
65
|
+
*/
|
|
66
|
+
export function UnifiedDiff({ lines, onLinePress, linePressHint, markedLines, className }: UnifiedDiffProps) {
|
|
67
|
+
const scheme = useScheme();
|
|
68
|
+
const largerText = useLargerText();
|
|
69
|
+
const fontSize = largerText ? LARGER_FONT_SIZE : UNIFIED_DIFF.fontSize;
|
|
70
|
+
const widest = lines.reduce(
|
|
71
|
+
(max, line) => (line.kind === "hunk" ? max : Math.max(max, line.oldLine ?? 0, line.newLine ?? 0)),
|
|
72
|
+
0,
|
|
73
|
+
);
|
|
74
|
+
const scale = fontSize / UNIFIED_DIFF.fontSize;
|
|
75
|
+
const gutter = String(widest).length * UNIFIED_DIFF.digitWidth * scale + UNIFIED_DIFF.gutterPadding * 2;
|
|
76
|
+
const mono = { fontFamily: MONO_FONT, fontSize, lineHeight: UNIFIED_DIFF.lineHeight * scale };
|
|
77
|
+
return (
|
|
78
|
+
<ScrollView horizontal className={className} contentContainerStyle={{ minWidth: "100%" }}>
|
|
79
|
+
<View style={{ flexGrow: 1 }}>
|
|
80
|
+
{lines.map((line, index) => {
|
|
81
|
+
if (line.kind === "hunk") {
|
|
82
|
+
return (
|
|
83
|
+
<Text
|
|
84
|
+
key={index}
|
|
85
|
+
numberOfLines={1}
|
|
86
|
+
style={[
|
|
87
|
+
mono,
|
|
88
|
+
textStyle("muted", scheme),
|
|
89
|
+
{ backgroundColor: tokens.color.surfaceSecondary[scheme], paddingHorizontal: UNIFIED_DIFF.gutterPadding },
|
|
90
|
+
]}
|
|
91
|
+
>
|
|
92
|
+
{line.text}
|
|
93
|
+
</Text>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
const tint =
|
|
97
|
+
line.kind === "added"
|
|
98
|
+
? withAlpha(EMERALD.fill, 0.1)
|
|
99
|
+
: line.kind === "removed"
|
|
100
|
+
? withAlpha(palette.danger[scheme], 0.1)
|
|
101
|
+
: undefined;
|
|
102
|
+
const number = line.kind === "removed" ? line.oldLine : line.newLine;
|
|
103
|
+
const cells = [
|
|
104
|
+
<Text
|
|
105
|
+
key="gutter"
|
|
106
|
+
style={[
|
|
107
|
+
mono,
|
|
108
|
+
textStyle(markedLines?.has(index) ? "foreground" : "muted", scheme),
|
|
109
|
+
TABULAR,
|
|
110
|
+
{ width: gutter, paddingHorizontal: UNIFIED_DIFF.gutterPadding, textAlign: "right" },
|
|
111
|
+
markedLines?.has(index) ? { backgroundColor: withAlpha(palette.primary[scheme], 0.12) } : null,
|
|
112
|
+
]}
|
|
113
|
+
>
|
|
114
|
+
{number ?? ""}
|
|
115
|
+
</Text>,
|
|
116
|
+
<Text
|
|
117
|
+
key="code"
|
|
118
|
+
selectable={onLinePress === undefined}
|
|
119
|
+
style={[mono, textStyle("foreground", scheme), { paddingRight: UNIFIED_DIFF.gutterPadding * 2 }]}
|
|
120
|
+
>
|
|
121
|
+
{`${SIGN[line.kind]} ${line.text}`}
|
|
122
|
+
</Text>,
|
|
123
|
+
];
|
|
124
|
+
if (onLinePress === undefined) {
|
|
125
|
+
return (
|
|
126
|
+
<View key={index} className="flex-row" style={{ backgroundColor: tint }}>
|
|
127
|
+
{cells}
|
|
128
|
+
</View>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return (
|
|
132
|
+
<Pressable
|
|
133
|
+
key={index}
|
|
134
|
+
accessibilityRole="button"
|
|
135
|
+
accessibilityLabel={`Line ${number ?? ""}`}
|
|
136
|
+
accessibilityHint={linePressHint}
|
|
137
|
+
// No slop: rows tile the diff edge to edge, so any slop would take
|
|
138
|
+
// the next line's taps. Each row is a full-width 22 high target,
|
|
139
|
+
// as dense as the code it holds.
|
|
140
|
+
hitSlop={0}
|
|
141
|
+
onPress={() => onLinePress(index)}
|
|
142
|
+
className="flex-row active:opacity-60"
|
|
143
|
+
style={{ backgroundColor: tint }}
|
|
144
|
+
>
|
|
145
|
+
{cells}
|
|
146
|
+
</Pressable>
|
|
147
|
+
);
|
|
148
|
+
})}
|
|
149
|
+
</View>
|
|
150
|
+
</ScrollView>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type DiffStatProps = {
|
|
155
|
+
additions: number;
|
|
156
|
+
deletions: number;
|
|
157
|
+
/** Drop a side that is zero, so a pure addition reads `+2`, not `+2 −0`. */
|
|
158
|
+
hideZero?: boolean;
|
|
159
|
+
className?: string;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/** The web `PrDiffStat`: `+n` in emerald and `−m` in the destructive colour, tabular mono. */
|
|
163
|
+
export function DiffStat({ additions, deletions, hideZero = false, className }: DiffStatProps) {
|
|
164
|
+
const scheme = useScheme();
|
|
165
|
+
const largerText = useLargerText();
|
|
166
|
+
const stat = [sizeStyle(chatTextSize("small", largerText)), TABULAR, { fontFamily: MONO_FONT }];
|
|
167
|
+
return (
|
|
168
|
+
<View
|
|
169
|
+
accessible
|
|
170
|
+
accessibilityLabel={`${additions} additions, ${deletions} deletions`}
|
|
171
|
+
className={className}
|
|
172
|
+
style={{ flexDirection: "row", flexShrink: 0, gap: 4 }}
|
|
173
|
+
>
|
|
174
|
+
{hideZero && additions === 0 ? null : (
|
|
175
|
+
<Text style={[stat, { color: ADDED_TEXT[scheme] }]}>{`+${additions.toLocaleString()}`}</Text>
|
|
176
|
+
)}
|
|
177
|
+
{hideZero && deletions === 0 ? null : (
|
|
178
|
+
<Text style={[stat, { color: palette.danger[scheme] }]}>{`−${deletions.toLocaleString()}`}</Text>
|
|
179
|
+
)}
|
|
180
|
+
</View>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
@@ -3,7 +3,7 @@ import { Platform } from "react-native";
|
|
|
3
3
|
import { useSSO } from "@clerk/clerk-expo";
|
|
4
4
|
import * as AuthSession from "expo-auth-session";
|
|
5
5
|
import * as WebBrowser from "expo-web-browser";
|
|
6
|
-
import { useRouter, type Href
|
|
6
|
+
import { useRouter, type Href } from "expo-router";
|
|
7
7
|
import { GoogleMark } from "../brand/GoogleMark";
|
|
8
8
|
import { Button, type ButtonProps } from "../Button";
|
|
9
9
|
import { toast } from "../toastStore";
|
|
@@ -17,8 +17,11 @@ export const GOOGLE_ERROR_MESSAGE = "Could not continue with Google";
|
|
|
17
17
|
export type AuthNavigationOptions = {
|
|
18
18
|
/** Where to go after a session starts. Default `"/"`. */
|
|
19
19
|
afterAuthHref?: Href;
|
|
20
|
-
/**
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Passed to `router.replace`, such as `{ withAnchor: true }`. Typed from
|
|
22
|
+
* `useRouter`, as expo-router 57 exports no `Router` type.
|
|
23
|
+
*/
|
|
24
|
+
replaceOptions?: Parameters<ReturnType<typeof useRouter>["replace"]>[1];
|
|
22
25
|
/** Called after a session starts, in place of `router.replace`. */
|
|
23
26
|
onSuccess?: () => void;
|
|
24
27
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState, type ReactNode } from "react";
|
|
2
|
-
import { Text, View } from "react-native";
|
|
1
|
+
import { useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import { Text, View, type TextInput } from "react-native";
|
|
3
3
|
import { useSignIn, useSignUp } from "@clerk/clerk-expo";
|
|
4
4
|
import { useRouter, type Href } from "expo-router";
|
|
5
5
|
import { Button } from "../Button";
|
|
@@ -203,6 +203,9 @@ export function SignInScreen({
|
|
|
203
203
|
const secondFactor = useSecondFactor({ stageTimeoutMs });
|
|
204
204
|
const [error, setError] = useState("");
|
|
205
205
|
const [submitting, setSubmitting] = useState(false);
|
|
206
|
+
// The field each "next" key moves to.
|
|
207
|
+
const passwordField = useRef<TextInput>(null);
|
|
208
|
+
const confirmField = useRef<TextInput>(null);
|
|
206
209
|
const signUp = mode === "sign-up";
|
|
207
210
|
const id = (name: string) => `${testIDPrefix ?? mode}-${name}`;
|
|
208
211
|
|
|
@@ -481,12 +484,15 @@ export function SignInScreen({
|
|
|
481
484
|
textContentType="newPassword"
|
|
482
485
|
autoFocus
|
|
483
486
|
returnKeyType="next"
|
|
487
|
+
submitBehavior="submit"
|
|
488
|
+
onSubmitEditing={() => confirmField.current?.focus()}
|
|
484
489
|
value={newPassword}
|
|
485
490
|
placeholder={AUTH_MESSAGES.newPasswordLabel}
|
|
486
491
|
onChangeText={setNewPassword}
|
|
487
492
|
/>
|
|
488
493
|
<PasswordInput
|
|
489
494
|
testID={id("confirm-password")}
|
|
495
|
+
ref={confirmField}
|
|
490
496
|
accessibilityLabel={AUTH_MESSAGES.confirmPasswordLabel}
|
|
491
497
|
autoCapitalize="none"
|
|
492
498
|
autoComplete="new-password"
|
|
@@ -661,12 +667,15 @@ export function SignInScreen({
|
|
|
661
667
|
textContentType="emailAddress"
|
|
662
668
|
keyboardType="email-address"
|
|
663
669
|
returnKeyType="next"
|
|
670
|
+
submitBehavior="submit"
|
|
671
|
+
onSubmitEditing={() => passwordField.current?.focus()}
|
|
664
672
|
value={email}
|
|
665
673
|
placeholder="Email"
|
|
666
674
|
onChangeText={setEmail}
|
|
667
675
|
/>
|
|
668
676
|
<PasswordInput
|
|
669
677
|
testID={id("password")}
|
|
678
|
+
ref={passwordField}
|
|
670
679
|
accessibilityLabel="Password"
|
|
671
680
|
autoCapitalize="none"
|
|
672
681
|
autoComplete={signUp ? "new-password" : "password"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { Platform, type ViewProps } from "react-native";
|
|
2
|
+
import Svg, { Path, Rect } from "react-native-svg";
|
|
3
|
+
import { svgA11yProps } from "../svgA11y";
|
|
4
|
+
import { useScheme } from "../useScheme";
|
|
5
|
+
|
|
6
|
+
/** A fill that follows the colour scheme, as the web `text-black dark:text-white`. */
|
|
7
|
+
type SchemeFill = string | { light: string; dark: string };
|
|
8
|
+
|
|
9
|
+
type ProviderMarkLayer =
|
|
10
|
+
| { d: string; fill: SchemeFill; evenOdd?: boolean }
|
|
11
|
+
/** A full-bleed square behind the glyph (the OpenCode tile). */
|
|
12
|
+
| { square: number; fill: SchemeFill };
|
|
13
|
+
|
|
14
|
+
export type ProviderMark = { viewBox: string; layers: readonly ProviderMarkLayer[] };
|
|
15
|
+
|
|
16
|
+
/** Providers with a mark. Any other provider draws the Claude mark, as the web. */
|
|
17
|
+
export type ProviderIconProvider = "claude" | "codex" | "opencode" | "cursor";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The provider marks of the web `ProviderIcon` (`@vvedantb/ui`
|
|
21
|
+
* `ai-elements/provider-icon.tsx`): the same path data, viewBoxes and fills.
|
|
22
|
+
* A test checks them against the web source.
|
|
23
|
+
*/
|
|
24
|
+
export const PROVIDER_MARKS: Record<ProviderIconProvider, ProviderMark> = {
|
|
25
|
+
claude: {
|
|
26
|
+
viewBox: "0 0 24 24",
|
|
27
|
+
layers: [
|
|
28
|
+
{
|
|
29
|
+
fill: "#D97757",
|
|
30
|
+
d: "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
codex: {
|
|
35
|
+
viewBox: "0 0 256 260",
|
|
36
|
+
layers: [
|
|
37
|
+
{
|
|
38
|
+
fill: { light: "#000", dark: "#fff" },
|
|
39
|
+
d: "M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483Zm-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87 51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601Zm-104.466-44.61a48.345 48.345 0 0 1-5.781-32.589l1.534.921 51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803ZM23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405v.256Zm179.466 41.695-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213Zm21.742-32.69-1.535-.922-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391v.205ZM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367l-.051 72.697Zm11.868-25.58 28.138-16.217 28.188 16.218v32.434l-28.086 16.218-28.188-16.218-.052-32.434Z",
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
opencode: {
|
|
44
|
+
viewBox: "0 0 512 512",
|
|
45
|
+
layers: [
|
|
46
|
+
{ square: 512, fill: "#FDFCFC" },
|
|
47
|
+
{ d: "M320 224V352H192V224H320Z", fill: "#E6E5E6" },
|
|
48
|
+
{ d: "M384 416H128V96H384V416ZM320 160H192V352H320V160Z", fill: "#17181C", evenOdd: true },
|
|
49
|
+
],
|
|
50
|
+
},
|
|
51
|
+
cursor: {
|
|
52
|
+
viewBox: "0 0 466.73 532.09",
|
|
53
|
+
layers: [
|
|
54
|
+
{
|
|
55
|
+
fill: { light: "#26251E", dark: "#EDECEC" },
|
|
56
|
+
d: "M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z",
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/** The mark for `provider`, the Claude mark for an unknown one (web `ProviderIcon` default). */
|
|
63
|
+
export function providerMark(provider: string): ProviderMark {
|
|
64
|
+
switch (provider) {
|
|
65
|
+
case "codex":
|
|
66
|
+
case "opencode":
|
|
67
|
+
case "cursor":
|
|
68
|
+
return PROVIDER_MARKS[provider];
|
|
69
|
+
default:
|
|
70
|
+
return PROVIDER_MARKS.claude;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The provider's name in a model picker, as the web `getProviderLabel`
|
|
76
|
+
* (`ai-elements/model-picker-types.ts`): "Claude", "GPT" for Codex,
|
|
77
|
+
* "Opencode", "Cursor", and any other provider as given.
|
|
78
|
+
*/
|
|
79
|
+
export function providerLabel(provider: string): string {
|
|
80
|
+
switch (provider) {
|
|
81
|
+
case "claude":
|
|
82
|
+
return "Claude";
|
|
83
|
+
case "codex":
|
|
84
|
+
return "GPT";
|
|
85
|
+
case "opencode":
|
|
86
|
+
return "Opencode";
|
|
87
|
+
case "cursor":
|
|
88
|
+
return "Cursor";
|
|
89
|
+
default:
|
|
90
|
+
return provider;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type ProviderIconProps = {
|
|
95
|
+
/** "claude", "codex", "opencode" or "cursor". Any other value draws the Claude mark, as the web. */
|
|
96
|
+
provider: string;
|
|
97
|
+
/** Width and height in points. Default 14, as the web. */
|
|
98
|
+
size?: number;
|
|
99
|
+
/** Accessible name. Without it the mark is hidden from assistive tech (web `aria-hidden`). */
|
|
100
|
+
title?: string;
|
|
101
|
+
/** Set as `data-testid` on web, where `Svg` does not map `testID`. */
|
|
102
|
+
testID?: string;
|
|
103
|
+
style?: ViewProps["style"];
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The brand mark of an AI provider, as the web `ProviderIcon`. The marks keep
|
|
108
|
+
* their brand colours; the OpenAI and Cursor marks switch with the scheme.
|
|
109
|
+
*/
|
|
110
|
+
export function ProviderIcon({ provider, size = 14, title, testID, style }: ProviderIconProps) {
|
|
111
|
+
const scheme = useScheme();
|
|
112
|
+
const { viewBox, layers } = providerMark(provider);
|
|
113
|
+
const paint = (fill: SchemeFill) => (typeof fill === "string" ? fill : fill[scheme]);
|
|
114
|
+
return (
|
|
115
|
+
<Svg
|
|
116
|
+
width={size}
|
|
117
|
+
height={size}
|
|
118
|
+
viewBox={viewBox}
|
|
119
|
+
{...svgA11yProps({ title, testID }, Platform.OS)}
|
|
120
|
+
style={style}
|
|
121
|
+
>
|
|
122
|
+
{layers.map((layer, index) =>
|
|
123
|
+
"square" in layer ? (
|
|
124
|
+
<Rect key={index} width={layer.square} height={layer.square} fill={paint(layer.fill)} />
|
|
125
|
+
) : (
|
|
126
|
+
<Path
|
|
127
|
+
key={index}
|
|
128
|
+
d={layer.d}
|
|
129
|
+
fill={paint(layer.fill)}
|
|
130
|
+
{...(layer.evenOdd ? { fillRule: "evenodd", clipRule: "evenodd" } : {})}
|
|
131
|
+
/>
|
|
132
|
+
),
|
|
133
|
+
)}
|
|
134
|
+
</Svg>
|
|
135
|
+
);
|
|
136
|
+
}
|
package/src/brand/index.ts
CHANGED
|
@@ -9,6 +9,8 @@ export { AppWordmark } from "./AppWordmark";
|
|
|
9
9
|
export type { AppWordmarkProps, ProductAppName } from "./AppWordmark";
|
|
10
10
|
export { GoogleMark } from "./GoogleMark";
|
|
11
11
|
export type { GoogleMarkProps } from "./GoogleMark";
|
|
12
|
+
export { PROVIDER_MARKS, ProviderIcon, providerLabel, providerMark } from "./ProviderIcon";
|
|
13
|
+
export type { ProviderIconProps, ProviderIconProvider, ProviderMark } from "./ProviderIcon";
|
|
12
14
|
export {
|
|
13
15
|
APP_MARKS,
|
|
14
16
|
APP_MARK_CROP_VIEWBOX,
|
package/src/configureTheme.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { palette, type Palette } from "./palette";
|
|
2
2
|
import { tokens, type Tokens } from "./tokens";
|
|
3
|
+
import { fontFamily, type ThemeFonts } from "./typography";
|
|
3
4
|
|
|
4
5
|
/** `tokens` and `palette` value types with the literal hex widened to `string` and `readonly` dropped. */
|
|
5
6
|
type Widen<T> = T extends string ? string : { -readonly [K in keyof T]: Widen<T[K]> };
|
|
@@ -15,6 +16,8 @@ export type ThemeConfig = {
|
|
|
15
16
|
color?: Partial<ThemeColors>;
|
|
16
17
|
/** Overrides for `palette` (primary, danger, overlay and the other web semantic colours). */
|
|
17
18
|
palette?: Partial<ThemePalette>;
|
|
19
|
+
/** Overrides for `fontFamily`: the family name for each weight, as the app loads the fonts. */
|
|
20
|
+
font?: Partial<ThemeFonts>;
|
|
18
21
|
};
|
|
19
22
|
|
|
20
23
|
type ColorValue = string | { light: string; dark: string };
|
|
@@ -24,7 +27,7 @@ type ColorValue = string | { light: string; dark: string };
|
|
|
24
27
|
* the existing pair object, so values that hold the pair (`floatingFill`,
|
|
25
28
|
* `floatingHairline`, `floatingSolidFill`) follow it.
|
|
26
29
|
*/
|
|
27
|
-
function
|
|
30
|
+
function assignValues(
|
|
28
31
|
target: { readonly [key: string]: ColorValue },
|
|
29
32
|
source: { readonly [key: string]: ColorValue | undefined },
|
|
30
33
|
) {
|
|
@@ -36,25 +39,50 @@ function assignColors(
|
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
/** Bumped by each `configureTheme` call; `useScheme` redraws on it. */
|
|
43
|
+
let themeVersion = 0;
|
|
44
|
+
const themeListeners = new Set<() => void>();
|
|
45
|
+
|
|
46
|
+
/** Calls `listener` after each `configureTheme`. Returns the unsubscribe. */
|
|
47
|
+
export function subscribeTheme(listener: () => void): () => void {
|
|
48
|
+
themeListeners.add(listener);
|
|
49
|
+
return () => {
|
|
50
|
+
themeListeners.delete(listener);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The number of `configureTheme` calls so far, a snapshot for `useSyncExternalStore`. */
|
|
55
|
+
export function getThemeVersion(): number {
|
|
56
|
+
return themeVersion;
|
|
57
|
+
}
|
|
58
|
+
|
|
39
59
|
/**
|
|
40
|
-
* Sets the app's own colours. It writes into the shared
|
|
41
|
-
* `palette` objects, which every component
|
|
42
|
-
* app start, before the first render (for
|
|
43
|
-
* `_layout.tsx`, outside the component). A
|
|
44
|
-
*
|
|
60
|
+
* Sets the app's own colours and fonts. It writes into the shared
|
|
61
|
+
* `tokens.color`, `palette` and `fontFamily` objects, which every component
|
|
62
|
+
* reads at render. Call it once at app start, before the first render (for
|
|
63
|
+
* example at the top of the root `_layout.tsx`, outside the component). A
|
|
64
|
+
* later call (for example a second dark palette the user picks) redraws the
|
|
65
|
+
* mounted components under `NativeUiProvider` that read the scheme (`useScheme`); app code that copies
|
|
66
|
+
* values out, or memoises on the scheme alone, must redraw on its own. Each
|
|
67
|
+
* pair gives both schemes:
|
|
45
68
|
*
|
|
46
69
|
* ```ts
|
|
47
70
|
* configureTheme({
|
|
48
71
|
* color: { background: { light: "#f4f5f6", dark: "#020202" } },
|
|
49
72
|
* palette: { primary: { light: "#09090b", dark: "#fafafa" } },
|
|
73
|
+
* font: { regular: "Inter_400Regular", semibold: "Inter_600SemiBold" },
|
|
50
74
|
* });
|
|
51
75
|
* ```
|
|
52
76
|
*
|
|
53
|
-
* Only the JS
|
|
54
|
-
* (`tailwind-preset` reads the package values from source
|
|
55
|
-
*
|
|
77
|
+
* Only the JS values change. NativeWind classes keep the app's Tailwind theme
|
|
78
|
+
* (`tailwind-preset` reads the package values from source; pass the same
|
|
79
|
+
* family names to `nativeUiPreset({ font })`), and constants copied out before
|
|
80
|
+
* the call (`NEUTRAL_500`) keep the package value. The app loads the font files.
|
|
56
81
|
*/
|
|
57
82
|
export function configureTheme(config: ThemeConfig): void {
|
|
58
|
-
if (config.color)
|
|
59
|
-
if (config.palette)
|
|
83
|
+
if (config.color) assignValues(tokens.color, config.color);
|
|
84
|
+
if (config.palette) assignValues(palette, config.palette);
|
|
85
|
+
if (config.font) assignValues(fontFamily, config.font);
|
|
86
|
+
themeVersion += 1;
|
|
87
|
+
for (const listener of themeListeners) listener();
|
|
60
88
|
}
|