@factorialco/f0-react-native 0.28.1 → 0.30.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 (133) hide show
  1. package/lib/module/components/Avatars/IconAvatar/index.js +1 -1
  2. package/lib/module/components/Avatars/IconAvatar/index.js.map +1 -1
  3. package/lib/module/components/Avatars/ModuleAvatar/index.js +1 -1
  4. package/lib/module/components/Avatars/ModuleAvatar/index.js.map +1 -1
  5. package/lib/module/components/Badge/index.js +1 -1
  6. package/lib/module/components/Badge/index.js.map +1 -1
  7. package/lib/module/components/Button/index.js +1 -1
  8. package/lib/module/components/Button/index.js.map +1 -1
  9. package/lib/module/components/Button/index.spec.js +1 -1
  10. package/lib/module/components/Button/index.spec.js.map +1 -1
  11. package/lib/module/components/Icon/index.js +1 -1
  12. package/lib/module/components/Icon/index.js.map +1 -1
  13. package/lib/module/components/OneChip/index.js +1 -1
  14. package/lib/module/components/OneChip/index.js.map +1 -1
  15. package/lib/module/components/Tags/AlertTab/index.js +1 -1
  16. package/lib/module/components/Tags/AlertTab/index.js.map +1 -1
  17. package/lib/module/components/Tags/RawTag/index.js +1 -1
  18. package/lib/module/components/Tags/RawTag/index.js.map +1 -1
  19. package/lib/module/components/experimental/Lists/DataList/ItemContainer.js +1 -1
  20. package/lib/module/components/experimental/Lists/DataList/ItemContainer.js.map +1 -1
  21. package/lib/module/components/experimental/Lists/DataList/actions/CopyAction.js +1 -1
  22. package/lib/module/components/experimental/Lists/DataList/actions/CopyAction.js.map +1 -1
  23. package/lib/module/components/experimental/Lists/DataList/actions/GenericAction.js +1 -1
  24. package/lib/module/components/experimental/Lists/DataList/actions/GenericAction.js.map +1 -1
  25. package/lib/module/components/exports.js +1 -1
  26. package/lib/module/components/exports.js.map +1 -1
  27. package/lib/module/components/primitives/F0Icon/F0Icon.js +2 -0
  28. package/lib/module/components/primitives/F0Icon/F0Icon.js.map +1 -0
  29. package/lib/module/components/primitives/F0Icon/F0Icon.md +187 -0
  30. package/lib/module/components/primitives/F0Icon/F0Icon.styles.js +2 -0
  31. package/lib/module/components/primitives/F0Icon/F0Icon.styles.js.map +1 -0
  32. package/lib/module/components/primitives/F0Icon/F0Icon.types.js +2 -0
  33. package/lib/module/components/primitives/F0Icon/F0Icon.types.js.map +1 -0
  34. package/lib/module/components/primitives/F0Icon/index.js +2 -0
  35. package/lib/module/components/primitives/F0Icon/index.js.map +1 -0
  36. package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.js +1 -1
  37. package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.js.map +1 -1
  38. package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.md +45 -8
  39. package/lib/module/components/primitives/F0Text/F0Text/F0Text.js +1 -1
  40. package/lib/module/components/primitives/F0Text/F0Text/F0Text.js.map +1 -1
  41. package/lib/module/components/primitives/F0Text/F0Text/F0Text.md +42 -31
  42. package/lib/module/components/primitives/F0Text/F0Text/F0Text.styles.js +1 -1
  43. package/lib/module/components/primitives/F0Text/F0Text/F0Text.styles.js.map +1 -1
  44. package/lib/module/components/primitives/F0Text/F0Text/F0Text.types.js +1 -1
  45. package/lib/module/components/primitives/F0Text/F0Text/F0Text.types.js.map +1 -1
  46. package/lib/module/icons/index.js +1 -1
  47. package/lib/module/icons/index.js.map +1 -1
  48. package/lib/module/lib/utils.js.map +1 -1
  49. package/lib/typescript/components/Activity/ActivityItem/index.d.ts +1 -1
  50. package/lib/typescript/components/Activity/ActivityItem/index.d.ts.map +1 -1
  51. package/lib/typescript/components/Avatars/IconAvatar/index.d.ts +1 -1
  52. package/lib/typescript/components/Avatars/IconAvatar/index.d.ts.map +1 -1
  53. package/lib/typescript/components/Avatars/ModuleAvatar/index.d.ts +1 -1
  54. package/lib/typescript/components/Avatars/ModuleAvatar/index.d.ts.map +1 -1
  55. package/lib/typescript/components/Badge/index.d.ts +1 -1
  56. package/lib/typescript/components/Badge/index.d.ts.map +1 -1
  57. package/lib/typescript/components/Button/index.d.ts +1 -1
  58. package/lib/typescript/components/Button/index.d.ts.map +1 -1
  59. package/lib/typescript/components/Icon/index.d.ts +7 -14
  60. package/lib/typescript/components/Icon/index.d.ts.map +1 -1
  61. package/lib/typescript/components/OneChip/index.d.ts +1 -1
  62. package/lib/typescript/components/OneChip/index.d.ts.map +1 -1
  63. package/lib/typescript/components/Tags/RawTag/index.d.ts +1 -1
  64. package/lib/typescript/components/Tags/RawTag/index.d.ts.map +1 -1
  65. package/lib/typescript/components/experimental/Lists/DataList/ItemContainer.d.ts +1 -1
  66. package/lib/typescript/components/experimental/Lists/DataList/ItemContainer.d.ts.map +1 -1
  67. package/lib/typescript/components/experimental/Lists/DataList/actions/CopyAction.d.ts.map +1 -1
  68. package/lib/typescript/components/experimental/Lists/DataList/index.d.ts +1 -1
  69. package/lib/typescript/components/experimental/Lists/DataList/index.d.ts.map +1 -1
  70. package/lib/typescript/components/exports.d.ts +2 -1
  71. package/lib/typescript/components/exports.d.ts.map +1 -1
  72. package/lib/typescript/components/primitives/F0Icon/F0Icon.d.ts +25 -0
  73. package/lib/typescript/components/primitives/F0Icon/F0Icon.d.ts.map +1 -0
  74. package/lib/typescript/components/primitives/F0Icon/F0Icon.styles.d.ts +90 -0
  75. package/lib/typescript/components/primitives/F0Icon/F0Icon.styles.d.ts.map +1 -0
  76. package/lib/typescript/components/primitives/F0Icon/F0Icon.types.d.ts +47 -0
  77. package/lib/typescript/components/primitives/F0Icon/F0Icon.types.d.ts.map +1 -0
  78. package/lib/typescript/components/primitives/F0Icon/index.d.ts +10 -0
  79. package/lib/typescript/components/primitives/F0Icon/index.d.ts.map +1 -0
  80. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.d.ts.map +1 -1
  81. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.d.ts +14 -0
  82. package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.d.ts.map +1 -1
  83. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.d.ts.map +1 -1
  84. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.styles.d.ts +2 -2
  85. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.styles.d.ts.map +1 -1
  86. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.types.d.ts +22 -18
  87. package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.types.d.ts.map +1 -1
  88. package/lib/typescript/icons/index.d.ts +0 -1
  89. package/lib/typescript/icons/index.d.ts.map +1 -1
  90. package/lib/typescript/lib/utils.d.ts +1 -2
  91. package/lib/typescript/lib/utils.d.ts.map +1 -1
  92. package/package.json +1 -1
  93. package/src/components/Activity/ActivityItem/index.spec.tsx +1 -1
  94. package/src/components/Activity/ActivityItem/index.tsx +1 -1
  95. package/src/components/Avatars/IconAvatar/index.tsx +6 -2
  96. package/src/components/Avatars/ModuleAvatar/index.tsx +1 -1
  97. package/src/components/Badge/index.tsx +2 -2
  98. package/src/components/Button/index.spec.tsx +3 -4
  99. package/src/components/Button/index.tsx +19 -13
  100. package/src/components/Icon/__tests__/Icon.spec.tsx +0 -4
  101. package/src/components/Icon/index.tsx +7 -26
  102. package/src/components/OneChip/index.tsx +3 -3
  103. package/src/components/Tags/AlertTab/index.tsx +2 -2
  104. package/src/components/Tags/RawTag/index.tsx +2 -2
  105. package/src/components/experimental/Lists/DataList/ItemContainer.tsx +2 -2
  106. package/src/components/experimental/Lists/DataList/actions/CopyAction.tsx +7 -10
  107. package/src/components/experimental/Lists/DataList/actions/GenericAction.tsx +2 -2
  108. package/src/components/experimental/Lists/DataList/index.tsx +1 -1
  109. package/src/components/experimental/Lists/DetailsItem/__snapshots__/index.spec.tsx.snap +4 -4
  110. package/src/components/experimental/Lists/DetailsItemsList/__snapshots__/index.spec.tsx.snap +1 -1
  111. package/src/components/exports.ts +2 -1
  112. package/src/components/primitives/F0Icon/F0Icon.md +187 -0
  113. package/src/components/primitives/F0Icon/F0Icon.styles.ts +43 -0
  114. package/src/components/primitives/F0Icon/F0Icon.tsx +73 -0
  115. package/src/components/primitives/F0Icon/F0Icon.types.ts +77 -0
  116. package/src/components/primitives/F0Icon/__tests__/F0Icon.spec.tsx +131 -0
  117. package/src/components/primitives/F0Icon/__tests__/F0Icon.tokens.spec.ts +39 -0
  118. package/src/components/primitives/F0Icon/index.ts +10 -0
  119. package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.md +45 -8
  120. package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.tsx +20 -8
  121. package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.ts +15 -0
  122. package/src/components/primitives/F0Text/AnimatedF0Text/__tests__/AnimatedF0Text.spec.tsx +220 -0
  123. package/src/components/primitives/F0Text/AnimatedF0Text/__tests__/__snapshots__/AnimatedF0Text.spec.tsx.snap +16 -16
  124. package/src/components/primitives/F0Text/F0Text/F0Text.md +42 -31
  125. package/src/components/primitives/F0Text/F0Text/F0Text.styles.ts +1 -1
  126. package/src/components/primitives/F0Text/F0Text/F0Text.tsx +17 -10
  127. package/src/components/primitives/F0Text/F0Text/F0Text.types.ts +22 -18
  128. package/src/components/primitives/F0Text/F0Text/__tests__/F0Text.spec.tsx +340 -16
  129. package/src/components/primitives/F0Text/F0Text/__tests__/__snapshots__/F0Text.spec.tsx.snap +36 -36
  130. package/src/icons/index.ts +0 -1
  131. package/src/lib/utils.ts +1 -2
  132. package/lib/module/components/Icon/README.md +0 -63
  133. package/src/components/Icon/README.md +0 -63
@@ -0,0 +1,39 @@
1
+ // @ts-ignore - Node built-ins are available in Jest runtime
2
+ import fs from "fs"
3
+ // @ts-ignore - Node built-ins are available in Jest runtime
4
+ import path from "path"
5
+
6
+ import { ICON_COLORS } from "../F0Icon.types"
7
+
8
+ describe("F0Icon token sync", () => {
9
+ it("ICON_COLORS matches f0-icon-* tokens in theme.css", () => {
10
+ const css = fs.readFileSync(
11
+ // @ts-ignore - __dirname is available in Jest runtime
12
+ path.resolve(__dirname, "../../../../styles/theme.css"),
13
+ "utf-8"
14
+ )
15
+
16
+ // Extract all --color-f0-icon-<name> tokens, excluding sub-tokens
17
+ // that are already captured (e.g. --color-f0-icon-mood-positive
18
+ // but not --color-f0-icon which maps to "default")
19
+ const tokenRegex = /--color-f0-icon-([a-z0-9][a-z0-9-]*):/g
20
+ const tokensFromCSS = new Set<string>()
21
+ let match: RegExpExecArray | null
22
+ while ((match = tokenRegex.exec(css)) !== null) {
23
+ tokensFromCSS.add(match[1])
24
+ }
25
+
26
+ // "default" in ICON_COLORS maps to the base --color-f0-icon token
27
+ const colorsFromType = new Set<string>(
28
+ ICON_COLORS.filter((c) => c !== "default")
29
+ )
30
+
31
+ const missingInType = [...tokensFromCSS].filter(
32
+ (t) => !colorsFromType.has(t)
33
+ )
34
+ const extraInType = [...colorsFromType].filter((t) => !tokensFromCSS.has(t))
35
+
36
+ expect(missingInType).toEqual([])
37
+ expect(extraInType).toEqual([])
38
+ })
39
+ })
@@ -0,0 +1,10 @@
1
+ /**
2
+ * F0Icon - Icon primitive component
3
+ *
4
+ * @see F0Icon.md for documentation
5
+ */
6
+
7
+ export { default as F0Icon } from "./F0Icon"
8
+ export type { F0IconProps, IconType, IconColor } from "./F0Icon.types"
9
+ export { ICON_COLORS } from "./F0Icon.types"
10
+ export { applyIconInterop } from "./F0Icon"
@@ -5,6 +5,7 @@ Animated text primitive for React Native with semantic typography variants and R
5
5
  ## Features
6
6
 
7
7
  - All F0Text semantic typography variants and colors
8
+ - `className` for layout/positioning (margin, padding, flex, etc.) — typography always wins via twMerge
8
9
  - Reanimated `entering`/`exiting` layout animations
9
10
  - Animated `style` prop for custom animations via `useAnimatedStyle`
10
11
  - Layout transition animations
@@ -90,6 +91,41 @@ import { LinearTransition } from "react-native-reanimated"
90
91
  </>
91
92
  ```
92
93
 
94
+ ### className for Layout/Positioning
95
+
96
+ `className` accepts Tailwind classes for layout and positioning. Typography classes
97
+ in `className` are ignored — semantic props always take precedence via twMerge.
98
+
99
+ <!-- prettier-ignore -->
100
+ ```tsx
101
+ import { AnimatedF0Text } from "@factorialco/f0-react-native"
102
+ import { FadeIn } from "react-native-reanimated"
103
+ import { useAnimatedStyle, useSharedValue } from "react-native-reanimated"
104
+
105
+ const opacity = useSharedValue(1)
106
+ const animatedStyle = useAnimatedStyle(() => ({ opacity: opacity.value }))
107
+
108
+ <>
109
+ {/* className for static layout, style for animated values */}
110
+ <AnimatedF0Text
111
+ variant="heading-sm"
112
+ className="mt-4 flex-1"
113
+ style={animatedStyle}
114
+ >
115
+ Positioned animated heading
116
+ </AnimatedF0Text>
117
+
118
+ {/* className for layout + entering animation */}
119
+ <AnimatedF0Text
120
+ variant="body-sm-default"
121
+ className="mb-2 self-center"
122
+ entering={FadeIn.duration(300)}
123
+ >
124
+ Centered fade-in text
125
+ </AnimatedF0Text>
126
+ </>
127
+ ```
128
+
93
129
  ## API Reference
94
130
 
95
131
  ### Props
@@ -98,14 +134,15 @@ AnimatedF0Text accepts all F0Text semantic props plus Reanimated animation props
98
134
 
99
135
  #### Typography Props (shared with F0Text)
100
136
 
101
- | Prop | Type | Default | Description |
102
- | --------------- | ------------------- | ------------------- | ---------------------------------------- |
103
- | `variant` | `TypographyVariant` | `'body-sm-default'` | Typography variant |
104
- | `color` | `TextColor` | `'default'` | Text color from F0 semantic color system |
105
- | `align` | `TextAlign` | `'left'` | Text alignment |
106
- | `decoration` | `TextDecoration` | `'none'` | Text decoration |
107
- | `transform` | `TextTransform` | `'none'` | Text transform |
108
- | `numberOfLines` | `number` | `undefined` | Max lines before truncation |
137
+ | Prop | Type | Default | Description |
138
+ | --------------- | ------------------- | ------------------- | --------------------------------------------------------------------------------------------------------- |
139
+ | `variant` | `TypographyVariant` | `'body-sm-default'` | Typography variant |
140
+ | `color` | `TextColor` | `'default'` | Text color from F0 semantic color system |
141
+ | `align` | `TextAlign` | `'left'` | Text alignment |
142
+ | `decoration` | `TextDecoration` | `'none'` | Text decoration |
143
+ | `transform` | `TextTransform` | `'none'` | Text transform |
144
+ | `numberOfLines` | `number` | `undefined` | Max lines before truncation |
145
+ | `className` | `string` | `undefined` | Tailwind classes for layout/positioning. Typography classes are overridden by semantic props via twMerge. |
109
146
 
110
147
  #### Animation Props
111
148
 
@@ -2,6 +2,7 @@ import React from "react"
2
2
  import { Text as RNText } from "react-native"
3
3
  import Animated from "react-native-reanimated"
4
4
 
5
+ import { cn } from "../../../../lib/utils"
5
6
  import { textVariants } from "../F0Text/F0Text.styles"
6
7
 
7
8
  import type { AnimatedF0TextProps } from "./AnimatedF0Text.types"
@@ -15,10 +16,17 @@ const AnimatedText = Animated.createAnimatedComponent(RNText)
15
16
  * typography system as F0Text. Supports `entering`, `exiting`, `layout`
16
17
  * animations and animated `style` props.
17
18
  *
19
+ * Typography is controlled by semantic props and always takes precedence.
20
+ * `className` is accepted for layout/positioning (margin, padding, flex, etc.).
21
+ * `style` is accepted for Reanimated animated values.
22
+ *
18
23
  * @example
19
24
  * <AnimatedF0Text variant="heading-xl" entering={FadeIn.duration(300)}>
20
25
  * Welcome back
21
26
  * </AnimatedF0Text>
27
+ * <AnimatedF0Text variant="body-sm-default" className="mt-4 flex-1" style={animatedStyle}>
28
+ * Positioned animated text
29
+ * </AnimatedF0Text>
22
30
  */
23
31
  const AnimatedF0TextComponent = React.forwardRef<
24
32
  React.ComponentRef<typeof AnimatedText>,
@@ -31,6 +39,7 @@ const AnimatedF0TextComponent = React.forwardRef<
31
39
  align = "left",
32
40
  decoration = "none",
33
41
  transform = "none",
42
+ className,
34
43
  children,
35
44
  numberOfLines,
36
45
  style,
@@ -40,14 +49,17 @@ const AnimatedF0TextComponent = React.forwardRef<
40
49
  ) => {
41
50
  const textClassName = React.useMemo(
42
51
  () =>
43
- textVariants({
44
- variant,
45
- color,
46
- align,
47
- decoration,
48
- transform,
49
- }),
50
- [variant, color, align, decoration, transform]
52
+ cn(
53
+ className,
54
+ textVariants({
55
+ variant,
56
+ color,
57
+ align,
58
+ decoration,
59
+ transform,
60
+ })
61
+ ),
62
+ [variant, color, align, decoration, transform, className]
51
63
  )
52
64
 
53
65
  return (
@@ -58,4 +58,19 @@ export interface AnimatedF0TextProps extends Omit<
58
58
  * Children content
59
59
  */
60
60
  children?: React.ReactNode
61
+
62
+ /**
63
+ * Tailwind classes for layout and positioning.
64
+ *
65
+ * Allowed: margin, padding, flex, position, width, height, opacity, z-index, etc.
66
+ * Ignored: font-size, font-weight, line-height, letter-spacing, color, text-align,
67
+ * text-decoration, text-transform — these are controlled by semantic props and
68
+ * always take precedence via twMerge.
69
+ *
70
+ * @example
71
+ * className="mt-4 flex-1"
72
+ * className="mb-2 self-center"
73
+ * className="absolute top-0 left-0"
74
+ */
75
+ className?: string
61
76
  }
@@ -128,4 +128,224 @@ describe("AnimatedF0Text", () => {
128
128
  expect(element.props.testID).toBe("test-animated-text")
129
129
  })
130
130
  })
131
+
132
+ describe("className — layout classes pass through", () => {
133
+ it("applies margin classes", () => {
134
+ const { getByText } = render(
135
+ <AnimatedF0Text className="mx-3 mt-4 mb-2">Margin text</AnimatedF0Text>
136
+ )
137
+ const element = getByText("Margin text")
138
+ expect(element.props.className).toContain("mt-4")
139
+ expect(element.props.className).toContain("mb-2")
140
+ expect(element.props.className).toContain("mx-3")
141
+ })
142
+
143
+ it("applies padding classes", () => {
144
+ const { getByText } = render(
145
+ <AnimatedF0Text className="p-4 px-2 py-1">Padded text</AnimatedF0Text>
146
+ )
147
+ const element = getByText("Padded text")
148
+ expect(element.props.className).toContain("p-4")
149
+ expect(element.props.className).toContain("px-2")
150
+ expect(element.props.className).toContain("py-1")
151
+ })
152
+
153
+ it("applies flex classes", () => {
154
+ const { getByText } = render(
155
+ <AnimatedF0Text className="flex-1 flex-shrink-0">
156
+ Flex text
157
+ </AnimatedF0Text>
158
+ )
159
+ const element = getByText("Flex text")
160
+ expect(element.props.className).toContain("flex-1")
161
+ expect(element.props.className).toContain("flex-shrink-0")
162
+ })
163
+
164
+ it("applies position classes", () => {
165
+ const { getByText } = render(
166
+ <AnimatedF0Text className="absolute top-0 left-0">
167
+ Positioned text
168
+ </AnimatedF0Text>
169
+ )
170
+ const element = getByText("Positioned text")
171
+ expect(element.props.className).toContain("absolute")
172
+ expect(element.props.className).toContain("top-0")
173
+ expect(element.props.className).toContain("left-0")
174
+ })
175
+
176
+ it("applies opacity classes", () => {
177
+ const { getByText } = render(
178
+ <AnimatedF0Text className="opacity-50">Faded text</AnimatedF0Text>
179
+ )
180
+ const element = getByText("Faded text")
181
+ expect(element.props.className).toContain("opacity-50")
182
+ })
183
+
184
+ it("preserves layout classes with all variant combinations", () => {
185
+ const { getByText } = render(
186
+ <AnimatedF0Text
187
+ variant="heading-lg"
188
+ color="critical"
189
+ align="center"
190
+ decoration="underline"
191
+ transform="uppercase"
192
+ className="mt-4 flex-1 self-end"
193
+ >
194
+ Full combo
195
+ </AnimatedF0Text>
196
+ )
197
+ const element = getByText("Full combo")
198
+ expect(element.props.className).toContain("mt-4")
199
+ expect(element.props.className).toContain("flex-1")
200
+ expect(element.props.className).toContain("self-end")
201
+ expect(element.props.className).toContain("text-[24px]")
202
+ expect(element.props.className).toContain("font-semibold")
203
+ expect(element.props.className).toContain("text-f0-foreground-critical")
204
+ expect(element.props.className).toContain("text-center")
205
+ expect(element.props.className).toContain("underline")
206
+ expect(element.props.className).toContain("uppercase")
207
+ })
208
+ })
209
+
210
+ describe("className — typography overrides are rejected", () => {
211
+ it("rejects font-size override", () => {
212
+ const { getByText } = render(
213
+ <AnimatedF0Text variant="body-sm-default" className="text-xs">
214
+ Size override attempt
215
+ </AnimatedF0Text>
216
+ )
217
+ const element = getByText("Size override attempt")
218
+ expect(element.props.className).toContain("text-[14px]")
219
+ expect(element.props.className).not.toContain("text-xs")
220
+ })
221
+
222
+ it("rejects font-weight override", () => {
223
+ const { getByText } = render(
224
+ <AnimatedF0Text variant="body-sm-default" className="font-bold">
225
+ Weight override attempt
226
+ </AnimatedF0Text>
227
+ )
228
+ const element = getByText("Weight override attempt")
229
+ expect(element.props.className).toContain("font-normal")
230
+ expect(element.props.className).not.toContain("font-bold")
231
+ })
232
+
233
+ it("rejects line-height override", () => {
234
+ const { getByText } = render(
235
+ <AnimatedF0Text variant="body-sm-default" className="leading-10">
236
+ Leading override attempt
237
+ </AnimatedF0Text>
238
+ )
239
+ const element = getByText("Leading override attempt")
240
+ expect(element.props.className).toContain("leading-[20px]")
241
+ expect(element.props.className).not.toContain("leading-10")
242
+ })
243
+
244
+ it("rejects text-color override", () => {
245
+ const { getByText } = render(
246
+ <AnimatedF0Text color="default" className="text-red-500">
247
+ Color override attempt
248
+ </AnimatedF0Text>
249
+ )
250
+ const element = getByText("Color override attempt")
251
+ expect(element.props.className).toContain("text-f0-foreground")
252
+ expect(element.props.className).not.toContain("text-red-500")
253
+ })
254
+
255
+ it("rejects multiple typography overrides simultaneously", () => {
256
+ const { getByText } = render(
257
+ <AnimatedF0Text
258
+ variant="body-sm-default"
259
+ color="default"
260
+ className="text-xl leading-loose font-bold tracking-wider text-blue-500"
261
+ >
262
+ Multi override attempt
263
+ </AnimatedF0Text>
264
+ )
265
+ const element = getByText("Multi override attempt")
266
+ expect(element.props.className).toContain("text-[14px]")
267
+ expect(element.props.className).toContain("font-normal")
268
+ expect(element.props.className).toContain("leading-[20px]")
269
+ expect(element.props.className).toContain("text-f0-foreground")
270
+ expect(element.props.className).not.toContain("text-xl")
271
+ expect(element.props.className).not.toContain("font-bold")
272
+ expect(element.props.className).not.toContain("leading-loose")
273
+ expect(element.props.className).not.toContain("text-blue-500")
274
+ expect(element.props.className).not.toContain("tracking-wider")
275
+ })
276
+ })
277
+
278
+ describe("className — mixed layout + typography attempts", () => {
279
+ it("keeps layout classes, rejects typography overrides", () => {
280
+ const { getByText } = render(
281
+ <AnimatedF0Text
282
+ variant="heading-md"
283
+ className="mt-4 flex-1 p-2 text-xl font-bold text-red-500"
284
+ >
285
+ Mixed attempt
286
+ </AnimatedF0Text>
287
+ )
288
+ const element = getByText("Mixed attempt")
289
+ expect(element.props.className).toContain("mt-4")
290
+ expect(element.props.className).toContain("p-2")
291
+ expect(element.props.className).toContain("flex-1")
292
+ expect(element.props.className).toContain("text-[20px]")
293
+ expect(element.props.className).toContain("font-semibold")
294
+ expect(element.props.className).toContain("text-f0-foreground")
295
+ expect(element.props.className).not.toContain("font-bold")
296
+ expect(element.props.className).not.toContain("text-red-500")
297
+ expect(element.props.className).not.toContain("text-xl")
298
+ })
299
+
300
+ it("works with both className and style simultaneously", () => {
301
+ const animatedStyle = { opacity: 0.5 }
302
+ const { getByText } = render(
303
+ <AnimatedF0Text
304
+ variant="heading-sm"
305
+ className="mb-2 flex-1"
306
+ style={animatedStyle}
307
+ >
308
+ className + style
309
+ </AnimatedF0Text>
310
+ )
311
+ const element = getByText("className + style")
312
+ expect(element.props.className).toContain("mb-2")
313
+ expect(element.props.className).toContain("flex-1")
314
+ expect(element.props.className).toContain("text-[16px]")
315
+ expect(element.props.className).toContain("font-semibold")
316
+ expect(element.props.style).toEqual(animatedStyle)
317
+ })
318
+ })
319
+
320
+ describe("className — edge cases", () => {
321
+ it("works correctly when className is undefined", () => {
322
+ const { getByText } = render(
323
+ <AnimatedF0Text>No className</AnimatedF0Text>
324
+ )
325
+ const element = getByText("No className")
326
+ expect(element.props.className).toContain("text-[14px]")
327
+ expect(element.props.className).toContain("font-normal")
328
+ expect(element.props.className).toContain("text-f0-foreground")
329
+ })
330
+
331
+ it("works correctly when className is empty string", () => {
332
+ const { getByText } = render(
333
+ <AnimatedF0Text className="">Empty className</AnimatedF0Text>
334
+ )
335
+ const element = getByText("Empty className")
336
+ expect(element.props.className).toContain("text-[14px]")
337
+ expect(element.props.className).toContain("font-normal")
338
+ })
339
+
340
+ it("handles className with extra whitespace", () => {
341
+ const { getByText } = render(
342
+ <AnimatedF0Text className=" mt-4 mb-2 ">
343
+ Whitespace className
344
+ </AnimatedF0Text>
345
+ )
346
+ const element = getByText("Whitespace className")
347
+ expect(element.props.className).toContain("mt-4")
348
+ expect(element.props.className).toContain("mb-2")
349
+ })
350
+ })
131
351
  })
@@ -2,7 +2,7 @@
2
2
 
3
3
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-md-default 1`] = `
4
4
  <Text
5
- className="text-[16px] leading-[24px] font-normal text-f0-foreground text-left"
5
+ className="no-underline normal-case tracking-normal text-[16px] leading-[24px] font-normal text-f0-foreground text-left"
6
6
  >
7
7
  body-md-default
8
8
  text
@@ -11,7 +11,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-
11
11
 
12
12
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-md-medium 1`] = `
13
13
  <Text
14
- className="text-[16px] leading-[24px] font-medium text-f0-foreground text-left"
14
+ className="no-underline normal-case tracking-normal text-[16px] leading-[24px] font-medium text-f0-foreground text-left"
15
15
  >
16
16
  body-md-medium
17
17
  text
@@ -20,7 +20,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-
20
20
 
21
21
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-md-semibold 1`] = `
22
22
  <Text
23
- className="text-[16px] leading-[24px] font-semibold text-f0-foreground text-left"
23
+ className="no-underline normal-case tracking-normal text-[16px] leading-[24px] font-semibold text-f0-foreground text-left"
24
24
  >
25
25
  body-md-semibold
26
26
  text
@@ -29,7 +29,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-
29
29
 
30
30
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-sm-default 1`] = `
31
31
  <Text
32
- className="text-[14px] leading-[20px] font-normal text-f0-foreground text-left"
32
+ className="no-underline normal-case tracking-normal text-[14px] leading-[20px] font-normal text-f0-foreground text-left"
33
33
  >
34
34
  body-sm-default
35
35
  text
@@ -38,7 +38,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-
38
38
 
39
39
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-sm-medium 1`] = `
40
40
  <Text
41
- className="text-[14px] leading-[20px] font-medium text-f0-foreground text-left"
41
+ className="no-underline normal-case tracking-normal text-[14px] leading-[20px] font-medium text-f0-foreground text-left"
42
42
  >
43
43
  body-sm-medium
44
44
  text
@@ -47,7 +47,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-
47
47
 
48
48
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-sm-semibold 1`] = `
49
49
  <Text
50
- className="text-[14px] leading-[20px] font-semibold text-f0-foreground text-left"
50
+ className="no-underline normal-case tracking-normal text-[14px] leading-[20px] font-semibold text-f0-foreground text-left"
51
51
  >
52
52
  body-sm-semibold
53
53
  text
@@ -56,7 +56,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-
56
56
 
57
57
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-xs-medium 1`] = `
58
58
  <Text
59
- className="text-[12px] leading-[16px] font-medium text-f0-foreground text-left"
59
+ className="no-underline normal-case tracking-normal text-[12px] leading-[16px] font-medium text-f0-foreground text-left"
60
60
  >
61
61
  body-xs-medium
62
62
  text
@@ -65,7 +65,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-body-
65
65
 
66
66
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-heading-lg 1`] = `
67
67
  <Text
68
- className="text-[24px] leading-[32px] tracking-[-0.2px] font-semibold text-f0-foreground text-left"
68
+ className="no-underline normal-case text-[24px] leading-[32px] tracking-[-0.2px] font-semibold text-f0-foreground text-left"
69
69
  >
70
70
  heading-lg
71
71
  text
@@ -74,7 +74,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-headi
74
74
 
75
75
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-heading-md 1`] = `
76
76
  <Text
77
- className="text-[20px] leading-[28px] tracking-[-0.2px] font-semibold text-f0-foreground text-left"
77
+ className="no-underline normal-case text-[20px] leading-[28px] tracking-[-0.2px] font-semibold text-f0-foreground text-left"
78
78
  >
79
79
  heading-md
80
80
  text
@@ -83,7 +83,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-headi
83
83
 
84
84
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-heading-sm 1`] = `
85
85
  <Text
86
- className="text-[16px] leading-[24px] font-semibold text-f0-foreground text-left"
86
+ className="no-underline normal-case tracking-normal text-[16px] leading-[24px] font-semibold text-f0-foreground text-left"
87
87
  >
88
88
  heading-sm
89
89
  text
@@ -92,7 +92,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-headi
92
92
 
93
93
  exports[`AnimatedF0Text Snapshots renders all typography variants: variant-heading-xl 1`] = `
94
94
  <Text
95
- className="text-[36px] leading-[40px] tracking-[-0.2px] font-semibold text-f0-foreground text-left"
95
+ className="no-underline normal-case text-[36px] leading-[40px] tracking-[-0.2px] font-semibold text-f0-foreground text-left"
96
96
  >
97
97
  heading-xl
98
98
  text
@@ -101,7 +101,7 @@ exports[`AnimatedF0Text Snapshots renders all typography variants: variant-headi
101
101
 
102
102
  exports[`AnimatedF0Text Snapshots renders with color variants: color-accent 1`] = `
103
103
  <Text
104
- className="text-[14px] leading-[20px] font-normal text-f0-foreground-accent text-left"
104
+ className="no-underline normal-case tracking-normal text-[14px] leading-[20px] font-normal text-f0-foreground-accent text-left"
105
105
  >
106
106
  accent
107
107
  text
@@ -110,7 +110,7 @@ exports[`AnimatedF0Text Snapshots renders with color variants: color-accent 1`]
110
110
 
111
111
  exports[`AnimatedF0Text Snapshots renders with color variants: color-critical 1`] = `
112
112
  <Text
113
- className="text-[14px] leading-[20px] font-normal text-f0-foreground-critical text-left"
113
+ className="no-underline normal-case tracking-normal text-[14px] leading-[20px] font-normal text-f0-foreground-critical text-left"
114
114
  >
115
115
  critical
116
116
  text
@@ -119,7 +119,7 @@ exports[`AnimatedF0Text Snapshots renders with color variants: color-critical 1`
119
119
 
120
120
  exports[`AnimatedF0Text Snapshots renders with color variants: color-default 1`] = `
121
121
  <Text
122
- className="text-[14px] leading-[20px] font-normal text-f0-foreground text-left"
122
+ className="no-underline normal-case tracking-normal text-[14px] leading-[20px] font-normal text-f0-foreground text-left"
123
123
  >
124
124
  default
125
125
  text
@@ -128,7 +128,7 @@ exports[`AnimatedF0Text Snapshots renders with color variants: color-default 1`]
128
128
 
129
129
  exports[`AnimatedF0Text Snapshots renders with color variants: color-secondary 1`] = `
130
130
  <Text
131
- className="text-[14px] leading-[20px] font-normal text-f0-foreground-secondary text-left"
131
+ className="no-underline normal-case tracking-normal text-[14px] leading-[20px] font-normal text-f0-foreground-secondary text-left"
132
132
  >
133
133
  secondary
134
134
  text
@@ -137,7 +137,7 @@ exports[`AnimatedF0Text Snapshots renders with color variants: color-secondary 1
137
137
 
138
138
  exports[`AnimatedF0Text Snapshots renders with default variant (body-sm-default) 1`] = `
139
139
  <Text
140
- className="text-[14px] leading-[20px] font-normal text-f0-foreground text-left"
140
+ className="no-underline normal-case tracking-normal text-[14px] leading-[20px] font-normal text-f0-foreground text-left"
141
141
  >
142
142
  Default text
143
143
  </Text>