@factorialco/f0-react-native 0.26.0 → 0.28.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.
Files changed (68) hide show
  1. package/README.md +2 -2
  2. package/lib/module/components/exports.js +1 -1
  3. package/lib/module/components/exports.js.map +1 -1
  4. package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.js +2 -0
  5. package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.js.map +1 -0
  6. package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.md +159 -0
  7. package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.js +2 -0
  8. package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.js.map +1 -0
  9. package/lib/module/components/primitives/F0Text/AnimatedF0Text/index.js +2 -0
  10. package/lib/module/components/primitives/F0Text/AnimatedF0Text/index.js.map +1 -0
  11. package/lib/module/components/primitives/F0Text/F0Text/F0Text.js +2 -0
  12. package/lib/module/components/primitives/F0Text/F0Text/F0Text.js.map +1 -0
  13. package/lib/module/components/primitives/F0Text/F0Text/F0Text.md +320 -0
  14. package/lib/module/components/primitives/F0Text/F0Text/F0Text.styles.js +2 -0
  15. package/lib/module/components/primitives/F0Text/F0Text/F0Text.styles.js.map +1 -0
  16. package/lib/module/components/primitives/F0Text/F0Text/F0Text.types.js +2 -0
  17. package/lib/module/components/primitives/F0Text/F0Text/F0Text.types.js.map +1 -0
  18. package/lib/module/components/primitives/F0Text/F0Text/index.js +2 -0
  19. package/lib/module/components/primitives/F0Text/F0Text/index.js.map +1 -0
  20. package/lib/module/components/primitives/F0Text/index.js +2 -0
  21. package/lib/module/components/primitives/F0Text/index.js.map +1 -0
  22. package/lib/module/index.js +1 -1
  23. package/lib/module/index.js.map +1 -1
  24. package/lib/module/lib/utils.js +1 -1
  25. package/lib/module/lib/utils.js.map +1 -1
  26. package/lib/module/styles/theme.css +7 -0
  27. package/lib/typescript/components/exports.d.ts +1 -0
  28. package/lib/typescript/components/exports.d.ts.map +1 -1
  29. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.d.ts +5 -0
  30. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.d.ts.map +1 -0
  31. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.d.ts +45 -0
  32. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.d.ts.map +1 -0
  33. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/index.d.ts +8 -0
  34. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/index.d.ts.map +1 -0
  35. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.d.ts +8 -0
  36. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.d.ts.map +1 -0
  37. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.styles.d.ts +144 -0
  38. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.styles.d.ts.map +1 -0
  39. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.types.d.ts +86 -0
  40. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.types.d.ts.map +1 -0
  41. package/lib/typescript/components/primitives/F0Text/F0Text/index.d.ts +9 -0
  42. package/lib/typescript/components/primitives/F0Text/F0Text/index.d.ts.map +1 -0
  43. package/lib/typescript/components/primitives/F0Text/index.d.ts +12 -0
  44. package/lib/typescript/components/primitives/F0Text/index.d.ts.map +1 -0
  45. package/lib/typescript/index.d.ts +1 -1
  46. package/lib/typescript/index.d.ts.map +1 -1
  47. package/lib/typescript/lib/utils.d.ts +9 -0
  48. package/lib/typescript/lib/utils.d.ts.map +1 -1
  49. package/package.json +1 -1
  50. package/src/components/exports.ts +3 -0
  51. package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.md +159 -0
  52. package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.tsx +72 -0
  53. package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.ts +61 -0
  54. package/src/components/primitives/F0Text/AnimatedF0Text/__tests__/AnimatedF0Text.spec.tsx +131 -0
  55. package/src/components/primitives/F0Text/AnimatedF0Text/__tests__/__snapshots__/AnimatedF0Text.spec.tsx.snap +144 -0
  56. package/src/components/primitives/F0Text/AnimatedF0Text/index.ts +8 -0
  57. package/src/components/primitives/F0Text/F0Text/F0Text.md +320 -0
  58. package/src/components/primitives/F0Text/F0Text/F0Text.styles.ts +71 -0
  59. package/src/components/primitives/F0Text/F0Text/F0Text.tsx +76 -0
  60. package/src/components/primitives/F0Text/F0Text/F0Text.types.ts +134 -0
  61. package/src/components/primitives/F0Text/F0Text/__tests__/F0Text.spec.tsx +228 -0
  62. package/src/components/primitives/F0Text/F0Text/__tests__/__snapshots__/F0Text.spec.tsx.snap +325 -0
  63. package/src/components/primitives/F0Text/F0Text/index.ts +22 -0
  64. package/src/components/primitives/F0Text/index.ts +26 -0
  65. package/src/index.ts +1 -1
  66. package/src/lib/__tests__/utils.spec.ts +48 -0
  67. package/src/lib/utils.ts +19 -0
  68. package/src/styles/theme.css +7 -0
@@ -0,0 +1,71 @@
1
+ import { tv, type VariantProps } from "tailwind-variants"
2
+
3
+ /**
4
+ * Text component variants using tailwind-variants
5
+ * Font weights (font-normal, font-medium, font-semibold) map to
6
+ * Inter font families (Inter-Regular, Inter-Medium, Inter-SemiBold)
7
+ */
8
+ export const textVariants = tv({
9
+ base: "",
10
+ variants: {
11
+ variant: {
12
+ // Heading variants
13
+ "heading-xl":
14
+ "text-[36px] leading-[40px] tracking-[-0.2px] font-semibold",
15
+ "heading-lg":
16
+ "text-[24px] leading-[32px] tracking-[-0.2px] font-semibold",
17
+ "heading-md":
18
+ "text-[20px] leading-[28px] tracking-[-0.2px] font-semibold",
19
+ "heading-sm": "text-[16px] leading-[24px] font-semibold",
20
+
21
+ // Body variants with weight included in variant name
22
+ "body-md-default": "text-[16px] leading-[24px] font-normal",
23
+ "body-md-medium": "text-[16px] leading-[24px] font-medium",
24
+ "body-md-semibold": "text-[16px] leading-[24px] font-semibold",
25
+ "body-sm-default": "text-[14px] leading-[20px] font-normal",
26
+ "body-sm-medium": "text-[14px] leading-[20px] font-medium",
27
+ "body-sm-semibold": "text-[14px] leading-[20px] font-semibold",
28
+ "body-xs-medium": "text-[12px] leading-[16px] font-medium",
29
+ },
30
+ color: {
31
+ default: "text-f0-foreground",
32
+ secondary: "text-f0-foreground-secondary",
33
+ tertiary: "text-f0-foreground-tertiary",
34
+ inverse: "text-f0-foreground-inverse",
35
+ "inverse-secondary": "text-f0-foreground-inverse-secondary",
36
+ disabled: "text-f0-foreground-disabled",
37
+ accent: "text-f0-foreground-accent",
38
+ critical: "text-f0-foreground-critical",
39
+ info: "text-f0-foreground-info",
40
+ warning: "text-f0-foreground-warning",
41
+ positive: "text-f0-foreground-positive",
42
+ selected: "text-f0-foreground-selected",
43
+ },
44
+ align: {
45
+ left: "text-left",
46
+ center: "text-center",
47
+ right: "text-right",
48
+ justify: "text-justify",
49
+ },
50
+ decoration: {
51
+ none: "",
52
+ underline: "underline",
53
+ "line-through": "line-through",
54
+ },
55
+ transform: {
56
+ none: "",
57
+ uppercase: "uppercase",
58
+ lowercase: "lowercase",
59
+ capitalize: "capitalize",
60
+ },
61
+ },
62
+ defaultVariants: {
63
+ variant: "body-sm-default",
64
+ color: "default",
65
+ align: "left",
66
+ decoration: "none",
67
+ transform: "none",
68
+ },
69
+ })
70
+
71
+ export type TextVariants = VariantProps<typeof textVariants>
@@ -0,0 +1,76 @@
1
+ import React from "react"
2
+ import { Text as RNText } from "react-native"
3
+
4
+ import { omitProps } from "../../../../lib/utils"
5
+
6
+ import { textVariants } from "./F0Text.styles"
7
+ import { F0_TEXT_BANNED_PROPS, type F0TextProps } from "./F0Text.types"
8
+
9
+ /**
10
+ * F0Text - Primitive Text component with semantic typography variants
11
+ *
12
+ * @example
13
+ * <F0Text variant="heading-lg">Large Heading</F0Text>
14
+ * <F0Text variant="body-sm-default" color="secondary">Secondary text</F0Text>
15
+ * <F0Text variant="body-md-medium" numberOfLines={2}>Truncated text...</F0Text>
16
+ */
17
+ const F0TextComponent = React.forwardRef<RNText, F0TextProps>(
18
+ (
19
+ {
20
+ variant = "body-sm-default",
21
+ color = "default",
22
+ align = "left",
23
+ decoration = "none",
24
+ transform = "none",
25
+ children,
26
+ numberOfLines,
27
+ ...rest
28
+ },
29
+ ref
30
+ ) => {
31
+ const textClassName = React.useMemo(
32
+ () =>
33
+ textVariants({
34
+ variant,
35
+ color,
36
+ align,
37
+ decoration,
38
+ transform,
39
+ }),
40
+ [variant, color, align, decoration, transform]
41
+ )
42
+
43
+ return (
44
+ <RNText
45
+ ref={ref}
46
+ {...omitProps(rest, F0_TEXT_BANNED_PROPS)}
47
+ className={textClassName}
48
+ numberOfLines={numberOfLines}
49
+ ellipsizeMode={numberOfLines ? "tail" : undefined}
50
+ >
51
+ {children}
52
+ </RNText>
53
+ )
54
+ }
55
+ )
56
+
57
+ F0TextComponent.displayName = "F0Text"
58
+
59
+ export const F0Text = React.memo(F0TextComponent)
60
+
61
+ // Export types
62
+ export type { F0TextProps }
63
+ export {
64
+ TYPOGRAPHY_VARIANTS,
65
+ TEXT_COLORS,
66
+ TEXT_ALIGN,
67
+ TEXT_DECORATIONS,
68
+ TEXT_TRANSFORMS,
69
+ } from "./F0Text.types"
70
+ export type {
71
+ TypographyVariant,
72
+ TextColor,
73
+ TextAlign,
74
+ TextDecoration,
75
+ TextTransform,
76
+ } from "./F0Text.types"
@@ -0,0 +1,134 @@
1
+ import type { TextProps as RNTextProps } from "react-native"
2
+
3
+ /**
4
+ * Props that must not be passed through to the underlying RN Text
5
+ * (`style` and `className` are handled by F0 instead).
6
+ * Used with omitProps for runtime safety.
7
+ */
8
+ export const F0_TEXT_BANNED_PROPS = ["style", "className"] as const
9
+
10
+ /**
11
+ * Typography variant types based on semantic design tokens
12
+ */
13
+ export const TYPOGRAPHY_VARIANTS = [
14
+ "heading-xl",
15
+ "heading-lg",
16
+ "heading-md",
17
+ "heading-sm",
18
+ "body-md-default",
19
+ "body-md-medium",
20
+ "body-md-semibold",
21
+ "body-sm-default",
22
+ "body-sm-medium",
23
+ "body-sm-semibold",
24
+ "body-xs-medium",
25
+ ] as const
26
+
27
+ export type TypographyVariant = (typeof TYPOGRAPHY_VARIANTS)[number]
28
+
29
+ /**
30
+ * Text color variants aligned with F0 semantic color system
31
+ */
32
+ export const TEXT_COLORS = [
33
+ "default",
34
+ "secondary",
35
+ "tertiary",
36
+ "inverse",
37
+ "inverse-secondary",
38
+ "disabled",
39
+ "accent",
40
+ "critical",
41
+ "info",
42
+ "warning",
43
+ "positive",
44
+ "selected",
45
+ ] as const
46
+
47
+ export type TextColor = (typeof TEXT_COLORS)[number]
48
+
49
+ /**
50
+ * Text alignment options
51
+ */
52
+ export const TEXT_ALIGN = ["left", "center", "right", "justify"] as const
53
+
54
+ export type TextAlign = (typeof TEXT_ALIGN)[number]
55
+
56
+ /**
57
+ * Text decoration options
58
+ */
59
+ export const TEXT_DECORATIONS = ["none", "underline", "line-through"] as const
60
+
61
+ export type TextDecoration = (typeof TEXT_DECORATIONS)[number]
62
+
63
+ /**
64
+ * Text transform options
65
+ */
66
+ export const TEXT_TRANSFORMS = [
67
+ "none",
68
+ "uppercase",
69
+ "lowercase",
70
+ "capitalize",
71
+ ] as const
72
+
73
+ export type TextTransform = (typeof TEXT_TRANSFORMS)[number]
74
+
75
+ /**
76
+ * Internal props for the F0Text component.
77
+ * @private
78
+ */
79
+ interface F0TextPropsInternal extends Omit<RNTextProps, "style"> {
80
+ /**
81
+ * Semantic typography variant
82
+ * @default "body-sm-default"
83
+ */
84
+ variant?: TypographyVariant
85
+
86
+ /**
87
+ * Text color from F0 semantic color system
88
+ * @default "default"
89
+ */
90
+ color?: TextColor
91
+
92
+ /**
93
+ * Text alignment
94
+ * @default "left"
95
+ */
96
+ align?: TextAlign
97
+
98
+ /**
99
+ * Text decoration
100
+ * @default "none"
101
+ */
102
+ decoration?: TextDecoration
103
+
104
+ /**
105
+ * Text transform
106
+ * @default "none"
107
+ */
108
+ transform?: TextTransform
109
+
110
+ /**
111
+ * Maximum number of lines before truncating with ellipsis
112
+ */
113
+ numberOfLines?: number
114
+
115
+ /**
116
+ * Children content
117
+ */
118
+ children?: React.ReactNode
119
+
120
+ /**
121
+ * Excluded from public API via Omit<F0TextPropsInternal, "className">.
122
+ * @private
123
+ */
124
+ className?: string
125
+ }
126
+
127
+ /**
128
+ * Public props for the F0Text component
129
+ *
130
+ * Note: `className` and `style` props are NOT available.
131
+ * Use semantic props (variant, color, align, etc.) for typography.
132
+ * For spacing/layout, wrap F0Text in a View with className.
133
+ */
134
+ export type F0TextProps = Omit<F0TextPropsInternal, "className">
@@ -0,0 +1,228 @@
1
+ import { render } from "@testing-library/react-native"
2
+ import React from "react"
3
+ import { Text as RNText } from "react-native"
4
+
5
+ import { F0Text } from "../F0Text"
6
+
7
+ describe("F0Text", () => {
8
+ describe("Snapshots", () => {
9
+ it("renders with default variant (body-sm-default)", () => {
10
+ const { toJSON } = render(<F0Text>Default text</F0Text>)
11
+ expect(toJSON()).toMatchSnapshot()
12
+ })
13
+
14
+ it("renders all typography variants", () => {
15
+ const variants = [
16
+ "heading-xl",
17
+ "heading-lg",
18
+ "heading-md",
19
+ "heading-sm",
20
+ "body-md-default",
21
+ "body-md-medium",
22
+ "body-md-semibold",
23
+ "body-sm-default",
24
+ "body-sm-medium",
25
+ "body-sm-semibold",
26
+ "body-xs-medium",
27
+ ] as const
28
+
29
+ variants.forEach((variant) => {
30
+ const { toJSON } = render(
31
+ <F0Text variant={variant}>{variant} text</F0Text>
32
+ )
33
+ expect(toJSON()).toMatchSnapshot(`variant-${variant}`)
34
+ })
35
+ })
36
+
37
+ it("renders all color variants", () => {
38
+ const colors = [
39
+ "default",
40
+ "secondary",
41
+ "tertiary",
42
+ "inverse",
43
+ "inverse-secondary",
44
+ "disabled",
45
+ "accent",
46
+ "critical",
47
+ "info",
48
+ "warning",
49
+ "positive",
50
+ "selected",
51
+ ] as const
52
+
53
+ colors.forEach((color) => {
54
+ const { toJSON } = render(<F0Text color={color}>{color} text</F0Text>)
55
+ expect(toJSON()).toMatchSnapshot(`color-${color}`)
56
+ })
57
+ })
58
+
59
+ it("renders with alignment options", () => {
60
+ const alignments = ["left", "center", "right", "justify"] as const
61
+
62
+ alignments.forEach((align) => {
63
+ const { toJSON } = render(<F0Text align={align}>{align} text</F0Text>)
64
+ expect(toJSON()).toMatchSnapshot(`align-${align}`)
65
+ })
66
+ })
67
+
68
+ it("renders with text decorations", () => {
69
+ const decorations = ["none", "underline", "line-through"] as const
70
+
71
+ decorations.forEach((decoration) => {
72
+ const { toJSON } = render(
73
+ <F0Text decoration={decoration}>{decoration} text</F0Text>
74
+ )
75
+ expect(toJSON()).toMatchSnapshot(`decoration-${decoration}`)
76
+ })
77
+ })
78
+
79
+ it("renders with text transforms", () => {
80
+ const transforms = [
81
+ "none",
82
+ "uppercase",
83
+ "lowercase",
84
+ "capitalize",
85
+ ] as const
86
+
87
+ transforms.forEach((transform) => {
88
+ const { toJSON } = render(
89
+ <F0Text transform={transform}>{transform} text</F0Text>
90
+ )
91
+ expect(toJSON()).toMatchSnapshot(`transform-${transform}`)
92
+ })
93
+ })
94
+
95
+ it("renders with numberOfLines truncation", () => {
96
+ const { toJSON } = render(
97
+ <F0Text numberOfLines={2}>
98
+ This is a very long text that should be truncated after two lines with
99
+ an ellipsis at the end
100
+ </F0Text>
101
+ )
102
+ expect(toJSON()).toMatchSnapshot()
103
+ })
104
+ })
105
+
106
+ describe("Behavior", () => {
107
+ it("renders children correctly", () => {
108
+ const { getByText } = render(<F0Text>Hello World</F0Text>)
109
+ expect(getByText("Hello World")).toBeTruthy()
110
+ })
111
+
112
+ it("applies correct variant classes", () => {
113
+ const { getByText } = render(
114
+ <F0Text variant="heading-lg">Large Heading</F0Text>
115
+ )
116
+ const element = getByText("Large Heading")
117
+ expect(element.props.className).toContain("text-[24px]")
118
+ expect(element.props.className).toContain("leading-[32px]")
119
+ expect(element.props.className).toContain("tracking-[-0.2px]")
120
+ expect(element.props.className).toContain("font-semibold")
121
+ })
122
+
123
+ it("applies correct color classes", () => {
124
+ const { getByText } = render(
125
+ <F0Text color="secondary">Secondary text</F0Text>
126
+ )
127
+ const element = getByText("Secondary text")
128
+ expect(element.props.className).toContain("text-f0-foreground-secondary")
129
+ })
130
+
131
+ it("applies correct alignment classes", () => {
132
+ const { getByText } = render(
133
+ <F0Text align="center">Centered text</F0Text>
134
+ )
135
+ const element = getByText("Centered text")
136
+ expect(element.props.className).toContain("text-center")
137
+ })
138
+
139
+ it("applies decoration classes", () => {
140
+ const { getByText } = render(
141
+ <F0Text decoration="underline">Underlined text</F0Text>
142
+ )
143
+ const element = getByText("Underlined text")
144
+ expect(element.props.className).toContain("underline")
145
+ })
146
+
147
+ it("applies transform classes", () => {
148
+ const { getByText } = render(
149
+ <F0Text transform="uppercase">uppercase text</F0Text>
150
+ )
151
+ const element = getByText("uppercase text")
152
+ expect(element.props.className).toContain("uppercase")
153
+ })
154
+
155
+ it("sets numberOfLines prop correctly", () => {
156
+ const { getByText } = render(
157
+ <F0Text numberOfLines={3}>Multiline text</F0Text>
158
+ )
159
+ const element = getByText("Multiline text")
160
+ expect(element.props.numberOfLines).toBe(3)
161
+ expect(element.props.ellipsizeMode).toBe("tail")
162
+ })
163
+
164
+ it("does not set ellipsizeMode when numberOfLines is not provided", () => {
165
+ const { getByText } = render(<F0Text>Normal text</F0Text>)
166
+ const element = getByText("Normal text")
167
+ expect(element.props.ellipsizeMode).toBeUndefined()
168
+ })
169
+
170
+ it("forwards additional props to React Native Text", () => {
171
+ const onPress = jest.fn()
172
+ const { getByText } = render(
173
+ <F0Text onPress={onPress} testID="test-text">
174
+ Pressable text
175
+ </F0Text>
176
+ )
177
+ const element = getByText("Pressable text")
178
+ expect(element.props.testID).toBe("test-text")
179
+ expect(element.props.onPress).toBe(onPress)
180
+ })
181
+
182
+ it("filters style prop at runtime (prevents override via spread)", () => {
183
+ const propsWithStyle = {
184
+ style: { color: "red" },
185
+ children: "Text with style attempt",
186
+ }
187
+ const { getByText } = render(
188
+ <F0Text {...(propsWithStyle as React.ComponentProps<typeof F0Text>)}>
189
+ Text with style attempt
190
+ </F0Text>
191
+ )
192
+ const element = getByText("Text with style attempt")
193
+ expect(element.props.style).toBeUndefined()
194
+ })
195
+
196
+ it("filters className prop at runtime (prevents override via spread)", () => {
197
+ const propsWithClassName = {
198
+ className: "font-bold text-red-500",
199
+ children: "Text with className attempt",
200
+ }
201
+ const { getByText } = render(
202
+ <F0Text
203
+ {...(propsWithClassName as React.ComponentProps<typeof F0Text>)}
204
+ >
205
+ Text with className attempt
206
+ </F0Text>
207
+ )
208
+ const element = getByText("Text with className attempt")
209
+ expect(element.props.className).not.toContain("font-bold")
210
+ expect(element.props.className).not.toContain("text-red-500")
211
+ })
212
+
213
+ it("handles ref forwarding", () => {
214
+ const ref = React.createRef<RNText>()
215
+ render(<F0Text ref={ref}>Ref text</F0Text>)
216
+ expect(ref.current).toBeTruthy()
217
+ })
218
+
219
+ it("renders nested children", () => {
220
+ const { getByText } = render(
221
+ <F0Text>
222
+ Parent <F0Text variant="body-xs-medium">nested</F0Text> text
223
+ </F0Text>
224
+ )
225
+ expect(getByText("nested")).toBeTruthy()
226
+ })
227
+ })
228
+ })