react-native-terra-ui 0.1.1 → 0.2.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/CHANGELOG.md +90 -0
- package/lib/module/components/avatar/Avatar.js +118 -35
- package/lib/module/components/avatar/Avatar.js.map +1 -1
- package/lib/module/components/box/Box.js +15 -2
- package/lib/module/components/box/Box.js.map +1 -1
- package/lib/module/components/box/utils.js +4 -1
- package/lib/module/components/box/utils.js.map +1 -1
- package/lib/module/components/button/Button.js +7 -14
- package/lib/module/components/button/Button.js.map +1 -1
- package/lib/module/components/chip/Chip.js +372 -0
- package/lib/module/components/chip/Chip.js.map +1 -0
- package/lib/module/components/chip/index.js +4 -0
- package/lib/module/components/chip/index.js.map +1 -0
- package/lib/module/components/chip/types.js +2 -0
- package/lib/module/components/chip/types.js.map +1 -0
- package/lib/module/components/chip/utils.js +36 -0
- package/lib/module/components/chip/utils.js.map +1 -0
- package/lib/module/components/empty-state/EmptyState.js +200 -0
- package/lib/module/components/empty-state/EmptyState.js.map +1 -0
- package/lib/module/components/empty-state/index.js +4 -0
- package/lib/module/components/empty-state/index.js.map +1 -0
- package/lib/module/components/empty-state/types.js +2 -0
- package/lib/module/components/empty-state/types.js.map +1 -0
- package/lib/module/components/header/header-buttons.js +31 -36
- package/lib/module/components/header/header-buttons.js.map +1 -1
- package/lib/module/components/header/index.js +1 -1
- package/lib/module/components/header/index.js.map +1 -1
- package/lib/module/components/header/parts/HeaderGradientOverlay.js +38 -0
- package/lib/module/components/header/parts/HeaderGradientOverlay.js.map +1 -0
- package/lib/module/components/header/variants/LargeTitleHeader/index.js +69 -99
- package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
- package/lib/module/components/header/variants/TitleHeader/index.js +32 -63
- package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
- package/lib/module/components/icon/Icon.js +20 -8
- package/lib/module/components/icon/Icon.js.map +1 -1
- package/lib/module/components/image/Image.js +54 -0
- package/lib/module/components/image/Image.js.map +1 -0
- package/lib/module/components/image/index.js +4 -0
- package/lib/module/components/image/index.js.map +1 -0
- package/lib/module/components/image/types.js +4 -0
- package/lib/module/components/image/types.js.map +1 -0
- package/lib/module/components/index.js +4 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/components/portal/Portal.js +42 -0
- package/lib/module/components/portal/Portal.js.map +1 -0
- package/lib/module/components/portal/PortalContext.js +1 -16
- package/lib/module/components/portal/PortalContext.js.map +1 -1
- package/lib/module/components/portal/PortalHost.js +16 -14
- package/lib/module/components/portal/PortalHost.js.map +1 -1
- package/lib/module/components/portal/PortalProvider.js +51 -19
- package/lib/module/components/portal/PortalProvider.js.map +1 -1
- package/lib/module/components/portal/index.js +1 -1
- package/lib/module/components/portal/index.js.map +1 -1
- package/lib/module/components/screen/Screen.js +25 -28
- package/lib/module/components/screen/Screen.js.map +1 -1
- package/lib/module/components/screen/ScreenContext.js +7 -5
- package/lib/module/components/screen/ScreenContext.js.map +1 -1
- package/lib/module/components/screen/ScreenFlashList.js +144 -0
- package/lib/module/components/screen/ScreenFlashList.js.map +1 -0
- package/lib/module/components/screen/ScreenFlatList.js +88 -27
- package/lib/module/components/screen/ScreenFlatList.js.map +1 -1
- package/lib/module/components/screen/ScreenScrollView.js +101 -34
- package/lib/module/components/screen/ScreenScrollView.js.map +1 -1
- package/lib/module/components/screen/index.js +1 -0
- package/lib/module/components/screen/index.js.map +1 -1
- package/lib/module/components/screen/parts/BottomSafeArea.js +62 -0
- package/lib/module/components/screen/parts/BottomSafeArea.js.map +1 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js +50 -0
- package/lib/module/components/screen/parts/ScreenPlaceholder.js.map +1 -0
- package/lib/module/components/screen/types.js +4 -0
- package/lib/module/components/screen/types.js.map +1 -0
- package/lib/module/components/screen/utils.js +12 -9
- package/lib/module/components/screen/utils.js.map +1 -1
- package/lib/module/components/surface/Surface.js +16 -9
- package/lib/module/components/surface/Surface.js.map +1 -1
- package/lib/module/components/text/Text.js +23 -5
- package/lib/module/components/text/Text.js.map +1 -1
- package/lib/module/components/toast/Toast.js +8 -8
- package/lib/module/components/toast/Toast.js.map +1 -1
- package/lib/module/components/toolbar/Toolbar.js +268 -0
- package/lib/module/components/toolbar/Toolbar.js.map +1 -0
- package/lib/module/components/toolbar/index.js +4 -0
- package/lib/module/components/toolbar/index.js.map +1 -0
- package/lib/module/context/ThemeProvider.js +5 -2
- package/lib/module/context/ThemeProvider.js.map +1 -1
- package/lib/module/{components/icon/utils.js → theme/defaults/icons.js} +1 -1
- package/lib/module/theme/defaults/icons.js.map +1 -0
- package/lib/module/theme/defaults/image.js +30 -0
- package/lib/module/theme/defaults/image.js.map +1 -0
- package/lib/module/theme/index.js +1 -1
- package/lib/module/theme/{configure.js → registry.js} +21 -28
- package/lib/module/theme/registry.js.map +1 -0
- package/lib/module/theme/runtime.js +1 -1
- package/lib/module/theme/theme.js +91 -10
- package/lib/module/theme/theme.js.map +1 -1
- package/lib/module/theme/tokens/dark.js +59 -68
- package/lib/module/theme/tokens/dark.js.map +1 -1
- package/lib/module/theme/tokens/light.js +49 -67
- package/lib/module/theme/tokens/light.js.map +1 -1
- package/lib/module/theme/tokens/primitives.js +241 -20
- package/lib/module/theme/tokens/primitives.js.map +1 -1
- package/lib/module/utils/accent-utils.js +4 -4
- package/lib/module/utils/accent-utils.js.map +1 -1
- package/lib/typescript/src/components/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/src/components/avatar/types.d.ts +4 -4
- package/lib/typescript/src/components/avatar/types.d.ts.map +1 -1
- package/lib/typescript/src/components/box/Box.d.ts.map +1 -1
- package/lib/typescript/src/components/box/utils.d.ts +6 -1
- package/lib/typescript/src/components/box/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/button/Button.d.ts +1 -1
- package/lib/typescript/src/components/button/Button.d.ts.map +1 -1
- package/lib/typescript/src/components/chip/Chip.d.ts +24 -0
- package/lib/typescript/src/components/chip/Chip.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/index.d.ts +4 -0
- package/lib/typescript/src/components/chip/index.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/types.d.ts +8 -0
- package/lib/typescript/src/components/chip/types.d.ts.map +1 -0
- package/lib/typescript/src/components/chip/utils.d.ts +8 -0
- package/lib/typescript/src/components/chip/utils.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts +45 -0
- package/lib/typescript/src/components/empty-state/EmptyState.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/index.d.ts +4 -0
- package/lib/typescript/src/components/empty-state/index.d.ts.map +1 -0
- package/lib/typescript/src/components/empty-state/types.d.ts +6 -0
- package/lib/typescript/src/components/empty-state/types.d.ts.map +1 -0
- package/lib/typescript/src/components/header/header-buttons.d.ts +27 -0
- package/lib/typescript/src/components/header/header-buttons.d.ts.map +1 -1
- package/lib/typescript/src/components/header/index.d.ts +1 -1
- package/lib/typescript/src/components/header/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts +7 -0
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts.map +1 -0
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +1 -1
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +18 -7
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/icon/Icon.d.ts.map +1 -1
- package/lib/typescript/src/components/image/Image.d.ts +16 -0
- package/lib/typescript/src/components/image/Image.d.ts.map +1 -0
- package/lib/typescript/src/components/image/index.d.ts +3 -0
- package/lib/typescript/src/components/image/index.d.ts.map +1 -0
- package/lib/typescript/src/components/image/types.d.ts +7 -0
- package/lib/typescript/src/components/image/types.d.ts.map +1 -0
- package/lib/typescript/src/components/index.d.ts +4 -0
- package/lib/typescript/src/components/index.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/Portal.d.ts +26 -0
- package/lib/typescript/src/components/portal/Portal.d.ts.map +1 -0
- package/lib/typescript/src/components/portal/PortalContext.d.ts +11 -14
- package/lib/typescript/src/components/portal/PortalContext.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/PortalHost.d.ts +9 -4
- package/lib/typescript/src/components/portal/PortalHost.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/PortalProvider.d.ts +3 -3
- package/lib/typescript/src/components/portal/PortalProvider.d.ts.map +1 -1
- package/lib/typescript/src/components/portal/index.d.ts +4 -2
- package/lib/typescript/src/components/portal/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/Screen.d.ts +18 -13
- package/lib/typescript/src/components/screen/Screen.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenContext.d.ts +12 -9
- package/lib/typescript/src/components/screen/ScreenContext.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts +43 -0
- package/lib/typescript/src/components/screen/ScreenFlashList.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts +17 -7
- package/lib/typescript/src/components/screen/ScreenFlatList.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts +51 -16
- package/lib/typescript/src/components/screen/ScreenScrollView.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/index.d.ts +3 -0
- package/lib/typescript/src/components/screen/index.d.ts.map +1 -1
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts +30 -0
- package/lib/typescript/src/components/screen/parts/BottomSafeArea.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts +22 -0
- package/lib/typescript/src/components/screen/parts/ScreenPlaceholder.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/types.d.ts +42 -0
- package/lib/typescript/src/components/screen/types.d.ts.map +1 -0
- package/lib/typescript/src/components/screen/utils.d.ts +9 -9
- package/lib/typescript/src/components/screen/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/surface/Surface.d.ts +7 -0
- package/lib/typescript/src/components/surface/Surface.d.ts.map +1 -1
- package/lib/typescript/src/components/text/Text.d.ts.map +1 -1
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts +115 -0
- package/lib/typescript/src/components/toolbar/Toolbar.d.ts.map +1 -0
- package/lib/typescript/src/components/toolbar/index.d.ts +3 -0
- package/lib/typescript/src/components/toolbar/index.d.ts.map +1 -0
- package/lib/typescript/src/context/ThemeProvider.d.ts.map +1 -1
- package/lib/typescript/src/{components/icon/utils.d.ts → theme/defaults/icons.d.ts} +2 -2
- package/lib/typescript/src/theme/defaults/icons.d.ts.map +1 -0
- package/lib/typescript/src/theme/defaults/image.d.ts +4 -0
- package/lib/typescript/src/theme/defaults/image.d.ts.map +1 -0
- package/lib/typescript/src/theme/index.d.ts +1 -1
- package/lib/typescript/src/theme/index.d.ts.map +1 -1
- package/lib/typescript/src/theme/{configure.d.ts → registry.d.ts} +1 -1
- package/lib/typescript/src/theme/registry.d.ts.map +1 -0
- package/lib/typescript/src/theme/theme.d.ts +21 -1
- package/lib/typescript/src/theme/theme.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/dark.d.ts +41 -64
- package/lib/typescript/src/theme/tokens/dark.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/light.d.ts +44 -65
- package/lib/typescript/src/theme/tokens/light.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/primitives.d.ts +228 -17
- package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
- package/lib/typescript/src/theme/types.d.ts +65 -35
- package/lib/typescript/src/theme/types.d.ts.map +1 -1
- package/package.json +23 -9
- package/skills/terra-ui/SKILL.md +193 -0
- package/skills/terra-ui/references/recipes.md +198 -0
- package/skills/terra-ui/references/troubleshooting.md +112 -0
- package/src/components/avatar/Avatar.tsx +128 -38
- package/src/components/avatar/types.ts +5 -5
- package/src/components/box/Box.tsx +13 -1
- package/src/components/box/utils.ts +10 -0
- package/src/components/button/Button.tsx +7 -15
- package/src/components/chip/Chip.tsx +444 -0
- package/src/components/chip/index.ts +3 -0
- package/src/components/chip/types.ts +15 -0
- package/src/components/chip/utils.ts +34 -0
- package/src/components/empty-state/EmptyState.tsx +209 -0
- package/src/components/empty-state/index.ts +3 -0
- package/src/components/empty-state/types.ts +5 -0
- package/src/components/header/header-buttons.tsx +33 -45
- package/src/components/header/index.ts +2 -0
- package/src/components/header/parts/HeaderGradientOverlay.tsx +29 -0
- package/src/components/header/variants/LargeTitleHeader/index.tsx +68 -102
- package/src/components/header/variants/TitleHeader/index.tsx +47 -56
- package/src/components/icon/Icon.tsx +20 -15
- package/src/components/image/Image.tsx +48 -0
- package/src/components/image/index.ts +2 -0
- package/src/components/image/types.ts +7 -0
- package/src/components/index.ts +4 -0
- package/src/components/portal/Portal.tsx +55 -0
- package/src/components/portal/PortalContext.tsx +14 -22
- package/src/components/portal/PortalHost.tsx +17 -10
- package/src/components/portal/PortalProvider.tsx +58 -21
- package/src/components/portal/index.ts +4 -2
- package/src/components/screen/Screen.tsx +45 -59
- package/src/components/screen/ScreenContext.tsx +19 -12
- package/src/components/screen/ScreenFlashList.tsx +196 -0
- package/src/components/screen/ScreenFlatList.tsx +100 -39
- package/src/components/screen/ScreenScrollView.tsx +114 -53
- package/src/components/screen/index.ts +3 -0
- package/src/components/screen/parts/BottomSafeArea.tsx +60 -0
- package/src/components/screen/parts/ScreenPlaceholder.tsx +55 -0
- package/src/components/screen/types.ts +43 -0
- package/src/components/screen/utils.ts +29 -15
- package/src/components/surface/Surface.tsx +17 -20
- package/src/components/text/Text.tsx +23 -5
- package/src/components/toast/Toast.tsx +8 -8
- package/src/components/toolbar/Toolbar.tsx +353 -0
- package/src/components/toolbar/index.ts +6 -0
- package/src/context/ThemeProvider.tsx +5 -4
- package/src/{components/icon/utils.tsx → theme/defaults/icons.tsx} +1 -1
- package/src/theme/defaults/image.tsx +34 -0
- package/src/theme/index.ts +1 -1
- package/src/theme/{configure.ts → registry.ts} +19 -34
- package/src/theme/runtime.ts +1 -1
- package/src/theme/theme.ts +127 -13
- package/src/theme/tokens/dark.ts +60 -69
- package/src/theme/tokens/light.ts +50 -68
- package/src/theme/tokens/primitives.ts +248 -20
- package/src/theme/types.ts +67 -35
- package/src/utils/accent-utils.ts +4 -4
- package/lib/module/components/icon/utils.js.map +0 -1
- package/lib/module/theme/configure.js.map +0 -1
- package/lib/typescript/src/components/icon/utils.d.ts.map +0 -1
- package/lib/typescript/src/theme/configure.d.ts.map +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
type SafeAreaViewProps = {
|
|
4
|
+
edges: { readonly bottom: true };
|
|
5
|
+
insetType: 'all';
|
|
6
|
+
style: { readonly flex: 0 };
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `SafeAreaView` is not re-exported from the `react-native-screens` root, so it
|
|
11
|
+
* has to be reached by path — and it has to be the TypeScript source entry, the
|
|
12
|
+
* one the package points React Native at. The built `lib/module` copy of the
|
|
13
|
+
* native component never went through React Native's codegen, so loading it
|
|
14
|
+
* fails at bundle time with "Could not find component config for native
|
|
15
|
+
* component".
|
|
16
|
+
*
|
|
17
|
+
* `require` rather than `import` so the dependency's own `.tsx` is not pulled
|
|
18
|
+
* into this package's build, where it trips compiler options it was never
|
|
19
|
+
* written against.
|
|
20
|
+
*/
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
22
|
+
const { SafeAreaView } =
|
|
23
|
+
require('react-native-screens/src/components/safe-area') as {
|
|
24
|
+
SafeAreaView: ComponentType<SafeAreaViewProps>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const BOTTOM_EDGE = { bottom: true } as const;
|
|
28
|
+
const SPACER = { flex: 0 } as const;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Bottom clearance that measures itself.
|
|
32
|
+
*
|
|
33
|
+
* The insets Unistyles reports are the window's, which cover the system bars and
|
|
34
|
+
* nothing else — 24dp for the gesture bar on a Pixel — while a native tab bar
|
|
35
|
+
* sits on top of them for another 80dp and overlaps the content beneath it.
|
|
36
|
+
* `react-native-screens` tracks that second one separately, as an *interface*
|
|
37
|
+
* inset, and reports it to its own `SafeAreaView`; asking a view is the only way
|
|
38
|
+
* to learn it. Neither `runtime.insets.bottom` nor `useSafeAreaInsets()` knows
|
|
39
|
+
* about it — both read 24 on a tab screen — and a constant would be wrong the
|
|
40
|
+
* moment the bar is absent, which is every pushed screen.
|
|
41
|
+
*
|
|
42
|
+
* `insetType="all"` covers system and interface both, so this view owns the
|
|
43
|
+
* whole bottom edge — which is why the scroll containers pad only their margin;
|
|
44
|
+
* adding the window inset there too would double-count the gesture bar.
|
|
45
|
+
* `"interface"` alone would be wrong: it measures the same on a tab screen (the
|
|
46
|
+
* bar's inset already spans the gesture bar beneath it) but reports nothing on a
|
|
47
|
+
* pushed screen, which would then lose its gesture-bar clearance.
|
|
48
|
+
*
|
|
49
|
+
* `insetType` is Android-only; on iOS the bottom safe area of a view inside a
|
|
50
|
+
* tab controller already includes the bar. Measured on a tab screen: 132dp on a
|
|
51
|
+
* Pixel, 95pt on an iPhone 17 — against the 24dp / 34pt the window insets
|
|
52
|
+
* report, which is the gap this closes.
|
|
53
|
+
*
|
|
54
|
+
* `flex: 0` cancels the `flex: 1` that `SafeAreaView` sets on itself, which
|
|
55
|
+
* would otherwise stretch it into the leftover space of a list shorter than the
|
|
56
|
+
* viewport. With no children, its height is exactly the padding it applies.
|
|
57
|
+
*/
|
|
58
|
+
export function BottomSafeArea() {
|
|
59
|
+
return <SafeAreaView edges={BOTTOM_EDGE} insetType="all" style={SPACER} />;
|
|
60
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
+
|
|
6
|
+
import { Spinner } from '../../spinner';
|
|
7
|
+
import type { ScreenScrollableProps } from '../types';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Centres a loading/empty placeholder in whatever space the content leaves.
|
|
11
|
+
* Grows so the placeholder sits in the middle of the content area rather than
|
|
12
|
+
* hugging the top, which needs the content container to grow as well.
|
|
13
|
+
*/
|
|
14
|
+
export function ScreenPlaceholder({ children }: { children: ReactNode }) {
|
|
15
|
+
return <View style={styles.placeholder}>{children}</View>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PlaceholderInput
|
|
19
|
+
extends Pick<
|
|
20
|
+
ScreenScrollableProps,
|
|
21
|
+
'loading' | 'LoadingComponent' | 'EmptyComponent'
|
|
22
|
+
> {
|
|
23
|
+
/** Whether the container has nothing to render (empty `data`, no children). */
|
|
24
|
+
isEmpty: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The placeholder a scroll container should show instead of its content, or
|
|
29
|
+
* `null` to render the content. Loading wins over empty: while data is in
|
|
30
|
+
* flight there is nothing to be empty about yet.
|
|
31
|
+
*/
|
|
32
|
+
export function renderScreenPlaceholder({
|
|
33
|
+
loading,
|
|
34
|
+
LoadingComponent,
|
|
35
|
+
EmptyComponent,
|
|
36
|
+
isEmpty,
|
|
37
|
+
}: PlaceholderInput): ReactElement | null {
|
|
38
|
+
if (loading) {
|
|
39
|
+
return (
|
|
40
|
+
<ScreenPlaceholder>{LoadingComponent ?? <Spinner />}</ScreenPlaceholder>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
if (isEmpty && EmptyComponent) {
|
|
44
|
+
return <ScreenPlaceholder>{EmptyComponent}</ScreenPlaceholder>;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const styles = StyleSheet.create(() => ({
|
|
50
|
+
placeholder: {
|
|
51
|
+
flexGrow: 1,
|
|
52
|
+
alignItems: 'center',
|
|
53
|
+
justifyContent: 'center',
|
|
54
|
+
},
|
|
55
|
+
}));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Which axes of the `layout.screen.margin` token a scroll container pads its
|
|
5
|
+
* content with.
|
|
6
|
+
*
|
|
7
|
+
* `'y'` pads the top and bottom of the content area. It stacks on top of the
|
|
8
|
+
* structural insets — the header height and the safe area — which apply
|
|
9
|
+
* whether or not margins are on.
|
|
10
|
+
*/
|
|
11
|
+
export type ScreenMargins = 'all' | 'x' | 'y' | 'none';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Props shared by `Screen.ScrollView`, `Screen.FlatList` and
|
|
15
|
+
* `Screen.FlashList`, so a screen can swap one container for another without
|
|
16
|
+
* relearning its API.
|
|
17
|
+
*/
|
|
18
|
+
export interface ScreenScrollableProps {
|
|
19
|
+
/** Lay content out along the x axis. Defaults to `false`. */
|
|
20
|
+
horizontal?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Renders a centred placeholder in the content area instead of the content —
|
|
23
|
+
* spares every screen from hand-rolling a loading branch while its data
|
|
24
|
+
* arrives. The `Screen.Header` still renders, so the chrome stays put.
|
|
25
|
+
*/
|
|
26
|
+
loading?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Node shown while `loading`. Defaults to a `Spinner`; it is centred in the
|
|
29
|
+
* content area either way.
|
|
30
|
+
*/
|
|
31
|
+
LoadingComponent?: ReactNode;
|
|
32
|
+
/**
|
|
33
|
+
* Node shown, centred, when there is nothing to render and `loading` is
|
|
34
|
+
* false — an empty `data` array on the list containers, no children on
|
|
35
|
+
* `Screen.ScrollView`. Omit it to render nothing.
|
|
36
|
+
*/
|
|
37
|
+
EmptyComponent?: ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* Which axes get `layout.screen.margin` padding. Defaults to the enclosing
|
|
40
|
+
* `Screen`'s `margins`.
|
|
41
|
+
*/
|
|
42
|
+
margins?: ScreenMargins;
|
|
43
|
+
}
|
|
@@ -1,17 +1,31 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
type ComponentType,
|
|
3
|
+
createElement,
|
|
4
|
+
isValidElement,
|
|
5
|
+
type ReactElement,
|
|
6
|
+
} from 'react';
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
import type { ScreenMargins } from './types';
|
|
9
|
+
|
|
10
|
+
export type ListHeaderComponent =
|
|
11
|
+
| ComponentType
|
|
12
|
+
| ReactElement
|
|
13
|
+
| null
|
|
14
|
+
| undefined;
|
|
15
|
+
|
|
16
|
+
/** Renders a `ListHeaderComponent`-shaped prop (element, component, or nullish) into an element. */
|
|
17
|
+
export function renderListHeader(
|
|
18
|
+
ListHeaderComponent: ListHeaderComponent
|
|
19
|
+
): ReactElement | null {
|
|
20
|
+
if (!ListHeaderComponent) return null;
|
|
21
|
+
if (isValidElement(ListHeaderComponent)) return ListHeaderComponent;
|
|
22
|
+
return createElement(ListHeaderComponent);
|
|
17
23
|
}
|
|
24
|
+
|
|
25
|
+
/** Whether `margins` pads the horizontal axis. */
|
|
26
|
+
export const hasXMargin = (margins: ScreenMargins): boolean =>
|
|
27
|
+
margins === 'all' || margins === 'x';
|
|
28
|
+
|
|
29
|
+
/** Whether `margins` pads the vertical axis (the bottom of the content area). */
|
|
30
|
+
export const hasYMargin = (margins: ScreenMargins): boolean =>
|
|
31
|
+
margins === 'all' || margins === 'y';
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { type ComponentRef, forwardRef } from 'react';
|
|
2
|
-
import type { View
|
|
2
|
+
import type { View } from 'react-native';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import { getDefaultRadius, getSurfaceElevation } from '#theme/configure';
|
|
4
|
+
import { getDefaultRadius, getSurfaceElevation } from '#theme/registry';
|
|
7
5
|
import type { ColorToken, ElevationKey } from '#theme/types';
|
|
8
6
|
|
|
9
7
|
import { type BorderWidthInput, Box, type BoxProps } from '../box';
|
|
@@ -42,10 +40,16 @@ const VARIANT_BORDER: Record<
|
|
|
42
40
|
transparent: {},
|
|
43
41
|
};
|
|
44
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Picks the elevation *token key* — never a resolved shadow, so this stays
|
|
45
|
+
* theme-independent. `transparent` collapses to `'none'` because a shadow needs
|
|
46
|
+
* an opaque fill behind it to render.
|
|
47
|
+
*/
|
|
45
48
|
const resolveElevation = (
|
|
46
49
|
variant: SurfaceVariant,
|
|
47
50
|
elevation?: ElevationKey
|
|
48
51
|
): ElevationKey => {
|
|
52
|
+
if (variant === 'transparent') return 'none';
|
|
49
53
|
if (elevation !== undefined) return elevation;
|
|
50
54
|
if (variant === 'raised') return 'sm';
|
|
51
55
|
return getSurfaceElevation();
|
|
@@ -57,6 +61,13 @@ const resolveElevation = (
|
|
|
57
61
|
* default (overridable via `radius`), and an optional drop shadow via
|
|
58
62
|
* `elevation`. All other Box style-props are accepted.
|
|
59
63
|
*
|
|
64
|
+
* Every visual decision is expressed as a **theme token key** handed to `Box`,
|
|
65
|
+
* which resolves it against the active theme. Surface therefore never reads the
|
|
66
|
+
* theme itself — no `useUnistyles()` in the render path, and no stylesheet
|
|
67
|
+
* crossing a component boundary (a Unistyles style handed to `Box` would land
|
|
68
|
+
* on a plain `View` the Babel plugin has not processed, and so would freeze at
|
|
69
|
+
* whichever theme was active when it was created).
|
|
70
|
+
*
|
|
60
71
|
* @example
|
|
61
72
|
* ```tsx
|
|
62
73
|
* <Surface variant="raised" elevation="md" p="4" radius="lg">
|
|
@@ -66,24 +77,10 @@ const resolveElevation = (
|
|
|
66
77
|
*/
|
|
67
78
|
export const Surface = forwardRef<ComponentRef<typeof View>, SurfaceProps>(
|
|
68
79
|
function Surface(
|
|
69
|
-
{
|
|
70
|
-
variant = 'base',
|
|
71
|
-
elevation,
|
|
72
|
-
radius,
|
|
73
|
-
borderWidth,
|
|
74
|
-
borderColor,
|
|
75
|
-
style,
|
|
76
|
-
...rest
|
|
77
|
-
},
|
|
80
|
+
{ variant = 'base', elevation, radius, borderWidth, borderColor, ...rest },
|
|
78
81
|
ref
|
|
79
82
|
) {
|
|
80
|
-
const { theme } = useUnistyles();
|
|
81
|
-
const resolvedElevation = resolveElevation(variant, elevation);
|
|
82
83
|
const variantBorder = VARIANT_BORDER[variant];
|
|
83
|
-
const shadowStyle =
|
|
84
|
-
variant !== 'transparent' && resolvedElevation !== 'none'
|
|
85
|
-
? (theme.elevation[resolvedElevation] as ViewStyle)
|
|
86
|
-
: undefined;
|
|
87
84
|
|
|
88
85
|
return (
|
|
89
86
|
<Box
|
|
@@ -92,7 +89,7 @@ export const Surface = forwardRef<ComponentRef<typeof View>, SurfaceProps>(
|
|
|
92
89
|
radius={radius ?? getDefaultRadius('surface')}
|
|
93
90
|
borderWidth={borderWidth ?? variantBorder.borderWidth}
|
|
94
91
|
borderColor={borderColor ?? variantBorder.borderColor}
|
|
95
|
-
|
|
92
|
+
elevation={resolveElevation(variant, elevation)}
|
|
96
93
|
{...rest}
|
|
97
94
|
/>
|
|
98
95
|
);
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type TextStyle,
|
|
6
6
|
} from 'react-native';
|
|
7
7
|
|
|
8
|
-
import { useUnistyles } from 'react-native-unistyles';
|
|
8
|
+
import { StyleSheet, useUnistyles } from 'react-native-unistyles';
|
|
9
9
|
import type {
|
|
10
10
|
ColorToken,
|
|
11
11
|
FontWeightToken,
|
|
@@ -77,7 +77,11 @@ export const Text = forwardRef<ComponentRef<typeof RNText>, TextProps>(
|
|
|
77
77
|
const v = theme.typography.variants[resolvedVariant];
|
|
78
78
|
const effectiveWeight: FontWeightToken = weight ?? v.fontWeight;
|
|
79
79
|
const fontFamily = theme.typography.fonts[effectiveWeight];
|
|
80
|
-
const
|
|
80
|
+
const italicFamily = italic
|
|
81
|
+
? theme.typography.fontsItalic?.[effectiveWeight]
|
|
82
|
+
: undefined;
|
|
83
|
+
const resolvedFamily = italicFamily ?? fontFamily;
|
|
84
|
+
const isSystemFont = resolvedFamily === SYSTEM_FONT;
|
|
81
85
|
|
|
82
86
|
const base: Mutable<TextStyle> = {
|
|
83
87
|
fontSize: v.fontSize,
|
|
@@ -92,10 +96,13 @@ export const Text = forwardRef<ComponentRef<typeof RNText>, TextProps>(
|
|
|
92
96
|
};
|
|
93
97
|
// System font: apply numeric weight. Custom font: family encodes the weight.
|
|
94
98
|
if (isSystemFont) base.fontWeight = FONT_WEIGHT_VALUE[effectiveWeight];
|
|
95
|
-
else base.fontFamily =
|
|
99
|
+
else base.fontFamily = resolvedFamily;
|
|
96
100
|
|
|
97
101
|
if (align) base.textAlign = align;
|
|
98
|
-
|
|
102
|
+
// A dedicated italic family already renders slanted; `fontStyle` is only
|
|
103
|
+
// needed (and only works) for the system font or as a best-effort
|
|
104
|
+
// fallback when no italic family is configured for this weight.
|
|
105
|
+
if (italic && !italicFamily) base.fontStyle = 'italic';
|
|
99
106
|
if (underline && strikeThrough)
|
|
100
107
|
base.textDecorationLine = 'underline line-through';
|
|
101
108
|
else if (underline) base.textDecorationLine = 'underline';
|
|
@@ -104,7 +111,7 @@ export const Text = forwardRef<ComponentRef<typeof RNText>, TextProps>(
|
|
|
104
111
|
return (
|
|
105
112
|
<RNText
|
|
106
113
|
ref={ref}
|
|
107
|
-
style={[base, style]}
|
|
114
|
+
style={[styles.root, base, style]}
|
|
108
115
|
maxFontSizeMultiplier={maxFontSizeMultiplier ?? v.maxFontSizeMultiplier}
|
|
109
116
|
numberOfLines={isTruncated ? 1 : numberOfLines}
|
|
110
117
|
ellipsizeMode={isTruncated ? 'tail' : ellipsizeMode}
|
|
@@ -113,3 +120,14 @@ export const Text = forwardRef<ComponentRef<typeof RNText>, TextProps>(
|
|
|
113
120
|
);
|
|
114
121
|
}
|
|
115
122
|
);
|
|
123
|
+
|
|
124
|
+
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
125
|
+
//
|
|
126
|
+
// Text has no static style of its own — this stylesheet exists so the
|
|
127
|
+
// Unistyles Babel plugin swaps the `RNText` imported above for its Unistyles-aware
|
|
128
|
+
// counterpart, which registers the ShadowNode with the native side. That is what
|
|
129
|
+
// keeps a Unistyles style passed in through `style` updating on theme change;
|
|
130
|
+
// without it the style freezes at whichever theme was active when it was
|
|
131
|
+
// created, and only callers that happen to subscribe to the theme themselves
|
|
132
|
+
// would appear to work.
|
|
133
|
+
const styles = StyleSheet.create(() => ({ root: {} }));
|
|
@@ -78,7 +78,7 @@ function getToastColors(variant: ToastVariant, theme: TerraTheme): ToastColors {
|
|
|
78
78
|
case 'accent':
|
|
79
79
|
return {
|
|
80
80
|
...base,
|
|
81
|
-
title: get('action.fg.subtle'),
|
|
81
|
+
title: get('action.fg.primary.subtle'),
|
|
82
82
|
icon: get('text.accent'),
|
|
83
83
|
actionBg: get('action.bg.primary'),
|
|
84
84
|
actionFg: get('action.fg.primary'),
|
|
@@ -89,18 +89,18 @@ function getToastColors(variant: ToastVariant, theme: TerraTheme): ToastColors {
|
|
|
89
89
|
case 'danger':
|
|
90
90
|
return {
|
|
91
91
|
...base,
|
|
92
|
-
title: get(`status
|
|
93
|
-
icon: get(`status
|
|
94
|
-
actionBg: get(`status
|
|
95
|
-
actionFg: get(`status
|
|
92
|
+
title: get(`status.${variant}.bg`),
|
|
93
|
+
icon: get(`status.${variant}.bg`),
|
|
94
|
+
actionBg: get(`status.${variant}.bg`),
|
|
95
|
+
actionFg: get(`status.${variant}.fg`),
|
|
96
96
|
};
|
|
97
97
|
case 'default':
|
|
98
98
|
return {
|
|
99
99
|
...base,
|
|
100
100
|
title: get('text.default'),
|
|
101
101
|
icon: get('text.subtle'),
|
|
102
|
-
actionBg: get('action.bg.
|
|
103
|
-
actionFg: get('action.fg.
|
|
102
|
+
actionBg: get('action.bg.secondary'),
|
|
103
|
+
actionFg: get('action.fg.secondary'),
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -242,7 +242,7 @@ export interface ToastActionProps
|
|
|
242
242
|
function renderActionChildren(children: ReactNode, color: string): ReactNode {
|
|
243
243
|
if (typeof children === 'string' || typeof children === 'number') {
|
|
244
244
|
return (
|
|
245
|
-
<Text variant="label-
|
|
245
|
+
<Text variant="label-md" weight="semibold" style={{ color }}>
|
|
246
246
|
{children}
|
|
247
247
|
</Text>
|
|
248
248
|
);
|