@cdx-ui/components 0.0.1-beta.39 → 0.0.1-beta.40
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.
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Components — authoring & styling conventions
|
|
2
|
+
|
|
3
|
+
Auto-loaded when working in `packages/components/src/components/**`. Covers both the
|
|
4
|
+
styled component files (`index.tsx`) and their co-located CVA variant files (`styles.ts`).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Component Authoring (`index.tsx`)
|
|
9
|
+
|
|
10
|
+
Styled components in `@cdx-ui/components` follow a consistent pattern. Read the full guides before making changes:
|
|
11
|
+
|
|
12
|
+
- `docs/internal/practices/composition.md` — compound component pattern, naming conventions
|
|
13
|
+
- `docs/internal/practices/types.md` — prop types, `I*Props` vs `*Props`, exporting
|
|
14
|
+
- `docs/internal/practices/styling.md` — `cn()`, CVA, token classes, `Platform.select()`
|
|
15
|
+
- `docs/internal/practices/state.md` — `useControllableState`, `composeEventHandlers`
|
|
16
|
+
- `docs/internal/practices/data-attributes.md` — `data-[state]` and `data-slot`
|
|
17
|
+
|
|
18
|
+
### Pattern summary
|
|
19
|
+
|
|
20
|
+
Each component follows this structure (see `Button/index.tsx` as the canonical example):
|
|
21
|
+
|
|
22
|
+
1. **Imports** — RN base components, primitive from `@cdx-ui/primitives`, `cn` + style context from `@cdx-ui/utils`, variant functions from `./styles`
|
|
23
|
+
2. **Scope constant** — `const SCOPE = 'COMPONENT_NAME'` for `withStyleContext` / `useStyleContext`
|
|
24
|
+
3. **Root wrapping** — `const Root = withStyleContext(BaseRNComponent, SCOPE)`
|
|
25
|
+
4. **Primitive instantiation** — `const Primitive = createComponent({ Root, Text, ... })` using the primitive factory
|
|
26
|
+
5. **Styled sub-components** — each wraps a single element, uses `forwardRef`, destructures variant props with defaults, computes `cn(variantFn({ ...variants }), className)`, passes `context` to the primitive root
|
|
27
|
+
6. **Compound export** — `Object.assign(RootComponent, { Sub1, Sub2 }) as CompoundType`
|
|
28
|
+
|
|
29
|
+
### Key rules
|
|
30
|
+
|
|
31
|
+
- Props interface extends RN base props + primitive `I*Props` + CVA `VariantProps` + `{ className?: string }`
|
|
32
|
+
- Always accept and spread `style` alongside `className`
|
|
33
|
+
- Variant defaults in destructuring must match CVA `defaultVariants`
|
|
34
|
+
- `displayName` set on every exported sub-component
|
|
35
|
+
- `className` is merged last via `cn()` so consumer overrides always win
|
|
36
|
+
- Never hardcode colors — use semantic token classes (`bg-surface-*`, `text-content-*`, `border-stroke-*`)
|
|
37
|
+
- Event handlers composed via `composeEventHandlers` from `@cdx-ui/utils`
|
|
38
|
+
- Third-party libraries fully abstracted — no adopted library types leak to consumers
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Styling Conventions (`styles.ts`)
|
|
43
|
+
|
|
44
|
+
Variant definitions live in co-located `styles.ts` files using CVA. Read the full guides before making changes:
|
|
45
|
+
|
|
46
|
+
- `docs/internal/practices/styling.md` — `cn()`, CVA, token architecture, dark mode, `Platform.select()`
|
|
47
|
+
- `docs/internal/practices/data-attributes.md` — `data-[state]` selectors for interaction states
|
|
48
|
+
|
|
49
|
+
### Pattern summary
|
|
50
|
+
|
|
51
|
+
Each `styles.ts` exports CVA variant functions and their derived `VariantProps` types (see `Button/styles.ts` as the canonical example):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { Platform } from 'react-native';
|
|
55
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
56
|
+
import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/primitives';
|
|
57
|
+
|
|
58
|
+
export const componentRootVariants = cva(
|
|
59
|
+
[
|
|
60
|
+
/* base classes */
|
|
61
|
+
],
|
|
62
|
+
{
|
|
63
|
+
variants: {
|
|
64
|
+
/* variant axes */
|
|
65
|
+
},
|
|
66
|
+
compoundVariants: [
|
|
67
|
+
/* variant × variant combinations */
|
|
68
|
+
],
|
|
69
|
+
defaultVariants: {
|
|
70
|
+
/* defaults matching the component's destructured defaults */
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
export type ComponentVariantProps = VariantProps<typeof componentRootVariants>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Key rules
|
|
79
|
+
|
|
80
|
+
- **Semantic token classes only** — use `bg-surface-action-strong`, `text-content-primary`, `border-stroke-primary`, etc. Never hardcode hex colors, `bg-white`, `text-black`, or raw Tailwind color scales for themed values.
|
|
81
|
+
- **`data-[state]` selectors** — use `data-[disabled=true]:`, `data-[hover=true]:`, `data-[active=true]:`, `data-[focus-visible=true]:` for interaction states. Primitives emit these data attributes.
|
|
82
|
+
- **`Platform.select()`** for platform-specific classes — web gets `data-[hover=true]` styles (hover exists on web); native responds only to `data-[active=true]` (press). Always set `default` (native) and `web` keys.
|
|
83
|
+
- **Shared style primitives** — import `DISABLED_CURSOR`, `TRANSITION_COLORS`, etc. from `../../styles/primitives` for cross-component consistency.
|
|
84
|
+
- **`web:` prefix** for web-only utilities — e.g., `web:outline-none`, `web:focus-visible:ring-2`.
|
|
85
|
+
- **`compoundVariants`** for variant × variant combinations where simple variant merging is insufficient.
|
|
86
|
+
- **`defaultVariants`** must match the destructured defaults in the component's `index.tsx`.
|
|
87
|
+
- **One `cva()` per sub-component** — root, text, icon, spinner, etc. each get their own variant function.
|
|
88
|
+
- **Export `VariantProps` types** — derived via `VariantProps<typeof variantFn>` for the component layer to consume.
|
|
89
|
+
- **No dynamic string construction** — all Tailwind class names must be statically detectable by the Tailwind scanner.
|
|
90
|
+
- **CSS variables for token values** — use `rounded-[var(--border-radius-button)]` syntax when referencing design tokens not mapped to Tailwind utilities.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Code Connect
|
|
2
|
+
|
|
3
|
+
Auto-loaded when working in `packages/components/src/figma/**` (`*.figma.ts`, `*.figma.batch.ts`, `*.figma.batch.json`).
|
|
4
|
+
|
|
5
|
+
Template files connect CDX UI components to Figma Dev Mode snippets. Read the full guide before making changes:
|
|
6
|
+
|
|
7
|
+
- `docs/internal/practices/code-connect.md` — complete workflow, mapping patterns, publishing, troubleshooting
|
|
8
|
+
|
|
9
|
+
## Pattern summary
|
|
10
|
+
|
|
11
|
+
Each `.figma.ts` file follows this structure (see `Button.figma.ts` as the canonical example):
|
|
12
|
+
|
|
13
|
+
1. **Metadata comments** — `// url=`, `// source=`, `// component=` at the top (required)
|
|
14
|
+
2. **`figma` import** — `import figma from 'figma'` (template API, not a runtime import)
|
|
15
|
+
3. **Instance handle** — `const instance = figma.selectedInstance`
|
|
16
|
+
4. **Property mappings** — `getEnum`, `getBoolean`, `getInstanceSwap`, `findText`, `findInstance`
|
|
17
|
+
5. **Default export** — `{ id, imports, example }` with optional `metadata`
|
|
18
|
+
|
|
19
|
+
## Key rules
|
|
20
|
+
|
|
21
|
+
- **Enum renaming** — pass a mapping object to `getEnum` when Figma and code names differ; list all values explicitly
|
|
22
|
+
- **Boolean-as-variant** — Figma VARIANT properties with `"true"`/`"false"` string values use `getEnum`, not `getBoolean`; map `"false"` to `undefined`; **always** render via `figma.helpers.react.renderProp` — never `isDisabled={${isDisabled}}` (produces `isDisabled={}` when undefined)
|
|
23
|
+
- **Instance swap** — use `getInstanceSwap` (not `findInstance`) for designer-swappable slots; call `.executeTemplate()` before interpolating; prefer `metadata.props.componentName` over raw `.example` for icon slots
|
|
24
|
+
- **Runtime states excluded** — `isHovered`, `isFocused`, `isPressed`, `isLoading` are runtime-managed; never map them
|
|
25
|
+
- **Falsy → `undefined`** — default or falsy values map to `undefined` so they don't clutter the snippet
|
|
26
|
+
- **Compound dot notation** — render sub-components as `Button.Label`, `Button.Icon`, not bare `Label`/`Icon`
|
|
27
|
+
- **`imports` array** — use `@cdx-ui/components` import path; Figma deduplicates identical strings
|
|
28
|
+
- **Nestable metadata** — leaf components (icons, badges) set `metadata: { nestable: true, props: { componentName } }`
|
|
29
|
+
- **Batch pattern** — for large sets with identical code shapes (icons), use `.figma.batch.ts` + `.figma.batch.json` with `figma.batch.name`/`figma.batch.id`
|
|
30
|
+
- **Publish** — `pnpm figma:publish` locally; CI publishes on merge to `main`
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Components — authoring & styling conventions
|
|
2
|
+
|
|
3
|
+
Auto-loaded when working in `packages/components/src/components/**`. Covers both the
|
|
4
|
+
styled component files (`index.tsx`) and their co-located CVA variant files (`styles.ts`).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Component Authoring (`index.tsx`)
|
|
9
|
+
|
|
10
|
+
Styled components in `@cdx-ui/components` follow a consistent pattern. Read the full guides before making changes:
|
|
11
|
+
|
|
12
|
+
- `docs/internal/practices/composition.md` — compound component pattern, naming conventions
|
|
13
|
+
- `docs/internal/practices/types.md` — prop types, `I*Props` vs `*Props`, exporting
|
|
14
|
+
- `docs/internal/practices/styling.md` — `cn()`, CVA, token classes, `Platform.select()`
|
|
15
|
+
- `docs/internal/practices/state.md` — `useControllableState`, `composeEventHandlers`
|
|
16
|
+
- `docs/internal/practices/data-attributes.md` — `data-[state]` and `data-slot`
|
|
17
|
+
|
|
18
|
+
### Pattern summary
|
|
19
|
+
|
|
20
|
+
Each component follows this structure (see `Button/index.tsx` as the canonical example):
|
|
21
|
+
|
|
22
|
+
1. **Imports** — RN base components, primitive from `@cdx-ui/primitives`, `cn` + style context from `@cdx-ui/utils`, variant functions from `./styles`
|
|
23
|
+
2. **Scope constant** — `const SCOPE = 'COMPONENT_NAME'` for `withStyleContext` / `useStyleContext`
|
|
24
|
+
3. **Root wrapping** — `const Root = withStyleContext(BaseRNComponent, SCOPE)`
|
|
25
|
+
4. **Primitive instantiation** — `const Primitive = createComponent({ Root, Text, ... })` using the primitive factory
|
|
26
|
+
5. **Styled sub-components** — each wraps a single element, uses `forwardRef`, destructures variant props with defaults, computes `cn(variantFn({ ...variants }), className)`, passes `context` to the primitive root
|
|
27
|
+
6. **Compound export** — `Object.assign(RootComponent, { Sub1, Sub2 }) as CompoundType`
|
|
28
|
+
|
|
29
|
+
### Key rules
|
|
30
|
+
|
|
31
|
+
- Props interface extends RN base props + primitive `I*Props` + CVA `VariantProps` + `{ className?: string }`
|
|
32
|
+
- Always accept and spread `style` alongside `className`
|
|
33
|
+
- Variant defaults in destructuring must match CVA `defaultVariants`
|
|
34
|
+
- `displayName` set on every exported sub-component
|
|
35
|
+
- `className` is merged last via `cn()` so consumer overrides always win
|
|
36
|
+
- Never hardcode colors — use semantic token classes (`bg-surface-*`, `text-content-*`, `border-stroke-*`)
|
|
37
|
+
- Event handlers composed via `composeEventHandlers` from `@cdx-ui/utils`
|
|
38
|
+
- Third-party libraries fully abstracted — no adopted library types leak to consumers
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Styling Conventions (`styles.ts`)
|
|
43
|
+
|
|
44
|
+
Variant definitions live in co-located `styles.ts` files using CVA. Read the full guides before making changes:
|
|
45
|
+
|
|
46
|
+
- `docs/internal/practices/styling.md` — `cn()`, CVA, token architecture, dark mode, `Platform.select()`
|
|
47
|
+
- `docs/internal/practices/data-attributes.md` — `data-[state]` selectors for interaction states
|
|
48
|
+
|
|
49
|
+
### Pattern summary
|
|
50
|
+
|
|
51
|
+
Each `styles.ts` exports CVA variant functions and their derived `VariantProps` types (see `Button/styles.ts` as the canonical example):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { Platform } from 'react-native';
|
|
55
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
56
|
+
import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/primitives';
|
|
57
|
+
|
|
58
|
+
export const componentRootVariants = cva(
|
|
59
|
+
[
|
|
60
|
+
/* base classes */
|
|
61
|
+
],
|
|
62
|
+
{
|
|
63
|
+
variants: {
|
|
64
|
+
/* variant axes */
|
|
65
|
+
},
|
|
66
|
+
compoundVariants: [
|
|
67
|
+
/* variant × variant combinations */
|
|
68
|
+
],
|
|
69
|
+
defaultVariants: {
|
|
70
|
+
/* defaults matching the component's destructured defaults */
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
export type ComponentVariantProps = VariantProps<typeof componentRootVariants>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Key rules
|
|
79
|
+
|
|
80
|
+
- **Semantic token classes only** — use `bg-surface-action-strong`, `text-content-primary`, `border-stroke-primary`, etc. Never hardcode hex colors, `bg-white`, `text-black`, or raw Tailwind color scales for themed values.
|
|
81
|
+
- **`data-[state]` selectors** — use `data-[disabled=true]:`, `data-[hover=true]:`, `data-[active=true]:`, `data-[focus-visible=true]:` for interaction states. Primitives emit these data attributes.
|
|
82
|
+
- **`Platform.select()`** for platform-specific classes — web gets `data-[hover=true]` styles (hover exists on web); native responds only to `data-[active=true]` (press). Always set `default` (native) and `web` keys.
|
|
83
|
+
- **Shared style primitives** — import `DISABLED_CURSOR`, `TRANSITION_COLORS`, etc. from `../../styles/primitives` for cross-component consistency.
|
|
84
|
+
- **`web:` prefix** for web-only utilities — e.g., `web:outline-none`, `web:focus-visible:ring-2`.
|
|
85
|
+
- **`compoundVariants`** for variant × variant combinations where simple variant merging is insufficient.
|
|
86
|
+
- **`defaultVariants`** must match the destructured defaults in the component's `index.tsx`.
|
|
87
|
+
- **One `cva()` per sub-component** — root, text, icon, spinner, etc. each get their own variant function.
|
|
88
|
+
- **Export `VariantProps` types** — derived via `VariantProps<typeof variantFn>` for the component layer to consume.
|
|
89
|
+
- **No dynamic string construction** — all Tailwind class names must be statically detectable by the Tailwind scanner.
|
|
90
|
+
- **CSS variables for token values** — use `rounded-[var(--border-radius-button)]` syntax when referencing design tokens not mapped to Tailwind utilities.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Code Connect
|
|
2
|
+
|
|
3
|
+
Auto-loaded when working in `packages/components/src/figma/**` (`*.figma.ts`, `*.figma.batch.ts`, `*.figma.batch.json`).
|
|
4
|
+
|
|
5
|
+
Template files connect CDX UI components to Figma Dev Mode snippets. Read the full guide before making changes:
|
|
6
|
+
|
|
7
|
+
- `docs/internal/practices/code-connect.md` — complete workflow, mapping patterns, publishing, troubleshooting
|
|
8
|
+
|
|
9
|
+
## Pattern summary
|
|
10
|
+
|
|
11
|
+
Each `.figma.ts` file follows this structure (see `Button.figma.ts` as the canonical example):
|
|
12
|
+
|
|
13
|
+
1. **Metadata comments** — `// url=`, `// source=`, `// component=` at the top (required)
|
|
14
|
+
2. **`figma` import** — `import figma from 'figma'` (template API, not a runtime import)
|
|
15
|
+
3. **Instance handle** — `const instance = figma.selectedInstance`
|
|
16
|
+
4. **Property mappings** — `getEnum`, `getBoolean`, `getInstanceSwap`, `findText`, `findInstance`
|
|
17
|
+
5. **Default export** — `{ id, imports, example }` with optional `metadata`
|
|
18
|
+
|
|
19
|
+
## Key rules
|
|
20
|
+
|
|
21
|
+
- **Enum renaming** — pass a mapping object to `getEnum` when Figma and code names differ; list all values explicitly
|
|
22
|
+
- **Boolean-as-variant** — Figma VARIANT properties with `"true"`/`"false"` string values use `getEnum`, not `getBoolean`; map `"false"` to `undefined`; **always** render via `figma.helpers.react.renderProp` — never `isDisabled={${isDisabled}}` (produces `isDisabled={}` when undefined)
|
|
23
|
+
- **Instance swap** — use `getInstanceSwap` (not `findInstance`) for designer-swappable slots; call `.executeTemplate()` before interpolating; prefer `metadata.props.componentName` over raw `.example` for icon slots
|
|
24
|
+
- **Runtime states excluded** — `isHovered`, `isFocused`, `isPressed`, `isLoading` are runtime-managed; never map them
|
|
25
|
+
- **Falsy → `undefined`** — default or falsy values map to `undefined` so they don't clutter the snippet
|
|
26
|
+
- **Compound dot notation** — render sub-components as `Button.Label`, `Button.Icon`, not bare `Label`/`Icon`
|
|
27
|
+
- **`imports` array** — use `@cdx-ui/components` import path; Figma deduplicates identical strings
|
|
28
|
+
- **Nestable metadata** — leaf components (icons, badges) set `metadata: { nestable: true, props: { componentName } }`
|
|
29
|
+
- **Batch pattern** — for large sets with identical code shapes (icons), use `.figma.batch.ts` + `.figma.batch.json` with `figma.batch.name`/`figma.batch.id`
|
|
30
|
+
- **Publish** — `pnpm figma:publish` locally; CI publishes on merge to `main`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdx-ui/components",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
3
|
+
"version": "0.0.1-beta.40",
|
|
4
4
|
"main": "lib/commonjs/index.js",
|
|
5
5
|
"module": "lib/module/index.js",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
"@gorhom/bottom-sheet": "^5.2.6",
|
|
68
68
|
"class-variance-authority": "^0.7.1",
|
|
69
69
|
"uniwind": "1.6.1",
|
|
70
|
-
"@cdx-ui/primitives": "0.0.1-beta.
|
|
71
|
-
"@cdx-ui/utils": "0.0.1-beta.
|
|
72
|
-
"@cdx-ui/icons": "0.0.1-beta.
|
|
70
|
+
"@cdx-ui/primitives": "0.0.1-beta.40",
|
|
71
|
+
"@cdx-ui/utils": "0.0.1-beta.40",
|
|
72
|
+
"@cdx-ui/icons": "0.0.1-beta.40"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
75
|
"@types/react": "*",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Components — authoring & styling conventions
|
|
2
|
+
|
|
3
|
+
Auto-loaded when working in `packages/components/src/components/**`. Covers both the
|
|
4
|
+
styled component files (`index.tsx`) and their co-located CVA variant files (`styles.ts`).
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Component Authoring (`index.tsx`)
|
|
9
|
+
|
|
10
|
+
Styled components in `@cdx-ui/components` follow a consistent pattern. Read the full guides before making changes:
|
|
11
|
+
|
|
12
|
+
- `docs/internal/practices/composition.md` — compound component pattern, naming conventions
|
|
13
|
+
- `docs/internal/practices/types.md` — prop types, `I*Props` vs `*Props`, exporting
|
|
14
|
+
- `docs/internal/practices/styling.md` — `cn()`, CVA, token classes, `Platform.select()`
|
|
15
|
+
- `docs/internal/practices/state.md` — `useControllableState`, `composeEventHandlers`
|
|
16
|
+
- `docs/internal/practices/data-attributes.md` — `data-[state]` and `data-slot`
|
|
17
|
+
|
|
18
|
+
### Pattern summary
|
|
19
|
+
|
|
20
|
+
Each component follows this structure (see `Button/index.tsx` as the canonical example):
|
|
21
|
+
|
|
22
|
+
1. **Imports** — RN base components, primitive from `@cdx-ui/primitives`, `cn` + style context from `@cdx-ui/utils`, variant functions from `./styles`
|
|
23
|
+
2. **Scope constant** — `const SCOPE = 'COMPONENT_NAME'` for `withStyleContext` / `useStyleContext`
|
|
24
|
+
3. **Root wrapping** — `const Root = withStyleContext(BaseRNComponent, SCOPE)`
|
|
25
|
+
4. **Primitive instantiation** — `const Primitive = createComponent({ Root, Text, ... })` using the primitive factory
|
|
26
|
+
5. **Styled sub-components** — each wraps a single element, uses `forwardRef`, destructures variant props with defaults, computes `cn(variantFn({ ...variants }), className)`, passes `context` to the primitive root
|
|
27
|
+
6. **Compound export** — `Object.assign(RootComponent, { Sub1, Sub2 }) as CompoundType`
|
|
28
|
+
|
|
29
|
+
### Key rules
|
|
30
|
+
|
|
31
|
+
- Props interface extends RN base props + primitive `I*Props` + CVA `VariantProps` + `{ className?: string }`
|
|
32
|
+
- Always accept and spread `style` alongside `className`
|
|
33
|
+
- Variant defaults in destructuring must match CVA `defaultVariants`
|
|
34
|
+
- `displayName` set on every exported sub-component
|
|
35
|
+
- `className` is merged last via `cn()` so consumer overrides always win
|
|
36
|
+
- Never hardcode colors — use semantic token classes (`bg-surface-*`, `text-content-*`, `border-stroke-*`)
|
|
37
|
+
- Event handlers composed via `composeEventHandlers` from `@cdx-ui/utils`
|
|
38
|
+
- Third-party libraries fully abstracted — no adopted library types leak to consumers
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Styling Conventions (`styles.ts`)
|
|
43
|
+
|
|
44
|
+
Variant definitions live in co-located `styles.ts` files using CVA. Read the full guides before making changes:
|
|
45
|
+
|
|
46
|
+
- `docs/internal/practices/styling.md` — `cn()`, CVA, token architecture, dark mode, `Platform.select()`
|
|
47
|
+
- `docs/internal/practices/data-attributes.md` — `data-[state]` selectors for interaction states
|
|
48
|
+
|
|
49
|
+
### Pattern summary
|
|
50
|
+
|
|
51
|
+
Each `styles.ts` exports CVA variant functions and their derived `VariantProps` types (see `Button/styles.ts` as the canonical example):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { Platform } from 'react-native';
|
|
55
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
56
|
+
import { DISABLED_CURSOR, TRANSITION_COLORS } from '../../styles/primitives';
|
|
57
|
+
|
|
58
|
+
export const componentRootVariants = cva(
|
|
59
|
+
[
|
|
60
|
+
/* base classes */
|
|
61
|
+
],
|
|
62
|
+
{
|
|
63
|
+
variants: {
|
|
64
|
+
/* variant axes */
|
|
65
|
+
},
|
|
66
|
+
compoundVariants: [
|
|
67
|
+
/* variant × variant combinations */
|
|
68
|
+
],
|
|
69
|
+
defaultVariants: {
|
|
70
|
+
/* defaults matching the component's destructured defaults */
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
export type ComponentVariantProps = VariantProps<typeof componentRootVariants>;
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Key rules
|
|
79
|
+
|
|
80
|
+
- **Semantic token classes only** — use `bg-surface-action-strong`, `text-content-primary`, `border-stroke-primary`, etc. Never hardcode hex colors, `bg-white`, `text-black`, or raw Tailwind color scales for themed values.
|
|
81
|
+
- **`data-[state]` selectors** — use `data-[disabled=true]:`, `data-[hover=true]:`, `data-[active=true]:`, `data-[focus-visible=true]:` for interaction states. Primitives emit these data attributes.
|
|
82
|
+
- **`Platform.select()`** for platform-specific classes — web gets `data-[hover=true]` styles (hover exists on web); native responds only to `data-[active=true]` (press). Always set `default` (native) and `web` keys.
|
|
83
|
+
- **Shared style primitives** — import `DISABLED_CURSOR`, `TRANSITION_COLORS`, etc. from `../../styles/primitives` for cross-component consistency.
|
|
84
|
+
- **`web:` prefix** for web-only utilities — e.g., `web:outline-none`, `web:focus-visible:ring-2`.
|
|
85
|
+
- **`compoundVariants`** for variant × variant combinations where simple variant merging is insufficient.
|
|
86
|
+
- **`defaultVariants`** must match the destructured defaults in the component's `index.tsx`.
|
|
87
|
+
- **One `cva()` per sub-component** — root, text, icon, spinner, etc. each get their own variant function.
|
|
88
|
+
- **Export `VariantProps` types** — derived via `VariantProps<typeof variantFn>` for the component layer to consume.
|
|
89
|
+
- **No dynamic string construction** — all Tailwind class names must be statically detectable by the Tailwind scanner.
|
|
90
|
+
- **CSS variables for token values** — use `rounded-[var(--border-radius-button)]` syntax when referencing design tokens not mapped to Tailwind utilities.
|