react-native-terra-ui 0.11.10 → 0.11.12
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 +17 -0
- package/lib/module/components/box/Box.js +35 -10
- package/lib/module/components/box/Box.js.map +1 -1
- package/lib/module/components/header/hooks/use-header-color.js +16 -0
- package/lib/module/components/header/hooks/use-header-color.js.map +1 -0
- package/lib/module/components/header/hooks/use-large-title-header.js +81 -0
- package/lib/module/components/header/hooks/use-large-title-header.js.map +1 -0
- package/lib/module/components/header/parts/HeaderAccessory.js +21 -6
- package/lib/module/components/header/parts/HeaderAccessory.js.map +1 -1
- package/lib/module/components/header/parts/HeaderBar.js +122 -0
- package/lib/module/components/header/parts/HeaderBar.js.map +1 -0
- package/lib/module/components/header/parts/HeaderGradientOverlay.js +20 -76
- package/lib/module/components/header/parts/HeaderGradientOverlay.js.map +1 -1
- package/lib/module/components/header/parts/HeaderSurface.js +16 -37
- package/lib/module/components/header/parts/HeaderSurface.js.map +1 -1
- package/lib/module/components/header/variants/LargeTitleHeader/index.js +51 -388
- package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
- package/lib/module/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.js +72 -0
- package/lib/module/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.js.map +1 -0
- package/lib/module/components/header/variants/TitleHeader/index.js +32 -161
- package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
- package/lib/module/components/surface/Surface.js +2 -0
- package/lib/module/components/surface/Surface.js.map +1 -1
- package/lib/typescript/src/components/box/Box.d.ts +28 -4
- package/lib/typescript/src/components/box/Box.d.ts.map +1 -1
- package/lib/typescript/src/components/header/hooks/use-header-color.d.ts +4 -0
- package/lib/typescript/src/components/header/hooks/use-header-color.d.ts.map +1 -0
- package/lib/typescript/src/components/header/hooks/use-large-title-header.d.ts +36 -0
- package/lib/typescript/src/components/header/hooks/use-large-title-header.d.ts.map +1 -0
- package/lib/typescript/src/components/header/parts/HeaderAccessory.d.ts +8 -5
- package/lib/typescript/src/components/header/parts/HeaderAccessory.d.ts.map +1 -1
- package/lib/typescript/src/components/header/parts/HeaderBar.d.ts +17 -0
- package/lib/typescript/src/components/header/parts/HeaderBar.d.ts.map +1 -0
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts +12 -15
- package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts.map +1 -1
- package/lib/typescript/src/components/header/parts/HeaderSurface.d.ts +8 -9
- package/lib/typescript/src/components/header/parts/HeaderSurface.d.ts.map +1 -1
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +3 -23
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.d.ts +11 -0
- package/lib/typescript/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.d.ts.map +1 -0
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +3 -19
- package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
- package/lib/typescript/src/components/surface/Surface.d.ts +1 -1
- package/lib/typescript/src/components/surface/Surface.d.ts.map +1 -1
- package/lib/typescript/src/components/toast/Toast.d.ts +1 -1
- package/lib/typescript/src/components/toast/Toast.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/box/Box.tsx +59 -16
- package/src/components/header/hooks/use-header-color.ts +14 -0
- package/src/components/header/hooks/use-large-title-header.ts +78 -0
- package/src/components/header/parts/HeaderAccessory.tsx +26 -7
- package/src/components/header/parts/HeaderBar.tsx +125 -0
- package/src/components/header/parts/HeaderGradientOverlay.tsx +18 -70
- package/src/components/header/parts/HeaderSurface.tsx +16 -35
- package/src/components/header/variants/LargeTitleHeader/index.tsx +44 -371
- package/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.tsx +88 -0
- package/src/components/header/variants/TitleHeader/index.tsx +32 -149
- package/src/components/surface/Surface.tsx +2 -1
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import { type LayoutChangeEvent, Text } from "react-native";
|
|
3
|
+
|
|
4
|
+
import Animated from "react-native-reanimated";
|
|
5
|
+
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
6
|
+
|
|
7
|
+
import { FONT_WEIGHT_VALUE } from "#utils/typography";
|
|
8
|
+
import { isTextTitle } from "../../../utils";
|
|
9
|
+
import type { LargeTitleHeaderProps } from "..";
|
|
10
|
+
|
|
11
|
+
export function LargeTitleContent({
|
|
12
|
+
title,
|
|
13
|
+
overline,
|
|
14
|
+
subtitle,
|
|
15
|
+
hasAccessoryBelow,
|
|
16
|
+
onHeightChange,
|
|
17
|
+
animatedStyle,
|
|
18
|
+
}: Pick<LargeTitleHeaderProps, "title" | "overline" | "subtitle"> & {
|
|
19
|
+
/** Reserve a gap before a sticky accessory below this block. */
|
|
20
|
+
hasAccessoryBelow: boolean;
|
|
21
|
+
/** Reports the large-title block's layout height in dp. */
|
|
22
|
+
onHeightChange?: (height: number) => void;
|
|
23
|
+
animatedStyle: ComponentProps<typeof Animated.View>["style"];
|
|
24
|
+
}) {
|
|
25
|
+
const { theme } = useUnistyles();
|
|
26
|
+
// Layout height excludes the collapse transform.
|
|
27
|
+
const handleLayout = (event: LayoutChangeEvent) => onHeightChange?.(event.nativeEvent.layout.height);
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<Animated.View style={[styles.portalContent(hasAccessoryBelow), animatedStyle]} onLayout={handleLayout}>
|
|
31
|
+
{!!overline && (
|
|
32
|
+
<Text
|
|
33
|
+
style={styles.overline}
|
|
34
|
+
maxFontSizeMultiplier={theme.typography.variants["label-sm"].maxFontSizeMultiplier}
|
|
35
|
+
>
|
|
36
|
+
{overline}
|
|
37
|
+
</Text>
|
|
38
|
+
)}
|
|
39
|
+
{isTextTitle(title) ? (
|
|
40
|
+
<Text
|
|
41
|
+
style={styles.largeTitle}
|
|
42
|
+
maxFontSizeMultiplier={theme.typography.variants["headline-lg"].maxFontSizeMultiplier}
|
|
43
|
+
>
|
|
44
|
+
{title}
|
|
45
|
+
</Text>
|
|
46
|
+
) : (
|
|
47
|
+
title
|
|
48
|
+
)}
|
|
49
|
+
{!!subtitle && (
|
|
50
|
+
<Text
|
|
51
|
+
style={styles.subtitle}
|
|
52
|
+
maxFontSizeMultiplier={theme.typography.variants["body-lg"].maxFontSizeMultiplier}
|
|
53
|
+
>
|
|
54
|
+
{subtitle}
|
|
55
|
+
</Text>
|
|
56
|
+
)}
|
|
57
|
+
</Animated.View>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const styles = StyleSheet.create((theme) => ({
|
|
62
|
+
portalContent: (hasAccessoryBelow: boolean) => ({
|
|
63
|
+
gap: theme.spacing["1"] / 2,
|
|
64
|
+
// Include the gap in the measured height used to position the accessory.
|
|
65
|
+
paddingBottom: hasAccessoryBelow ? theme.spacing["2"] : 0,
|
|
66
|
+
paddingHorizontal: theme.layout.screen.padding.x,
|
|
67
|
+
}),
|
|
68
|
+
overline: {
|
|
69
|
+
fontSize: theme.typography.variants["label-sm"].fontSize,
|
|
70
|
+
lineHeight: theme.typography.variants["label-sm"].lineHeight,
|
|
71
|
+
letterSpacing: theme.typography.variants["label-sm"].letterSpacing,
|
|
72
|
+
fontWeight: FONT_WEIGHT_VALUE[theme.typography.variants["label-sm"].fontWeight],
|
|
73
|
+
color: (theme.color as unknown as Record<string, string | undefined>)["text.tertiary"] ?? "",
|
|
74
|
+
},
|
|
75
|
+
largeTitle: {
|
|
76
|
+
fontSize: theme.typography.variants["headline-lg"].fontSize,
|
|
77
|
+
lineHeight: theme.typography.variants["headline-lg"].lineHeight,
|
|
78
|
+
letterSpacing: theme.typography.variants["headline-lg"].letterSpacing,
|
|
79
|
+
fontWeight: FONT_WEIGHT_VALUE[theme.typography.variants["headline-lg"].fontWeight],
|
|
80
|
+
color: (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ?? "",
|
|
81
|
+
},
|
|
82
|
+
subtitle: {
|
|
83
|
+
fontSize: theme.typography.variants["body-lg"].fontSize,
|
|
84
|
+
lineHeight: theme.typography.variants["body-lg"].lineHeight,
|
|
85
|
+
letterSpacing: theme.typography.variants["body-lg"].letterSpacing,
|
|
86
|
+
color: (theme.color as unknown as Record<string, string | undefined>)["text.tertiary"] ?? "",
|
|
87
|
+
},
|
|
88
|
+
}));
|
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import { type ReactNode,
|
|
2
|
-
import {
|
|
1
|
+
import { type ReactNode, useContext } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
3
|
|
|
4
|
-
import { StyleSheet
|
|
4
|
+
import { StyleSheet } from "react-native-unistyles";
|
|
5
5
|
|
|
6
6
|
import type { ColorToken } from "#theme/types";
|
|
7
|
-
import { resolveThemeColor } from "#utils/resolve-theme-color";
|
|
8
|
-
import { FONT_WEIGHT_VALUE } from "#utils/typography";
|
|
9
7
|
import { useHeaderBackground } from "../../../screen/parts/ScreenHeaderAppearance";
|
|
10
8
|
import { ScreenContext } from "../../../screen/ScreenContext";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
9
|
+
import type { HeaderDismissProps } from "../../header-buttons";
|
|
10
|
+
import { useHeaderColor } from "../../hooks/use-header-color";
|
|
13
11
|
import { HeaderAccessory } from "../../parts/HeaderAccessory";
|
|
14
|
-
import {
|
|
12
|
+
import { HeaderBar } from "../../parts/HeaderBar";
|
|
15
13
|
import type { HeaderAccessoryPadding } from "../../types";
|
|
16
|
-
import { isCustomTitle, isTextTitle } from "../../utils";
|
|
17
14
|
|
|
18
15
|
export interface TitleHeaderProps extends HeaderDismissProps {
|
|
19
16
|
/**
|
|
@@ -56,23 +53,7 @@ export interface TitleHeaderProps extends HeaderDismissProps {
|
|
|
56
53
|
bg?: ColorToken;
|
|
57
54
|
}
|
|
58
55
|
|
|
59
|
-
/**
|
|
60
|
-
* Compact title bar (leading slot · title · trailing slot) with a top safe-area
|
|
61
|
-
* inset outside Screen. Within Screen, the root owns safe-area clearance. The inline counterpart to `Header.LargeTitle`; use it for screens that
|
|
62
|
-
* don't need a collapsing large title.
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```tsx
|
|
66
|
-
* <Screen.Header>
|
|
67
|
-
* <Header.Title
|
|
68
|
-
* title="Stats"
|
|
69
|
-
* trailingActions={
|
|
70
|
-
* <Toolbar.Button icon="add" accessibilityLabel="Add" onPress={add} />
|
|
71
|
-
* }
|
|
72
|
-
* />
|
|
73
|
-
* </Screen.Header>
|
|
74
|
-
* ```
|
|
75
|
-
*/
|
|
56
|
+
/** Compact header. Screen owns the safe area when present; standalone headers reserve it here. */
|
|
76
57
|
export function TitleHeader({
|
|
77
58
|
title,
|
|
78
59
|
leadingActions,
|
|
@@ -81,140 +62,42 @@ export function TitleHeader({
|
|
|
81
62
|
RightToolbar,
|
|
82
63
|
accessory,
|
|
83
64
|
accessoryPadding = "x",
|
|
84
|
-
dismissAction = "none",
|
|
85
|
-
navigation,
|
|
86
|
-
onDismiss,
|
|
87
65
|
titleAlignment = "center",
|
|
88
66
|
background,
|
|
89
67
|
bg,
|
|
68
|
+
...dismissProps
|
|
90
69
|
}: TitleHeaderProps) {
|
|
91
|
-
const { theme } = useUnistyles();
|
|
92
|
-
|
|
93
70
|
const screen = useContext(ScreenContext);
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
(colorToken ? resolveThemeColor(colorToken, theme) : screen?.background) ?? theme.color["surface.canvas"];
|
|
97
|
-
useHeaderBackground(bgColor);
|
|
98
|
-
const leadingContent = leadingActions !== undefined ? leadingActions : LeftToolbar;
|
|
99
|
-
const trailingContent = trailingActions !== undefined ? trailingActions : RightToolbar;
|
|
100
|
-
const setAccessoryInset = screen?.setHeaderAccessoryHeight;
|
|
101
|
-
// Compact headers always reserve the accessory height as top padding.
|
|
102
|
-
const handleAccessoryHeightChange = useCallback(
|
|
103
|
-
(height: number) => setAccessoryInset?.(height),
|
|
104
|
-
[setAccessoryInset]
|
|
105
|
-
);
|
|
106
|
-
const isTransparent = bgColor === "transparent";
|
|
107
|
-
const gradientColor = bgColor;
|
|
108
|
-
styles.useVariants({ titleAlignment });
|
|
109
|
-
|
|
110
|
-
const leading =
|
|
111
|
-
dismissAction === "back" ? (
|
|
112
|
-
<>
|
|
113
|
-
<HeaderDismissButton dismissAction={dismissAction} navigation={navigation} onDismiss={onDismiss} />
|
|
114
|
-
{leadingContent}
|
|
115
|
-
</>
|
|
116
|
-
) : (
|
|
117
|
-
leadingContent
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
const trailing =
|
|
121
|
-
dismissAction === "close" ? (
|
|
122
|
-
<>
|
|
123
|
-
{trailingContent}
|
|
124
|
-
<HeaderDismissButton dismissAction={dismissAction} navigation={navigation} onDismiss={onDismiss} />
|
|
125
|
-
</>
|
|
126
|
-
) : (
|
|
127
|
-
trailingContent
|
|
128
|
-
);
|
|
71
|
+
const color = useHeaderColor(background ?? bg);
|
|
72
|
+
useHeaderBackground(color);
|
|
129
73
|
return (
|
|
130
74
|
<View style={styles.container}>
|
|
131
|
-
<View style={styles.safeArea(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
{accessory}
|
|
150
|
-
</HeaderAccessory>
|
|
151
|
-
)}
|
|
152
|
-
</View>
|
|
75
|
+
<View style={styles.safeArea(color, screen !== null)} />
|
|
76
|
+
<HeaderBar
|
|
77
|
+
{...dismissProps}
|
|
78
|
+
title={title}
|
|
79
|
+
titleAlignment={titleAlignment}
|
|
80
|
+
color={color}
|
|
81
|
+
leading={leadingActions !== undefined ? leadingActions : LeftToolbar}
|
|
82
|
+
trailing={trailingActions !== undefined ? trailingActions : RightToolbar}
|
|
83
|
+
/>
|
|
84
|
+
{accessory != null && (
|
|
85
|
+
<HeaderAccessory
|
|
86
|
+
padding={accessoryPadding}
|
|
87
|
+
color={color}
|
|
88
|
+
onHeightChange={screen?.setHeaderAccessoryHeight}
|
|
89
|
+
>
|
|
90
|
+
{accessory}
|
|
91
|
+
</HeaderAccessory>
|
|
92
|
+
)}
|
|
153
93
|
</View>
|
|
154
94
|
);
|
|
155
95
|
}
|
|
156
96
|
|
|
157
|
-
const styles = StyleSheet.create((
|
|
158
|
-
container: {
|
|
159
|
-
|
|
160
|
-
top: 0,
|
|
161
|
-
left: 0,
|
|
162
|
-
right: 0,
|
|
163
|
-
zIndex: 1000,
|
|
164
|
-
},
|
|
165
|
-
safeArea: (isTransparent: boolean, bgColor: string, withinScreen: boolean) => ({
|
|
97
|
+
const styles = StyleSheet.create((_, runtime) => ({
|
|
98
|
+
container: { position: "absolute", top: 0, left: 0, right: 0, zIndex: 1000 },
|
|
99
|
+
safeArea: (color: string, withinScreen: boolean) => ({
|
|
166
100
|
height: withinScreen ? 0 : runtime.insets.top,
|
|
167
|
-
backgroundColor:
|
|
101
|
+
backgroundColor: color,
|
|
168
102
|
}),
|
|
169
|
-
// Anchors the absolutely positioned surface over the bar and the accessory both.
|
|
170
|
-
barGroup: { position: "relative" },
|
|
171
|
-
bar: {
|
|
172
|
-
height: theme.layout.header.height,
|
|
173
|
-
flexDirection: "row",
|
|
174
|
-
alignItems: "center",
|
|
175
|
-
paddingHorizontal: theme.layout.screen.padding.x,
|
|
176
|
-
gap: theme.spacing["3"],
|
|
177
|
-
justifyContent: "space-between",
|
|
178
|
-
},
|
|
179
|
-
// A centered title is absolutely positioned, so it leaves the flex row. With
|
|
180
|
-
// no leading slot the trailing toolbar would be the only in-flow child and
|
|
181
|
-
// `space-between` would park it on the left — the auto margin pins it right.
|
|
182
|
-
trailing: {
|
|
183
|
-
justifyContent: "flex-end",
|
|
184
|
-
variants: {
|
|
185
|
-
titleAlignment: {
|
|
186
|
-
center: { marginLeft: "auto" },
|
|
187
|
-
left: {},
|
|
188
|
-
},
|
|
189
|
-
},
|
|
190
|
-
},
|
|
191
|
-
titleContainer: {
|
|
192
|
-
variants: {
|
|
193
|
-
titleAlignment: {
|
|
194
|
-
center: {
|
|
195
|
-
position: "absolute",
|
|
196
|
-
left: 0,
|
|
197
|
-
right: 0,
|
|
198
|
-
alignItems: "center",
|
|
199
|
-
},
|
|
200
|
-
left: {
|
|
201
|
-
flex: 1,
|
|
202
|
-
alignItems: "flex-start",
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
title: {
|
|
208
|
-
fontSize: theme.typography.variants["title-md"].fontSize,
|
|
209
|
-
lineHeight: theme.typography.variants["title-md"].lineHeight,
|
|
210
|
-
letterSpacing: theme.typography.variants["title-md"].letterSpacing,
|
|
211
|
-
fontWeight: FONT_WEIGHT_VALUE[theme.typography.variants["title-md"].fontWeight],
|
|
212
|
-
color: (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ?? "",
|
|
213
|
-
variants: {
|
|
214
|
-
titleAlignment: {
|
|
215
|
-
center: { textAlign: "center" },
|
|
216
|
-
left: { textAlign: "left" },
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
103
|
}));
|
|
@@ -69,7 +69,7 @@ const resolveElevation = (variant: SurfaceVariant, elevation?: ElevationKey): El
|
|
|
69
69
|
* ```
|
|
70
70
|
*/
|
|
71
71
|
export const Surface = forwardRef<ComponentRef<typeof View>, SurfaceProps>(function Surface(
|
|
72
|
-
{ variant = "base", elevation, radius, borderWidth, borderColor, ...rest },
|
|
72
|
+
{ variant = "base", p = "3", elevation, radius, borderWidth, borderColor, ...rest },
|
|
73
73
|
ref
|
|
74
74
|
) {
|
|
75
75
|
const variantBorder = VARIANT_BORDER[variant];
|
|
@@ -78,6 +78,7 @@ export const Surface = forwardRef<ComponentRef<typeof View>, SurfaceProps>(funct
|
|
|
78
78
|
<Box
|
|
79
79
|
ref={ref}
|
|
80
80
|
background={VARIANT_BG[variant]}
|
|
81
|
+
p={p}
|
|
81
82
|
radius={radius ?? getDefaultRadius("surface")}
|
|
82
83
|
borderWidth={borderWidth ?? variantBorder.borderWidth}
|
|
83
84
|
borderColor={borderColor ?? variantBorder.borderColor}
|