react-native-terra-ui 0.12.0 → 0.13.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 +23 -0
- package/lib/module/components/accordion/Accordion.js +14 -33
- package/lib/module/components/accordion/Accordion.js.map +1 -1
- package/lib/module/components/accordion/parts/AccordionContent.js +11 -15
- package/lib/module/components/accordion/parts/AccordionContent.js.map +1 -1
- package/lib/module/components/accordion/parts/AccordionTrigger.js +5 -5
- package/lib/module/components/accordion/parts/AccordionTrigger.js.map +1 -1
- package/lib/module/components/avatar/Avatar.js +15 -3
- package/lib/module/components/avatar/Avatar.js.map +1 -1
- package/lib/module/components/badge/Badge.js +31 -22
- package/lib/module/components/badge/Badge.js.map +1 -1
- package/lib/module/components/badge/utils.js +12 -3
- package/lib/module/components/badge/utils.js.map +1 -1
- package/lib/module/components/chip/Chip.js +1 -0
- package/lib/module/components/chip/Chip.js.map +1 -1
- package/lib/module/components/header/parts/HeaderBar.js +91 -82
- package/lib/module/components/header/parts/HeaderBar.js.map +1 -1
- package/lib/module/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.js +50 -33
- package/lib/module/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.js.map +1 -1
- package/lib/module/components/input/parts/InputField.js +96 -82
- package/lib/module/components/input/parts/InputField.js.map +1 -1
- package/lib/module/components/text/Text.js +10 -6
- package/lib/module/components/text/Text.js.map +1 -1
- package/lib/module/theme/tokens/dark.js +2 -2
- package/lib/module/theme/tokens/dark.js.map +1 -1
- package/lib/module/theme/tokens/light.js +2 -2
- package/lib/module/theme/tokens/light.js.map +1 -1
- package/lib/module/theme/tokens/primitives.js +24 -4
- package/lib/module/theme/tokens/primitives.js.map +1 -1
- package/lib/typescript/src/components/accordion/Accordion.d.ts.map +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts +1 -1
- package/lib/typescript/src/components/accordion/index.d.ts.map +1 -1
- package/lib/typescript/src/components/accordion/parts/AccordionContent.d.ts.map +1 -1
- package/lib/typescript/src/components/accordion/types.d.ts +0 -7
- package/lib/typescript/src/components/accordion/types.d.ts.map +1 -1
- package/lib/typescript/src/components/avatar/Avatar.d.ts.map +1 -1
- package/lib/typescript/src/components/badge/Badge.d.ts.map +1 -1
- package/lib/typescript/src/components/badge/utils.d.ts +6 -0
- package/lib/typescript/src/components/badge/utils.d.ts.map +1 -1
- package/lib/typescript/src/components/input/parts/InputField.d.ts.map +1 -1
- package/lib/typescript/src/theme/tokens/primitives.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/accordion/Accordion.tsx +5 -19
- package/src/components/accordion/index.ts +0 -1
- package/src/components/accordion/parts/AccordionContent.tsx +14 -12
- package/src/components/accordion/parts/AccordionTrigger.tsx +3 -3
- package/src/components/accordion/types.ts +0 -8
- package/src/components/avatar/Avatar.tsx +12 -2
- package/src/components/badge/Badge.tsx +25 -15
- package/src/components/badge/utils.ts +12 -3
- package/src/components/chip/Chip.tsx +1 -0
- package/src/components/header/parts/HeaderBar.tsx +80 -70
- package/src/components/header/variants/LargeTitleHeader/parts/LargeTitleContent.tsx +47 -29
- package/src/components/input/parts/InputField.tsx +63 -55
- package/src/components/text/Text.tsx +10 -6
- package/src/theme/tokens/dark.ts +2 -2
- package/src/theme/tokens/light.ts +2 -2
- package/src/theme/tokens/primitives.ts +24 -4
|
@@ -24,6 +24,15 @@ export function hasLabel(label: string | number | undefined): label is string |
|
|
|
24
24
|
return label !== undefined && label !== "";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* `true` when the label is one character, which the badge draws as a circle
|
|
29
|
+
* instead of a pill. Counts code points so a single emoji or astral character
|
|
30
|
+
* still counts as one.
|
|
31
|
+
*/
|
|
32
|
+
export function isSingleCharacter(label: string | number): boolean {
|
|
33
|
+
return Array.from(String(label)).length === 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
/**
|
|
28
37
|
* Outer diameter of the dot per size. The badge's own stylesheet repeats
|
|
29
38
|
* these as literals — the unistyles Babel plugin reads that object statically,
|
|
@@ -31,9 +40,9 @@ export function hasLabel(label: string | number | undefined): label is string |
|
|
|
31
40
|
* test suite is what keeps the two in step.
|
|
32
41
|
*/
|
|
33
42
|
export const DOT_SIZE: Record<BadgeSize, number> = {
|
|
34
|
-
sm:
|
|
35
|
-
md:
|
|
36
|
-
lg:
|
|
43
|
+
sm: 10,
|
|
44
|
+
md: 12,
|
|
45
|
+
lg: 14,
|
|
37
46
|
};
|
|
38
47
|
|
|
39
48
|
export interface AnchorPosition {
|
|
@@ -293,6 +293,7 @@ const styles = StyleSheet.create((theme) => {
|
|
|
293
293
|
// safe here even though they would resolve correctly under test.
|
|
294
294
|
label: {
|
|
295
295
|
paddingHorizontal: 2,
|
|
296
|
+
includeFontPadding: false,
|
|
296
297
|
fontFamily: isSystemLabelFont ? undefined : labelFont,
|
|
297
298
|
fontWeight: isSystemLabelFont ? FONT_WEIGHT_VALUE.semibold : undefined,
|
|
298
299
|
variants: {
|
|
@@ -5,7 +5,7 @@ import Animated from "react-native-reanimated";
|
|
|
5
5
|
import { ScrollToTopGuard } from "react-native-screens/experimental";
|
|
6
6
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
7
7
|
|
|
8
|
-
import { FONT_WEIGHT_VALUE } from "#utils/typography";
|
|
8
|
+
import { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "#utils/typography";
|
|
9
9
|
import { resolveScreenPadding } from "../../../theme/screen-padding";
|
|
10
10
|
import { Toolbar } from "../../toolbar";
|
|
11
11
|
import { HeaderDismissButton, type HeaderDismissProps } from "../header-buttons";
|
|
@@ -112,79 +112,89 @@ export function HeaderBar({
|
|
|
112
112
|
);
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
const styles = StyleSheet.create((theme, runtime) =>
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
titleAlignment: {
|
|
156
|
-
center: { marginLeft: "auto" },
|
|
157
|
-
left: {},
|
|
158
|
-
},
|
|
115
|
+
const styles = StyleSheet.create((theme, runtime) => {
|
|
116
|
+
// Resolved inline: the unistyles Babel plugin rewrites this factory, so an
|
|
117
|
+
// imported helper called from inside it is not in scope at runtime.
|
|
118
|
+
const titleWeight = theme.typography.variants["title-md"].fontWeight;
|
|
119
|
+
const titleFont = theme.typography.fonts[titleWeight];
|
|
120
|
+
const isSystemTitleFont = titleFont === SYSTEM_FONT;
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
windowInsets: (left: number, right: number) => ({
|
|
124
|
+
paddingLeft: Math.max(resolveScreenPadding(theme, runtime.breakpoint).x, left),
|
|
125
|
+
paddingRight: Math.max(resolveScreenPadding(theme, runtime.breakpoint).x, right),
|
|
126
|
+
}),
|
|
127
|
+
// A centered title spans the region between the window controls and is inset
|
|
128
|
+
// by the same amount from both of its edges, so it stays centered in that
|
|
129
|
+
// region. The inset is whichever side reaches further in — bar padding plus
|
|
130
|
+
// that side's toolbar — so a long title truncates before either toolbar
|
|
131
|
+
// instead of running underneath it. A left-aligned title is in the flex row
|
|
132
|
+
// already and needs none of this.
|
|
133
|
+
titleInsets: (
|
|
134
|
+
left: number,
|
|
135
|
+
right: number,
|
|
136
|
+
leadingWidth: number,
|
|
137
|
+
trailingWidth: number,
|
|
138
|
+
alignment: "center" | "left"
|
|
139
|
+
) => {
|
|
140
|
+
if (alignment !== "center") return {};
|
|
141
|
+
const padding = resolveScreenPadding(theme, runtime.breakpoint).x;
|
|
142
|
+
const gap = theme.spacing["3"];
|
|
143
|
+
const reachLeft = Math.max(padding, left) + (leadingWidth > 0 ? leadingWidth + gap : 0) - left;
|
|
144
|
+
const reachRight = Math.max(padding, right) + (trailingWidth > 0 ? trailingWidth + gap : 0) - right;
|
|
145
|
+
const inset = Math.max(reachLeft, reachRight);
|
|
146
|
+
return { left: left + inset, right: right + inset };
|
|
147
|
+
},
|
|
148
|
+
bar: {
|
|
149
|
+
height: theme.layout.header.height,
|
|
150
|
+
flexDirection: "row",
|
|
151
|
+
alignItems: "center",
|
|
152
|
+
paddingHorizontal: theme.layout.screen.padding.x,
|
|
153
|
+
gap: theme.spacing["3"],
|
|
154
|
+
justifyContent: "space-between",
|
|
159
155
|
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
156
|
+
// A centered title is absolutely positioned, so it leaves the flex row. With
|
|
157
|
+
// no leading slot the trailing toolbar would be the only in-flow child and
|
|
158
|
+
// `space-between` would park it on the left — the auto margin pins it right.
|
|
159
|
+
trailing: {
|
|
160
|
+
justifyContent: "flex-end",
|
|
161
|
+
variants: {
|
|
162
|
+
titleAlignment: {
|
|
163
|
+
center: { marginLeft: "auto" },
|
|
164
|
+
left: {},
|
|
169
165
|
},
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
titleContainer: {
|
|
169
|
+
variants: {
|
|
170
|
+
titleAlignment: {
|
|
171
|
+
center: {
|
|
172
|
+
position: "absolute",
|
|
173
|
+
left: 0,
|
|
174
|
+
right: 0,
|
|
175
|
+
alignItems: "center",
|
|
176
|
+
},
|
|
177
|
+
left: {
|
|
178
|
+
flex: 1,
|
|
179
|
+
alignItems: "flex-start",
|
|
180
|
+
},
|
|
173
181
|
},
|
|
174
182
|
},
|
|
175
183
|
},
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
184
|
+
title: {
|
|
185
|
+
fontSize: theme.typography.variants["title-md"].fontSize,
|
|
186
|
+
lineHeight: theme.typography.variants["title-md"].lineHeight,
|
|
187
|
+
letterSpacing: theme.typography.variants["title-md"].letterSpacing,
|
|
188
|
+
fontFamily: isSystemTitleFont ? undefined : titleFont,
|
|
189
|
+
fontWeight: isSystemTitleFont ? FONT_WEIGHT_VALUE[titleWeight] : undefined,
|
|
190
|
+
includeFontPadding: false,
|
|
191
|
+
color: (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ?? "",
|
|
192
|
+
variants: {
|
|
193
|
+
titleAlignment: {
|
|
194
|
+
center: { textAlign: "center" },
|
|
195
|
+
left: { textAlign: "left" },
|
|
196
|
+
},
|
|
187
197
|
},
|
|
188
198
|
},
|
|
189
|
-
}
|
|
190
|
-
})
|
|
199
|
+
};
|
|
200
|
+
});
|
|
@@ -4,7 +4,7 @@ import { type LayoutChangeEvent, Text } from "react-native";
|
|
|
4
4
|
import Animated from "react-native-reanimated";
|
|
5
5
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
6
6
|
|
|
7
|
-
import { FONT_WEIGHT_VALUE } from "#utils/typography";
|
|
7
|
+
import { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "#utils/typography";
|
|
8
8
|
import { isTextTitle } from "../../../utils";
|
|
9
9
|
import type { LargeTitleHeaderProps } from "..";
|
|
10
10
|
|
|
@@ -58,31 +58,49 @@ export function LargeTitleContent({
|
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
const styles = StyleSheet.create((theme) =>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
}
|
|
61
|
+
const styles = StyleSheet.create((theme) => {
|
|
62
|
+
// Resolved inline: the unistyles Babel plugin rewrites this factory, so an
|
|
63
|
+
// imported helper called from inside it is not in scope at runtime.
|
|
64
|
+
const overlineWeight = theme.typography.variants["label-sm"].fontWeight;
|
|
65
|
+
const overlineFont = theme.typography.fonts[overlineWeight];
|
|
66
|
+
const isSystemOverlineFont = overlineFont === SYSTEM_FONT;
|
|
67
|
+
const titleWeight = theme.typography.variants["headline-lg"].fontWeight;
|
|
68
|
+
const titleFont = theme.typography.fonts[titleWeight];
|
|
69
|
+
const isSystemTitleFont = titleFont === SYSTEM_FONT;
|
|
70
|
+
const subtitleWeight = theme.typography.variants["body-lg"].fontWeight;
|
|
71
|
+
const subtitleFont = theme.typography.fonts[subtitleWeight];
|
|
72
|
+
const isSystemSubtitleFont = subtitleFont === SYSTEM_FONT;
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
portalContent: (hasAccessoryBelow: boolean) => ({
|
|
76
|
+
gap: theme.spacing["1"] / 2,
|
|
77
|
+
// Include the gap in the measured height used to position the accessory.
|
|
78
|
+
paddingBottom: hasAccessoryBelow ? theme.spacing["2"] : 0,
|
|
79
|
+
paddingHorizontal: theme.layout.screen.padding.x,
|
|
80
|
+
}),
|
|
81
|
+
overline: {
|
|
82
|
+
fontSize: theme.typography.variants["label-sm"].fontSize,
|
|
83
|
+
lineHeight: theme.typography.variants["label-sm"].lineHeight,
|
|
84
|
+
letterSpacing: theme.typography.variants["label-sm"].letterSpacing,
|
|
85
|
+
fontFamily: isSystemOverlineFont ? undefined : overlineFont,
|
|
86
|
+
fontWeight: isSystemOverlineFont ? FONT_WEIGHT_VALUE[overlineWeight] : undefined,
|
|
87
|
+
color: (theme.color as unknown as Record<string, string | undefined>)["text.tertiary"] ?? "",
|
|
88
|
+
},
|
|
89
|
+
largeTitle: {
|
|
90
|
+
fontSize: theme.typography.variants["headline-lg"].fontSize,
|
|
91
|
+
lineHeight: theme.typography.variants["headline-lg"].lineHeight,
|
|
92
|
+
letterSpacing: theme.typography.variants["headline-lg"].letterSpacing,
|
|
93
|
+
fontFamily: isSystemTitleFont ? undefined : titleFont,
|
|
94
|
+
fontWeight: isSystemTitleFont ? FONT_WEIGHT_VALUE[titleWeight] : undefined,
|
|
95
|
+
color: (theme.color as unknown as Record<string, string | undefined>)["text.primary"] ?? "",
|
|
96
|
+
},
|
|
97
|
+
subtitle: {
|
|
98
|
+
fontSize: theme.typography.variants["body-lg"].fontSize,
|
|
99
|
+
lineHeight: theme.typography.variants["body-lg"].lineHeight,
|
|
100
|
+
letterSpacing: theme.typography.variants["body-lg"].letterSpacing,
|
|
101
|
+
fontFamily: isSystemSubtitleFont ? undefined : subtitleFont,
|
|
102
|
+
fontWeight: isSystemSubtitleFont ? FONT_WEIGHT_VALUE[subtitleWeight] : undefined,
|
|
103
|
+
color: (theme.color as unknown as Record<string, string | undefined>)["text.tertiary"] ?? "",
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
});
|
|
@@ -5,7 +5,7 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
|
5
5
|
|
|
6
6
|
import type { ColorToken, RadiusKey } from "#theme/types";
|
|
7
7
|
import { resolveThemeColor } from "#utils/resolve-theme-color";
|
|
8
|
-
import { FONT_WEIGHT_VALUE } from "#utils/typography";
|
|
8
|
+
import { FONT_WEIGHT_VALUE, SYSTEM_FONT } from "#utils/typography";
|
|
9
9
|
import type { InputBaseProps, InputSize } from "../types";
|
|
10
10
|
|
|
11
11
|
/** Typographic role of the text, per size. */
|
|
@@ -68,6 +68,9 @@ export const InputField = forwardRef<ComponentRef<typeof TextInput>, InputBasePr
|
|
|
68
68
|
editable={!isDisabled}
|
|
69
69
|
placeholderTextColor={theme.color["text.tertiary"]}
|
|
70
70
|
style={styles.input}
|
|
71
|
+
// The container height is fixed per size, so scaling stops where the
|
|
72
|
+
// body role does rather than growing past the box.
|
|
73
|
+
maxFontSizeMultiplier={theme.typography.variants[TEXT_VARIANT[size]].maxFontSizeMultiplier}
|
|
71
74
|
onFocus={handleFocus}
|
|
72
75
|
onBlur={handleBlur}
|
|
73
76
|
{...rest}
|
|
@@ -77,62 +80,67 @@ export const InputField = forwardRef<ComponentRef<typeof TextInput>, InputBasePr
|
|
|
77
80
|
);
|
|
78
81
|
});
|
|
79
82
|
|
|
80
|
-
const styles = StyleSheet.create((theme) =>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
disabled: {
|
|
101
|
-
true: { opacity: theme.opacity.disabled },
|
|
102
|
-
false: {},
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
// `background` is an open `ColorToken` and `radius` an eight-step scale, so
|
|
107
|
-
// both resolve in a dynamic function rather than enumerating as variants.
|
|
108
|
-
fill: (background: ColorToken, radius: RadiusKey) => ({
|
|
109
|
-
backgroundColor: resolveThemeColor(background, theme),
|
|
110
|
-
borderRadius: theme.radius[radius],
|
|
111
|
-
}),
|
|
112
|
-
input: {
|
|
113
|
-
flex: 1,
|
|
114
|
-
// Android centres text in a taller box by default; zeroing the padding lets
|
|
115
|
-
// the container's own height own the geometry on both platforms.
|
|
116
|
-
paddingVertical: 0,
|
|
117
|
-
color: theme.color["text.primary"],
|
|
118
|
-
variants: {
|
|
119
|
-
size: {
|
|
120
|
-
sm: {
|
|
121
|
-
fontSize: theme.typography.variants[TEXT_VARIANT.sm].fontSize,
|
|
122
|
-
fontWeight: FONT_WEIGHT_VALUE[theme.typography.variants[TEXT_VARIANT.sm].fontWeight],
|
|
83
|
+
const styles = StyleSheet.create((theme) => {
|
|
84
|
+
// Resolved inline rather than through a helper: the unistyles Babel plugin
|
|
85
|
+
// rewrites this factory, and an imported function called from inside it is
|
|
86
|
+
// not in scope at runtime. Every body role shares one weight, so one lookup
|
|
87
|
+
// covers all three sizes.
|
|
88
|
+
const bodyWeight = theme.typography.variants[TEXT_VARIANT.md].fontWeight;
|
|
89
|
+
const bodyFont = theme.typography.fonts[bodyWeight];
|
|
90
|
+
const isSystemBodyFont = bodyFont === SYSTEM_FONT;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
container: {
|
|
94
|
+
flexDirection: "row",
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
borderWidth: StyleSheet.hairlineWidth,
|
|
97
|
+
borderColor: theme.color["border.subtle"],
|
|
98
|
+
variants: {
|
|
99
|
+
size: {
|
|
100
|
+
sm: { height: 36, paddingHorizontal: theme.spacing["4"], gap: 6 },
|
|
101
|
+
md: { height: 44, paddingHorizontal: theme.spacing["4"], gap: 6 },
|
|
102
|
+
lg: { height: 52, paddingHorizontal: theme.spacing["4"], gap: 6 },
|
|
123
103
|
},
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
104
|
+
focused: {
|
|
105
|
+
true: { borderColor: theme.color["accent.border"] },
|
|
106
|
+
false: {},
|
|
127
107
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
108
|
+
invalid: {
|
|
109
|
+
true: { borderColor: theme.color["danger.text"] },
|
|
110
|
+
false: {},
|
|
111
|
+
},
|
|
112
|
+
disabled: {
|
|
113
|
+
true: { opacity: theme.opacity.disabled },
|
|
114
|
+
false: {},
|
|
131
115
|
},
|
|
132
116
|
},
|
|
133
|
-
focused: { true: {}, false: {} },
|
|
134
|
-
invalid: { true: {}, false: {} },
|
|
135
|
-
disabled: { true: {}, false: {} },
|
|
136
117
|
},
|
|
137
|
-
|
|
138
|
-
|
|
118
|
+
// `background` is an open `ColorToken` and `radius` an eight-step scale, so
|
|
119
|
+
// both resolve in a dynamic function rather than enumerating as variants.
|
|
120
|
+
fill: (background: ColorToken, radius: RadiusKey) => ({
|
|
121
|
+
backgroundColor: resolveThemeColor(background, theme),
|
|
122
|
+
borderRadius: theme.radius[radius],
|
|
123
|
+
}),
|
|
124
|
+
input: {
|
|
125
|
+
flex: 1,
|
|
126
|
+
// Android centres text in a taller box by default; zeroing the padding lets
|
|
127
|
+
// the container's own height own the geometry on both platforms.
|
|
128
|
+
paddingVertical: 0,
|
|
129
|
+
color: theme.color["text.primary"],
|
|
130
|
+
// Custom fonts encode weight in the family name; the system font takes a
|
|
131
|
+
// numeric weight instead.
|
|
132
|
+
fontFamily: isSystemBodyFont ? undefined : bodyFont,
|
|
133
|
+
fontWeight: isSystemBodyFont ? FONT_WEIGHT_VALUE[bodyWeight] : undefined,
|
|
134
|
+
variants: {
|
|
135
|
+
size: {
|
|
136
|
+
sm: { fontSize: theme.typography.variants[TEXT_VARIANT.sm].fontSize },
|
|
137
|
+
md: { fontSize: theme.typography.variants[TEXT_VARIANT.md].fontSize },
|
|
138
|
+
lg: { fontSize: theme.typography.variants[TEXT_VARIANT.lg].fontSize },
|
|
139
|
+
},
|
|
140
|
+
focused: { true: {}, false: {} },
|
|
141
|
+
invalid: { true: {}, false: {} },
|
|
142
|
+
disabled: { true: {}, false: {} },
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
});
|
|
@@ -19,10 +19,10 @@ export type TextAlign = "left" | "center" | "right" | "justify";
|
|
|
19
19
|
const HEADING_VARIANT_ALIAS: Record<HeadingTextVariant, RoleTextVariant> = {
|
|
20
20
|
h1: "headline-lg",
|
|
21
21
|
h2: "headline-md",
|
|
22
|
-
h3: "
|
|
23
|
-
h4: "title-
|
|
24
|
-
h5: "title-
|
|
25
|
-
h6: "
|
|
22
|
+
h3: "headline-sm",
|
|
23
|
+
h4: "title-lg",
|
|
24
|
+
h5: "title-md",
|
|
25
|
+
h6: "title-sm",
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
function resolveTextVariant(variant: TextVariant): RoleTextVariant {
|
|
@@ -116,11 +116,15 @@ export const Text = forwardRef<ComponentRef<typeof RNText>, TextProps>(function
|
|
|
116
116
|
|
|
117
117
|
// ─── Styles ───────────────────────────────────────────────────────────────────
|
|
118
118
|
//
|
|
119
|
-
//
|
|
119
|
+
// Besides the Android font-padding reset, this stylesheet exists so the
|
|
120
120
|
// Unistyles Babel plugin swaps the `RNText` imported above for its Unistyles-aware
|
|
121
121
|
// counterpart, which registers the ShadowNode with the native side. That is what
|
|
122
122
|
// keeps a Unistyles style passed in through `style` updating on theme change;
|
|
123
123
|
// without it the style freezes at whichever theme was active when it was
|
|
124
124
|
// created, and only callers that happen to subscribe to the theme themselves
|
|
125
125
|
// would appear to work.
|
|
126
|
-
const styles = StyleSheet.create(() => ({
|
|
126
|
+
const styles = StyleSheet.create(() => ({
|
|
127
|
+
// Android pads glyphs with the font's ascender/descender by default, which
|
|
128
|
+
// drops text below centre inside fixed-height controls. iOS never adds it.
|
|
129
|
+
root: { includeFontPadding: false },
|
|
130
|
+
}));
|
package/src/theme/tokens/dark.ts
CHANGED
|
@@ -80,8 +80,8 @@ export const dark = {
|
|
|
80
80
|
// Danger
|
|
81
81
|
"color.danger.text": "{palette.red.400}",
|
|
82
82
|
|
|
83
|
-
"color.danger.solid.bg": "
|
|
84
|
-
"color.danger.solid.fg": "{palette.
|
|
83
|
+
"color.danger.solid.bg": "#dc0010",
|
|
84
|
+
"color.danger.solid.fg": "{palette.white}",
|
|
85
85
|
"color.danger.solid.border": "{palette.red.800}",
|
|
86
86
|
|
|
87
87
|
"color.danger.soft.bg": "{palette.red.950}",
|
|
@@ -79,8 +79,8 @@ export const light = {
|
|
|
79
79
|
// Danger
|
|
80
80
|
"color.danger.text": "{palette.rose.700}",
|
|
81
81
|
|
|
82
|
-
"color.danger.solid.bg": "
|
|
83
|
-
"color.danger.solid.fg": "{palette.
|
|
82
|
+
"color.danger.solid.bg": "#dc0010",
|
|
83
|
+
"color.danger.solid.fg": "{palette.white}",
|
|
84
84
|
"color.danger.solid.border": "{palette.red.300}",
|
|
85
85
|
|
|
86
86
|
"color.danger.soft.bg": "{palette.red.100}",
|
|
@@ -371,6 +371,26 @@ export const primitives = {
|
|
|
371
371
|
"typography.fonts.bold": "System",
|
|
372
372
|
|
|
373
373
|
// ── typography: role variants ─────────────────────────────────────────────
|
|
374
|
+
// Base sizes in React Native logical units, before OS text scaling; these
|
|
375
|
+
// are not physical pixels. Pass them directly to Text without multiplying
|
|
376
|
+
// by PixelRatio or fontScale (native Text handles accessibility scaling).
|
|
377
|
+
// Each role pairs fontSize with an absolute lineHeight, not a ratio.
|
|
378
|
+
// Line heights use a 4-unit grid; font sizes need not follow that grid.
|
|
379
|
+
//
|
|
380
|
+
// Font size / line height, listed from lg to sm:
|
|
381
|
+
// display: 40/48, 36/44, 32/40 — short, prominent hero text.
|
|
382
|
+
// headline: 28/36, 24/32, 22/28 — page and section headings.
|
|
383
|
+
// title: 20/28, 18/24, 16/24 — card, list, and navigation titles.
|
|
384
|
+
// body: 18/28, 16/24, 14/20 — reading text; body-md is Text's default.
|
|
385
|
+
// label: 16/20, 14/20, 12/16 — short control labels, not paragraphs.
|
|
386
|
+
// caption: 12/16 — brief supplementary text.
|
|
387
|
+
// Prefer body sizes for essential information; reserve 12 for compact text.
|
|
388
|
+
// These are Terra's custom roles, not native platform text-style presets.
|
|
389
|
+
//
|
|
390
|
+
// maxFontSizeMultiplier limits accessibility enlargement when scaling is
|
|
391
|
+
// enabled. The caps below are layout tradeoffs, not accessibility targets.
|
|
392
|
+
// RN uses 0 for no cap; Terra Text also accepts a per-instance override.
|
|
393
|
+
// Validate wrapping and clipping at large OS text sizes and with custom fonts.
|
|
374
394
|
"typography.variants.display-lg.fontSize": 40,
|
|
375
395
|
"typography.variants.display-lg.lineHeight": 48,
|
|
376
396
|
"typography.variants.display-lg.fontWeight": "bold",
|
|
@@ -412,12 +432,12 @@ export const primitives = {
|
|
|
412
432
|
"typography.variants.title-md.letterSpacing": 0,
|
|
413
433
|
"typography.variants.title-md.maxFontSizeMultiplier": 1.4,
|
|
414
434
|
"typography.variants.title-sm.fontSize": 16,
|
|
415
|
-
"typography.variants.title-sm.lineHeight":
|
|
435
|
+
"typography.variants.title-sm.lineHeight": 24,
|
|
416
436
|
"typography.variants.title-sm.fontWeight": "semibold",
|
|
417
437
|
"typography.variants.title-sm.letterSpacing": 0,
|
|
418
438
|
"typography.variants.title-sm.maxFontSizeMultiplier": 1.4,
|
|
419
439
|
"typography.variants.body-lg.fontSize": 18,
|
|
420
|
-
"typography.variants.body-lg.lineHeight":
|
|
440
|
+
"typography.variants.body-lg.lineHeight": 28,
|
|
421
441
|
"typography.variants.body-lg.fontWeight": "regular",
|
|
422
442
|
"typography.variants.body-lg.letterSpacing": 0,
|
|
423
443
|
"typography.variants.body-lg.maxFontSizeMultiplier": 1.8,
|
|
@@ -437,7 +457,7 @@ export const primitives = {
|
|
|
437
457
|
"typography.variants.label-lg.letterSpacing": 0.1,
|
|
438
458
|
"typography.variants.label-lg.maxFontSizeMultiplier": 1.5,
|
|
439
459
|
"typography.variants.label-md.fontSize": 14,
|
|
440
|
-
"typography.variants.label-md.lineHeight":
|
|
460
|
+
"typography.variants.label-md.lineHeight": 20,
|
|
441
461
|
"typography.variants.label-md.fontWeight": "medium",
|
|
442
462
|
"typography.variants.label-md.letterSpacing": 0.1,
|
|
443
463
|
"typography.variants.label-md.maxFontSizeMultiplier": 1.5,
|
|
@@ -450,7 +470,7 @@ export const primitives = {
|
|
|
450
470
|
"typography.variants.caption.lineHeight": 16,
|
|
451
471
|
"typography.variants.caption.fontWeight": "regular",
|
|
452
472
|
"typography.variants.caption.letterSpacing": 0.2,
|
|
453
|
-
"typography.variants.caption.maxFontSizeMultiplier":
|
|
473
|
+
"typography.variants.caption.maxFontSizeMultiplier": 2,
|
|
454
474
|
|
|
455
475
|
// ── component opacity multipliers ─────────────────────────────────────────────────
|
|
456
476
|
"opacity.disabled": 0.45,
|