@vvedantb/native-ui 0.2.1 → 0.3.13
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 +171 -9
- package/package.json +84 -2
- package/src/AlertDialog.tsx +73 -0
- package/src/AmbientBackground.tsx +26 -11
- package/src/Avatar.tsx +141 -0
- package/src/Badge.tsx +108 -0
- package/src/BottomSheetContainer.tsx +7 -1
- package/src/Button.tsx +153 -52
- package/src/Card.tsx +111 -36
- package/src/Checkbox.tsx +78 -0
- package/src/Chip.tsx +5 -3
- package/src/ConfirmDialog.tsx +105 -0
- package/src/DateTimeField.tsx +6 -2
- package/src/Dialog.tsx +253 -0
- package/src/EmptyState.tsx +122 -7
- package/src/FloatingFAB.tsx +2 -4
- package/src/FloatingSurface.tsx +31 -9
- package/src/FloatingTabBar.tsx +39 -11
- package/src/Input.tsx +15 -4
- package/src/Label.tsx +31 -0
- package/src/LabeledSwitchRow.tsx +96 -0
- package/src/LiquidGlass.tsx +2 -10
- package/src/ListRow.tsx +7 -2
- package/src/PageTitle.tsx +81 -0
- package/src/PillTabs.tsx +28 -14
- package/src/Progress.tsx +67 -0
- package/src/SearchInput.tsx +163 -0
- package/src/Select.tsx +32 -6
- package/src/Separator.tsx +38 -0
- package/src/Sheet.tsx +107 -0
- package/src/Skeleton.tsx +25 -5
- package/src/Spinner.tsx +92 -0
- package/src/StatusDot.tsx +66 -0
- package/src/Switch.tsx +24 -7
- package/src/Tabs.tsx +190 -0
- package/src/Textarea.tsx +47 -0
- package/src/Toast.tsx +170 -0
- package/src/blur.ts +48 -0
- package/src/brand/AppIcons.tsx +106 -0
- package/src/brand/AppMark.tsx +83 -0
- package/src/brand/AppWordmark.tsx +97 -0
- package/src/brand/familyApps.ts +24 -0
- package/src/brand/index.ts +34 -0
- package/src/brand/svg.ts +263 -0
- package/src/brand/vProAppBenefits.ts +22 -0
- package/src/controlStyles.ts +395 -0
- package/src/dialogLayout.ts +20 -0
- package/src/entries/ambient.ts +3 -0
- package/src/entries/bottom-sheet.ts +2 -0
- package/src/entries/brand.ts +2 -0
- package/src/entries/button.ts +3 -0
- package/src/entries/date-time.ts +3 -0
- package/src/entries/floating.ts +24 -0
- package/src/entries/layout.ts +10 -0
- package/src/entries/marketing.ts +10 -0
- package/src/entries/page-title.ts +4 -0
- package/src/entries/primitives.ts +99 -0
- package/src/entries/search-input.ts +4 -0
- package/src/entries/tab-bar.ts +10 -0
- package/src/entries/theme.ts +15 -0
- package/src/entries/toast.ts +6 -0
- package/src/examples/ParityGallery.tsx +513 -0
- package/src/floating.ts +5 -1
- package/src/glassBlur.tsx +95 -0
- package/src/gradient.ts +21 -0
- package/src/haptics.ts +22 -2
- package/src/index.ts +101 -2
- package/src/initials.ts +10 -0
- package/src/marketing/PricingModal.tsx +410 -0
- package/src/marketing/UpgradeToProCard.tsx +103 -0
- package/src/marketing/pricing.ts +76 -0
- package/src/palette.ts +43 -0
- package/src/primitives/button.tsx +3 -1
- package/src/primitives/card.tsx +5 -2
- package/src/primitives/input.tsx +9 -2
- package/src/primitives/native-only-animated-view.tsx +10 -3
- package/src/primitives/overlay.tsx +179 -0
- package/src/primitives/select.tsx +25 -12
- package/src/primitives/switch.tsx +12 -13
- package/src/primitives/text.tsx +25 -8
- package/src/primitives/use-controllable-state.ts +24 -0
- package/src/settings/SettingsField.tsx +11 -2
- package/src/settings/SettingsSection.tsx +20 -6
- package/src/settings/SettingsToggleRow.tsx +7 -2
- package/src/tabBarRoutes.ts +52 -0
- package/src/toastChrome.ts +103 -0
- package/src/toastStore.ts +86 -0
- package/src/tokens.ts +19 -14
- package/src/typography.ts +24 -0
- package/src/useReduceMotion.ts +24 -0
- package/src/useScheme.ts +6 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colours for `Button`, `Input`, `Textarea`, the `Select` trigger, `Switch`,
|
|
3
|
+
* `Checkbox`, `Tabs`, `Card` and component text, taken from `palette` and
|
|
4
|
+
* `tokens` instead of NativeWind theme keys (`bg-primary`, `bg-input`,
|
|
5
|
+
* `text-foreground`) that apps do not define, or define without a dark value.
|
|
6
|
+
* Also text weights and sizes as styles, the page title type, the
|
|
7
|
+
* `SearchInput` metrics, the compact window test for dialogs and the
|
|
8
|
+
* touch-target maths. No React Native import, so the tests can load this file.
|
|
9
|
+
*/
|
|
10
|
+
import { palette, withAlpha } from "./palette";
|
|
11
|
+
import { tokens } from "./tokens";
|
|
12
|
+
|
|
13
|
+
type Scheme = "light" | "dark";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `primary` and `default` are the same (`default` is the web name). `outline`
|
|
17
|
+
* is the web bordered neutral button. `tinted` is the pink tint that `outline`
|
|
18
|
+
* drew before 0.3.0.
|
|
19
|
+
*/
|
|
20
|
+
export type ButtonVariant =
|
|
21
|
+
| "primary"
|
|
22
|
+
| "default"
|
|
23
|
+
| "secondary"
|
|
24
|
+
| "outline"
|
|
25
|
+
| "destructive"
|
|
26
|
+
| "ghost"
|
|
27
|
+
| "link"
|
|
28
|
+
| "tinted";
|
|
29
|
+
|
|
30
|
+
export type ButtonColors = {
|
|
31
|
+
backgroundColor: string;
|
|
32
|
+
borderColor: string;
|
|
33
|
+
color: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Fill, border and label colour per variant, from the web classes
|
|
38
|
+
* (`bg-primary text-primary-foreground`, `bg-default text-default-foreground`
|
|
39
|
+
* and so on). `null` for `tinted`, which keeps its pink NativeWind classes.
|
|
40
|
+
*/
|
|
41
|
+
export function buttonColors(variant: ButtonVariant, scheme: Scheme): ButtonColors | null {
|
|
42
|
+
const none = "transparent";
|
|
43
|
+
switch (variant) {
|
|
44
|
+
case "secondary":
|
|
45
|
+
return {
|
|
46
|
+
backgroundColor: palette.default[scheme],
|
|
47
|
+
borderColor: none,
|
|
48
|
+
color: palette.defaultForeground[scheme],
|
|
49
|
+
};
|
|
50
|
+
case "outline":
|
|
51
|
+
return {
|
|
52
|
+
backgroundColor: none,
|
|
53
|
+
borderColor: tokens.color.border[scheme],
|
|
54
|
+
color: tokens.color.foreground[scheme],
|
|
55
|
+
};
|
|
56
|
+
case "destructive":
|
|
57
|
+
return { backgroundColor: palette.destructive, borderColor: none, color: tokens.color.white };
|
|
58
|
+
case "ghost":
|
|
59
|
+
return { backgroundColor: none, borderColor: none, color: tokens.color.muted[scheme] };
|
|
60
|
+
case "link":
|
|
61
|
+
return { backgroundColor: none, borderColor: none, color: palette.primary[scheme] };
|
|
62
|
+
case "tinted":
|
|
63
|
+
return null;
|
|
64
|
+
default:
|
|
65
|
+
return {
|
|
66
|
+
backgroundColor: palette.primary[scheme],
|
|
67
|
+
borderColor: none,
|
|
68
|
+
color: palette.primaryForeground[scheme],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The web field look (`rounded-field border border-border bg-field-background`),
|
|
75
|
+
* radius 10 as on the web. A flat object: on web the `Select` trigger is a
|
|
76
|
+
* Radix slot, which spreads `style` and so drops arrays.
|
|
77
|
+
*/
|
|
78
|
+
export function fieldStyle(scheme: Scheme) {
|
|
79
|
+
return {
|
|
80
|
+
backgroundColor: tokens.color.surface[scheme],
|
|
81
|
+
borderColor: tokens.color.border[scheme],
|
|
82
|
+
borderRadius: tokens.radius.field,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Text on `fieldStyle`. From the same scheme as the fill, so the two cannot disagree. */
|
|
87
|
+
export function fieldTextStyle(scheme: Scheme) {
|
|
88
|
+
return textStyle("foreground", scheme);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* `foreground` and `muted` are the web `text-foreground` and
|
|
93
|
+
* `text-muted-foreground`; `overlayMuted` is the web `DialogDescription`.
|
|
94
|
+
*/
|
|
95
|
+
export type TextTone = "foreground" | "muted" | "overlayMuted";
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Text colour as a style. Where an app defines the NativeWind `foreground` key
|
|
99
|
+
* with a nested `dark` shade, `text-foreground` keeps the light value in dark
|
|
100
|
+
* mode, so class-coloured text was near-black on dark surfaces.
|
|
101
|
+
*/
|
|
102
|
+
export function textStyle(tone: TextTone, scheme: Scheme) {
|
|
103
|
+
return {
|
|
104
|
+
color: tone === "overlayMuted" ? palette.overlayMuted[scheme] : tokens.color[tone][scheme],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The web `font-medium` and `font-semibold`. */
|
|
109
|
+
export type TypeWeight = "medium" | "semibold";
|
|
110
|
+
|
|
111
|
+
const FONT_WEIGHT = { medium: "500", semibold: "600" } as const;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Font weight as a style, set beside the `font-instrument-sans-*` class with
|
|
115
|
+
* the same weight. Expo web and many apps map every one of those classes to
|
|
116
|
+
* the one `Instrument Sans` family with no weight, so the class alone draws
|
|
117
|
+
* Regular. There is no `bold`: the web titles are semibold, and on Android 700
|
|
118
|
+
* can add a synthetic bold to a face that is already bold.
|
|
119
|
+
*/
|
|
120
|
+
export function typeStyle(weight: TypeWeight) {
|
|
121
|
+
return { fontWeight: FONT_WEIGHT[weight] };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Tailwind `text-*` sizes with their default line heights, in points. */
|
|
125
|
+
const TEXT_SIZE = {
|
|
126
|
+
"text-xs": { fontSize: 12, lineHeight: 16 },
|
|
127
|
+
"text-sm": { fontSize: 14, lineHeight: 20 },
|
|
128
|
+
"text-base": { fontSize: 16, lineHeight: 24 },
|
|
129
|
+
"text-lg": { fontSize: 18, lineHeight: 28 },
|
|
130
|
+
"text-xl": { fontSize: 20, lineHeight: 28 },
|
|
131
|
+
"text-2xl": { fontSize: 24, lineHeight: 32 },
|
|
132
|
+
} as const;
|
|
133
|
+
|
|
134
|
+
export type TextSize = keyof typeof TEXT_SIZE;
|
|
135
|
+
|
|
136
|
+
/** Tailwind `leading-tight` and `leading-snug`, as multiples of the size. */
|
|
137
|
+
const LEADING = { tight: 1.25, snug: 1.375 } as const;
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Font size and line height of a `text-*` class as a style, set beside the
|
|
141
|
+
* class. On Expo web at 390 wide (0.3.6 gallery), `Button` labels with
|
|
142
|
+
* `text-sm` measured 16, so the class alone does not always reach the text.
|
|
143
|
+
* `"tight"` and `"snug"` are the Tailwind `leading-tight` (1.25 × the size)
|
|
144
|
+
* and `leading-snug` (1.375 ×).
|
|
145
|
+
*/
|
|
146
|
+
export function sizeStyle(size: TextSize, leading?: "tight" | "snug") {
|
|
147
|
+
const { fontSize, lineHeight } = TEXT_SIZE[size];
|
|
148
|
+
return { fontSize, lineHeight: leading ? fontSize * LEADING[leading] : lineHeight };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Tailwind `tracking-tight`, in em. */
|
|
152
|
+
const TRACKING = { tight: -0.025 } as const;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Letter spacing of a `tracking-*` class at `fontSize`, as a style in points.
|
|
156
|
+
* React Native has no `em`: `tracking-tight` (-0.025em) at 20 is -0.5.
|
|
157
|
+
*/
|
|
158
|
+
export function trackingStyle(fontSize: number, tracking: keyof typeof TRACKING) {
|
|
159
|
+
return { letterSpacing: fontSize * TRACKING[tracking] };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The web page title, `text-xl font-semibold leading-tight tracking-tight` on
|
|
164
|
+
* the `Breadcrumb` nav (`breadcrumb.tsx`), with the Instrument Sans semibold
|
|
165
|
+
* face for `font-semibold`. `PageTitle` sets `pageTitleStyle()` beside it.
|
|
166
|
+
*/
|
|
167
|
+
export const PAGE_TITLE_CLASS = "font-instrument-sans-semibold text-xl leading-tight tracking-tight";
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* `PAGE_TITLE_CLASS` as a style: Instrument Sans 600 at 20 / 25 with -0.5
|
|
171
|
+
* letter spacing. On Android and Expo web the face class alone can draw
|
|
172
|
+
* Regular (see `typeStyle`).
|
|
173
|
+
*/
|
|
174
|
+
export function pageTitleStyle() {
|
|
175
|
+
const size = sizeStyle("text-xl", "tight");
|
|
176
|
+
return { ...size, ...typeStyle("semibold"), ...trackingStyle(size.fontSize, "tight") };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Height of the `PageHeader` row below the top inset, in points. A minimum,
|
|
181
|
+
* so the title sits at the same height with or without an action, and still
|
|
182
|
+
* grows with the system font size. Native only: the web title has no row.
|
|
183
|
+
*/
|
|
184
|
+
export const PAGE_HEADER_HEIGHT = 56;
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The web `COMPACT_OVERLAY_QUERY`, `(max-width: 639px), (max-height: 500px)`:
|
|
188
|
+
* phones, and short landscape windows such as 844 × 390.
|
|
189
|
+
*/
|
|
190
|
+
export const COMPACT_OVERLAY = { maxWidth: 639, maxHeight: 500 } as const;
|
|
191
|
+
|
|
192
|
+
/** Whether a window of `width` × `height` points is compact: `DialogContent` is a bottom sheet there. */
|
|
193
|
+
export function isCompactOverlay(width: number, height: number) {
|
|
194
|
+
return width <= COMPACT_OVERLAY.maxWidth || height <= COMPACT_OVERLAY.maxHeight;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* The web `Card` root (`rounded-lg bg-surface-card`): a flat fill at radius 16,
|
|
199
|
+
* with no border and no shadow. Also `SettingsSection`. A flat object, like
|
|
200
|
+
* `fieldStyle`.
|
|
201
|
+
*/
|
|
202
|
+
export function cardStyle(scheme: Scheme) {
|
|
203
|
+
return {
|
|
204
|
+
backgroundColor: tokens.color.surfaceCard[scheme],
|
|
205
|
+
borderRadius: tokens.radius.lg,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The smallest touch target on a phone, in points (Apple's 44; Material asks 48). */
|
|
210
|
+
export const MIN_TOUCH_TARGET = 44;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* `hitSlop` that makes a control painted `width` × `height` points reach
|
|
214
|
+
* `MIN_TOUCH_TARGET` on each axis, so it can keep the web size on screen. An
|
|
215
|
+
* axis that is already large enough gets 0.
|
|
216
|
+
*/
|
|
217
|
+
export function touchSlop(width: number, height: number = width) {
|
|
218
|
+
const pad = (size: number) => Math.max(0, Math.ceil((MIN_TOUCH_TARGET - size) / 2));
|
|
219
|
+
const x = pad(width);
|
|
220
|
+
const y = pad(height);
|
|
221
|
+
return { top: y, bottom: y, left: x, right: x };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export type SearchInputSize = "sm" | "md";
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Field, icon and clear-button metrics for `SearchInput`, in points. Without
|
|
228
|
+
* larger text these are the web sizes (`search-input.tsx`, `clear-input.tsx`):
|
|
229
|
+
* `sm` is `h-8 pl-8 text-sm` with the icon at `left-2`, `md` is `h-10 pl-9`
|
|
230
|
+
* (the phone `text-base`) with the icon at `left-3`. Both have a 16 search
|
|
231
|
+
* icon, the field `px-3.5` at the end, and the `h-6 w-6` clear button at
|
|
232
|
+
* `right-2` with a 14 cross, for which a filled field reserves `pr-9`.
|
|
233
|
+
* `height` locks the field at the web height, border included (the web
|
|
234
|
+
* `h-8` and `h-10` are border-box), so the line height of the `text-*` class
|
|
235
|
+
* cannot grow it. `SearchInput` scales it with the system font size
|
|
236
|
+
* (`scaledFieldHeight`), so a large Dynamic Type size still grows the field.
|
|
237
|
+
* The padding leaves room for the line inside the lock: `sm` pads 4 above and
|
|
238
|
+
* below and `md` 7, not the web 8, because a native field adds its padding to
|
|
239
|
+
* the line height (8 drew `md` 42 high on Expo web). With larger text both
|
|
240
|
+
* sizes are the larger `Input` (64 high, `text-xl`), with a minimum height
|
|
241
|
+
* and no lock.
|
|
242
|
+
*/
|
|
243
|
+
export function searchInputSize(size: SearchInputSize, largerText: boolean) {
|
|
244
|
+
if (largerText) {
|
|
245
|
+
return {
|
|
246
|
+
minHeight: 64,
|
|
247
|
+
height: undefined,
|
|
248
|
+
paddingY: 16,
|
|
249
|
+
paddingLeft: 48,
|
|
250
|
+
paddingRight: 16,
|
|
251
|
+
filledPaddingRight: 48,
|
|
252
|
+
text: "text-xl",
|
|
253
|
+
icon: 24,
|
|
254
|
+
iconLeft: 16,
|
|
255
|
+
clear: 32,
|
|
256
|
+
clearIcon: 20,
|
|
257
|
+
clearRight: 12,
|
|
258
|
+
} as const;
|
|
259
|
+
}
|
|
260
|
+
const shared = {
|
|
261
|
+
paddingRight: 14,
|
|
262
|
+
filledPaddingRight: 36,
|
|
263
|
+
icon: 16,
|
|
264
|
+
clear: 24,
|
|
265
|
+
clearIcon: 14,
|
|
266
|
+
clearRight: 8,
|
|
267
|
+
} as const;
|
|
268
|
+
return size === "md"
|
|
269
|
+
? { ...shared, minHeight: 40, height: 40, paddingY: 7, paddingLeft: 36, text: "text-base", iconLeft: 12 } as const
|
|
270
|
+
: { ...shared, minHeight: 32, height: 32, paddingY: 4, paddingLeft: 32, text: "text-sm", iconLeft: 8 } as const;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* A locked field height at the system font scale (`useWindowDimensions().fontScale`):
|
|
275
|
+
* `height` up to a scale of 1, and `height` × the scale above it, as a web
|
|
276
|
+
* `rem` height grows with the browser text size. `undefined` (no lock) stays
|
|
277
|
+
* `undefined`.
|
|
278
|
+
*/
|
|
279
|
+
export function scaledFieldHeight(height: number | undefined, fontScale: number) {
|
|
280
|
+
return height === undefined ? undefined : height * Math.max(1, fontScale);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export type TabsVariant = "pills" | "segmented" | "soft";
|
|
284
|
+
|
|
285
|
+
export type TabColors = {
|
|
286
|
+
backgroundColor: string;
|
|
287
|
+
color: string;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Fill and label colour of one tab, from the web tab variables
|
|
292
|
+
* (`tabs-pills.css`). Unselected: no fill, `--muted` label. Selected: a
|
|
293
|
+
* `--default` pill or a `--segment` segment with the `--segment-foreground`
|
|
294
|
+
* label (the neutral primary, not the accent). `soft` is the web soft trigger
|
|
295
|
+
* (`bg-surface-secondary text-foreground`). Fill and label come from one
|
|
296
|
+
* scheme, so a selected label cannot match its fill.
|
|
297
|
+
*/
|
|
298
|
+
export function tabColors(variant: TabsVariant, selected: boolean, scheme: Scheme): TabColors {
|
|
299
|
+
if (!selected) return { backgroundColor: "transparent", color: tokens.color.muted[scheme] };
|
|
300
|
+
switch (variant) {
|
|
301
|
+
case "segmented":
|
|
302
|
+
return { backgroundColor: palette.segment[scheme], color: palette.segmentForeground[scheme] };
|
|
303
|
+
case "soft":
|
|
304
|
+
return {
|
|
305
|
+
backgroundColor: tokens.color.surfaceSecondary[scheme],
|
|
306
|
+
color: tokens.color.foreground[scheme],
|
|
307
|
+
};
|
|
308
|
+
default:
|
|
309
|
+
return { backgroundColor: palette.default[scheme], color: palette.segmentForeground[scheme] };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export type TabsSize = "default" | "sm";
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Trigger box and type for `TabsTrigger` and `PillTabs`. Without larger text
|
|
317
|
+
* these are the web sizes: `pills` and `segmented` are the web `.t-tab` (30
|
|
318
|
+
* high, `px-3 py-1`, `text-sm`), `soft` the web soft trigger (`px-4 py-2
|
|
319
|
+
* text-sm`) and `sm` the web `sm` list (`px-2.5 py-1 text-xs`). With larger
|
|
320
|
+
* text they are the 0.3.4 sizes. `minHeight` is a minimum, so a large Dynamic
|
|
321
|
+
* Type size grows the trigger instead of clipping its label.
|
|
322
|
+
*/
|
|
323
|
+
export function tabTriggerSize(variant: TabsVariant, size: TabsSize, largerText: boolean) {
|
|
324
|
+
if (largerText) {
|
|
325
|
+
return size === "sm"
|
|
326
|
+
? { minHeight: 48, className: "px-4 py-2", textClassName: "text-lg" }
|
|
327
|
+
: { minHeight: 56, className: "px-4 py-3", textClassName: "text-xl" };
|
|
328
|
+
}
|
|
329
|
+
if (size === "sm") return { minHeight: 30, className: "px-2.5 py-1", textClassName: "text-xs" };
|
|
330
|
+
return variant === "soft"
|
|
331
|
+
? { minHeight: 36, className: "px-4 py-2", textClassName: "text-sm" }
|
|
332
|
+
: { minHeight: 30, className: "px-3 py-1", textClassName: "text-sm" };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** The segmented track, web `--tabs-bar-bg` (`--default`). */
|
|
336
|
+
export function tabTrackStyle(scheme: Scheme) {
|
|
337
|
+
return { backgroundColor: palette.default[scheme] };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Box and check colours, from the web `Checkbox` classes: `bg-field-background
|
|
342
|
+
* border-border`, and `bg-primary border-primary text-primary-foreground` when
|
|
343
|
+
* checked (also when indeterminate).
|
|
344
|
+
*/
|
|
345
|
+
export function checkboxColors(checked: boolean, scheme: Scheme) {
|
|
346
|
+
return checked
|
|
347
|
+
? {
|
|
348
|
+
backgroundColor: palette.primary[scheme],
|
|
349
|
+
borderColor: palette.primary[scheme],
|
|
350
|
+
color: palette.primaryForeground[scheme],
|
|
351
|
+
}
|
|
352
|
+
: {
|
|
353
|
+
backgroundColor: tokens.color.surface[scheme],
|
|
354
|
+
borderColor: tokens.color.border[scheme],
|
|
355
|
+
color: palette.primaryForeground[scheme],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Track and thumb size classes, and the track size in points for the hit slop.
|
|
361
|
+
* Without larger text it is the web `Switch`: a `h-5 w-9` track (20 × 36,
|
|
362
|
+
* with a 1 border) and a `size-4` thumb that moves `translate-x-4`. With larger
|
|
363
|
+
* text it is the 0.3.4 size. `justify-center` on the track centres the thumb.
|
|
364
|
+
*/
|
|
365
|
+
export function switchSize(largerText: boolean) {
|
|
366
|
+
return largerText
|
|
367
|
+
? { track: "h-8 w-14", thumb: "h-7 w-7", on: "translate-x-6", width: 56, height: 32 }
|
|
368
|
+
: { track: "h-5 w-9", thumb: "h-4 w-4", on: "translate-x-4", width: 36, height: 20 };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Track and thumb colours, from the web `Switch` classes. The light off thumb
|
|
373
|
+
* is white (web `bg-background`) so it shows on the grey track. Flat objects,
|
|
374
|
+
* for the same reason as `fieldStyle`.
|
|
375
|
+
*/
|
|
376
|
+
export function switchColors(checked: boolean, scheme: Scheme) {
|
|
377
|
+
if (checked) {
|
|
378
|
+
return {
|
|
379
|
+
track: { backgroundColor: palette.primary[scheme], borderColor: palette.primary[scheme] },
|
|
380
|
+
thumb: { backgroundColor: palette.primaryForeground[scheme] },
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
return scheme === "dark"
|
|
384
|
+
? {
|
|
385
|
+
track: {
|
|
386
|
+
backgroundColor: withAlpha(tokens.color.muted.dark, 0.4),
|
|
387
|
+
borderColor: withAlpha(tokens.color.muted.dark, 0.6),
|
|
388
|
+
},
|
|
389
|
+
thumb: { backgroundColor: tokens.color.foreground.dark },
|
|
390
|
+
}
|
|
391
|
+
: {
|
|
392
|
+
track: { backgroundColor: palette.default.light, borderColor: tokens.color.border.light },
|
|
393
|
+
thumb: { backgroundColor: tokens.color.surface.light },
|
|
394
|
+
};
|
|
395
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where a `Button` sits in a dialog. The web sizes buttons in the compact
|
|
5
|
+
* dialog with descendant selectors, which React Native lacks, so `DialogContent`
|
|
6
|
+
* and `DialogFooter` pass this down instead. `compact`: inside the phone bottom
|
|
7
|
+
* sheet, where buttons are at least 44 high (web `[&_button]:min-h-11`).
|
|
8
|
+
* `stacked`: inside its footer too, where buttons fill the width (web
|
|
9
|
+
* `flex-col-reverse`).
|
|
10
|
+
*/
|
|
11
|
+
export type DialogLayout = { compact: boolean; stacked: boolean };
|
|
12
|
+
|
|
13
|
+
export const DialogLayoutContext = createContext<DialogLayout>({
|
|
14
|
+
compact: false,
|
|
15
|
+
stacked: false,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export function useDialogLayout(): DialogLayout {
|
|
19
|
+
return useContext(DialogLayoutContext);
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// `@vvedantb/native-ui/floating`: floating surfaces, glass blur targets and
|
|
2
|
+
// metrics. Reaches `expo-blur` only through its optional-peer loader.
|
|
3
|
+
export {
|
|
4
|
+
FLOATING_INSET,
|
|
5
|
+
FLOATING_TAB_BAR_HEIGHT,
|
|
6
|
+
FLOATING_TAB_BAR_HEIGHT_LARGE,
|
|
7
|
+
FLOATING_RADIUS,
|
|
8
|
+
FLOATING_ROW_RADIUS,
|
|
9
|
+
FLOATING_PILL_RADIUS,
|
|
10
|
+
floatingShadow,
|
|
11
|
+
floatingHairline,
|
|
12
|
+
floatingFill,
|
|
13
|
+
floatingSolidFill,
|
|
14
|
+
tabBarReserve,
|
|
15
|
+
} from "../floating";
|
|
16
|
+
export { useReduceTransparency } from "../useReduceTransparency";
|
|
17
|
+
export { FloatingSurface, GlassSheetBackground } from "../FloatingSurface";
|
|
18
|
+
export {
|
|
19
|
+
LiquidGlass,
|
|
20
|
+
GlassBlurProvider,
|
|
21
|
+
GlassBlurTarget,
|
|
22
|
+
} from "../LiquidGlass";
|
|
23
|
+
export type { GlassBlurTargetProps } from "../LiquidGlass";
|
|
24
|
+
export { FloatingFAB } from "../FloatingFAB";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// `@vvedantb/native-ui/layout`: screen containers, page headers and settings
|
|
2
|
+
// chrome.
|
|
3
|
+
export { SafeAreaContainer } from "../SafeAreaContainer";
|
|
4
|
+
export { ScreenContainer } from "../ScreenContainer";
|
|
5
|
+
export { PageTitle, PageHeader } from "../PageTitle";
|
|
6
|
+
export type { PageTitleProps, PageHeaderProps } from "../PageTitle";
|
|
7
|
+
export { SettingsSection } from "../settings/SettingsSection";
|
|
8
|
+
export { SettingsField } from "../settings/SettingsField";
|
|
9
|
+
export { SettingsToggleRow } from "../settings/SettingsToggleRow";
|
|
10
|
+
export { SettingsStack } from "../settings/SettingsStack";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// `@vvedantb/native-ui/marketing`: the upgrade card and pricing modal.
|
|
2
|
+
export { UpgradeToProCard } from "../marketing/UpgradeToProCard";
|
|
3
|
+
export type { UpgradeToProCardProps } from "../marketing/UpgradeToProCard";
|
|
4
|
+
export { PricingModal } from "../marketing/PricingModal";
|
|
5
|
+
export type { PricingModalProps } from "../marketing/PricingModal";
|
|
6
|
+
export type {
|
|
7
|
+
PricingModalPeriod,
|
|
8
|
+
PricingPlan,
|
|
9
|
+
PricingPlanBenefit,
|
|
10
|
+
} from "../marketing/pricing";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// `@vvedantb/native-ui/primitives`: fields, display, tabs, overlays and
|
|
2
|
+
// toasts. `BottomSheetContainer`, `DateTimeField` and `AmbientBackground` are
|
|
3
|
+
// not here; they have their own entries.
|
|
4
|
+
export { Button } from "../Button";
|
|
5
|
+
export type { ButtonProps, ButtonSize, ButtonVariant } from "../Button";
|
|
6
|
+
export { Input } from "../Input";
|
|
7
|
+
export type { InputProps } from "../Input";
|
|
8
|
+
export { SearchInput } from "../SearchInput";
|
|
9
|
+
export type { SearchInputProps } from "../SearchInput";
|
|
10
|
+
export { Textarea } from "../Textarea";
|
|
11
|
+
export type { TextareaProps } from "../Textarea";
|
|
12
|
+
export { Label } from "../Label";
|
|
13
|
+
export type { LabelProps } from "../Label";
|
|
14
|
+
export { Select } from "../Select";
|
|
15
|
+
export type { SelectOption, SelectProps } from "../Select";
|
|
16
|
+
export { Switch } from "../Switch";
|
|
17
|
+
export type { SwitchProps } from "../Switch";
|
|
18
|
+
export { LabeledSwitchRow } from "../LabeledSwitchRow";
|
|
19
|
+
export type { LabeledSwitchRowProps } from "../LabeledSwitchRow";
|
|
20
|
+
export { Checkbox } from "../Checkbox";
|
|
21
|
+
export type { CheckboxProps } from "../Checkbox";
|
|
22
|
+
export { Badge } from "../Badge";
|
|
23
|
+
export type { BadgeProps, BadgeVariant } from "../Badge";
|
|
24
|
+
export { StatusDot } from "../StatusDot";
|
|
25
|
+
export type { StatusDotProps, StatusTone } from "../StatusDot";
|
|
26
|
+
export { Separator } from "../Separator";
|
|
27
|
+
export type { SeparatorProps } from "../Separator";
|
|
28
|
+
export { Progress } from "../Progress";
|
|
29
|
+
export type { ProgressProps } from "../Progress";
|
|
30
|
+
export { Spinner, CenteredSpinner } from "../Spinner";
|
|
31
|
+
export type { SpinnerProps } from "../Spinner";
|
|
32
|
+
export { Avatar, AvatarImage, AvatarFallback } from "../Avatar";
|
|
33
|
+
export type { AvatarProps, AvatarImageProps, AvatarFallbackProps } from "../Avatar";
|
|
34
|
+
export { initials } from "../initials";
|
|
35
|
+
export { Chip } from "../Chip";
|
|
36
|
+
export { PillTabs } from "../PillTabs";
|
|
37
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from "../Tabs";
|
|
38
|
+
export type {
|
|
39
|
+
TabsProps,
|
|
40
|
+
TabsListProps,
|
|
41
|
+
TabsListVariant,
|
|
42
|
+
TabsTriggerProps,
|
|
43
|
+
TabsContentProps,
|
|
44
|
+
} from "../Tabs";
|
|
45
|
+
export { ListRow } from "../ListRow";
|
|
46
|
+
export { HeaderIconButton } from "../HeaderIconButton";
|
|
47
|
+
export {
|
|
48
|
+
Card,
|
|
49
|
+
CardHeader,
|
|
50
|
+
CardTitle,
|
|
51
|
+
CardDescription,
|
|
52
|
+
CardContent,
|
|
53
|
+
CardFooter,
|
|
54
|
+
} from "../Card";
|
|
55
|
+
export { Skeleton, CardSkeleton } from "../Skeleton";
|
|
56
|
+
export { EmptyState } from "../EmptyState";
|
|
57
|
+
export type { EmptyStateVariant } from "../EmptyState";
|
|
58
|
+
export {
|
|
59
|
+
Dialog,
|
|
60
|
+
DialogTrigger,
|
|
61
|
+
DialogClose,
|
|
62
|
+
DialogContent,
|
|
63
|
+
DialogHeader,
|
|
64
|
+
DialogTitle,
|
|
65
|
+
DialogDescription,
|
|
66
|
+
DialogBody,
|
|
67
|
+
DialogFooter,
|
|
68
|
+
} from "../Dialog";
|
|
69
|
+
export type { DialogProps, DialogContentProps } from "../Dialog";
|
|
70
|
+
export {
|
|
71
|
+
AlertDialog,
|
|
72
|
+
AlertDialogTrigger,
|
|
73
|
+
AlertDialogContent,
|
|
74
|
+
AlertDialogHeader,
|
|
75
|
+
AlertDialogTitle,
|
|
76
|
+
AlertDialogDescription,
|
|
77
|
+
AlertDialogFooter,
|
|
78
|
+
AlertDialogAction,
|
|
79
|
+
AlertDialogCancel,
|
|
80
|
+
} from "../AlertDialog";
|
|
81
|
+
export type { AlertDialogProps, AlertDialogContentProps } from "../AlertDialog";
|
|
82
|
+
export { ConfirmDialog } from "../ConfirmDialog";
|
|
83
|
+
export type { ConfirmDialogProps } from "../ConfirmDialog";
|
|
84
|
+
export {
|
|
85
|
+
Sheet,
|
|
86
|
+
SheetTrigger,
|
|
87
|
+
SheetClose,
|
|
88
|
+
SheetContent,
|
|
89
|
+
SheetHandle,
|
|
90
|
+
SheetHeader,
|
|
91
|
+
SheetTitle,
|
|
92
|
+
SheetDescription,
|
|
93
|
+
SheetBody,
|
|
94
|
+
SheetFooter,
|
|
95
|
+
} from "../Sheet";
|
|
96
|
+
export type { SheetProps, SheetContentProps } from "../Sheet";
|
|
97
|
+
export { Toaster } from "../Toast";
|
|
98
|
+
export { toast } from "../toastStore";
|
|
99
|
+
export type { ToastOptions, ToastVariant } from "../toastStore";
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// `@vvedantb/native-ui/tab-bar`. Does not import `expo-router`; reaches
|
|
2
|
+
// `expo-blur` and `expo-haptics` only through their optional-peer loaders.
|
|
3
|
+
export { FloatingTabBar } from "../FloatingTabBar";
|
|
4
|
+
export type { FloatingTabBarProps } from "../FloatingTabBar";
|
|
5
|
+
export {
|
|
6
|
+
FLOATING_INSET,
|
|
7
|
+
FLOATING_TAB_BAR_HEIGHT,
|
|
8
|
+
FLOATING_TAB_BAR_HEIGHT_LARGE,
|
|
9
|
+
tabBarReserve,
|
|
10
|
+
} from "../floating";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// `@vvedantb/native-ui/theme`: the provider, tokens and colour helpers.
|
|
2
|
+
export { cn } from "../cn";
|
|
3
|
+
export {
|
|
4
|
+
tokens,
|
|
5
|
+
NativeUiProvider,
|
|
6
|
+
useNativeUi,
|
|
7
|
+
useLargerText,
|
|
8
|
+
useAccentColor,
|
|
9
|
+
DEFAULT_ACCENT,
|
|
10
|
+
} from "../theme";
|
|
11
|
+
export type { NativeUiTheme } from "../theme";
|
|
12
|
+
export { palette, withAlpha } from "../palette";
|
|
13
|
+
export type { Palette } from "../palette";
|
|
14
|
+
export { fontClass } from "../typography";
|
|
15
|
+
export { PINK_50, PINK_100, PINK_600, PINK_700, NEUTRAL_100, NEUTRAL_500, NEUTRAL_900, Colors } from "../colors";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// `@vvedantb/native-ui/toast`: `toast()` and `<Toaster />`. Reaches no heavy
|
|
2
|
+
// optional peer, not even a loader (the toast surface is flat from 0.3.12): no
|
|
3
|
+
// `expo-blur`, bottom sheet, date picker, `dayjs` or `expo-router`.
|
|
4
|
+
export { Toaster } from "../Toast";
|
|
5
|
+
export { toast } from "../toastStore";
|
|
6
|
+
export type { ToastOptions, ToastVariant } from "../toastStore";
|