@vvedantb/native-ui 0.8.0 → 0.10.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.
@@ -0,0 +1,202 @@
1
+ import { useState } from "react";
2
+ import { useSignIn } from "@clerk/clerk-expo";
3
+ import { authErrorMessage } from "./authErrors";
4
+
5
+ /** The messages the second-factor step can show. Generic: none names a provider. */
6
+ export const TOTP_MESSAGES = {
7
+ invalidCode: "Incorrect code. Check your authenticator app and try again.",
8
+ invalidCodeGeneric: "Incorrect code. Check it and try again.",
9
+ sendFailed: "Could not send the code. Try again.",
10
+ needsAnotherStep: "This account needs another step to sign in. Finish it on the web.",
11
+ notReady: "Sign in is not ready yet. Try again in a moment.",
12
+ } as const;
13
+
14
+ /** The second-factor strategies the built-in step handles. */
15
+ export type SecondFactorStrategy = "totp" | "backup_code" | "phone_code" | "email_code";
16
+
17
+ /** Default order: the first one the account supports wins. */
18
+ export const SECOND_FACTOR_PREFERENCE: readonly SecondFactorStrategy[] = [
19
+ "totp",
20
+ "email_code",
21
+ "phone_code",
22
+ "backup_code",
23
+ ];
24
+
25
+ /** One entry of `signIn.supportedSecondFactors`. */
26
+ export type SecondFactor = {
27
+ strategy: string;
28
+ emailAddressId?: string;
29
+ phoneNumberId?: string;
30
+ safeIdentifier?: string;
31
+ };
32
+
33
+ type AttemptResult = { status: string | null; createdSessionId: string | null };
34
+
35
+ /** The part of the sign-in resource that TOTP needs. Tests pass a mock. */
36
+ export type TotpSignIn = {
37
+ attemptSecondFactor(params: { strategy: "totp"; code: string }): Promise<AttemptResult>;
38
+ };
39
+
40
+ /** The part of the sign-in resource that any second factor needs. Tests pass a mock. */
41
+ export type SecondFactorSignIn = {
42
+ supportedSecondFactors?: SecondFactor[] | null;
43
+ prepareSecondFactor?(params: {
44
+ strategy: "phone_code" | "email_code";
45
+ phoneNumberId?: string;
46
+ emailAddressId?: string;
47
+ }): Promise<unknown>;
48
+ attemptSecondFactor(params: { strategy: SecondFactorStrategy; code: string }): Promise<AttemptResult>;
49
+ };
50
+
51
+ export type TotpSetActive = (params: { session: string }) => Promise<void>;
52
+
53
+ export type TotpResult = { ok: true } | { ok: false; error: string };
54
+
55
+ /** The strategies in `factors` that the built-in step handles, in the order given. */
56
+ export function supportedSecondFactorStrategies(
57
+ factors: readonly SecondFactor[] | null | undefined,
58
+ ): SecondFactorStrategy[] {
59
+ const found = new Set((factors ?? []).map((factor) => factor.strategy));
60
+ return SECOND_FACTOR_PREFERENCE.filter((strategy) => found.has(strategy));
61
+ }
62
+
63
+ /** The default strategy: `totp`, then `email_code`, `phone_code`, `backup_code`. `null` when none is supported. */
64
+ export function pickSecondFactorStrategy(
65
+ factors: readonly SecondFactor[] | null | undefined,
66
+ ): SecondFactorStrategy | null {
67
+ return supportedSecondFactorStrategies(factors)[0] ?? null;
68
+ }
69
+
70
+ /**
71
+ * Sends the code for `phone_code` and `email_code`, with the `phoneNumberId` or
72
+ * `emailAddressId` of the matching factor. `totp` and `backup_code` need no
73
+ * prepare: it returns `{ ok: true }` without a call. Never throws.
74
+ */
75
+ export async function prepareSecondFactorIfNeeded(args: {
76
+ signIn: Pick<SecondFactorSignIn, "supportedSecondFactors" | "prepareSecondFactor">;
77
+ strategy: SecondFactorStrategy;
78
+ factor?: SecondFactor;
79
+ }): Promise<TotpResult> {
80
+ const { signIn, strategy } = args;
81
+ if (strategy !== "phone_code" && strategy !== "email_code") return { ok: true };
82
+ if (!signIn.prepareSecondFactor) return { ok: false, error: TOTP_MESSAGES.sendFailed };
83
+ const factor = args.factor ?? signIn.supportedSecondFactors?.find((item) => item.strategy === strategy);
84
+ try {
85
+ if (strategy === "phone_code") {
86
+ await signIn.prepareSecondFactor({
87
+ strategy,
88
+ ...(factor?.phoneNumberId ? { phoneNumberId: factor.phoneNumberId } : {}),
89
+ });
90
+ } else {
91
+ await signIn.prepareSecondFactor({
92
+ strategy,
93
+ ...(factor?.emailAddressId ? { emailAddressId: factor.emailAddressId } : {}),
94
+ });
95
+ }
96
+ return { ok: true };
97
+ } catch (caught) {
98
+ return { ok: false, error: authErrorMessage(caught, TOTP_MESSAGES.sendFailed) };
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Checks a second-factor code and starts the session. Call
104
+ * `prepareSecondFactorIfNeeded` first for `phone_code` and `email_code`.
105
+ * Never throws: a failure is `{ ok: false, error }`. An API error shows its
106
+ * own text (such as "Incorrect code") unless it names the provider; anything
107
+ * else gives `TOTP_MESSAGES.invalidCode` (`totp`) or `invalidCodeGeneric`.
108
+ */
109
+ export async function submitSecondFactor(args: {
110
+ signIn: Pick<SecondFactorSignIn, "attemptSecondFactor">;
111
+ setActive: TotpSetActive;
112
+ code: string;
113
+ strategy: SecondFactorStrategy;
114
+ }): Promise<TotpResult> {
115
+ try {
116
+ const attempt = await args.signIn.attemptSecondFactor({ strategy: args.strategy, code: args.code });
117
+ if (attempt.status === "complete" && attempt.createdSessionId) {
118
+ await args.setActive({ session: attempt.createdSessionId });
119
+ return { ok: true };
120
+ }
121
+ return { ok: false, error: TOTP_MESSAGES.needsAnotherStep };
122
+ } catch (caught) {
123
+ const fallback = args.strategy === "totp" ? TOTP_MESSAGES.invalidCode : TOTP_MESSAGES.invalidCodeGeneric;
124
+ return { ok: false, error: authErrorMessage(caught, fallback) };
125
+ }
126
+ }
127
+
128
+ /** Checks a TOTP code and starts the session. TOTP needs no `prepareSecondFactor`. Same as `submitSecondFactor` with `strategy: "totp"`. */
129
+ export function submitTotpSecondFactor(args: {
130
+ signIn: TotpSignIn;
131
+ setActive: TotpSetActive;
132
+ code: string;
133
+ }): Promise<TotpResult> {
134
+ return submitSecondFactor({ signIn: args.signIn, setActive: args.setActive, code: args.code, strategy: "totp" });
135
+ }
136
+
137
+ export type UseSecondFactorOptions = {
138
+ /** The strategy `submit` uses until `setStrategy` changes it. Default `"totp"`. */
139
+ strategy?: SecondFactorStrategy;
140
+ };
141
+
142
+ export type UseSecondFactorResult = {
143
+ /** Checks the code and starts the session. Never throws. Sets `error` on failure. */
144
+ submit: (code: string) => Promise<TotpResult>;
145
+ /** Sends the code for `phone_code` and `email_code` (no call for the others). Never throws. Sets `error` on failure. */
146
+ prepare: (strategy?: SecondFactorStrategy, factor?: SecondFactor) => Promise<TotpResult>;
147
+ /** The strategy `submit` uses. */
148
+ strategy: SecondFactorStrategy;
149
+ setStrategy: (strategy: SecondFactorStrategy) => void;
150
+ /** True while `submit` or `prepare` runs. */
151
+ loading: boolean;
152
+ /** The last failure message, or `""`. */
153
+ error: string;
154
+ clearError: () => void;
155
+ };
156
+
157
+ /**
158
+ * Completes a sign-in that returned `needs_second_factor`. Default strategy
159
+ * `"totp"` (an authenticator-app code); pass `{ strategy }` or call
160
+ * `setStrategy` for the others. Pair with `SecondFactorScreen`:
161
+ * `onSubmit={submit}`, `error={error}`, `loading={loading}`. After
162
+ * `{ ok: true }` the session is active: navigate in the caller.
163
+ */
164
+ export function useSecondFactor(options: UseSecondFactorOptions = {}): UseSecondFactorResult {
165
+ const { isLoaded, signIn, setActive } = useSignIn();
166
+ const [chosen, setChosen] = useState<SecondFactorStrategy | null>(null);
167
+ const strategy = chosen ?? options.strategy ?? "totp";
168
+ const [loading, setLoading] = useState(false);
169
+ const [error, setError] = useState("");
170
+
171
+ const submit = async (code: string): Promise<TotpResult> => {
172
+ if (loading) return { ok: false, error };
173
+ if (!isLoaded || !signIn || !setActive) {
174
+ setError(TOTP_MESSAGES.notReady);
175
+ return { ok: false, error: TOTP_MESSAGES.notReady };
176
+ }
177
+ setLoading(true);
178
+ setError("");
179
+ const result = await submitSecondFactor({ signIn, setActive, code, strategy });
180
+ if (!result.ok) setError(result.error);
181
+ setLoading(false);
182
+ return result;
183
+ };
184
+
185
+ const prepare = async (
186
+ prepareStrategy: SecondFactorStrategy = strategy,
187
+ factor?: SecondFactor,
188
+ ): Promise<TotpResult> => {
189
+ if (!isLoaded || !signIn) {
190
+ setError(TOTP_MESSAGES.notReady);
191
+ return { ok: false, error: TOTP_MESSAGES.notReady };
192
+ }
193
+ setLoading(true);
194
+ setError("");
195
+ const result = await prepareSecondFactorIfNeeded({ signIn, strategy: prepareStrategy, factor });
196
+ if (!result.ok) setError(result.error);
197
+ setLoading(false);
198
+ return result;
199
+ };
200
+
201
+ return { submit, prepare, strategy, setStrategy: setChosen, loading, error, clearError: () => setError("") };
202
+ }
@@ -0,0 +1,60 @@
1
+ import { palette, type Palette } from "./palette";
2
+ import { tokens, type Tokens } from "./tokens";
3
+
4
+ /** `tokens` and `palette` value types with the literal hex widened to `string` and `readonly` dropped. */
5
+ type Widen<T> = T extends string ? string : { -readonly [K in keyof T]: Widen<T[K]> };
6
+
7
+ /** The `tokens.color` shape: each key a `{ light, dark }` pair, except `white` and `black`. */
8
+ export type ThemeColors = Widen<Tokens["color"]>;
9
+
10
+ /** The `palette` shape: each key a `{ light, dark }` pair, except `destructive`. */
11
+ export type ThemePalette = Widen<Palette>;
12
+
13
+ export type ThemeConfig = {
14
+ /** Overrides for `tokens.color` (neutrals and surfaces). Keys not given keep the package value. */
15
+ color?: Partial<ThemeColors>;
16
+ /** Overrides for `palette` (primary, danger, overlay and the other web semantic colours). */
17
+ palette?: Partial<ThemePalette>;
18
+ };
19
+
20
+ type ColorValue = string | { light: string; dark: string };
21
+
22
+ /**
23
+ * Copies each given key onto `target`. A `{ light, dark }` pair is copied into
24
+ * the existing pair object, so values that hold the pair (`floatingFill`,
25
+ * `floatingHairline`, `floatingSolidFill`) follow it.
26
+ */
27
+ function assignColors(
28
+ target: { readonly [key: string]: ColorValue },
29
+ source: { readonly [key: string]: ColorValue | undefined },
30
+ ) {
31
+ for (const [key, value] of Object.entries(source)) {
32
+ if (value === undefined) continue;
33
+ const current = target[key];
34
+ if (typeof current === "object" && typeof value === "object") Object.assign(current, value);
35
+ else Object.assign(target, { [key]: value });
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Sets the app's own colours. It writes into the shared `tokens.color` and
41
+ * `palette` objects, which every component reads at render, so call it once at
42
+ * app start, before the first render (for example at the top of the root
43
+ * `_layout.tsx`, outside the component). A call after the first render does not
44
+ * redraw mounted components. Each pair gives both schemes:
45
+ *
46
+ * ```ts
47
+ * configureTheme({
48
+ * color: { background: { light: "#f4f5f6", dark: "#020202" } },
49
+ * palette: { primary: { light: "#09090b", dark: "#fafafa" } },
50
+ * });
51
+ * ```
52
+ *
53
+ * Only the JS colours change. NativeWind classes keep the app's Tailwind theme
54
+ * (`tailwind-preset` reads the package values from source), and constants copied
55
+ * out before the call (`NEUTRAL_500`) keep the package value.
56
+ */
57
+ export function configureTheme(config: ThemeConfig): void {
58
+ if (config.color) assignColors(tokens.color, config.color);
59
+ if (config.palette) assignColors(palette, config.palette);
60
+ }
@@ -0,0 +1,5 @@
1
+ // `@vvedantb/native-ui/markdown`: `Markdown`, chat markdown styled as the web
2
+ // `.markdown` block. Not on the root entry: it imports the optional peer
3
+ // `react-native-marked`, and Metro does not tree-shake.
4
+ export { Markdown, markdownStyles, inlineCodeStyle, MARKDOWN_SCALE, MARKDOWN_LIST } from "../markdown/Markdown";
5
+ export type { MarkdownProps } from "../markdown/Markdown";
@@ -59,6 +59,8 @@ export type {
59
59
  TabsContentProps,
60
60
  } from "../Tabs";
61
61
  export { ListRow } from "../ListRow";
62
+ export { DividedList } from "../DividedList";
63
+ export type { DividedListProps } from "../DividedList";
62
64
  export type { ListRowProps } from "../ListRow";
63
65
  export { HeaderIconButton } from "../HeaderIconButton";
64
66
  export { ThemeToggle } from "../ThemeToggle";
@@ -100,6 +102,26 @@ export {
100
102
  } from "../AlertDialog";
101
103
  export type { AlertDialogProps, AlertDialogContentProps } from "../AlertDialog";
102
104
  export { ConfirmDialog } from "../ConfirmDialog";
105
+ export { BulkActionBar, BULK_BAR_LABEL_MIN_WIDTH } from "../BulkActionBar";
106
+ export type { BulkActionBarProps, BulkBarAction } from "../BulkActionBar";
107
+ export {
108
+ DropdownMenu,
109
+ DropdownMenuTrigger,
110
+ DropdownMenuContent,
111
+ DropdownMenuLabel,
112
+ DropdownMenuItem,
113
+ DropdownMenuRadioItem,
114
+ DropdownMenuSeparator,
115
+ } from "../DropdownMenu";
116
+ export type {
117
+ DropdownMenuProps,
118
+ DropdownMenuTriggerProps,
119
+ DropdownMenuContentProps,
120
+ DropdownMenuAlign,
121
+ DropdownMenuLabelProps,
122
+ DropdownMenuItemProps,
123
+ DropdownMenuRadioItemProps,
124
+ } from "../DropdownMenu";
103
125
  export type { ConfirmDialogProps } from "../ConfirmDialog";
104
126
  export {
105
127
  Sheet,
@@ -11,5 +11,7 @@ export {
11
11
  export type { NativeUiTheme } from "../theme";
12
12
  export { palette, withAlpha } from "../palette";
13
13
  export type { Palette } from "../palette";
14
+ export { configureTheme } from "../configureTheme";
15
+ export type { ThemeConfig, ThemeColors, ThemePalette } from "../configureTheme";
14
16
  export { fontClass } from "../typography";
15
17
  export { PINK_50, PINK_100, PINK_600, PINK_700, NEUTRAL_100, NEUTRAL_500, NEUTRAL_900, Colors } from "../colors";
@@ -7,7 +7,9 @@ import { useState, type ReactNode } from "react";
7
7
  import { ScrollView, Text, View, type TextProps } from "react-native";
8
8
  import {
9
9
  IconCopy,
10
+ IconDots,
10
11
  IconInbox,
12
+ IconPencil,
11
13
  IconPhoto,
12
14
  IconRefresh,
13
15
  IconTrash,
@@ -28,6 +30,14 @@ import {
28
30
  AmbientBackground,
29
31
  AppMark,
30
32
  AppWordmark,
33
+ BulkActionBar,
34
+ DropdownMenu,
35
+ DropdownMenuContent,
36
+ DropdownMenuItem,
37
+ DropdownMenuLabel,
38
+ DropdownMenuRadioItem,
39
+ DropdownMenuSeparator,
40
+ DropdownMenuTrigger,
31
41
  Avatar,
32
42
  AvatarFallback,
33
43
  Badge,
@@ -226,6 +236,8 @@ function GalleryScreen() {
226
236
  const [muted, setMuted] = useState(false);
227
237
  const [sort, setSort] = useState<"newest" | "name">("newest");
228
238
  const [page, setPage] = useState(0);
239
+ const [menuSort, setMenuSort] = useState<"newest" | "oldest">("newest");
240
+ const [picked, setPicked] = useState(2);
229
241
  const scheme = useScheme();
230
242
 
231
243
  return (
@@ -563,6 +575,50 @@ function GalleryScreen() {
563
575
  </View>
564
576
  </Section>
565
577
 
578
+ <Section title="DropdownMenu and BulkActionBar">
579
+ <View className="flex-row justify-end">
580
+ <DropdownMenu>
581
+ <DropdownMenuTrigger
582
+ accessibilityLabel="List options"
583
+ hitSlop={6}
584
+ className="h-8 w-8 items-center justify-center"
585
+ >
586
+ <IconDots size={18} color={tokens.color.muted[scheme]} />
587
+ </DropdownMenuTrigger>
588
+ <DropdownMenuContent align="end">
589
+ <DropdownMenuLabel>Sort</DropdownMenuLabel>
590
+ <DropdownMenuRadioItem checked={menuSort === "newest"} onSelect={() => setMenuSort("newest")}>
591
+ Newest first
592
+ </DropdownMenuRadioItem>
593
+ <DropdownMenuRadioItem checked={menuSort === "oldest"} onSelect={() => setMenuSort("oldest")}>
594
+ Oldest first
595
+ </DropdownMenuRadioItem>
596
+ <DropdownMenuSeparator />
597
+ <DropdownMenuItem icon={IconPencil} onSelect={() => toast("Rename")}>
598
+ Rename
599
+ </DropdownMenuItem>
600
+ <DropdownMenuItem icon={IconTrash} destructive onSelect={() => toast.error("Deleted")}>
601
+ Delete
602
+ </DropdownMenuItem>
603
+ </DropdownMenuContent>
604
+ </DropdownMenu>
605
+ </View>
606
+ <View style={{ height: 88 }}>
607
+ <BulkActionBar
608
+ isSelecting
609
+ selectedCount={picked}
610
+ totalCount={5}
611
+ bottomOffset={0}
612
+ onExitSelect={() => setPicked(0)}
613
+ onSelectAll={() => setPicked(5)}
614
+ onClearSelection={() => setPicked(0)}
615
+ primaryActions={[{ key: "copy", label: "Copy items", shortLabel: "Copy", icon: IconCopy, onClick: () => toast("Copied") }]}
616
+ moreActions={[{ key: "rename", label: "Rename", icon: IconPencil, onClick: () => toast("Rename") }]}
617
+ destructiveAction={{ key: "delete", label: "Delete items", shortLabel: "Delete", icon: IconTrash, destructive: true, onClick: () => toast.error("Deleted") }}
618
+ />
619
+ </View>
620
+ </Section>
621
+
566
622
  <Section title="EmptyState">
567
623
  <EmptyState title="Nothing yet" description="Items you add show here." compact />
568
624
  <EmptyState.Root variant="panel">
package/src/floating.ts CHANGED
@@ -45,10 +45,8 @@ export const floatingHairline = tokens.color.hairline;
45
45
 
46
46
  export const floatingFill = tokens.color.fill;
47
47
 
48
- export const floatingSolidFill = {
49
- light: tokens.color.surface.light,
50
- dark: tokens.color.surface.dark,
51
- } as const;
48
+ /** The `tokens.color.surface` pair itself, so `configureTheme` reaches it. */
49
+ export const floatingSolidFill = tokens.color.surface;
52
50
 
53
51
  export function tabBarReserve(largerText: boolean, bottomInset: number) {
54
52
  const bar = largerText
@@ -0,0 +1,117 @@
1
+ import type * as PrismReactRenderer from "prism-react-renderer";
2
+
3
+ export type PrismReactRendererModule = typeof PrismReactRenderer;
4
+
5
+ let prismReactRenderer: PrismReactRendererModule | null | undefined;
6
+
7
+ /**
8
+ * `prism-react-renderer`, or `null` when the optional peer is not installed.
9
+ * `CodeBlock` highlights with it. Metro bundles a `require` placed directly in
10
+ * a `try` block as an optional dependency, so a missing peer does not break
11
+ * the bundle.
12
+ */
13
+ export function loadPrismReactRenderer(): PrismReactRendererModule | null {
14
+ if (prismReactRenderer === undefined) {
15
+ try {
16
+ prismReactRenderer = require("prism-react-renderer");
17
+ } catch {
18
+ prismReactRenderer = null;
19
+ }
20
+ }
21
+ // `?? null`: with the real peer types installed, TypeScript keeps `undefined`
22
+ // in the type after the `try` (the package typecheck only sees a stub).
23
+ return prismReactRenderer ?? null;
24
+ }
25
+
26
+ export type CodeColorRole =
27
+ | "plain"
28
+ | "comment"
29
+ | "keyword"
30
+ | "string"
31
+ | "constant"
32
+ | "function"
33
+ | "tag"
34
+ | "variable"
35
+ | "inserted"
36
+ | "deleted";
37
+
38
+ /**
39
+ * The shiki `github-light` and `github-dark` colours the web `CodeBlock`
40
+ * uses, by role. Plain text is the theme foreground.
41
+ */
42
+ export const GITHUB_CODE_COLORS: Record<"light" | "dark", Record<CodeColorRole, string>> = {
43
+ light: {
44
+ plain: "#24292e",
45
+ comment: "#6a737d",
46
+ keyword: "#d73a49",
47
+ string: "#032f62",
48
+ constant: "#005cc5",
49
+ function: "#6f42c1",
50
+ tag: "#22863a",
51
+ variable: "#e36209",
52
+ inserted: "#22863a",
53
+ deleted: "#b31d28",
54
+ },
55
+ dark: {
56
+ plain: "#e1e4e8",
57
+ comment: "#6a737d",
58
+ keyword: "#f97583",
59
+ string: "#9ecbff",
60
+ constant: "#79b8ff",
61
+ function: "#b392f0",
62
+ tag: "#85e89d",
63
+ variable: "#ffab70",
64
+ inserted: "#85e89d",
65
+ deleted: "#fdaeb7",
66
+ },
67
+ };
68
+
69
+ /** Prism token types to their GitHub role. Types not listed are plain. */
70
+ const PRISM_ROLE: Record<string, CodeColorRole> = {
71
+ comment: "comment",
72
+ prolog: "comment",
73
+ doctype: "comment",
74
+ cdata: "comment",
75
+ keyword: "keyword",
76
+ operator: "keyword",
77
+ atrule: "keyword",
78
+ important: "keyword",
79
+ rule: "keyword",
80
+ string: "string",
81
+ char: "string",
82
+ "attr-value": "string",
83
+ regex: "string",
84
+ url: "string",
85
+ "template-string": "string",
86
+ number: "constant",
87
+ boolean: "constant",
88
+ constant: "constant",
89
+ property: "constant",
90
+ "attr-name": "constant",
91
+ symbol: "constant",
92
+ builtin: "constant",
93
+ entity: "constant",
94
+ function: "function",
95
+ "function-variable": "function",
96
+ "class-name": "function",
97
+ maybe_class_name: "function",
98
+ tag: "tag",
99
+ selector: "tag",
100
+ variable: "variable",
101
+ parameter: "variable",
102
+ inserted: "inserted",
103
+ deleted: "deleted",
104
+ punctuation: "plain",
105
+ plain: "plain",
106
+ };
107
+
108
+ /**
109
+ * The colour of a Prism token from its types. Later types win, as in
110
+ * `prism-react-renderer`'s theme lookup: the `<` of a tag (`["tag",
111
+ * "punctuation"]`) is plain.
112
+ */
113
+ export function codeTokenColor(types: readonly string[], scheme: "light" | "dark"): string {
114
+ let role: CodeColorRole = "plain";
115
+ for (const type of types) role = PRISM_ROLE[type] ?? role;
116
+ return GITHUB_CODE_COLORS[scheme][role];
117
+ }
package/src/index.ts CHANGED
@@ -10,6 +10,8 @@ export {
10
10
  export type { NativeUiTheme } from "./theme";
11
11
  export { palette, withAlpha } from "./palette";
12
12
  export type { Palette } from "./palette";
13
+ export { configureTheme } from "./configureTheme";
14
+ export type { ThemeConfig, ThemeColors, ThemePalette } from "./configureTheme";
13
15
  export { fontClass } from "./typography";
14
16
  export {
15
17
  FLOATING_INSET,
@@ -93,6 +95,8 @@ export type {
93
95
  TabsContentProps,
94
96
  } from "./Tabs";
95
97
  export { ListRow } from "./ListRow";
98
+ export { DividedList } from "./DividedList";
99
+ export type { DividedListProps } from "./DividedList";
96
100
  export type { ListRowProps } from "./ListRow";
97
101
  export { HeaderIconButton } from "./HeaderIconButton";
98
102
  export { ThemeToggle } from "./ThemeToggle";
@@ -134,6 +138,26 @@ export {
134
138
  } from "./AlertDialog";
135
139
  export type { AlertDialogProps, AlertDialogContentProps } from "./AlertDialog";
136
140
  export { ConfirmDialog } from "./ConfirmDialog";
141
+ export { BulkActionBar, BULK_BAR_LABEL_MIN_WIDTH } from "./BulkActionBar";
142
+ export type { BulkActionBarProps, BulkBarAction } from "./BulkActionBar";
143
+ export {
144
+ DropdownMenu,
145
+ DropdownMenuTrigger,
146
+ DropdownMenuContent,
147
+ DropdownMenuLabel,
148
+ DropdownMenuItem,
149
+ DropdownMenuRadioItem,
150
+ DropdownMenuSeparator,
151
+ } from "./DropdownMenu";
152
+ export type {
153
+ DropdownMenuProps,
154
+ DropdownMenuTriggerProps,
155
+ DropdownMenuContentProps,
156
+ DropdownMenuAlign,
157
+ DropdownMenuLabelProps,
158
+ DropdownMenuItemProps,
159
+ DropdownMenuRadioItemProps,
160
+ } from "./DropdownMenu";
137
161
  export type { ConfirmDialogProps } from "./ConfirmDialog";
138
162
  export {
139
163
  Sheet,