|
| 1 | +--- |
| 2 | +description: React Native library conventions for this starter. Apply when creating or editing components, hooks, types, or tests in src/. |
| 3 | +globs: ["src/**/*.ts", "src/**/*.tsx"] |
| 4 | +alwaysApply: true |
| 5 | +--- |
| 6 | + |
| 7 | +# Library Conventions |
| 8 | + |
| 9 | +## Component Structure |
| 10 | + |
| 11 | +Every component lives in its own folder under `src/components/`: |
| 12 | + |
| 13 | +``` |
| 14 | +src/components/MyComponent/ |
| 15 | +├── MyComponent.tsx # Implementation |
| 16 | +├── MyComponent.types.ts # Props interface |
| 17 | +└── index.ts # Barrel: re-exports default + named + types |
| 18 | +``` |
| 19 | + |
| 20 | +### Props Interface Pattern |
| 21 | + |
| 22 | +```tsx |
| 23 | +// MyComponent.types.ts |
| 24 | +import type { StyleProp, ViewStyle } from "react-native"; |
| 25 | + |
| 26 | +/** |
| 27 | + * Props for the {@link MyComponent} component. |
| 28 | + */ |
| 29 | +export interface MyComponentProps { |
| 30 | + /** Required title — always document every prop with TSDoc. */ |
| 31 | + title: string; |
| 32 | + |
| 33 | + /** Optional. Always mark optional props with `?`. */ |
| 34 | + style?: StyleProp<ViewStyle>; |
| 35 | + |
| 36 | + /** @defaultValue false */ |
| 37 | + enabled?: boolean; |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +### Component Implementation Pattern |
| 42 | + |
| 43 | +```tsx |
| 44 | +// MyComponent.tsx |
| 45 | +import React from "react"; |
| 46 | +import { StyleSheet, View } from "react-native"; |
| 47 | +import type { MyComponentProps } from "./MyComponent.types"; |
| 48 | + |
| 49 | +/** |
| 50 | + * TSDoc summary line. |
| 51 | + * |
| 52 | + * @example |
| 53 | + * ```tsx |
| 54 | + * <MyComponent title="Hello" /> |
| 55 | + * ``` |
| 56 | + */ |
| 57 | +const MyComponent: React.FC<MyComponentProps> = ({ |
| 58 | + title, |
| 59 | + style, |
| 60 | + enabled = false, |
| 61 | + testID = "my-component", |
| 62 | +}) => { |
| 63 | + return ( |
| 64 | + <View style={[styles.container, style]} testID={testID}> |
| 65 | + {/* implementation */} |
| 66 | + </View> |
| 67 | + ); |
| 68 | +}; |
| 69 | + |
| 70 | +const styles = StyleSheet.create({ |
| 71 | + container: {}, |
| 72 | +}); |
| 73 | + |
| 74 | +export default MyComponent; |
| 75 | +``` |
| 76 | + |
| 77 | +### Barrel Pattern |
| 78 | + |
| 79 | +```ts |
| 80 | +// index.ts |
| 81 | +export { default } from "./MyComponent"; |
| 82 | +export { default as MyComponent } from "./MyComponent"; |
| 83 | +export type { MyComponentProps } from "./MyComponent.types"; |
| 84 | +``` |
| 85 | + |
| 86 | +## Hook Pattern |
| 87 | + |
| 88 | +```ts |
| 89 | +// src/hooks/useMyHook.ts |
| 90 | +export interface UseMyHookOptions { |
| 91 | + /** TSDoc every option. */ |
| 92 | + initialValue?: number; |
| 93 | +} |
| 94 | + |
| 95 | +export interface UseMyHookReturn { |
| 96 | + /** TSDoc every returned value. */ |
| 97 | + value: number; |
| 98 | + setValue: (v: number) => void; |
| 99 | +} |
| 100 | + |
| 101 | +/** |
| 102 | + * @example |
| 103 | + * const { value, setValue } = useMyHook({ initialValue: 0 }); |
| 104 | + */ |
| 105 | +export function useMyHook(options: UseMyHookOptions = {}): UseMyHookReturn { |
| 106 | + // implementation |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +- Named export only (no default export for hooks). |
| 111 | +- Export `Options` and `Return` interfaces. |
| 112 | + |
| 113 | +## Public API (`src/index.ts`) |
| 114 | + |
| 115 | +- Export every public value AND its types. |
| 116 | +- Never export internal helpers. |
| 117 | + |
| 118 | +```ts |
| 119 | +export { MyComponent } from "./components/MyComponent"; |
| 120 | +export type { MyComponentProps } from "./components/MyComponent"; |
| 121 | +export { useMyHook } from "./hooks/useMyHook"; |
| 122 | +export type { UseMyHookOptions, UseMyHookReturn } from "./hooks/useMyHook"; |
| 123 | +``` |
| 124 | + |
| 125 | +## TypeScript Rules |
| 126 | + |
| 127 | +- `import type` for type-only imports. |
| 128 | +- No `any` — use `unknown` or proper generics. |
| 129 | +- No `ts-ignore` without an explanatory comment. |
| 130 | +- Props defaults should be destructured with `= value` syntax. |
| 131 | + |
| 132 | +## Styling Rules |
| 133 | + |
| 134 | +- Always use `StyleSheet.create()`. |
| 135 | +- No inline style objects (`style={{ margin: 8 }}` in JSX is off for production code). |
| 136 | +- Style overrides accepted as `StyleProp<ViewStyle | TextStyle | ImageStyle>`. |
| 137 | + |
| 138 | +## testID Convention |
| 139 | + |
| 140 | +- Accept `testID?: string` with a sensible default (e.g. `"my-component"`). |
| 141 | +- Derive child testIDs: `${testID}-title`, `${testID}-button`, etc. |
| 142 | + |
| 143 | +## Testing Rules |
| 144 | + |
| 145 | +- Tests live in `src/__tests__/` or `src/components/MyComponent/__tests__/`. |
| 146 | +- Use `@testing-library/react-native`. |
| 147 | +- Group with `describe` blocks: `rendering`, `interactions`, `accessibility`. |
| 148 | +- Hook tests use `renderHook` + `act`. |
| 149 | +- Never use `snapshot` tests for this library. |
| 150 | + |
| 151 | +## File Naming |
| 152 | + |
| 153 | +| Type | Convention | Example | |
| 154 | +|---|---|---| |
| 155 | +| Component | PascalCase | `MyButton.tsx` | |
| 156 | +| Component types | PascalCase + `.types` | `MyButton.types.ts` | |
| 157 | +| Hook | camelCase with `use` prefix | `useDebounce.ts` | |
| 158 | +| Test | same name + `.test` | `MyButton.test.tsx` | |
| 159 | +| Type utilities | camelCase | `index.ts` | |
| 160 | + |
| 161 | +## Commit Messages (Conventional Commits) |
| 162 | + |
| 163 | +``` |
| 164 | +feat: add MyButton component |
| 165 | +fix: correct accessibility role on MyComponent |
| 166 | +test: add useMyHook boundary tests |
| 167 | +docs: add TSDoc examples to MyComponent props |
| 168 | +``` |
0 commit comments