@vvedantb/native-ui 0.17.2 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AlertDialog.jsx +123 -0
- package/dist/AmbientBackground.jsx +73 -0
- package/dist/ArtifactList.jsx +59 -0
- package/dist/Avatar.jsx +285 -0
- package/dist/Badge.jsx +154 -0
- package/dist/BottomSheetContainer.jsx +208 -0
- package/dist/BulkActionBar.jsx +717 -0
- package/dist/Button.jsx +344 -0
- package/dist/CaptureButton.jsx +104 -0
- package/dist/Card.jsx +371 -0
- package/dist/Checkbox.jsx +177 -0
- package/dist/Chip.jsx +79 -0
- package/dist/ColorSwatches.jsx +159 -0
- package/dist/ConfirmDialog.jsx +157 -0
- package/dist/DateTimeField.jsx +204 -0
- package/dist/Dialog.jsx +614 -0
- package/dist/DividedList.jsx +120 -0
- package/dist/DropdownMenu.jsx +849 -0
- package/dist/EmptyState.jsx +434 -0
- package/dist/ExpoWebRoot.jsx +134 -0
- package/dist/FloatingFAB.jsx +95 -0
- package/dist/FloatingSurface.jsx +226 -0
- package/dist/FloatingTabBar.jsx +403 -0
- package/dist/HeaderIconButton.jsx +53 -0
- package/dist/IconButton.jsx +157 -0
- package/dist/Input.jsx +96 -0
- package/dist/Label.jsx +96 -0
- package/dist/LabeledSwitchRow.jsx +170 -0
- package/dist/LiquidGlass.jsx +2 -0
- package/dist/ListRow.jsx +281 -0
- package/dist/LoadingState.jsx +205 -0
- package/dist/PageIndicator.jsx +128 -0
- package/dist/PageTitle.jsx +205 -0
- package/dist/Pager.jsx +105 -0
- package/dist/PasswordInput.jsx +120 -0
- package/dist/PillTabs.jsx +300 -0
- package/dist/PinnedBotTile.jsx +212 -0
- package/dist/Progress.jsx +147 -0
- package/dist/SafeAreaContainer.jsx +38 -0
- package/dist/ScreenContainer.jsx +117 -0
- package/dist/SearchInput.jsx +150 -0
- package/dist/SectionLabel.jsx +128 -0
- package/dist/Select.jsx +223 -0
- package/dist/Separator.jsx +95 -0
- package/dist/Sheet.jsx +343 -0
- package/dist/SheetRow.jsx +198 -0
- package/dist/Skeleton.jsx +311 -0
- package/dist/Spinner.jsx +195 -0
- package/dist/StackPageHeader.jsx +160 -0
- package/dist/StatGrid.jsx +74 -0
- package/dist/StatusDot.jsx +129 -0
- package/dist/SurfaceRow.jsx +143 -0
- package/dist/SwipeableRow.jsx +50 -0
- package/dist/Switch.jsx +130 -0
- package/dist/Tabs.jsx +385 -0
- package/dist/Textarea.jsx +103 -0
- package/dist/ThemeToggle.jsx +78 -0
- package/dist/Toast.jsx +396 -0
- package/dist/ai/activity-step-detail.jsx +179 -0
- package/dist/ai/activity-step-label.js +816 -0
- package/dist/ai/activity-step-types.js +14 -0
- package/dist/ai/activity-step.jsx +98 -0
- package/dist/ai/activity-tasks-utils.js +172 -0
- package/dist/ai/activity-timeline.js +47 -0
- package/dist/ai/attachments.jsx +258 -0
- package/dist/ai/card-button.jsx +188 -0
- package/dist/ai/chain-of-thought.jsx +459 -0
- package/dist/ai/chat-jump-rail.jsx +57 -0
- package/dist/ai/chat-text.js +44 -0
- package/dist/ai/chat-thread.jsx +135 -0
- package/dist/ai/choice-question.jsx +925 -0
- package/dist/ai/code-block.jsx +574 -0
- package/dist/ai/collapsible.jsx +275 -0
- package/dist/ai/conversation.jsx +723 -0
- package/dist/ai/file-chip.jsx +198 -0
- package/dist/ai/icon-button.jsx +179 -0
- package/dist/ai/index.js +37 -0
- package/dist/ai/message-action-sheet.jsx +408 -0
- package/dist/ai/message-quote.jsx +241 -0
- package/dist/ai/message.jsx +739 -0
- package/dist/ai/prompt-input-menu.jsx +365 -0
- package/dist/ai/prompt-input.jsx +626 -0
- package/dist/ai/reasoning.jsx +446 -0
- package/dist/ai/reply-quote-chip.jsx +278 -0
- package/dist/ai/response-text.jsx +130 -0
- package/dist/ai/shimmer.jsx +149 -0
- package/dist/ai/sources.jsx +260 -0
- package/dist/ai/subagent-cards.jsx +645 -0
- package/dist/ai/suggestion.jsx +186 -0
- package/dist/ai/tool-approval-card.jsx +359 -0
- package/dist/ai/trace.jsx +728 -0
- package/dist/ai/unified-diff.jsx +280 -0
- package/dist/ai/use-streaming-disclosure.js +70 -0
- package/dist/auth/AuthProviders.jsx +27 -0
- package/dist/auth/AuthScreen.jsx +292 -0
- package/dist/auth/GoogleSignInButton.jsx +188 -0
- package/dist/auth/SecondFactorScreen.jsx +133 -0
- package/dist/auth/SignInScreen.jsx +482 -0
- package/dist/auth/authErrors.js +131 -0
- package/dist/auth/index.js +11 -0
- package/dist/auth/totp.js +378 -0
- package/dist/blur.js +36 -0
- package/dist/brand/AppIcons.jsx +118 -0
- package/dist/brand/AppMark.jsx +128 -0
- package/dist/brand/AppWordmark.jsx +138 -0
- package/dist/brand/GoogleMark.jsx +71 -0
- package/dist/brand/ProviderIcon.jsx +192 -0
- package/dist/brand/familyApps.js +33 -0
- package/dist/brand/index.js +11 -0
- package/dist/brand/svg.js +252 -0
- package/dist/brand/vProAppBenefits.js +19 -0
- package/dist/cn.js +5 -0
- package/dist/colors.js +15 -0
- package/dist/configureTheme.js +72 -0
- package/dist/controlStyles.js +520 -0
- package/dist/definiteSize.js +12 -0
- package/dist/dialogLayout.js +18 -0
- package/dist/entries/ai.js +4 -0
- package/dist/entries/ambient.js +3 -0
- package/dist/entries/auth.js +5 -0
- package/dist/entries/bottom-sheet.js +2 -0
- package/dist/entries/brand.js +2 -0
- package/dist/entries/button.js +2 -0
- package/dist/entries/date-time.js +3 -0
- package/dist/entries/expo-router.js +4 -0
- package/dist/entries/expo-web-root.js +3 -0
- package/dist/entries/floating.js +7 -0
- package/dist/entries/gesture-handler.js +4 -0
- package/dist/entries/keyboard-controller.js +4 -0
- package/dist/entries/layout.js +16 -0
- package/dist/entries/markdown.js +4 -0
- package/dist/entries/marketing.js +3 -0
- package/dist/entries/page-title.js +7 -0
- package/dist/entries/password-input.js +3 -0
- package/dist/entries/primitives.js +57 -0
- package/dist/entries/search-input.js +3 -0
- package/dist/entries/tab-bar.js +4 -0
- package/dist/entries/theme.js +7 -0
- package/dist/entries/toast.js +5 -0
- package/dist/entries/video.js +3 -0
- package/dist/entries/webview.js +4 -0
- package/dist/examples/AiChatGallery.jsx +509 -0
- package/dist/examples/ParityGallery.jsx +1307 -0
- package/dist/floating.js +62 -0
- package/dist/glassBlur.jsx +145 -0
- package/dist/gradient.js +17 -0
- package/dist/haptics.js +44 -0
- package/dist/highlight.js +382 -0
- package/dist/index.js +85 -0
- package/dist/initials.js +4 -0
- package/dist/loadingGrid.js +81 -0
- package/dist/markdown/Markdown.jsx +515 -0
- package/dist/markdown/MarkdownTable.jsx +443 -0
- package/dist/markdown/tableText.js +68 -0
- package/dist/marketing/PricingModal.jsx +608 -0
- package/dist/marketing/UpgradeToProCard.jsx +164 -0
- package/dist/marketing/pricing.js +34 -0
- package/dist/media/ImageGallery.jsx +580 -0
- package/dist/media/MediaCard.jsx +162 -0
- package/dist/media/imageGrid.js +54 -0
- package/dist/motion.js +33 -0
- package/dist/pagerSwipe.js +104 -0
- package/dist/palette.js +104 -0
- package/dist/primitives/button.jsx +127 -0
- package/dist/primitives/card.jsx +249 -0
- package/dist/primitives/icon.jsx +37 -0
- package/dist/primitives/input.jsx +56 -0
- package/dist/primitives/keyboard-controller.jsx +207 -0
- package/dist/primitives/keyboard-parts.jsx +75 -0
- package/dist/primitives/native-only-animated-view.jsx +68 -0
- package/dist/primitives/overlay.jsx +391 -0
- package/dist/primitives/pager-gesture.jsx +131 -0
- package/dist/primitives/select-host.jsx +103 -0
- package/dist/primitives/select.jsx +555 -0
- package/dist/primitives/sheet-drag.jsx +420 -0
- package/dist/primitives/sheet-gesture.jsx +219 -0
- package/dist/primitives/swipe-row-gesture.jsx +341 -0
- package/dist/primitives/switch.jsx +105 -0
- package/dist/primitives/text.jsx +145 -0
- package/dist/primitives/toast-drag-gesture.jsx +94 -0
- package/dist/primitives/use-controllable-state.js +40 -0
- package/dist/primitives/web-click-guard.js +69 -0
- package/dist/push.js +166 -0
- package/dist/relativeTime.js +70 -0
- package/dist/router/RouterStackPageHeader.jsx +53 -0
- package/dist/selectA11y.js +32 -0
- package/dist/settings/MoreFromProjectV.jsx +168 -0
- package/dist/settings/SettingsEmptyState.jsx +102 -0
- package/dist/settings/SettingsField.jsx +49 -0
- package/dist/settings/SettingsSection.jsx +101 -0
- package/dist/settings/SettingsStack.jsx +17 -0
- package/dist/settings/SettingsToggleRow.jsx +163 -0
- package/dist/settings/projectVMobileApps.js +126 -0
- package/dist/settings/relaxedSizeStyle.js +11 -0
- package/dist/sheetDrag.js +76 -0
- package/dist/svgA11y.js +46 -0
- package/dist/swipeRow.js +161 -0
- package/dist/tabBarRoutes.js +41 -0
- package/dist/tabIndicator.js +139 -0
- package/dist/tailwind-preset.cjs +255 -0
- package/dist/tailwind-preset.d.cts +55 -0
- package/dist/theme.jsx +62 -0
- package/dist/toastChrome.js +110 -0
- package/dist/toastDrag.js +51 -0
- package/dist/toastStore.js +54 -0
- package/dist/tokens.js +70 -0
- package/dist/typography.js +40 -0
- package/dist/useReduceMotion.js +32 -0
- package/dist/useReduceTransparency.js +27 -0
- package/dist/useScheme.js +15 -0
- package/dist/video/InlineVideo.jsx +115 -0
- package/dist/webview/HtmlView.jsx +169 -0
- package/dist/webview/HtmlView.web.jsx +63 -0
- package/dist/webview/htmlView.js +43 -0
- package/package.json +70 -33
- package/src/DropdownMenu.tsx +28 -16
- package/src/FloatingTabBar.tsx +78 -41
- package/src/ai/conversation.tsx +12 -8
- package/src/glassBlur.tsx +47 -22
- package/src/primitives/swipe-row-gesture.tsx +11 -2
package/dist/theme.jsx
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { createContext, useContext, useSyncExternalStore } from "react";
|
|
3
|
+
import { getThemeVersion, subscribeTheme } from "./configureTheme";
|
|
4
|
+
import { tokens } from "./tokens";
|
|
5
|
+
import { ThemeVersionContext } from "./useScheme";
|
|
6
|
+
export const DEFAULT_ACCENT = "#db2777";
|
|
7
|
+
const NativeUiContext = createContext({
|
|
8
|
+
largerText: false,
|
|
9
|
+
accentColor: DEFAULT_ACCENT
|
|
10
|
+
});
|
|
11
|
+
export function NativeUiProvider(t0) {
|
|
12
|
+
const $ = _c(9);
|
|
13
|
+
const {
|
|
14
|
+
largerText: t1,
|
|
15
|
+
accentColor: t2,
|
|
16
|
+
children
|
|
17
|
+
} = t0;
|
|
18
|
+
const largerText = t1 === undefined ? false : t1;
|
|
19
|
+
const accentColor = t2 === undefined ? DEFAULT_ACCENT : t2;
|
|
20
|
+
const themeVersion = useSyncExternalStore(subscribeTheme, getThemeVersion, getThemeVersion);
|
|
21
|
+
let t3;
|
|
22
|
+
if ($[0] !== accentColor || $[1] !== largerText) {
|
|
23
|
+
t3 = {
|
|
24
|
+
largerText,
|
|
25
|
+
accentColor
|
|
26
|
+
};
|
|
27
|
+
$[0] = accentColor;
|
|
28
|
+
$[1] = largerText;
|
|
29
|
+
$[2] = t3;
|
|
30
|
+
} else {
|
|
31
|
+
t3 = $[2];
|
|
32
|
+
}
|
|
33
|
+
let t4;
|
|
34
|
+
if ($[3] !== children || $[4] !== themeVersion) {
|
|
35
|
+
t4 = <ThemeVersionContext.Provider value={themeVersion}>{children}</ThemeVersionContext.Provider>;
|
|
36
|
+
$[3] = children;
|
|
37
|
+
$[4] = themeVersion;
|
|
38
|
+
$[5] = t4;
|
|
39
|
+
} else {
|
|
40
|
+
t4 = $[5];
|
|
41
|
+
}
|
|
42
|
+
let t5;
|
|
43
|
+
if ($[6] !== t3 || $[7] !== t4) {
|
|
44
|
+
t5 = <NativeUiContext.Provider value={t3}>{t4}</NativeUiContext.Provider>;
|
|
45
|
+
$[6] = t3;
|
|
46
|
+
$[7] = t4;
|
|
47
|
+
$[8] = t5;
|
|
48
|
+
} else {
|
|
49
|
+
t5 = $[8];
|
|
50
|
+
}
|
|
51
|
+
return t5;
|
|
52
|
+
}
|
|
53
|
+
export function useNativeUi() {
|
|
54
|
+
return useContext(NativeUiContext);
|
|
55
|
+
}
|
|
56
|
+
export function useLargerText() {
|
|
57
|
+
return useContext(NativeUiContext).largerText;
|
|
58
|
+
}
|
|
59
|
+
export function useAccentColor() {
|
|
60
|
+
return useContext(NativeUiContext).accentColor;
|
|
61
|
+
}
|
|
62
|
+
export { tokens };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `Toaster` look, from the web `SonnerToaster` at its default density
|
|
3
|
+
* (`createToasterClassNames` in `sonner.tsx`) with `toastIcons="filled24"`
|
|
4
|
+
* (`sonner-icons.tsx`). No React Native import, so the tests can load this
|
|
5
|
+
* file.
|
|
6
|
+
*/
|
|
7
|
+
import { palette, withAlpha } from "./palette";
|
|
8
|
+
import { tokens } from "./tokens";
|
|
9
|
+
/** Web `rounded-lg` (`--radius`, 16), the default `toastRadius`. */
|
|
10
|
+
export const TOAST_RADIUS = tokens.radius.lg;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The web `smooth-shadow-ring-md`: a 1 px ring in the border colour at 60%
|
|
14
|
+
* (`color-mix(in srgb, var(--color-border) 60%, transparent)`) and a
|
|
15
|
+
* `0 4px 12px rgb(0 0 0 / 0.08)` drop shadow. Native draws the ring as a
|
|
16
|
+
* border, so the painted toast is the web toast plus its ring.
|
|
17
|
+
*/
|
|
18
|
+
export const TOAST_RING = {
|
|
19
|
+
width: 1,
|
|
20
|
+
alpha: 0.6
|
|
21
|
+
};
|
|
22
|
+
export const TOAST_SHADOW = {
|
|
23
|
+
offsetY: 4,
|
|
24
|
+
blur: 12,
|
|
25
|
+
opacity: 0.08
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fill, ring and radius of a toast. The fill is the web `glass-panel-strong`,
|
|
30
|
+
* the flat `--overlay` (`palette.overlay`), with no blur.
|
|
31
|
+
*/
|
|
32
|
+
export function toastSurfaceStyle(scheme) {
|
|
33
|
+
return {
|
|
34
|
+
backgroundColor: palette.overlay[scheme],
|
|
35
|
+
borderColor: withAlpha(tokens.color.border[scheme], TOAST_RING.alpha),
|
|
36
|
+
borderWidth: TOAST_RING.width,
|
|
37
|
+
borderRadius: TOAST_RADIUS
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The drop shadow as a style: `boxShadow` on Android and web, the shadow
|
|
43
|
+
* props on iOS. The iOS radius is half the CSS blur, as a CSS blur is twice
|
|
44
|
+
* the Gaussian deviation.
|
|
45
|
+
*/
|
|
46
|
+
export function toastShadowStyle(os) {
|
|
47
|
+
const {
|
|
48
|
+
offsetY,
|
|
49
|
+
blur,
|
|
50
|
+
opacity
|
|
51
|
+
} = TOAST_SHADOW;
|
|
52
|
+
if (os === "ios") {
|
|
53
|
+
return {
|
|
54
|
+
shadowColor: tokens.color.black,
|
|
55
|
+
shadowOffset: {
|
|
56
|
+
width: 0,
|
|
57
|
+
height: offsetY
|
|
58
|
+
},
|
|
59
|
+
shadowOpacity: opacity,
|
|
60
|
+
shadowRadius: blur / 2
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
boxShadow: `0px ${offsetY}px ${blur}px rgba(0, 0, 0, ${opacity})`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Web `h-6 w-6`. */
|
|
68
|
+
export const TOAST_ICON_SIZE = 24;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The web `filledToastIcons`, path for path: filled shapes painted in the
|
|
72
|
+
* variant colour, with no stroke. `error` keeps the web 20 × 20 viewBox and
|
|
73
|
+
* `warning` its even-odd fill.
|
|
74
|
+
*/
|
|
75
|
+
export const TOAST_ICONS = {
|
|
76
|
+
success: {
|
|
77
|
+
viewBox: "0 0 24 24",
|
|
78
|
+
d: "M12 2C6.49 2 2 6.49 2 12C2 17.51 6.49 22 12 22C17.51 22 22 17.51 22 12C22 6.49 17.51 2 12 2ZM16.78 9.7L11.11 15.37C10.97 15.51 10.78 15.59 10.58 15.59C10.38 15.59 10.19 15.51 10.05 15.37L7.22 12.54C6.93 12.25 6.93 11.77 7.22 11.48C7.51 11.19 7.99 11.19 8.28 11.48L10.58 13.78L15.72 8.64C16.01 8.35 16.49 8.35 16.78 8.64C17.07 8.93 17.07 9.4 16.78 9.7Z"
|
|
79
|
+
},
|
|
80
|
+
info: {
|
|
81
|
+
viewBox: "0 0 24 24",
|
|
82
|
+
d: "M12 22C17.51 22 22 17.51 22 12C22 6.49 17.51 2 12 2C6.49 2 2 6.49 2 12C2 17.51 6.49 22 12 22ZM12.75 16C12.75 16.41 12.41 16.75 12 16.75C11.59 16.75 11.25 16.41 11.25 16L11.25 11C11.25 10.59 11.59 10.25 12 10.25C12.41 10.25 12.75 10.59 12.75 11L12.75 16ZM11.08 7.62C11.13 7.49 11.2 7.39 11.29 7.29C11.39 7.2 11.5 7.13 11.62 7.08C11.74 7.03 11.87 7 12 7C12.13 7 12.26 7.03 12.38 7.08C12.5 7.13 12.61 7.2 12.71 7.29C12.8 7.39 12.87 7.49 12.92 7.62C12.97 7.74 13 7.87 13 8C13 8.13 12.97 8.26 12.92 8.38C12.87 8.5 12.8 8.61 12.71 8.71C12.61 8.8 12.5 8.87 12.38 8.92C12.14 9.02 11.86 9.02 11.62 8.92C11.5 8.87 11.39 8.8 11.29 8.71C11.2 8.61 11.13 8.5 11.08 8.38C11.03 8.26 11 8.13 11 8C11 7.87 11.03 7.74 11.08 7.62Z"
|
|
83
|
+
},
|
|
84
|
+
warning: {
|
|
85
|
+
viewBox: "0 0 24 24",
|
|
86
|
+
d: "M3 10.417C3 7.219 3 5.62 3.378 5.082C3.755 4.545 5.258 4.03 8.265 3.001L8.838 2.805C10.405 2.268 11.188 2 12 2C12.812 2 13.595 2.268 15.162 2.805L15.735 3.001C18.742 4.03 20.245 4.545 20.622 5.082C21 5.62 21 7.22 21 10.417V11.991C21 17.629 16.761 20.366 14.101 21.527C13.38 21.842 13.02 22 12 22C10.98 22 10.62 21.842 9.899 21.527C7.239 20.365 3 17.63 3 11.991V10.417ZM12 7.25C12.1989 7.25 12.3897 7.32902 12.5303 7.46967C12.671 7.61032 12.75 7.80109 12.75 8V12C12.75 12.1989 12.671 12.3897 12.5303 12.5303C12.3897 12.671 12.1989 12.75 12 12.75C11.8011 12.75 11.6103 12.671 11.4697 12.5303C11.329 12.3897 11.25 12.1989 11.25 12V8C11.25 7.80109 11.329 7.61032 11.4697 7.46967C11.6103 7.32902 11.8011 7.25 12 7.25ZM12 16C12.2652 16 12.5196 15.8946 12.7071 15.7071C12.8946 15.5196 13 15.2652 13 15C13 14.7348 12.8946 14.4804 12.7071 14.2929C12.5196 14.1054 12.2652 14 12 14C11.7348 14 11.4804 14.1054 11.2929 14.2929C11.1054 14.4804 11 14.7348 11 15C11 15.2652 11.1054 15.5196 11.2929 15.7071C11.4804 15.8946 11.7348 16 12 16Z",
|
|
87
|
+
evenOdd: true
|
|
88
|
+
},
|
|
89
|
+
error: {
|
|
90
|
+
viewBox: "0 0 20 20",
|
|
91
|
+
d: "M17.51 3.85L11.57 0.42C10.6 -0.14 9.4 -0.14 8.42 0.42L2.49 3.85C1.52 4.41 0.919998 5.45 0.919998 6.58V13.42C0.919998 14.54 1.52 15.58 2.49 16.15L8.43 19.58C9.4 20.14 10.6 20.14 11.58 19.58L17.52 16.15C18.49 15.59 19.09 14.55 19.09 13.42V6.58C19.08 5.45 18.48 4.42 17.51 3.85ZM9.25 5.75C9.25 5.34 9.59 5 10 5C10.41 5 10.75 5.34 10.75 5.75V11C10.75 11.41 10.41 11.75 10 11.75C9.59 11.75 9.25 11.41 9.25 11V5.75ZM10.92 14.63C10.87 14.75 10.8 14.86 10.71 14.96C10.52 15.15 10.27 15.25 10 15.25C9.87 15.25 9.74 15.22 9.62 15.17C9.49 15.12 9.39 15.05 9.29 14.96C9.2 14.86 9.13 14.75 9.07 14.63C9.02 14.51 9 14.38 9 14.25C9 13.99 9.1 13.73 9.29 13.54C9.39 13.45 9.49 13.38 9.62 13.33C9.99 13.17 10.43 13.26 10.71 13.54C10.8 13.64 10.87 13.74 10.92 13.87C10.97 13.99 11 14.12 11 14.25C11 14.38 10.97 14.51 10.92 14.63Z"
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Icon colour per variant: the web `text-success`, `text-destructive` (the
|
|
97
|
+
* `--danger` token), `text-warning` and `text-primary`.
|
|
98
|
+
*/
|
|
99
|
+
export function toastIconColor(variant, scheme) {
|
|
100
|
+
switch (variant) {
|
|
101
|
+
case "success":
|
|
102
|
+
return palette.success[scheme];
|
|
103
|
+
case "error":
|
|
104
|
+
return palette.danger[scheme];
|
|
105
|
+
case "warning":
|
|
106
|
+
return palette.warning[scheme];
|
|
107
|
+
case "info":
|
|
108
|
+
return palette.primary[scheme];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createContext } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The rules of a toast's drag to dismiss, as plain numbers, so they stay
|
|
5
|
+
* testable apart from the gesture. A toast enters down from the top edge, so
|
|
6
|
+
* it leaves the same way: up. `dy` is the drag's travel in points (up
|
|
7
|
+
* negative) and `vy` the finger's speed in points per second (the Gesture
|
|
8
|
+
* Handler unit). Each rule is a worklet, so the drag runs it on the UI thread.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Vertical travel (pt) before a drag takes the toast: a tap still presses it. */
|
|
12
|
+
export const TOAST_DRAG_ACTIVE_OFFSET_Y = 8;
|
|
13
|
+
|
|
14
|
+
/** A flick up at this speed (pt/s) dismisses the toast, however short the drag. */
|
|
15
|
+
const FLICK_VELOCITY = 500;
|
|
16
|
+
|
|
17
|
+
/** How far ahead (s) the release velocity carries the toast when deciding to dismiss it. */
|
|
18
|
+
const PROJECTION_S = 0.2;
|
|
19
|
+
|
|
20
|
+
/** How far a downward pull may move the toast: it is already as low as it goes. */
|
|
21
|
+
const DOWN_REACH = 24;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Where the toast sits for a drag of `dy`: it follows the finger up 1:1, and
|
|
25
|
+
* down it meets growing resistance, up to `DOWN_REACH`.
|
|
26
|
+
*/
|
|
27
|
+
export function toastDragOffset(dy) {
|
|
28
|
+
"worklet";
|
|
29
|
+
|
|
30
|
+
if (dy <= 0) return dy;
|
|
31
|
+
return DOWN_REACH * (1 - 1 / (1 + dy / (DOWN_REACH * 2)));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Whether a released drag dismisses the toast: a quick flick up, or a drag
|
|
36
|
+
* whose position, carried on by its speed, passes half the toast's height.
|
|
37
|
+
* A slow, short drag springs back.
|
|
38
|
+
*/
|
|
39
|
+
export function toastDragDismisses(dy, vy, height) {
|
|
40
|
+
"worklet";
|
|
41
|
+
|
|
42
|
+
if (dy >= 0) return false;
|
|
43
|
+
if (vy <= -FLICK_VELOCITY) return true;
|
|
44
|
+
return dy + Math.min(0, vy) * PROJECTION_S < -height / 2;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The Gesture Handler toast drag, from `@vvedantb/native-ui/gesture-handler`
|
|
48
|
+
* (`GestureHandlerProvider`). A context, so only that entry imports
|
|
49
|
+
* `react-native-gesture-handler`; without it a toast closes on a tap only.
|
|
50
|
+
*/
|
|
51
|
+
export const ToastDragContext = createContext(null);
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Module-level toast queue behind `toast()` and `<Toaster />`. No React Native
|
|
2
|
+
// imports, so it runs anywhere (tests, non-component code).
|
|
3
|
+
|
|
4
|
+
/** At most this many toasts show; the oldest drop first. */
|
|
5
|
+
export const MAX_TOASTS = 3;
|
|
6
|
+
export const DEFAULT_TOAST_DURATION = 4000;
|
|
7
|
+
let toasts = [];
|
|
8
|
+
let counter = 0;
|
|
9
|
+
const listeners = new Set();
|
|
10
|
+
function emit() {
|
|
11
|
+
for (const listener of listeners) listener();
|
|
12
|
+
}
|
|
13
|
+
export function subscribeToasts(listener) {
|
|
14
|
+
listeners.add(listener);
|
|
15
|
+
return () => {
|
|
16
|
+
listeners.delete(listener);
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function getToasts() {
|
|
20
|
+
return toasts;
|
|
21
|
+
}
|
|
22
|
+
function show(variant, title, options = {}) {
|
|
23
|
+
const id = options.id ?? `toast-${++counter}`;
|
|
24
|
+
const item = {
|
|
25
|
+
id,
|
|
26
|
+
variant,
|
|
27
|
+
title,
|
|
28
|
+
description: options.description,
|
|
29
|
+
duration: options.duration ?? (variant === "loading" ? Infinity : DEFAULT_TOAST_DURATION),
|
|
30
|
+
action: options.action
|
|
31
|
+
};
|
|
32
|
+
toasts = toasts.some(toast => toast.id === id) ? toasts.map(toast => toast.id === id ? item : toast) : [...toasts, item].slice(-MAX_TOASTS);
|
|
33
|
+
emit();
|
|
34
|
+
return id;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Shows a toast and returns its id. The sonner-style shape of the web `toast`:
|
|
39
|
+
* `toast("Saved")`, `toast.success(...)`, `toast.error(...)`, `toast.warning(...)`,
|
|
40
|
+
* `toast.info(...)`, `toast.loading(...)` and `toast.dismiss(id?)`. Needs one
|
|
41
|
+
* `<Toaster />` mounted near the app root.
|
|
42
|
+
*/
|
|
43
|
+
export const toast = Object.assign((title, options) => show("default", title, options), {
|
|
44
|
+
success: (title, options) => show("success", title, options),
|
|
45
|
+
error: (title, options) => show("error", title, options),
|
|
46
|
+
warning: (title, options) => show("warning", title, options),
|
|
47
|
+
info: (title, options) => show("info", title, options),
|
|
48
|
+
loading: (title, options) => show("loading", title, options),
|
|
49
|
+
/** Closes one toast, or every toast without an id. */
|
|
50
|
+
dismiss(id) {
|
|
51
|
+
toasts = id === undefined ? [] : toasts.filter(toast => toast.id !== id);
|
|
52
|
+
emit();
|
|
53
|
+
}
|
|
54
|
+
});
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JS tokens for React Native: colours, radii and spacing (1rem = 16).
|
|
3
|
+
*
|
|
4
|
+
* Copy of `packages/ui/tokens/src/native.ts`, kept here so native-ui has no
|
|
5
|
+
* dependency on the web package. A test in `@vvedantb/ui` and one in this
|
|
6
|
+
* package fail when the two differ below this header: change both together.
|
|
7
|
+
*
|
|
8
|
+
* The neutrals and `radius.field` match `tokens.css` (oklch → sRGB hex); a
|
|
9
|
+
* test converts `tokens.css` and fails when they drift. `fill` and `hairline`
|
|
10
|
+
* are native glass chrome with no web token. `radius.card` has no web token
|
|
11
|
+
* either; `docs/native-ui-parity.md` lists it. The web semantic colours
|
|
12
|
+
* (primary, danger, success, warning) are in `palette.ts`.
|
|
13
|
+
*/
|
|
14
|
+
export const tokens = {
|
|
15
|
+
color: {
|
|
16
|
+
background: {
|
|
17
|
+
light: "#f5f5f5",
|
|
18
|
+
dark: "#060606"
|
|
19
|
+
},
|
|
20
|
+
foreground: {
|
|
21
|
+
light: "#181818",
|
|
22
|
+
dark: "#fcfcfc"
|
|
23
|
+
},
|
|
24
|
+
muted: {
|
|
25
|
+
light: "#727272",
|
|
26
|
+
dark: "#a0a0a0"
|
|
27
|
+
},
|
|
28
|
+
border: {
|
|
29
|
+
light: "#dedede",
|
|
30
|
+
dark: "#292929"
|
|
31
|
+
},
|
|
32
|
+
surface: {
|
|
33
|
+
light: "#ffffff",
|
|
34
|
+
dark: "#181818"
|
|
35
|
+
},
|
|
36
|
+
surfaceSecondary: {
|
|
37
|
+
light: "#efefef",
|
|
38
|
+
dark: "#242323"
|
|
39
|
+
},
|
|
40
|
+
surfaceCard: {
|
|
41
|
+
light: "#f8f8f8",
|
|
42
|
+
dark: "#242323"
|
|
43
|
+
},
|
|
44
|
+
separator: {
|
|
45
|
+
light: "#e4e4e4",
|
|
46
|
+
dark: "#222222"
|
|
47
|
+
},
|
|
48
|
+
white: "#ffffff",
|
|
49
|
+
black: "#000000",
|
|
50
|
+
hairline: {
|
|
51
|
+
light: "rgba(255, 255, 255, 0.7)",
|
|
52
|
+
dark: "rgba(255, 255, 255, 0.12)"
|
|
53
|
+
},
|
|
54
|
+
fill: {
|
|
55
|
+
light: "rgba(255, 255, 255, 0.86)",
|
|
56
|
+
dark: "rgba(24, 24, 24, 0.88)"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
radius: {
|
|
60
|
+
lg: 16,
|
|
61
|
+
field: 10,
|
|
62
|
+
md: 14,
|
|
63
|
+
sm: 12,
|
|
64
|
+
card: 24,
|
|
65
|
+
pill: 999
|
|
66
|
+
},
|
|
67
|
+
spacing: {
|
|
68
|
+
unit: 16
|
|
69
|
+
}
|
|
70
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** The four font families: one per weight, as the app loads them. */
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The font family names JS styles use, Instrument Sans by default (the
|
|
5
|
+
* Expo Google Fonts names, as `INSTRUMENT_SANS` in tailwind-preset.cjs).
|
|
6
|
+
* `configureTheme({ font })` writes into this object, and components read it at
|
|
7
|
+
* render, so do not copy a value out at import.
|
|
8
|
+
*/
|
|
9
|
+
export const fontFamily = {
|
|
10
|
+
regular: "InstrumentSans_400Regular",
|
|
11
|
+
medium: "InstrumentSans_500Medium",
|
|
12
|
+
semibold: "InstrumentSans_600SemiBold",
|
|
13
|
+
bold: "InstrumentSans_700Bold"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* NativeWind classes for the four faces (Instrument Sans by default). The app
|
|
18
|
+
* loads the font files and maps these classes to them (see README, and the
|
|
19
|
+
* preset's `font` option). Literal strings, so Tailwind's source scan
|
|
20
|
+
* generates them.
|
|
21
|
+
*/
|
|
22
|
+
export const fontClass = {
|
|
23
|
+
regular: "font-instrument-sans",
|
|
24
|
+
medium: "font-instrument-sans-medium",
|
|
25
|
+
semibold: "font-instrument-sans-semibold",
|
|
26
|
+
bold: "font-instrument-sans-bold"
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* `AppWordmark` geometry, the same numbers as the web `AppWordmark` and
|
|
31
|
+
* `.landing-wordmark-text`: Instrument Sans 500, letter spacing -0.02em, icon
|
|
32
|
+
* 1.4x the font size, gap 0.2em. A test checks them against the web source.
|
|
33
|
+
*/
|
|
34
|
+
export const WORDMARK = {
|
|
35
|
+
fontClass: fontClass.medium,
|
|
36
|
+
fontWeight: 500,
|
|
37
|
+
letterSpacingEm: -0.02,
|
|
38
|
+
iconScale: 1.4,
|
|
39
|
+
gapEm: 0.2
|
|
40
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { AccessibilityInfo } from "react-native";
|
|
4
|
+
import { useReducedMotion } from "react-native-reanimated";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The system Reduce Motion setting (iOS) or Remove animations (Android). The
|
|
8
|
+
* first value is Reanimated's synchronous read, so an overlay that mounts open
|
|
9
|
+
* does not animate once before the setting loads. Later changes come from
|
|
10
|
+
* `AccessibilityInfo`.
|
|
11
|
+
*/
|
|
12
|
+
export function useReduceMotion() {
|
|
13
|
+
const $ = _c(2);
|
|
14
|
+
const initial = useReducedMotion();
|
|
15
|
+
const [enabled, setEnabled] = useState(initial);
|
|
16
|
+
let t0;
|
|
17
|
+
let t1;
|
|
18
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
19
|
+
t0 = () => {
|
|
20
|
+
const subscription = AccessibilityInfo.addEventListener("reduceMotionChanged", setEnabled);
|
|
21
|
+
return () => subscription.remove();
|
|
22
|
+
};
|
|
23
|
+
t1 = [];
|
|
24
|
+
$[0] = t0;
|
|
25
|
+
$[1] = t1;
|
|
26
|
+
} else {
|
|
27
|
+
t0 = $[0];
|
|
28
|
+
t1 = $[1];
|
|
29
|
+
}
|
|
30
|
+
useEffect(t0, t1);
|
|
31
|
+
return enabled;
|
|
32
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { AccessibilityInfo, Platform } from "react-native";
|
|
4
|
+
export function useReduceTransparency() {
|
|
5
|
+
const $ = _c(2);
|
|
6
|
+
const [enabled, setEnabled] = useState(false);
|
|
7
|
+
let t0;
|
|
8
|
+
let t1;
|
|
9
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
10
|
+
t0 = () => {
|
|
11
|
+
if (Platform.OS !== "ios" || typeof AccessibilityInfo.isReduceTransparencyEnabled !== "function") {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
AccessibilityInfo.isReduceTransparencyEnabled().then(setEnabled);
|
|
15
|
+
const subscription = AccessibilityInfo.addEventListener("reduceTransparencyChanged", setEnabled);
|
|
16
|
+
return () => subscription.remove();
|
|
17
|
+
};
|
|
18
|
+
t1 = [];
|
|
19
|
+
$[0] = t0;
|
|
20
|
+
$[1] = t1;
|
|
21
|
+
} else {
|
|
22
|
+
t0 = $[0];
|
|
23
|
+
t1 = $[1];
|
|
24
|
+
}
|
|
25
|
+
useEffect(t0, t1);
|
|
26
|
+
return enabled;
|
|
27
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useColorScheme } from "nativewind";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The number of `configureTheme` calls, given by `NativeUiProvider`. A part
|
|
6
|
+
* that reads it redraws after each call, so a palette set at run time shows
|
|
7
|
+
* at once, even under a memoised parent.
|
|
8
|
+
*/
|
|
9
|
+
export const ThemeVersionContext = createContext(0);
|
|
10
|
+
|
|
11
|
+
/** Active colour scheme as a `tokens` / `palette` key. Redraws the caller after each `configureTheme`. */
|
|
12
|
+
export function useScheme() {
|
|
13
|
+
useContext(ThemeVersionContext);
|
|
14
|
+
return useColorScheme().colorScheme === "dark" ? "dark" : "light";
|
|
15
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { Image, Pressable, StyleSheet, View } from "react-native";
|
|
4
|
+
import { useVideoPlayer, VideoView } from "expo-video";
|
|
5
|
+
import { IconPlayerPlayFilled } from "@tabler/icons-react-native";
|
|
6
|
+
import { MEDIA } from "../media/imageGrid";
|
|
7
|
+
import { withAlpha } from "../palette";
|
|
8
|
+
import { tokens } from "../tokens";
|
|
9
|
+
import { useScheme } from "../useScheme";
|
|
10
|
+
|
|
11
|
+
/** The box before playback: 16:9 unless the caller knows the video's shape. */
|
|
12
|
+
export const INLINE_VIDEO_ASPECT = 16 / 9;
|
|
13
|
+
/**
|
|
14
|
+
* An inline video on `expo-video`, as the web `VideoPreview`: no autoplay and
|
|
15
|
+
* sound on. Until the play button is pressed it is a still box (the poster,
|
|
16
|
+
* or black), so a list of replies holds no players; pressing it mounts the
|
|
17
|
+
* player, starts playback and shows the platform controls (play and pause,
|
|
18
|
+
* scrub, full screen). The box keeps one size throughout, letterboxing the
|
|
19
|
+
* video (`contain`). Nothing plays or moves on its own, so it needs no Reduce
|
|
20
|
+
* Motion branch.
|
|
21
|
+
*
|
|
22
|
+
* `@vvedantb/native-ui/video` is the only entry that imports `expo-video` (an
|
|
23
|
+
* optional peer).
|
|
24
|
+
*/
|
|
25
|
+
export function InlineVideo(t0) {
|
|
26
|
+
const $ = _c(12);
|
|
27
|
+
const {
|
|
28
|
+
url,
|
|
29
|
+
aspectRatio: t1,
|
|
30
|
+
posterUrl,
|
|
31
|
+
accessibilityLabel: t2,
|
|
32
|
+
testID
|
|
33
|
+
} = t0;
|
|
34
|
+
const aspectRatio = t1 === undefined ? INLINE_VIDEO_ASPECT : t1;
|
|
35
|
+
const accessibilityLabel = t2 === undefined ? "Play video" : t2;
|
|
36
|
+
const scheme = useScheme();
|
|
37
|
+
const [started, setStarted] = useState(false);
|
|
38
|
+
const t3 = MEDIA.outline[scheme];
|
|
39
|
+
let t4;
|
|
40
|
+
if ($[0] !== aspectRatio || $[1] !== t3) {
|
|
41
|
+
t4 = {
|
|
42
|
+
width: "100%",
|
|
43
|
+
aspectRatio,
|
|
44
|
+
borderRadius: MEDIA.radius,
|
|
45
|
+
borderWidth: 1,
|
|
46
|
+
borderColor: t3,
|
|
47
|
+
overflow: "hidden",
|
|
48
|
+
backgroundColor: tokens.color.black
|
|
49
|
+
};
|
|
50
|
+
$[0] = aspectRatio;
|
|
51
|
+
$[1] = t3;
|
|
52
|
+
$[2] = t4;
|
|
53
|
+
} else {
|
|
54
|
+
t4 = $[2];
|
|
55
|
+
}
|
|
56
|
+
let t5;
|
|
57
|
+
if ($[3] !== accessibilityLabel || $[4] !== posterUrl || $[5] !== started || $[6] !== url) {
|
|
58
|
+
t5 = started ? <Player url={url} /> : <Pressable accessibilityRole="button" accessibilityLabel={accessibilityLabel} onPress={() => setStarted(true)} className="flex-1 items-center justify-center active:opacity-80">{posterUrl ? <Image source={{
|
|
59
|
+
uri: posterUrl
|
|
60
|
+
}} resizeMode="cover" style={StyleSheet.absoluteFill} /> : null}<View className="h-14 w-14 items-center justify-center rounded-full" style={{
|
|
61
|
+
backgroundColor: withAlpha(tokens.color.black, 0.5),
|
|
62
|
+
borderWidth: 1,
|
|
63
|
+
borderColor: withAlpha(tokens.color.white, 0.2)
|
|
64
|
+
}}><IconPlayerPlayFilled size={24} color={tokens.color.white} /></View></Pressable>;
|
|
65
|
+
$[3] = accessibilityLabel;
|
|
66
|
+
$[4] = posterUrl;
|
|
67
|
+
$[5] = started;
|
|
68
|
+
$[6] = url;
|
|
69
|
+
$[7] = t5;
|
|
70
|
+
} else {
|
|
71
|
+
t5 = $[7];
|
|
72
|
+
}
|
|
73
|
+
let t6;
|
|
74
|
+
if ($[8] !== t4 || $[9] !== t5 || $[10] !== testID) {
|
|
75
|
+
t6 = <View testID={testID} style={t4}>{t5}</View>;
|
|
76
|
+
$[8] = t4;
|
|
77
|
+
$[9] = t5;
|
|
78
|
+
$[10] = testID;
|
|
79
|
+
$[11] = t6;
|
|
80
|
+
} else {
|
|
81
|
+
t6 = $[11];
|
|
82
|
+
}
|
|
83
|
+
return t6;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Mounted on the first press: one player per pressed video, released when the row unmounts. */
|
|
87
|
+
function Player(t0) {
|
|
88
|
+
const $ = _c(3);
|
|
89
|
+
const {
|
|
90
|
+
url
|
|
91
|
+
} = t0;
|
|
92
|
+
const player = useVideoPlayer(url, _temp);
|
|
93
|
+
let t1;
|
|
94
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
95
|
+
t1 = {
|
|
96
|
+
enable: true
|
|
97
|
+
};
|
|
98
|
+
$[0] = t1;
|
|
99
|
+
} else {
|
|
100
|
+
t1 = $[0];
|
|
101
|
+
}
|
|
102
|
+
let t2;
|
|
103
|
+
if ($[1] !== player) {
|
|
104
|
+
t2 = <VideoView player={player} nativeControls={true} contentFit="contain" fullscreenOptions={t1} allowsPictureInPicture={false} style={StyleSheet.absoluteFill} />;
|
|
105
|
+
$[1] = player;
|
|
106
|
+
$[2] = t2;
|
|
107
|
+
} else {
|
|
108
|
+
t2 = $[2];
|
|
109
|
+
}
|
|
110
|
+
return t2;
|
|
111
|
+
}
|
|
112
|
+
function _temp(created) {
|
|
113
|
+
created.loop = false;
|
|
114
|
+
created.play();
|
|
115
|
+
}
|