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.
Files changed (59) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/lib/module/components/box/Box.js +35 -10
  3. package/lib/module/components/box/Box.js.map +1 -1
  4. package/lib/module/components/header/hooks/use-header-color.js +16 -0
  5. package/lib/module/components/header/hooks/use-header-color.js.map +1 -0
  6. package/lib/module/components/header/hooks/use-large-title-header.js +81 -0
  7. package/lib/module/components/header/hooks/use-large-title-header.js.map +1 -0
  8. package/lib/module/components/header/parts/HeaderAccessory.js +21 -6
  9. package/lib/module/components/header/parts/HeaderAccessory.js.map +1 -1
  10. package/lib/module/components/header/parts/HeaderBar.js +122 -0
  11. package/lib/module/components/header/parts/HeaderBar.js.map +1 -0
  12. package/lib/module/components/header/parts/HeaderGradientOverlay.js +20 -76
  13. package/lib/module/components/header/parts/HeaderGradientOverlay.js.map +1 -1
  14. package/lib/module/components/header/parts/HeaderSurface.js +16 -37
  15. package/lib/module/components/header/parts/HeaderSurface.js.map +1 -1
  16. package/lib/module/components/header/variants/LargeTitleHeader/index.js +51 -388
  17. package/lib/module/components/header/variants/LargeTitleHeader/index.js.map +1 -1
  18. package/lib/module/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.js +72 -0
  19. package/lib/module/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.js.map +1 -0
  20. package/lib/module/components/header/variants/TitleHeader/index.js +32 -161
  21. package/lib/module/components/header/variants/TitleHeader/index.js.map +1 -1
  22. package/lib/module/components/surface/Surface.js +2 -0
  23. package/lib/module/components/surface/Surface.js.map +1 -1
  24. package/lib/typescript/src/components/box/Box.d.ts +28 -4
  25. package/lib/typescript/src/components/box/Box.d.ts.map +1 -1
  26. package/lib/typescript/src/components/header/hooks/use-header-color.d.ts +4 -0
  27. package/lib/typescript/src/components/header/hooks/use-header-color.d.ts.map +1 -0
  28. package/lib/typescript/src/components/header/hooks/use-large-title-header.d.ts +36 -0
  29. package/lib/typescript/src/components/header/hooks/use-large-title-header.d.ts.map +1 -0
  30. package/lib/typescript/src/components/header/parts/HeaderAccessory.d.ts +8 -5
  31. package/lib/typescript/src/components/header/parts/HeaderAccessory.d.ts.map +1 -1
  32. package/lib/typescript/src/components/header/parts/HeaderBar.d.ts +17 -0
  33. package/lib/typescript/src/components/header/parts/HeaderBar.d.ts.map +1 -0
  34. package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts +12 -15
  35. package/lib/typescript/src/components/header/parts/HeaderGradientOverlay.d.ts.map +1 -1
  36. package/lib/typescript/src/components/header/parts/HeaderSurface.d.ts +8 -9
  37. package/lib/typescript/src/components/header/parts/HeaderSurface.d.ts.map +1 -1
  38. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts +3 -23
  39. package/lib/typescript/src/components/header/variants/LargeTitleHeader/index.d.ts.map +1 -1
  40. package/lib/typescript/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.d.ts +11 -0
  41. package/lib/typescript/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.d.ts.map +1 -0
  42. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts +3 -19
  43. package/lib/typescript/src/components/header/variants/TitleHeader/index.d.ts.map +1 -1
  44. package/lib/typescript/src/components/surface/Surface.d.ts +1 -1
  45. package/lib/typescript/src/components/surface/Surface.d.ts.map +1 -1
  46. package/lib/typescript/src/components/toast/Toast.d.ts +1 -1
  47. package/lib/typescript/src/components/toast/Toast.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/src/components/box/Box.tsx +59 -16
  50. package/src/components/header/hooks/use-header-color.ts +14 -0
  51. package/src/components/header/hooks/use-large-title-header.ts +78 -0
  52. package/src/components/header/parts/HeaderAccessory.tsx +26 -7
  53. package/src/components/header/parts/HeaderBar.tsx +125 -0
  54. package/src/components/header/parts/HeaderGradientOverlay.tsx +18 -70
  55. package/src/components/header/parts/HeaderSurface.tsx +16 -35
  56. package/src/components/header/variants/LargeTitleHeader/index.tsx +44 -371
  57. package/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.tsx +88 -0
  58. package/src/components/header/variants/TitleHeader/index.tsx +32 -149
  59. 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, useCallback, useContext } from "react";
2
- import { Text, View } from "react-native";
1
+ import { type ReactNode, useContext } from "react";
2
+ import { View } from "react-native";
3
3
 
4
- import { StyleSheet, useUnistyles } from "react-native-unistyles";
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 { Toolbar } from "../../../toolbar";
12
- import { HeaderDismissButton, type HeaderDismissProps } from "../../header-buttons";
9
+ import type { HeaderDismissProps } from "../../header-buttons";
10
+ import { useHeaderColor } from "../../hooks/use-header-color";
13
11
  import { HeaderAccessory } from "../../parts/HeaderAccessory";
14
- import { HeaderSurface } from "../../parts/HeaderSurface";
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 colorToken = background ?? bg;
95
- const bgColor =
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(isTransparent, bgColor, screen !== null)} />
132
- {/* The fade covers the bar and any accessory as one surface, so content
133
- scrolling under the header meets a single boundary rather than two. */}
134
- <View style={styles.barGroup}>
135
- <HeaderSurface color={gradientColor} />
136
- <View style={styles.bar}>
137
- {leading != null && <Toolbar>{leading}</Toolbar>}
138
- {/* Text can never be the touch target, but custom content may be, so its
139
- container passes touches through to it instead of swallowing them. */}
140
- <View style={styles.titleContainer} pointerEvents={isCustomTitle(title) ? "box-none" : "none"}>
141
- {isTextTitle(title)
142
- ? String(title).trim().length > 0 && <Text style={styles.title}>{title}</Text>
143
- : title}
144
- </View>
145
- {trailing != null && <Toolbar style={styles.trailing}>{trailing}</Toolbar>}
146
- </View>
147
- {accessory != null && (
148
- <HeaderAccessory padding={accessoryPadding} onHeightChange={handleAccessoryHeightChange}>
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((theme, runtime) => ({
158
- container: {
159
- position: "absolute",
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: isTransparent ? "transparent" : bgColor,
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}