@butternutbox/pawprint-native 0.0.1
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/.turbo/turbo-build.log +30 -0
- package/COMPONENT_GUIDELINES.md +610 -0
- package/README.md +72 -0
- package/dist/ibm-plex-sans-condensed-400-normal-I2XLJNNB.woff2 +0 -0
- package/dist/ibm-plex-sans-condensed-500-normal-IEQBNVGX.woff2 +0 -0
- package/dist/ibm-plex-sans-condensed-600-normal-UX5ZU5T6.woff2 +0 -0
- package/dist/ibm-plex-sans-condensed-700-normal-4PFYFTSO.woff2 +0 -0
- package/dist/ida-narrow-500-normal-C6I2PK4T.woff2 +0 -0
- package/dist/ida-narrow-700-normal-UPHPRIN6.woff2 +0 -0
- package/dist/index.cjs +2686 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +780 -0
- package/dist/index.d.ts +780 -0
- package/dist/index.js +2617 -0
- package/dist/index.js.map +1 -0
- package/eslint.config.js +3 -0
- package/llms.txt +458 -0
- package/package.json +57 -0
- package/src/components/atoms/Avatar/Avatar.stories.tsx +125 -0
- package/src/components/atoms/Avatar/Avatar.tsx +159 -0
- package/src/components/atoms/Avatar/index.ts +7 -0
- package/src/components/atoms/Badge/Badge.stories.tsx +231 -0
- package/src/components/atoms/Badge/Badge.tsx +184 -0
- package/src/components/atoms/Badge/index.ts +2 -0
- package/src/components/atoms/Button/Button.stories.tsx +145 -0
- package/src/components/atoms/Button/Button.tsx +261 -0
- package/src/components/atoms/Button/index.ts +7 -0
- package/src/components/atoms/Hint/Hint.stories.tsx +84 -0
- package/src/components/atoms/Hint/Hint.tsx +59 -0
- package/src/components/atoms/Hint/index.ts +2 -0
- package/src/components/atoms/Icon/Icon.stories.tsx +200 -0
- package/src/components/atoms/Icon/Icon.tsx +112 -0
- package/src/components/atoms/Icon/index.ts +8 -0
- package/src/components/atoms/IconButton/IconButton.stories.tsx +162 -0
- package/src/components/atoms/IconButton/IconButton.tsx +227 -0
- package/src/components/atoms/IconButton/index.ts +7 -0
- package/src/components/atoms/Illustration/Illustration.stories.tsx +167 -0
- package/src/components/atoms/Illustration/Illustration.tsx +81 -0
- package/src/components/atoms/Illustration/index.ts +6 -0
- package/src/components/atoms/Input/Input.stories.tsx +142 -0
- package/src/components/atoms/Input/Input.tsx +110 -0
- package/src/components/atoms/Input/InputDescription.tsx +49 -0
- package/src/components/atoms/Input/InputError.tsx +39 -0
- package/src/components/atoms/Input/InputField.tsx +119 -0
- package/src/components/atoms/Input/InputLabel.tsx +61 -0
- package/src/components/atoms/Input/index.ts +10 -0
- package/src/components/atoms/Link/Link.stories.tsx +119 -0
- package/src/components/atoms/Link/Link.tsx +118 -0
- package/src/components/atoms/Link/index.ts +2 -0
- package/src/components/atoms/Logo/Logo.registry.ts +39 -0
- package/src/components/atoms/Logo/Logo.tsx +68 -0
- package/src/components/atoms/Logo/index.ts +4 -0
- package/src/components/atoms/Spinner/Spinner.stories.tsx +98 -0
- package/src/components/atoms/Spinner/Spinner.tsx +91 -0
- package/src/components/atoms/Spinner/index.ts +2 -0
- package/src/components/atoms/Switch/Switch.stories.tsx +120 -0
- package/src/components/atoms/Switch/Switch.tsx +196 -0
- package/src/components/atoms/Switch/index.ts +2 -0
- package/src/components/atoms/Tag/Tag.stories.tsx +89 -0
- package/src/components/atoms/Tag/Tag.tsx +122 -0
- package/src/components/atoms/Tag/index.ts +2 -0
- package/src/components/atoms/Typography/Typography.stories.tsx +315 -0
- package/src/components/atoms/Typography/Typography.tsx +284 -0
- package/src/components/atoms/Typography/index.ts +2 -0
- package/src/components/atoms/index.ts +14 -0
- package/src/components/index.ts +2 -0
- package/src/components/molecules/ButtonDock/ButtonDock.stories.tsx +95 -0
- package/src/components/molecules/ButtonDock/ButtonDock.tsx +148 -0
- package/src/components/molecules/ButtonDock/index.ts +2 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.stories.tsx +82 -0
- package/src/components/molecules/ButtonGroup/ButtonGroup.tsx +94 -0
- package/src/components/molecules/ButtonGroup/index.ts +2 -0
- package/src/components/molecules/Checkbox/Checkbox.stories.tsx +148 -0
- package/src/components/molecules/Checkbox/Checkbox.tsx +279 -0
- package/src/components/molecules/Checkbox/CheckboxGroup.tsx +53 -0
- package/src/components/molecules/Checkbox/index.ts +4 -0
- package/src/components/molecules/Radio/Radio.stories.tsx +182 -0
- package/src/components/molecules/Radio/Radio.tsx +249 -0
- package/src/components/molecules/Radio/RadioGroup.tsx +142 -0
- package/src/components/molecules/Radio/index.ts +4 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.stories.tsx +151 -0
- package/src/components/molecules/SegmentedControl/SegmentedControl.tsx +323 -0
- package/src/components/molecules/SegmentedControl/index.ts +5 -0
- package/src/components/molecules/Slider/Slider.stories.tsx +144 -0
- package/src/components/molecules/Slider/Slider.tsx +303 -0
- package/src/components/molecules/Slider/index.ts +2 -0
- package/src/components/molecules/index.ts +6 -0
- package/src/fonts/ibm-plex-sans-condensed-400-normal.woff2 +0 -0
- package/src/fonts/ibm-plex-sans-condensed-500-normal.woff2 +0 -0
- package/src/fonts/ibm-plex-sans-condensed-600-normal.woff2 +0 -0
- package/src/fonts/ibm-plex-sans-condensed-700-normal.woff2 +0 -0
- package/src/fonts/ida-narrow-500-normal.woff2 +0 -0
- package/src/fonts/ida-narrow-700-normal.woff2 +0 -0
- package/src/fonts/index.ts +49 -0
- package/src/index.ts +9 -0
- package/src/theme/PawprintProvider.tsx +26 -0
- package/src/theme/ThemeProvider.tsx +63 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/theme.ts +3 -0
- package/src/theme/utils.ts +31 -0
- package/src/types/fonts.d.ts +4 -0
- package/src/types/index.ts +1 -0
- package/src/types/theme.ts +24 -0
- package/tsconfig.json +5 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { View, StyleSheet } from "react-native"
|
|
2
|
+
import { Typography } from "./Typography"
|
|
3
|
+
import type { TypographyProps } from "./Typography"
|
|
4
|
+
|
|
5
|
+
const bodyText =
|
|
6
|
+
"We're all familiar with the various sounds our dogs make. The polite little 'woof' at the door, the frantic yipping during a game of fetch."
|
|
7
|
+
const headingText = "Fresh food. Happy dogs."
|
|
8
|
+
const shortText = "Fresh food. Happy dogs."
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
title: "Atoms/Typography",
|
|
12
|
+
component: Typography,
|
|
13
|
+
argTypes: {
|
|
14
|
+
variant: {
|
|
15
|
+
control: { type: "select" },
|
|
16
|
+
options: ["body", "heading", "display"],
|
|
17
|
+
description: "Text style variant"
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
control: { type: "select" },
|
|
21
|
+
options: ["2xs", "xs", "sm", "md", "lg", "xl"],
|
|
22
|
+
description: "Size (available options vary by variant)"
|
|
23
|
+
},
|
|
24
|
+
weight: {
|
|
25
|
+
control: { type: "select" },
|
|
26
|
+
options: ["medium", "semiBold", "bold"],
|
|
27
|
+
description: "Font weight (body variant only)"
|
|
28
|
+
},
|
|
29
|
+
color: {
|
|
30
|
+
control: { type: "select" },
|
|
31
|
+
options: [
|
|
32
|
+
"primary",
|
|
33
|
+
"secondary",
|
|
34
|
+
"success",
|
|
35
|
+
"warning",
|
|
36
|
+
"error",
|
|
37
|
+
"promo",
|
|
38
|
+
"placeholder",
|
|
39
|
+
"disabled",
|
|
40
|
+
"tertiary",
|
|
41
|
+
"alt"
|
|
42
|
+
],
|
|
43
|
+
description: "Semantic text colour"
|
|
44
|
+
},
|
|
45
|
+
align: {
|
|
46
|
+
control: { type: "select" },
|
|
47
|
+
options: ["left", "center", "right", "justify"],
|
|
48
|
+
description: "Text alignment"
|
|
49
|
+
},
|
|
50
|
+
textTransform: {
|
|
51
|
+
control: { type: "select" },
|
|
52
|
+
options: ["none", "uppercase", "lowercase", "capitalize"],
|
|
53
|
+
description: "CSS text-transform"
|
|
54
|
+
},
|
|
55
|
+
textDecoration: {
|
|
56
|
+
control: { type: "select" },
|
|
57
|
+
options: ["none", "underline", "line-through"],
|
|
58
|
+
description: "Text decoration style"
|
|
59
|
+
},
|
|
60
|
+
noWrap: {
|
|
61
|
+
control: { type: "boolean" },
|
|
62
|
+
description: "Truncate with ellipsis instead of wrapping"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const Playground = (args: TypographyProps) => (
|
|
68
|
+
<View style={styles.container}>
|
|
69
|
+
<Typography {...args}>{bodyText}</Typography>
|
|
70
|
+
</View>
|
|
71
|
+
)
|
|
72
|
+
Playground.args = {
|
|
73
|
+
variant: "body",
|
|
74
|
+
size: "md",
|
|
75
|
+
weight: "medium",
|
|
76
|
+
color: "primary"
|
|
77
|
+
} as TypographyProps
|
|
78
|
+
|
|
79
|
+
export const AllVariants = () => (
|
|
80
|
+
<View style={styles.column}>
|
|
81
|
+
<View style={styles.section}>
|
|
82
|
+
<Typography variant="heading" size="sm" color="tertiary">
|
|
83
|
+
Display
|
|
84
|
+
</Typography>
|
|
85
|
+
{(["lg", "md"] as const).map((size) => (
|
|
86
|
+
<View key={size} style={styles.item}>
|
|
87
|
+
<Typography size="sm" weight="semiBold" color="promo">
|
|
88
|
+
Display {size.toUpperCase()}
|
|
89
|
+
</Typography>
|
|
90
|
+
<Typography variant="display" size={size}>
|
|
91
|
+
{headingText}
|
|
92
|
+
</Typography>
|
|
93
|
+
</View>
|
|
94
|
+
))}
|
|
95
|
+
</View>
|
|
96
|
+
|
|
97
|
+
<View style={styles.section}>
|
|
98
|
+
<Typography variant="heading" size="sm" color="tertiary">
|
|
99
|
+
Heading
|
|
100
|
+
</Typography>
|
|
101
|
+
{(["xl", "lg", "md", "sm", "xs", "2xs"] as const).map((size) => (
|
|
102
|
+
<View key={size} style={styles.item}>
|
|
103
|
+
<Typography size="sm" weight="semiBold" color="promo">
|
|
104
|
+
Heading {size.toUpperCase()}
|
|
105
|
+
</Typography>
|
|
106
|
+
<Typography variant="heading" size={size}>
|
|
107
|
+
{headingText}
|
|
108
|
+
</Typography>
|
|
109
|
+
</View>
|
|
110
|
+
))}
|
|
111
|
+
</View>
|
|
112
|
+
|
|
113
|
+
<View style={styles.section}>
|
|
114
|
+
<Typography variant="heading" size="sm" color="tertiary">
|
|
115
|
+
Body
|
|
116
|
+
</Typography>
|
|
117
|
+
{(["lg", "md", "sm", "xs"] as const).map((size) => (
|
|
118
|
+
<View key={size} style={styles.item}>
|
|
119
|
+
<Typography size="sm" weight="semiBold" color="promo">
|
|
120
|
+
Body {size.toUpperCase()} — medium / semiBold / bold
|
|
121
|
+
</Typography>
|
|
122
|
+
<View style={styles.weightColumn}>
|
|
123
|
+
{(["medium", "semiBold", "bold"] as const).map((weight) => (
|
|
124
|
+
<Typography key={weight} size={size} weight={weight}>
|
|
125
|
+
{bodyText}
|
|
126
|
+
</Typography>
|
|
127
|
+
))}
|
|
128
|
+
</View>
|
|
129
|
+
</View>
|
|
130
|
+
))}
|
|
131
|
+
</View>
|
|
132
|
+
</View>
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
export const Body = () => (
|
|
136
|
+
<View style={styles.column}>
|
|
137
|
+
<View style={styles.section}>
|
|
138
|
+
<Typography variant="heading" size="sm" color="tertiary">
|
|
139
|
+
All Sizes
|
|
140
|
+
</Typography>
|
|
141
|
+
{(["xs", "sm", "md", "lg"] as const).map((size) => (
|
|
142
|
+
<View key={size} style={styles.item}>
|
|
143
|
+
<Typography size="sm" weight="semiBold" color="promo">
|
|
144
|
+
Body {size.toUpperCase()}
|
|
145
|
+
</Typography>
|
|
146
|
+
<Typography size={size}>{bodyText}</Typography>
|
|
147
|
+
</View>
|
|
148
|
+
))}
|
|
149
|
+
</View>
|
|
150
|
+
|
|
151
|
+
<View style={styles.section}>
|
|
152
|
+
<Typography variant="heading" size="sm" color="tertiary">
|
|
153
|
+
All Weights
|
|
154
|
+
</Typography>
|
|
155
|
+
{(["medium", "semiBold", "bold"] as const).map((weight) => (
|
|
156
|
+
<View key={weight} style={styles.item}>
|
|
157
|
+
<Typography size="sm" weight="semiBold" color="promo">
|
|
158
|
+
{weight}
|
|
159
|
+
</Typography>
|
|
160
|
+
<Typography size="md" weight={weight}>
|
|
161
|
+
{bodyText}
|
|
162
|
+
</Typography>
|
|
163
|
+
</View>
|
|
164
|
+
))}
|
|
165
|
+
</View>
|
|
166
|
+
</View>
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
export const Heading = () => (
|
|
170
|
+
<View style={styles.column}>
|
|
171
|
+
<Typography variant="heading" size="sm" color="tertiary">
|
|
172
|
+
All Sizes
|
|
173
|
+
</Typography>
|
|
174
|
+
{(["xl", "lg", "md", "sm", "xs", "2xs"] as const).map((size) => (
|
|
175
|
+
<View key={size} style={styles.item}>
|
|
176
|
+
<Typography size="sm" weight="semiBold" color="promo">
|
|
177
|
+
Heading {size.toUpperCase()}
|
|
178
|
+
</Typography>
|
|
179
|
+
<Typography variant="heading" size={size}>
|
|
180
|
+
{headingText}
|
|
181
|
+
</Typography>
|
|
182
|
+
</View>
|
|
183
|
+
))}
|
|
184
|
+
</View>
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
export const Display = () => (
|
|
188
|
+
<View style={styles.column}>
|
|
189
|
+
<Typography variant="heading" size="sm" color="tertiary">
|
|
190
|
+
All Sizes
|
|
191
|
+
</Typography>
|
|
192
|
+
{(["lg", "md"] as const).map((size) => (
|
|
193
|
+
<View key={size} style={styles.item}>
|
|
194
|
+
<Typography size="sm" weight="semiBold" color="promo">
|
|
195
|
+
Display {size.toUpperCase()}
|
|
196
|
+
</Typography>
|
|
197
|
+
<Typography variant="display" size={size}>
|
|
198
|
+
{headingText}
|
|
199
|
+
</Typography>
|
|
200
|
+
</View>
|
|
201
|
+
))}
|
|
202
|
+
</View>
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
export const TextAlignment = () => (
|
|
206
|
+
<View style={styles.column}>
|
|
207
|
+
{(["left", "center", "right", "justify"] as const).map((align) => (
|
|
208
|
+
<View key={align} style={styles.item}>
|
|
209
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
210
|
+
{align.charAt(0).toUpperCase() + align.slice(1)}
|
|
211
|
+
</Typography>
|
|
212
|
+
<Typography align={align}>{bodyText}</Typography>
|
|
213
|
+
</View>
|
|
214
|
+
))}
|
|
215
|
+
</View>
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
export const TextTransform = () => (
|
|
219
|
+
<View style={styles.column}>
|
|
220
|
+
{(["none", "uppercase", "lowercase", "capitalize"] as const).map(
|
|
221
|
+
(transform) => (
|
|
222
|
+
<View key={transform} style={styles.item}>
|
|
223
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
224
|
+
{transform}
|
|
225
|
+
</Typography>
|
|
226
|
+
<Typography textTransform={transform}>
|
|
227
|
+
{transform === "capitalize" ? "fresh food. happy dogs." : shortText}
|
|
228
|
+
</Typography>
|
|
229
|
+
</View>
|
|
230
|
+
)
|
|
231
|
+
)}
|
|
232
|
+
</View>
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
export const TextDecoration = () => (
|
|
236
|
+
<View style={styles.column}>
|
|
237
|
+
{(["none", "underline", "line-through"] as const).map((decoration) => (
|
|
238
|
+
<View key={decoration} style={styles.item}>
|
|
239
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
240
|
+
{decoration}
|
|
241
|
+
</Typography>
|
|
242
|
+
<Typography textDecoration={decoration}>{shortText}</Typography>
|
|
243
|
+
</View>
|
|
244
|
+
))}
|
|
245
|
+
</View>
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
export const NoWrap = () => (
|
|
249
|
+
<View style={styles.column}>
|
|
250
|
+
<View style={styles.item}>
|
|
251
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
252
|
+
Normal (wraps)
|
|
253
|
+
</Typography>
|
|
254
|
+
<View style={styles.constrainedBox}>
|
|
255
|
+
<Typography>{bodyText}</Typography>
|
|
256
|
+
</View>
|
|
257
|
+
</View>
|
|
258
|
+
|
|
259
|
+
<View style={styles.item}>
|
|
260
|
+
<Typography size="sm" weight="semiBold" color="tertiary">
|
|
261
|
+
No Wrap (truncates with ellipsis)
|
|
262
|
+
</Typography>
|
|
263
|
+
<View style={styles.constrainedBox}>
|
|
264
|
+
<Typography noWrap>{bodyText}</Typography>
|
|
265
|
+
</View>
|
|
266
|
+
</View>
|
|
267
|
+
</View>
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
export const Colors = () => (
|
|
271
|
+
<View style={styles.column}>
|
|
272
|
+
{(
|
|
273
|
+
[
|
|
274
|
+
"primary",
|
|
275
|
+
"secondary",
|
|
276
|
+
"success",
|
|
277
|
+
"warning",
|
|
278
|
+
"error",
|
|
279
|
+
"promo",
|
|
280
|
+
"placeholder",
|
|
281
|
+
"disabled",
|
|
282
|
+
"tertiary"
|
|
283
|
+
] as const
|
|
284
|
+
).map((color) => (
|
|
285
|
+
<Typography key={color} color={color}>
|
|
286
|
+
{`${color} — ${shortText}`}
|
|
287
|
+
</Typography>
|
|
288
|
+
))}
|
|
289
|
+
</View>
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
const styles = StyleSheet.create({
|
|
293
|
+
container: {
|
|
294
|
+
maxWidth: 600
|
|
295
|
+
},
|
|
296
|
+
column: {
|
|
297
|
+
flexDirection: "column",
|
|
298
|
+
gap: 32
|
|
299
|
+
},
|
|
300
|
+
section: {
|
|
301
|
+
flexDirection: "column",
|
|
302
|
+
gap: 16
|
|
303
|
+
},
|
|
304
|
+
item: {
|
|
305
|
+
flexDirection: "column",
|
|
306
|
+
gap: 4
|
|
307
|
+
},
|
|
308
|
+
weightColumn: {
|
|
309
|
+
flexDirection: "column",
|
|
310
|
+
gap: 4
|
|
311
|
+
},
|
|
312
|
+
constrainedBox: {
|
|
313
|
+
maxWidth: 300
|
|
314
|
+
}
|
|
315
|
+
})
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Text, TextStyle, TextProps } from "react-native"
|
|
3
|
+
import { useTheme } from "@emotion/react"
|
|
4
|
+
import styled from "@emotion/native"
|
|
5
|
+
import { type BrandThemeSemanticsColourText } from "@butternutbox/pawprint-tokens"
|
|
6
|
+
import { resolveFont } from "../../../fonts"
|
|
7
|
+
|
|
8
|
+
type BodySize = "xs" | "sm" | "md" | "lg"
|
|
9
|
+
type HeadingSize = "2xs" | "xs" | "sm" | "md" | "lg" | "xl"
|
|
10
|
+
type DisplaySize = "md" | "lg"
|
|
11
|
+
type BodyWeight = "medium" | "semiBold" | "bold"
|
|
12
|
+
|
|
13
|
+
type TextAlign = "left" | "right" | "center" | "justify"
|
|
14
|
+
type TextTransform = "none" | "capitalize" | "uppercase" | "lowercase"
|
|
15
|
+
type TextDecoration = "none" | "underline" | "line-through"
|
|
16
|
+
|
|
17
|
+
type SemanticTextColorKey = Exclude<
|
|
18
|
+
keyof BrandThemeSemanticsColourText,
|
|
19
|
+
"link" | "action"
|
|
20
|
+
>
|
|
21
|
+
|
|
22
|
+
type FlattenTextColorValues<T> = T extends string
|
|
23
|
+
? T
|
|
24
|
+
: T extends object
|
|
25
|
+
? {
|
|
26
|
+
[K in keyof T]: FlattenTextColorValues<T[K]>
|
|
27
|
+
}[keyof T]
|
|
28
|
+
: never
|
|
29
|
+
|
|
30
|
+
type SemanticTextColorValue =
|
|
31
|
+
FlattenTextColorValues<BrandThemeSemanticsColourText>
|
|
32
|
+
|
|
33
|
+
type NativeTypographyToken = {
|
|
34
|
+
fontFamily?: string
|
|
35
|
+
fontWeight?: string
|
|
36
|
+
fontSize?: string
|
|
37
|
+
lineHeight?: string
|
|
38
|
+
letterSpacing?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type NativeTextProps = Omit<TextProps, "style">
|
|
42
|
+
|
|
43
|
+
type BodyVariant = {
|
|
44
|
+
variant?: "body"
|
|
45
|
+
size?: BodySize
|
|
46
|
+
weight?: BodyWeight
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type HeadingVariant = {
|
|
50
|
+
variant: "heading"
|
|
51
|
+
size?: HeadingSize
|
|
52
|
+
weight?: never
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type DisplayVariant = {
|
|
56
|
+
variant: "display"
|
|
57
|
+
size?: DisplaySize
|
|
58
|
+
weight?: never
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
type CommonProps = {
|
|
62
|
+
align?: TextAlign
|
|
63
|
+
noWrap?: boolean
|
|
64
|
+
textTransform?: TextTransform
|
|
65
|
+
textDecoration?: TextDecoration
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type SemanticVariant = (BodyVariant | HeadingVariant | DisplayVariant) & {
|
|
69
|
+
color?: SemanticTextColorKey
|
|
70
|
+
token?: never
|
|
71
|
+
} & CommonProps
|
|
72
|
+
|
|
73
|
+
type TokenVariant = {
|
|
74
|
+
token: NativeTypographyToken
|
|
75
|
+
color?: SemanticTextColorValue
|
|
76
|
+
} & CommonProps
|
|
77
|
+
|
|
78
|
+
export type TypographyProps =
|
|
79
|
+
| (SemanticVariant & NativeTextProps)
|
|
80
|
+
| (TokenVariant & NativeTextProps)
|
|
81
|
+
|
|
82
|
+
const parseTokenValue = (value: string): number => parseFloat(value)
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Converts a letter-spacing token value to an absolute pixel number.
|
|
86
|
+
* Token values can be percentage strings (e.g. "2%", "0.12%") or
|
|
87
|
+
* pixel strings (e.g. "1px"). Percentages are resolved relative to fontSize.
|
|
88
|
+
*/
|
|
89
|
+
const resolveLetterSpacing = (
|
|
90
|
+
letterSpacing: string,
|
|
91
|
+
fontSize: number
|
|
92
|
+
): number => {
|
|
93
|
+
if (letterSpacing.endsWith("%")) {
|
|
94
|
+
return (parseFloat(letterSpacing) / 100) * fontSize
|
|
95
|
+
}
|
|
96
|
+
return parseFloat(letterSpacing)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const semanticToStyle = (token: {
|
|
100
|
+
fontFamily: string
|
|
101
|
+
fontWeight: string
|
|
102
|
+
fontSize: string
|
|
103
|
+
lineHeight: string
|
|
104
|
+
letterSpacing?: string
|
|
105
|
+
}): TextStyle => {
|
|
106
|
+
const { fontFamily } = resolveFont(token.fontFamily, token.fontWeight)
|
|
107
|
+
const fontSize = parseTokenValue(token.fontSize)
|
|
108
|
+
return {
|
|
109
|
+
fontFamily,
|
|
110
|
+
fontSize,
|
|
111
|
+
lineHeight: parseTokenValue(token.lineHeight),
|
|
112
|
+
...(token.letterSpacing && {
|
|
113
|
+
letterSpacing: resolveLetterSpacing(token.letterSpacing, fontSize)
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const nativeTokenToStyle = (token: NativeTypographyToken): TextStyle => {
|
|
119
|
+
const style: TextStyle = {}
|
|
120
|
+
if (token.fontFamily && token.fontWeight) {
|
|
121
|
+
const { fontFamily } = resolveFont(token.fontFamily, token.fontWeight)
|
|
122
|
+
style.fontFamily = fontFamily
|
|
123
|
+
} else if (token.fontFamily) {
|
|
124
|
+
style.fontFamily = token.fontFamily
|
|
125
|
+
}
|
|
126
|
+
if (token.fontSize) style.fontSize = parseTokenValue(token.fontSize)
|
|
127
|
+
if (token.lineHeight) style.lineHeight = parseTokenValue(token.lineHeight)
|
|
128
|
+
if (token.letterSpacing && style.fontSize) {
|
|
129
|
+
style.letterSpacing = resolveLetterSpacing(
|
|
130
|
+
token.letterSpacing,
|
|
131
|
+
style.fontSize
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
return style
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const VARIANT_DEFAULTS = {
|
|
138
|
+
body: { size: "md" } as const,
|
|
139
|
+
heading: { size: "md" } as const,
|
|
140
|
+
display: { size: "lg" } as const
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const StyledText = styled(Text)({})
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Typography component for body text, headings, and display copy.
|
|
147
|
+
* Styling and semantics only — no i18n concerns.
|
|
148
|
+
*
|
|
149
|
+
* **Semantic mode:**
|
|
150
|
+
* - `variant="body"` — 4 sizes (xs–lg), 3 weights.
|
|
151
|
+
* - `variant="heading"` — 6 sizes (2xs–xl), responsive (steps down one size on narrow screens).
|
|
152
|
+
* - `variant="display"` — 2 sizes (md/lg), responsive.
|
|
153
|
+
*
|
|
154
|
+
* **Token mode:**
|
|
155
|
+
* - `token` — Pass resolved typography token object from theme.tokens.components
|
|
156
|
+
*
|
|
157
|
+
* @param {"body" | "heading" | "display"} [variant="body"] - Text style variant.
|
|
158
|
+
* @param {"xs" | "sm" | "md" | "lg" | "2xs" | "xl"} [size="md"] - Size token. Available sizes depend on variant.
|
|
159
|
+
* @param {"medium" | "semiBold" | "bold"} [weight="medium"] - Font weight. Body only — heading and display have fixed weight.
|
|
160
|
+
* @param {NativeTypographyToken} [token] - Resolved typography token object (alternative to semantic props).
|
|
161
|
+
* @param {SemanticTextColorKey} [color] - Semantic text color key for semantic mode.
|
|
162
|
+
* @param {SemanticTextColorValue} [color] - Hex color string for token mode.
|
|
163
|
+
* @param {"left" | "right" | "center" | "justify"} [align] - Text alignment.
|
|
164
|
+
* @param {boolean} [noWrap] - If true, text is truncated to one line with an ellipsis.
|
|
165
|
+
* @param {"none" | "capitalize" | "uppercase" | "lowercase"} [textTransform] - Text transformation.
|
|
166
|
+
* @param {"none" | "underline" | "line-through"} [textDecoration] - Text decoration.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* // Semantic usage
|
|
170
|
+
* <Typography size="md" weight="semiBold">Body text</Typography>
|
|
171
|
+
* <Typography variant="heading" size="xl">Page title</Typography>
|
|
172
|
+
* <Typography variant="display" size="lg" color="secondary">Hero copy</Typography>
|
|
173
|
+
*
|
|
174
|
+
* // Token usage (for component-owned text)
|
|
175
|
+
* <Typography
|
|
176
|
+
* token={theme.tokens.components.checkbox.typography.label}
|
|
177
|
+
* color={theme.tokens.components.checkbox.colour.text.title}
|
|
178
|
+
* >
|
|
179
|
+
* Accept terms
|
|
180
|
+
* </Typography>
|
|
181
|
+
*/
|
|
182
|
+
export const Typography = React.forwardRef<Text, TypographyProps>(
|
|
183
|
+
(props, ref) => {
|
|
184
|
+
const theme = useTheme()
|
|
185
|
+
const { typography, colour } = theme.tokens.semantics
|
|
186
|
+
|
|
187
|
+
const getCommonStyles = (
|
|
188
|
+
align?: TextAlign,
|
|
189
|
+
textTransform?: TextTransform,
|
|
190
|
+
textDecoration?: TextDecoration
|
|
191
|
+
): TextStyle => ({
|
|
192
|
+
...(align && { textAlign: align }),
|
|
193
|
+
...(textTransform && { textTransform }),
|
|
194
|
+
...(textDecoration && {
|
|
195
|
+
textDecorationLine: textDecoration as TextStyle["textDecorationLine"]
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
if ("token" in props && props.token) {
|
|
200
|
+
const {
|
|
201
|
+
token,
|
|
202
|
+
color,
|
|
203
|
+
align,
|
|
204
|
+
noWrap,
|
|
205
|
+
textTransform,
|
|
206
|
+
textDecoration,
|
|
207
|
+
...rest
|
|
208
|
+
} = props
|
|
209
|
+
|
|
210
|
+
const style: TextStyle = {
|
|
211
|
+
...nativeTokenToStyle(token),
|
|
212
|
+
...(color ? { color } : {}),
|
|
213
|
+
...getCommonStyles(align, textTransform, textDecoration)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<StyledText
|
|
218
|
+
ref={ref}
|
|
219
|
+
style={style}
|
|
220
|
+
numberOfLines={noWrap ? 1 : undefined}
|
|
221
|
+
ellipsizeMode={noWrap ? "tail" : undefined}
|
|
222
|
+
{...rest}
|
|
223
|
+
/>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const {
|
|
228
|
+
variant = "body",
|
|
229
|
+
size,
|
|
230
|
+
weight = "medium",
|
|
231
|
+
color = "primary",
|
|
232
|
+
align,
|
|
233
|
+
noWrap,
|
|
234
|
+
textTransform,
|
|
235
|
+
textDecoration,
|
|
236
|
+
...rest
|
|
237
|
+
} = props
|
|
238
|
+
|
|
239
|
+
const resolvedSize = size ?? VARIANT_DEFAULTS[variant].size
|
|
240
|
+
const commonStyles = getCommonStyles(align, textTransform, textDecoration)
|
|
241
|
+
|
|
242
|
+
const getSemanticColor = (colorKey: SemanticTextColorKey): TextStyle => ({
|
|
243
|
+
color: colour.text[colorKey]
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
let textStyle: TextStyle = {}
|
|
247
|
+
|
|
248
|
+
if (variant === "body") {
|
|
249
|
+
textStyle = {
|
|
250
|
+
...semanticToStyle(typography.body[weight][resolvedSize as BodySize]),
|
|
251
|
+
...getSemanticColor(color),
|
|
252
|
+
...commonStyles
|
|
253
|
+
}
|
|
254
|
+
} else if (variant === "heading") {
|
|
255
|
+
const headingSize = resolvedSize as HeadingSize
|
|
256
|
+
const tokenSet = typography.heading.mobile[headingSize]
|
|
257
|
+
textStyle = {
|
|
258
|
+
...semanticToStyle(tokenSet),
|
|
259
|
+
...getSemanticColor(color),
|
|
260
|
+
...commonStyles
|
|
261
|
+
}
|
|
262
|
+
} else if (variant === "display") {
|
|
263
|
+
const displaySize = resolvedSize as DisplaySize
|
|
264
|
+
const tokenSet = typography.heading.display.mobile[displaySize]
|
|
265
|
+
textStyle = {
|
|
266
|
+
...semanticToStyle(tokenSet),
|
|
267
|
+
...getSemanticColor(color),
|
|
268
|
+
...commonStyles
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<StyledText
|
|
274
|
+
ref={ref}
|
|
275
|
+
style={textStyle}
|
|
276
|
+
numberOfLines={noWrap ? 1 : undefined}
|
|
277
|
+
ellipsizeMode={noWrap ? "tail" : undefined}
|
|
278
|
+
{...rest}
|
|
279
|
+
/>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
Typography.displayName = "Typography"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./Avatar"
|
|
2
|
+
export * from "./Typography"
|
|
3
|
+
export * from "./Badge"
|
|
4
|
+
export * from "./Button"
|
|
5
|
+
export * from "./Hint"
|
|
6
|
+
export * from "./Icon"
|
|
7
|
+
export * from "./IconButton"
|
|
8
|
+
export * from "./Illustration"
|
|
9
|
+
export * from "./Input"
|
|
10
|
+
export * from "./Link"
|
|
11
|
+
export * from "./Logo"
|
|
12
|
+
export * from "./Spinner"
|
|
13
|
+
export * from "./Switch"
|
|
14
|
+
export * from "./Tag"
|