@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.
- package/lib/module/components/Avatars/IconAvatar/index.js +1 -1
- package/lib/module/components/Avatars/IconAvatar/index.js.map +1 -1
- package/lib/module/components/Avatars/ModuleAvatar/index.js +1 -1
- package/lib/module/components/Avatars/ModuleAvatar/index.js.map +1 -1
- package/lib/module/components/Badge/index.js +1 -1
- package/lib/module/components/Badge/index.js.map +1 -1
- package/lib/module/components/Button/index.js +1 -1
- package/lib/module/components/Button/index.js.map +1 -1
- package/lib/module/components/Button/index.spec.js +1 -1
- package/lib/module/components/Button/index.spec.js.map +1 -1
- package/lib/module/components/Icon/index.js +1 -1
- package/lib/module/components/Icon/index.js.map +1 -1
- package/lib/module/components/OneChip/index.js +1 -1
- package/lib/module/components/OneChip/index.js.map +1 -1
- package/lib/module/components/Tags/AlertTab/index.js +1 -1
- package/lib/module/components/Tags/AlertTab/index.js.map +1 -1
- package/lib/module/components/Tags/RawTag/index.js +1 -1
- package/lib/module/components/Tags/RawTag/index.js.map +1 -1
- package/lib/module/components/experimental/Lists/DataList/ItemContainer.js +1 -1
- package/lib/module/components/experimental/Lists/DataList/ItemContainer.js.map +1 -1
- package/lib/module/components/experimental/Lists/DataList/actions/CopyAction.js +1 -1
- package/lib/module/components/experimental/Lists/DataList/actions/CopyAction.js.map +1 -1
- package/lib/module/components/experimental/Lists/DataList/actions/GenericAction.js +1 -1
- package/lib/module/components/experimental/Lists/DataList/actions/GenericAction.js.map +1 -1
- package/lib/module/components/exports.js +1 -1
- package/lib/module/components/exports.js.map +1 -1
- package/lib/module/components/primitives/F0Icon/F0Icon.js +2 -0
- package/lib/module/components/primitives/F0Icon/F0Icon.js.map +1 -0
- package/lib/module/components/primitives/F0Icon/F0Icon.md +187 -0
- package/lib/module/components/primitives/F0Icon/F0Icon.styles.js +2 -0
- package/lib/module/components/primitives/F0Icon/F0Icon.styles.js.map +1 -0
- package/lib/module/components/primitives/F0Icon/F0Icon.types.js +2 -0
- package/lib/module/components/primitives/F0Icon/F0Icon.types.js.map +1 -0
- package/lib/module/components/primitives/F0Icon/index.js +2 -0
- package/lib/module/components/primitives/F0Icon/index.js.map +1 -0
- package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.js +1 -1
- package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.js.map +1 -1
- package/lib/module/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.md +45 -8
- package/lib/module/components/primitives/F0Text/F0Text/F0Text.js +1 -1
- package/lib/module/components/primitives/F0Text/F0Text/F0Text.js.map +1 -1
- package/lib/module/components/primitives/F0Text/F0Text/F0Text.md +42 -31
- package/lib/module/components/primitives/F0Text/F0Text/F0Text.styles.js +1 -1
- package/lib/module/components/primitives/F0Text/F0Text/F0Text.styles.js.map +1 -1
- package/lib/module/components/primitives/F0Text/F0Text/F0Text.types.js +1 -1
- package/lib/module/components/primitives/F0Text/F0Text/F0Text.types.js.map +1 -1
- package/lib/module/icons/index.js +1 -1
- package/lib/module/icons/index.js.map +1 -1
- package/lib/module/lib/utils.js.map +1 -1
- package/lib/typescript/components/Activity/ActivityItem/index.d.ts +1 -1
- package/lib/typescript/components/Activity/ActivityItem/index.d.ts.map +1 -1
- package/lib/typescript/components/Avatars/IconAvatar/index.d.ts +1 -1
- package/lib/typescript/components/Avatars/IconAvatar/index.d.ts.map +1 -1
- package/lib/typescript/components/Avatars/ModuleAvatar/index.d.ts +1 -1
- package/lib/typescript/components/Avatars/ModuleAvatar/index.d.ts.map +1 -1
- package/lib/typescript/components/Badge/index.d.ts +1 -1
- package/lib/typescript/components/Badge/index.d.ts.map +1 -1
- package/lib/typescript/components/Button/index.d.ts +1 -1
- package/lib/typescript/components/Button/index.d.ts.map +1 -1
- package/lib/typescript/components/Icon/index.d.ts +7 -14
- package/lib/typescript/components/Icon/index.d.ts.map +1 -1
- package/lib/typescript/components/OneChip/index.d.ts +1 -1
- package/lib/typescript/components/OneChip/index.d.ts.map +1 -1
- package/lib/typescript/components/Tags/RawTag/index.d.ts +1 -1
- package/lib/typescript/components/Tags/RawTag/index.d.ts.map +1 -1
- package/lib/typescript/components/experimental/Lists/DataList/ItemContainer.d.ts +1 -1
- package/lib/typescript/components/experimental/Lists/DataList/ItemContainer.d.ts.map +1 -1
- package/lib/typescript/components/experimental/Lists/DataList/actions/CopyAction.d.ts.map +1 -1
- package/lib/typescript/components/experimental/Lists/DataList/index.d.ts +1 -1
- package/lib/typescript/components/experimental/Lists/DataList/index.d.ts.map +1 -1
- package/lib/typescript/components/exports.d.ts +2 -1
- package/lib/typescript/components/exports.d.ts.map +1 -1
- package/lib/typescript/components/primitives/F0Icon/F0Icon.d.ts +25 -0
- package/lib/typescript/components/primitives/F0Icon/F0Icon.d.ts.map +1 -0
- package/lib/typescript/components/primitives/F0Icon/F0Icon.styles.d.ts +90 -0
- package/lib/typescript/components/primitives/F0Icon/F0Icon.styles.d.ts.map +1 -0
- package/lib/typescript/components/primitives/F0Icon/F0Icon.types.d.ts +47 -0
- package/lib/typescript/components/primitives/F0Icon/F0Icon.types.d.ts.map +1 -0
- package/lib/typescript/components/primitives/F0Icon/index.d.ts +10 -0
- package/lib/typescript/components/primitives/F0Icon/index.d.ts.map +1 -0
- package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.d.ts.map +1 -1
- package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.d.ts +14 -0
- package/lib/typescript/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.d.ts.map +1 -1
- package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.d.ts.map +1 -1
- package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.styles.d.ts +2 -2
- package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.styles.d.ts.map +1 -1
- package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.types.d.ts +22 -18
- package/lib/typescript/components/primitives/F0Text/F0Text/F0Text.types.d.ts.map +1 -1
- package/lib/typescript/icons/index.d.ts +0 -1
- package/lib/typescript/icons/index.d.ts.map +1 -1
- package/lib/typescript/lib/utils.d.ts +1 -2
- package/lib/typescript/lib/utils.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Activity/ActivityItem/index.spec.tsx +1 -1
- package/src/components/Activity/ActivityItem/index.tsx +1 -1
- package/src/components/Avatars/IconAvatar/index.tsx +6 -2
- package/src/components/Avatars/ModuleAvatar/index.tsx +1 -1
- package/src/components/Badge/index.tsx +2 -2
- package/src/components/Button/index.spec.tsx +3 -4
- package/src/components/Button/index.tsx +19 -13
- package/src/components/Icon/__tests__/Icon.spec.tsx +0 -4
- package/src/components/Icon/index.tsx +7 -26
- package/src/components/OneChip/index.tsx +3 -3
- package/src/components/Tags/AlertTab/index.tsx +2 -2
- package/src/components/Tags/RawTag/index.tsx +2 -2
- package/src/components/experimental/Lists/DataList/ItemContainer.tsx +2 -2
- package/src/components/experimental/Lists/DataList/actions/CopyAction.tsx +7 -10
- package/src/components/experimental/Lists/DataList/actions/GenericAction.tsx +2 -2
- package/src/components/experimental/Lists/DataList/index.tsx +1 -1
- package/src/components/experimental/Lists/DetailsItem/__snapshots__/index.spec.tsx.snap +4 -4
- package/src/components/experimental/Lists/DetailsItemsList/__snapshots__/index.spec.tsx.snap +1 -1
- package/src/components/exports.ts +2 -1
- package/src/components/primitives/F0Icon/F0Icon.md +187 -0
- package/src/components/primitives/F0Icon/F0Icon.styles.ts +43 -0
- package/src/components/primitives/F0Icon/F0Icon.tsx +73 -0
- package/src/components/primitives/F0Icon/F0Icon.types.ts +77 -0
- package/src/components/primitives/F0Icon/__tests__/F0Icon.spec.tsx +131 -0
- package/src/components/primitives/F0Icon/__tests__/F0Icon.tokens.spec.ts +39 -0
- package/src/components/primitives/F0Icon/index.ts +10 -0
- package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.md +45 -8
- package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.tsx +20 -8
- package/src/components/primitives/F0Text/AnimatedF0Text/AnimatedF0Text.types.ts +15 -0
- package/src/components/primitives/F0Text/AnimatedF0Text/__tests__/AnimatedF0Text.spec.tsx +220 -0
- package/src/components/primitives/F0Text/AnimatedF0Text/__tests__/__snapshots__/AnimatedF0Text.spec.tsx.snap +16 -16
- package/src/components/primitives/F0Text/F0Text/F0Text.md +42 -31
- package/src/components/primitives/F0Text/F0Text/F0Text.styles.ts +1 -1
- package/src/components/primitives/F0Text/F0Text/F0Text.tsx +17 -10
- package/src/components/primitives/F0Text/F0Text/F0Text.types.ts +22 -18
- package/src/components/primitives/F0Text/F0Text/__tests__/F0Text.spec.tsx +340 -16
- package/src/components/primitives/F0Text/F0Text/__tests__/__snapshots__/F0Text.spec.tsx.snap +36 -36
- package/src/icons/index.ts +0 -1
- package/src/lib/utils.ts +1 -2
- package/lib/module/components/Icon/README.md +0 -63
- package/src/components/Icon/README.md +0 -63
|
@@ -2,7 +2,7 @@ import { ReactElement, ReactNode } from "react"
|
|
|
2
2
|
import { View, Text } from "react-native"
|
|
3
3
|
|
|
4
4
|
import { cn } from "../../../../lib/utils"
|
|
5
|
-
import {
|
|
5
|
+
import { F0Icon, type IconType } from "../../../primitives/F0Icon"
|
|
6
6
|
|
|
7
7
|
import { CopyAction } from "./actions/CopyAction"
|
|
8
8
|
import { GenericAction } from "./actions/GenericAction"
|
|
@@ -34,7 +34,7 @@ export const ItemContainer = (props: ItemContainerProps) => {
|
|
|
34
34
|
(typeof LeftIcon === "function" ? (
|
|
35
35
|
LeftIcon({})
|
|
36
36
|
) : (
|
|
37
|
-
<
|
|
37
|
+
<F0Icon icon={LeftIcon} size="md" />
|
|
38
38
|
))}
|
|
39
39
|
<Text className="line-clamp-5 text-left text-f0-foreground">
|
|
40
40
|
{text}
|
|
@@ -5,7 +5,7 @@ import { Pressable, View } from "react-native"
|
|
|
5
5
|
import { CopyActionType } from ".."
|
|
6
6
|
import { CheckCircle, LayersFront } from "../../../../../icons/app"
|
|
7
7
|
import { cn } from "../../../../../lib/utils"
|
|
8
|
-
import {
|
|
8
|
+
import { F0Icon } from "../../../../primitives/F0Icon"
|
|
9
9
|
|
|
10
10
|
const COPIED_SHOWN_MS = 750
|
|
11
11
|
|
|
@@ -47,24 +47,21 @@ export const CopyAction = ({ text, children }: CopyActionProps) => {
|
|
|
47
47
|
<View className="flex flex-row items-center gap-1.5">{children}</View>
|
|
48
48
|
<View className="flex">
|
|
49
49
|
{!copied && (
|
|
50
|
-
<
|
|
50
|
+
<F0Icon
|
|
51
51
|
icon={LayersFront}
|
|
52
52
|
size="md"
|
|
53
53
|
aria-hidden={true}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
)}
|
|
54
|
+
color="bold"
|
|
55
|
+
className="col-start-1 col-end-2 row-start-1 row-end-2"
|
|
57
56
|
/>
|
|
58
57
|
)}
|
|
59
58
|
{copied && (
|
|
60
|
-
<
|
|
59
|
+
<F0Icon
|
|
61
60
|
icon={CheckCircle}
|
|
62
61
|
size="md"
|
|
63
62
|
aria-hidden={true}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"text-f0-icon-positive"
|
|
67
|
-
)}
|
|
63
|
+
color="positive"
|
|
64
|
+
className="col-start-1 col-end-2 row-start-1 row-end-2"
|
|
68
65
|
/>
|
|
69
66
|
)}
|
|
70
67
|
</View>
|
|
@@ -4,7 +4,7 @@ import { Pressable, View } from "react-native"
|
|
|
4
4
|
import { GenericActionType } from ".."
|
|
5
5
|
import { ChevronRight } from "../../../../../icons/app"
|
|
6
6
|
import { cn } from "../../../../../lib/utils"
|
|
7
|
-
import {
|
|
7
|
+
import { F0Icon } from "../../../../primitives/F0Icon"
|
|
8
8
|
|
|
9
9
|
export type GenericActionProps = {
|
|
10
10
|
children: ReactNode
|
|
@@ -24,7 +24,7 @@ export const GenericAction = memo(
|
|
|
24
24
|
)}
|
|
25
25
|
>
|
|
26
26
|
<View className="flex flex-row items-center gap-1.5">{children}</View>
|
|
27
|
-
<
|
|
27
|
+
<F0Icon
|
|
28
28
|
aria-hidden={true}
|
|
29
29
|
icon={ChevronRight}
|
|
30
30
|
size="md"
|
|
@@ -5,7 +5,7 @@ import { cn } from "../../../../lib/utils"
|
|
|
5
5
|
import { CompanyAvatar } from "../../../Avatars/CompanyAvatar"
|
|
6
6
|
import { PersonAvatar } from "../../../Avatars/PersonAvatar"
|
|
7
7
|
import { TeamAvatar } from "../../../Avatars/TeamAvatar"
|
|
8
|
-
import { IconType } from "../../../
|
|
8
|
+
import { type IconType } from "../../../primitives/F0Icon"
|
|
9
9
|
import { DotTag, DotTagProps } from "../../../Tags/DotTag"
|
|
10
10
|
|
|
11
11
|
import { ItemContainer } from "./ItemContainer"
|
|
@@ -101,7 +101,7 @@ exports[`DetailsItem Snapshot type company 1`] = `
|
|
|
101
101
|
aria-hidden={true}
|
|
102
102
|
bbHeight="100%"
|
|
103
103
|
bbWidth="100%"
|
|
104
|
-
className="shrink-0 w-5 h-5 stroke-md col-start-1 col-end-2 row-start-1 row-end-2
|
|
104
|
+
className="shrink-0 w-5 h-5 stroke-md text-f0-icon-bold col-start-1 col-end-2 row-start-1 row-end-2"
|
|
105
105
|
fill="none"
|
|
106
106
|
focusable={false}
|
|
107
107
|
meetOrSlice={0}
|
|
@@ -343,7 +343,7 @@ exports[`DetailsItem Snapshot type item 1`] = `
|
|
|
343
343
|
aria-hidden={true}
|
|
344
344
|
bbHeight="100%"
|
|
345
345
|
bbWidth="100%"
|
|
346
|
-
className="shrink-0 w-5 h-5 stroke-md col-start-1 col-end-2 row-start-1 row-end-2
|
|
346
|
+
className="shrink-0 w-5 h-5 stroke-md text-f0-icon-bold col-start-1 col-end-2 row-start-1 row-end-2"
|
|
347
347
|
fill="none"
|
|
348
348
|
focusable={false}
|
|
349
349
|
meetOrSlice={0}
|
|
@@ -519,7 +519,7 @@ exports[`DetailsItem Snapshot type person 1`] = `
|
|
|
519
519
|
aria-hidden={true}
|
|
520
520
|
bbHeight="100%"
|
|
521
521
|
bbWidth="100%"
|
|
522
|
-
className="shrink-0 w-5 h-5 stroke-md col-start-1 col-end-2 row-start-1 row-end-2
|
|
522
|
+
className="shrink-0 w-5 h-5 stroke-md text-f0-icon-bold col-start-1 col-end-2 row-start-1 row-end-2"
|
|
523
523
|
fill="none"
|
|
524
524
|
focusable={false}
|
|
525
525
|
meetOrSlice={0}
|
|
@@ -695,7 +695,7 @@ exports[`DetailsItem Snapshot type team 1`] = `
|
|
|
695
695
|
aria-hidden={true}
|
|
696
696
|
bbHeight="100%"
|
|
697
697
|
bbWidth="100%"
|
|
698
|
-
className="shrink-0 w-5 h-5 stroke-md col-start-1 col-end-2 row-start-1 row-end-2
|
|
698
|
+
className="shrink-0 w-5 h-5 stroke-md text-f0-icon-bold col-start-1 col-end-2 row-start-1 row-end-2"
|
|
699
699
|
fill="none"
|
|
700
700
|
focusable={false}
|
|
701
701
|
meetOrSlice={0}
|
package/src/components/experimental/Lists/DetailsItemsList/__snapshots__/index.spec.tsx.snap
CHANGED
|
@@ -79,7 +79,7 @@ exports[`DetailsItemsList Snapshot 1`] = `
|
|
|
79
79
|
aria-hidden={true}
|
|
80
80
|
bbHeight="100%"
|
|
81
81
|
bbWidth="100%"
|
|
82
|
-
className="shrink-0 w-5 h-5 stroke-md col-start-1 col-end-2 row-start-1 row-end-2
|
|
82
|
+
className="shrink-0 w-5 h-5 stroke-md text-f0-icon-bold col-start-1 col-end-2 row-start-1 row-end-2"
|
|
83
83
|
fill="none"
|
|
84
84
|
focusable={false}
|
|
85
85
|
meetOrSlice={0}
|
|
@@ -5,7 +5,7 @@ export * from "./Badge"
|
|
|
5
5
|
export * from "./Button"
|
|
6
6
|
export * from "./Counter"
|
|
7
7
|
export * from "./ExampleComponent"
|
|
8
|
-
export
|
|
8
|
+
export { Icon, type IconProps } from "./Icon"
|
|
9
9
|
export * from "./Navigation/PageHeader"
|
|
10
10
|
export * from "./OneChip"
|
|
11
11
|
export * from "./OnePreset"
|
|
@@ -17,3 +17,4 @@ export * from "./experimental/Lists/DetailsItemsList"
|
|
|
17
17
|
|
|
18
18
|
// Export primitives
|
|
19
19
|
export * from "./primitives/F0Text"
|
|
20
|
+
export * from "./primitives/F0Icon"
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# F0Icon
|
|
2
|
+
|
|
3
|
+
Icon component for rendering SVG icons with consistent sizing and semantic colors from the F0 Design System.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
F0Icon is an atomic component that wraps SVG icon components with automatic UniWind interop, standardized sizing variants, and semantic icon colors. It follows the same pattern as F0Text: a `color` prop maps to design system tokens, with `className` available as an escape hatch.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
- **Pattern:** Props API (Atomic Component)
|
|
12
|
+
- **Category:** Primitive Component
|
|
13
|
+
- **Location:** `src/components/primitives/F0Icon/`
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
<!-- prettier-ignore -->
|
|
18
|
+
```tsx
|
|
19
|
+
import { F0Icon } from "@factorialco/f0-react-native"
|
|
20
|
+
import { Archive } from "@factorialco/f0-react-native/icons/app"
|
|
21
|
+
import { Home } from "@factorialco/f0-react-native/icons/modules"
|
|
22
|
+
|
|
23
|
+
<F0Icon icon={Archive} />
|
|
24
|
+
|
|
25
|
+
<F0Icon icon={Home} size="lg" />
|
|
26
|
+
|
|
27
|
+
<F0Icon icon={Archive} color="critical" />
|
|
28
|
+
|
|
29
|
+
<F0Icon icon={Archive} size="sm" color="positive" testID="archive-icon" />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Props
|
|
33
|
+
|
|
34
|
+
### `icon` (required)
|
|
35
|
+
|
|
36
|
+
- **Type:** `IconType`
|
|
37
|
+
- **Description:** SVG icon component to render (from `icons/` directory)
|
|
38
|
+
|
|
39
|
+
<!-- prettier-ignore -->
|
|
40
|
+
```tsx
|
|
41
|
+
import { Archive } from "@factorialco/f0-react-native/icons/app"
|
|
42
|
+
<F0Icon icon={Archive} />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `size`
|
|
46
|
+
|
|
47
|
+
- **Type:** `'xl' | 'lg' | 'md' | 'sm' | 'xs'`
|
|
48
|
+
- **Default:** `'md'`
|
|
49
|
+
- **Description:** Size variant for the icon
|
|
50
|
+
|
|
51
|
+
<!-- prettier-ignore -->
|
|
52
|
+
```tsx
|
|
53
|
+
<F0Icon icon={Archive} size="xl" /> // 32x32px
|
|
54
|
+
<F0Icon icon={Archive} size="lg" /> // 24x24px
|
|
55
|
+
<F0Icon icon={Archive} size="md" /> // 20x20px (default)
|
|
56
|
+
<F0Icon icon={Archive} size="sm" /> // 16x16px
|
|
57
|
+
<F0Icon icon={Archive} size="xs" /> // 12x12px
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### `color`
|
|
61
|
+
|
|
62
|
+
- **Type:** `IconColor` (see below)
|
|
63
|
+
- **Default:** none (no color class applied; icon inherits from parent)
|
|
64
|
+
- **Description:** Semantic icon color from the F0 design system. Maps to `f0-icon-*` tokens.
|
|
65
|
+
|
|
66
|
+
<!-- prettier-ignore -->
|
|
67
|
+
```tsx
|
|
68
|
+
<F0Icon icon={Archive} color="default" />
|
|
69
|
+
<F0Icon icon={Archive} color="critical" />
|
|
70
|
+
<F0Icon icon={Archive} color="positive" />
|
|
71
|
+
<F0Icon icon={Archive} color="info" />
|
|
72
|
+
<F0Icon icon={Archive} color="warning" />
|
|
73
|
+
<F0Icon icon={Archive} color="accent" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `testID`
|
|
77
|
+
|
|
78
|
+
- **Type:** `string`
|
|
79
|
+
- **Description:** Test identifier for testing library
|
|
80
|
+
|
|
81
|
+
<!-- prettier-ignore -->
|
|
82
|
+
```tsx
|
|
83
|
+
<F0Icon icon={Archive} testID="my-icon" />
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `className`
|
|
87
|
+
|
|
88
|
+
- **Type:** `string`
|
|
89
|
+
- **Description:** Tailwind classes for custom styling. Use as an escape hatch when `color` doesn't cover the need (e.g. layout adjustments, one-off colors).
|
|
90
|
+
|
|
91
|
+
<!-- prettier-ignore -->
|
|
92
|
+
```tsx
|
|
93
|
+
<F0Icon icon={Archive} className="-ml-0.5" />
|
|
94
|
+
<F0Icon icon={Star} className="text-yellow-500" />
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Other Props
|
|
98
|
+
|
|
99
|
+
F0Icon accepts all SVG props from `react-native-svg` except `style` (use `color` or `className` instead).
|
|
100
|
+
|
|
101
|
+
## Size Variants
|
|
102
|
+
|
|
103
|
+
| Variant | Width/Height | Stroke Width | Use Case |
|
|
104
|
+
| ------- | -------------- | ------------ | ---------------------------------- |
|
|
105
|
+
| `xl` | 32px (w-8 h-8) | stroke-xl | Large feature icons, hero sections |
|
|
106
|
+
| `lg` | 24px (w-6 h-6) | stroke-lg | Primary buttons, major actions |
|
|
107
|
+
| `md` | 20px (w-5 h-5) | stroke-md | Default size, most UI elements |
|
|
108
|
+
| `sm` | 16px (w-4 h-4) | stroke-sm | Secondary buttons, compact UI |
|
|
109
|
+
| `xs` | 12px (w-3 h-3) | stroke-xs | Inline icons, dense layouts |
|
|
110
|
+
|
|
111
|
+
## Color Variants
|
|
112
|
+
|
|
113
|
+
Derived from `f0-icon-*` tokens in `src/styles/theme.css`. Sync is enforced by `F0Icon.tokens.spec.ts`.
|
|
114
|
+
|
|
115
|
+
| Color | Token | Use Case |
|
|
116
|
+
| --------------------- | ---------------------------------- | ------------------------- |
|
|
117
|
+
| `default` | `text-f0-icon` | Standard icon color |
|
|
118
|
+
| `secondary` | `text-f0-icon-secondary` | Muted/secondary icons |
|
|
119
|
+
| `inverse` | `text-f0-icon-inverse` | Icons on dark backgrounds |
|
|
120
|
+
| `bold` | `text-f0-icon-bold` | High emphasis icons |
|
|
121
|
+
| `critical` | `text-f0-icon-critical` | Error/destructive |
|
|
122
|
+
| `critical-bold` | `text-f0-icon-critical-bold` | Bold critical emphasis |
|
|
123
|
+
| `accent` | `text-f0-icon-accent` | Brand accent |
|
|
124
|
+
| `info` | `text-f0-icon-info` | Informational |
|
|
125
|
+
| `warning` | `text-f0-icon-warning` | Warning states |
|
|
126
|
+
| `positive` | `text-f0-icon-positive` | Success/positive |
|
|
127
|
+
| `promote` | `text-f0-icon-promote` | Promotional highlights |
|
|
128
|
+
| `selected` | `text-f0-icon-selected` | Selected state |
|
|
129
|
+
| `selected-hover` | `text-f0-icon-selected-hover` | Selected hover state |
|
|
130
|
+
| `mood-super-negative` | `text-f0-icon-mood-super-negative` | Mood: very negative |
|
|
131
|
+
| `mood-negative` | `text-f0-icon-mood-negative` | Mood: negative |
|
|
132
|
+
| `mood-neutral` | `text-f0-icon-mood-neutral` | Mood: neutral |
|
|
133
|
+
| `mood-positive` | `text-f0-icon-mood-positive` | Mood: positive |
|
|
134
|
+
| `mood-super-positive` | `text-f0-icon-mood-super-positive` | Mood: very positive |
|
|
135
|
+
|
|
136
|
+
## Implementation Details
|
|
137
|
+
|
|
138
|
+
### UniWind Interop
|
|
139
|
+
|
|
140
|
+
F0Icon automatically applies UniWind interop to icon components using `withUniwind()`. Icons are cached in a `WeakSet` to ensure the wrapper is only applied once per icon type.
|
|
141
|
+
|
|
142
|
+
### Performance
|
|
143
|
+
|
|
144
|
+
- Wrapped in `React.memo` to prevent unnecessary re-renders
|
|
145
|
+
- `useMemo` for className generation
|
|
146
|
+
- Icon interop caching via `WeakSet`
|
|
147
|
+
|
|
148
|
+
### TypeScript
|
|
149
|
+
|
|
150
|
+
Style prop is blocked at compile-time to enforce `color`/`className` usage:
|
|
151
|
+
|
|
152
|
+
<!-- prettier-ignore -->
|
|
153
|
+
```tsx
|
|
154
|
+
<F0Icon icon={Archive} color="critical" />
|
|
155
|
+
|
|
156
|
+
// TypeScript error - style not allowed
|
|
157
|
+
<F0Icon icon={Archive} style={{ color: 'red' }} />
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Token Sync
|
|
161
|
+
|
|
162
|
+
`ICON_COLORS` in `F0Icon.types.ts` is kept in sync with `f0-icon-*` tokens from `theme.css` by a CI test (`F0Icon.tokens.spec.ts`). Adding or removing a token in theme.css without updating `ICON_COLORS` will fail the test.
|
|
163
|
+
|
|
164
|
+
## File Structure
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
src/components/primitives/F0Icon/
|
|
168
|
+
├── index.ts # Barrel exports
|
|
169
|
+
├── F0Icon.tsx # Component implementation
|
|
170
|
+
├── F0Icon.types.ts # TypeScript type definitions (ICON_COLORS, IconColor)
|
|
171
|
+
├── F0Icon.styles.ts # tailwind-variants configuration (size + color)
|
|
172
|
+
├── F0Icon.md # This documentation
|
|
173
|
+
└── __tests__/
|
|
174
|
+
├── F0Icon.spec.tsx # Component tests
|
|
175
|
+
└── F0Icon.tokens.spec.ts # Token sync test (theme.css ↔ ICON_COLORS)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Related Components
|
|
179
|
+
|
|
180
|
+
- **F0Text** - Text primitive with semantic `color` prop (foreground tokens)
|
|
181
|
+
- **Button** - Uses F0Icon for icon slots
|
|
182
|
+
- **Badge** - Can include F0Icon
|
|
183
|
+
|
|
184
|
+
## References
|
|
185
|
+
|
|
186
|
+
- Design tokens: `src/styles/theme.css`
|
|
187
|
+
- Icon source: `src/icons/`
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { tv } from "tailwind-variants"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* F0Icon tailwind-variants configuration
|
|
5
|
+
* Size and color variants from the F0 design system
|
|
6
|
+
*/
|
|
7
|
+
export const iconVariants = tv({
|
|
8
|
+
base: "shrink-0",
|
|
9
|
+
|
|
10
|
+
variants: {
|
|
11
|
+
size: {
|
|
12
|
+
xl: "w-8 h-8 stroke-xl",
|
|
13
|
+
lg: "w-6 h-6 stroke-lg",
|
|
14
|
+
md: "w-5 h-5 stroke-md",
|
|
15
|
+
sm: "w-4 h-4 stroke-sm",
|
|
16
|
+
xs: "w-3 h-3 stroke-xs",
|
|
17
|
+
},
|
|
18
|
+
color: {
|
|
19
|
+
default: "text-f0-icon",
|
|
20
|
+
secondary: "text-f0-icon-secondary",
|
|
21
|
+
inverse: "text-f0-icon-inverse",
|
|
22
|
+
bold: "text-f0-icon-bold",
|
|
23
|
+
critical: "text-f0-icon-critical",
|
|
24
|
+
"critical-bold": "text-f0-icon-critical-bold",
|
|
25
|
+
accent: "text-f0-icon-accent",
|
|
26
|
+
info: "text-f0-icon-info",
|
|
27
|
+
warning: "text-f0-icon-warning",
|
|
28
|
+
positive: "text-f0-icon-positive",
|
|
29
|
+
promote: "text-f0-icon-promote",
|
|
30
|
+
selected: "text-f0-icon-selected",
|
|
31
|
+
"selected-hover": "text-f0-icon-selected-hover",
|
|
32
|
+
"mood-super-negative": "text-f0-icon-mood-super-negative",
|
|
33
|
+
"mood-negative": "text-f0-icon-mood-negative",
|
|
34
|
+
"mood-neutral": "text-f0-icon-mood-neutral",
|
|
35
|
+
"mood-positive": "text-f0-icon-mood-positive",
|
|
36
|
+
"mood-super-positive": "text-f0-icon-mood-super-positive",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
defaultVariants: {
|
|
41
|
+
size: "md",
|
|
42
|
+
},
|
|
43
|
+
})
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React, { useMemo } from "react"
|
|
2
|
+
import type { Svg } from "react-native-svg"
|
|
3
|
+
import { withUniwind } from "uniwind"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../../lib/utils"
|
|
6
|
+
|
|
7
|
+
import { iconVariants } from "./F0Icon.styles"
|
|
8
|
+
import type { F0IconProps, IconType } from "./F0Icon.types"
|
|
9
|
+
|
|
10
|
+
// Cache original icon -> wrapped icon so withUniwind is only applied once per icon type
|
|
11
|
+
const interopCache = new WeakMap<IconType, IconType>()
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Applies UniWind interop to an icon component
|
|
15
|
+
* Ensures withUniwind is only applied once per icon type
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export function applyIconInterop(icon: IconType): IconType {
|
|
19
|
+
let wrapped = interopCache.get(icon)
|
|
20
|
+
if (!wrapped) {
|
|
21
|
+
wrapped = withUniwind(icon) as IconType
|
|
22
|
+
interopCache.set(icon, wrapped)
|
|
23
|
+
}
|
|
24
|
+
return wrapped
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* F0Icon - Icon component for the F0 Design System
|
|
29
|
+
*
|
|
30
|
+
* Renders SVG icons with consistent sizing and semantic colors.
|
|
31
|
+
* Icons are automatically wrapped with UniWind for className support.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* import { Archive } from '@/icons/app';
|
|
35
|
+
*
|
|
36
|
+
* <F0Icon icon={Archive} size="lg" />
|
|
37
|
+
* <F0Icon icon={Archive} color="critical" />
|
|
38
|
+
* <F0Icon icon={Archive} size="sm" color="positive" />
|
|
39
|
+
*/
|
|
40
|
+
const F0Icon = React.memo(
|
|
41
|
+
React.forwardRef<Svg, F0IconProps>(
|
|
42
|
+
(
|
|
43
|
+
{ size = "md", color, icon, testID, className: customClassName, ...rest },
|
|
44
|
+
ref
|
|
45
|
+
) => {
|
|
46
|
+
const IconComponent = useMemo(
|
|
47
|
+
() => (icon ? applyIconInterop(icon) : null),
|
|
48
|
+
[icon]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
const className = useMemo(
|
|
52
|
+
() => cn(iconVariants({ size, color }), customClassName),
|
|
53
|
+
[size, color, customClassName]
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
// Early return if no icon provided (after all hooks)
|
|
57
|
+
if (!icon || !IconComponent) return null
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<IconComponent
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={className}
|
|
63
|
+
testID={testID}
|
|
64
|
+
{...rest}
|
|
65
|
+
/>
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
F0Icon.displayName = "F0Icon"
|
|
72
|
+
|
|
73
|
+
export default F0Icon
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ForwardRefExoticComponent, RefAttributes } from "react"
|
|
2
|
+
import type { SvgProps } from "react-native-svg"
|
|
3
|
+
import type { Svg } from "react-native-svg"
|
|
4
|
+
import type { VariantProps } from "tailwind-variants"
|
|
5
|
+
|
|
6
|
+
import type { iconVariants } from "./F0Icon.styles"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Icon component type - forward ref to SVG component with className support
|
|
10
|
+
*/
|
|
11
|
+
export type IconType = ForwardRefExoticComponent<
|
|
12
|
+
SvgProps &
|
|
13
|
+
RefAttributes<Svg> & {
|
|
14
|
+
className?: string
|
|
15
|
+
}
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Icon color variants derived from f0-icon-* tokens in src/styles/theme.css
|
|
20
|
+
* Sync is enforced by F0Icon.tokens.spec.ts
|
|
21
|
+
*/
|
|
22
|
+
export const ICON_COLORS = [
|
|
23
|
+
"default",
|
|
24
|
+
"secondary",
|
|
25
|
+
"inverse",
|
|
26
|
+
"bold",
|
|
27
|
+
"critical",
|
|
28
|
+
"critical-bold",
|
|
29
|
+
"accent",
|
|
30
|
+
"info",
|
|
31
|
+
"warning",
|
|
32
|
+
"positive",
|
|
33
|
+
"promote",
|
|
34
|
+
"selected",
|
|
35
|
+
"selected-hover",
|
|
36
|
+
"mood-super-negative",
|
|
37
|
+
"mood-negative",
|
|
38
|
+
"mood-neutral",
|
|
39
|
+
"mood-positive",
|
|
40
|
+
"mood-super-positive",
|
|
41
|
+
] as const
|
|
42
|
+
|
|
43
|
+
export type IconColor = (typeof ICON_COLORS)[number]
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Public F0Icon props
|
|
47
|
+
* Supports semantic color via `color` prop, with `className` as escape hatch.
|
|
48
|
+
*/
|
|
49
|
+
export interface F0IconProps extends Omit<SvgProps, "style"> {
|
|
50
|
+
/**
|
|
51
|
+
* Tailwind className for custom styling or color overrides.
|
|
52
|
+
* Prefer the `color` prop for semantic icon colors.
|
|
53
|
+
*/
|
|
54
|
+
className?: string
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Semantic icon color from the F0 design system
|
|
58
|
+
* Maps to f0-icon-* tokens (e.g. color="critical" -> text-f0-icon-critical)
|
|
59
|
+
*/
|
|
60
|
+
color?: IconColor
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Icon component to render (from icons directory)
|
|
64
|
+
*/
|
|
65
|
+
icon: IconType
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Icon size variant
|
|
69
|
+
* @default 'md'
|
|
70
|
+
*/
|
|
71
|
+
size?: VariantProps<typeof iconVariants>["size"]
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Test ID for testing
|
|
75
|
+
*/
|
|
76
|
+
testID?: string
|
|
77
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { render } from "@testing-library/react-native"
|
|
2
|
+
import React from "react"
|
|
3
|
+
|
|
4
|
+
import { Archive } from "../../../../icons/app"
|
|
5
|
+
import { Home } from "../../../../icons/modules"
|
|
6
|
+
import { applyIconInterop } from "../F0Icon"
|
|
7
|
+
import F0Icon from "../F0Icon"
|
|
8
|
+
|
|
9
|
+
describe("F0Icon", () => {
|
|
10
|
+
it("renders correctly with an app icon", () => {
|
|
11
|
+
const { getByTestId } = render(<F0Icon icon={Archive} testID="icon" />)
|
|
12
|
+
expect(getByTestId("icon")).toBeTruthy()
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("renders correctly with a module icon", () => {
|
|
16
|
+
const { getByTestId } = render(<F0Icon icon={Home} testID="icon" />)
|
|
17
|
+
expect(getByTestId("icon")).toBeTruthy()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it("applies the correct size variant", () => {
|
|
21
|
+
const { getByTestId } = render(
|
|
22
|
+
<F0Icon icon={Archive} size="lg" testID="icon" />
|
|
23
|
+
)
|
|
24
|
+
expect(getByTestId("icon")).toBeTruthy()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it("returns null when no icon is provided", () => {
|
|
28
|
+
// @ts-expect-error - Testing runtime behavior
|
|
29
|
+
const { queryByTestId } = render(<F0Icon testID="icon" />)
|
|
30
|
+
expect(queryByTestId("icon")).toBeNull()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("applies default size when size prop is not provided", () => {
|
|
34
|
+
const { getByTestId } = render(<F0Icon icon={Archive} testID="icon" />)
|
|
35
|
+
expect(getByTestId("icon")).toBeTruthy()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it("forwards ref correctly", () => {
|
|
39
|
+
const ref = React.createRef<React.ElementRef<typeof F0Icon>>()
|
|
40
|
+
render(<F0Icon icon={Archive} ref={ref} testID="icon" />)
|
|
41
|
+
expect(ref.current).toBeTruthy()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it("renders with a color prop", () => {
|
|
45
|
+
const { getByTestId } = render(
|
|
46
|
+
<F0Icon icon={Archive} color="critical" testID="icon" />
|
|
47
|
+
)
|
|
48
|
+
expect(getByTestId("icon")).toBeTruthy()
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it("caches wrapped icon: applyIconInterop returns same instance for same icon", () => {
|
|
52
|
+
const wrapped1 = applyIconInterop(Archive)
|
|
53
|
+
const wrapped2 = applyIconInterop(Archive)
|
|
54
|
+
expect(wrapped1).toBe(wrapped2)
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it("caches per icon type: different icons return different wrapped instances", () => {
|
|
58
|
+
const wrappedArchive = applyIconInterop(Archive)
|
|
59
|
+
const wrappedHome = applyIconInterop(Home)
|
|
60
|
+
expect(wrappedArchive).not.toBe(wrappedHome)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it("renders with both color and className", () => {
|
|
64
|
+
const { getByTestId } = render(
|
|
65
|
+
<F0Icon icon={Archive} color="info" className="-ml-0.5" testID="icon" />
|
|
66
|
+
)
|
|
67
|
+
expect(getByTestId("icon")).toBeTruthy()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
describe("className — cn() merging", () => {
|
|
71
|
+
it("passes through layout classes from className", () => {
|
|
72
|
+
const { getByTestId } = render(
|
|
73
|
+
<F0Icon icon={Archive} className="-ml-0.5" testID="icon" />
|
|
74
|
+
)
|
|
75
|
+
const element = getByTestId("icon")
|
|
76
|
+
expect(element.props.className).toContain("-ml-0.5")
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("merges conflicting size: custom w-* overrides variant", () => {
|
|
80
|
+
const { getByTestId } = render(
|
|
81
|
+
<F0Icon icon={Archive} size="md" className="h-10 w-10" testID="icon" />
|
|
82
|
+
)
|
|
83
|
+
const element = getByTestId("icon")
|
|
84
|
+
expect(element.props.className).toContain("w-10")
|
|
85
|
+
expect(element.props.className).toContain("h-10")
|
|
86
|
+
expect(element.props.className).not.toContain("w-5")
|
|
87
|
+
expect(element.props.className).not.toContain("h-5")
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it("merges conflicting color: custom text-* overrides variant", () => {
|
|
91
|
+
const { getByTestId } = render(
|
|
92
|
+
<F0Icon
|
|
93
|
+
icon={Archive}
|
|
94
|
+
color="critical"
|
|
95
|
+
className="text-red-500"
|
|
96
|
+
testID="icon"
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
const element = getByTestId("icon")
|
|
100
|
+
expect(element.props.className).toContain("text-red-500")
|
|
101
|
+
expect(element.props.className).not.toContain("text-f0-icon-critical")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it("uses only variant classes when className is undefined", () => {
|
|
105
|
+
const { getByTestId } = render(
|
|
106
|
+
<F0Icon icon={Archive} size="sm" color="positive" testID="icon" />
|
|
107
|
+
)
|
|
108
|
+
const element = getByTestId("icon")
|
|
109
|
+
expect(element.props.className).toContain("w-4")
|
|
110
|
+
expect(element.props.className).toContain("h-4")
|
|
111
|
+
expect(element.props.className).toContain("text-f0-icon-positive")
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it("combines non-conflicting variant and className", () => {
|
|
115
|
+
const { getByTestId } = render(
|
|
116
|
+
<F0Icon
|
|
117
|
+
icon={Archive}
|
|
118
|
+
size="lg"
|
|
119
|
+
color="info"
|
|
120
|
+
className="opacity-50"
|
|
121
|
+
testID="icon"
|
|
122
|
+
/>
|
|
123
|
+
)
|
|
124
|
+
const element = getByTestId("icon")
|
|
125
|
+
expect(element.props.className).toContain("w-6")
|
|
126
|
+
expect(element.props.className).toContain("h-6")
|
|
127
|
+
expect(element.props.className).toContain("text-f0-icon-info")
|
|
128
|
+
expect(element.props.className).toContain("opacity-50")
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
})
|