@thewhileloop/whileui 0.1.1 → 0.2.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 (82) hide show
  1. package/README.md +1243 -0
  2. package/dist/blocks/layout/action-bar.d.ts +8 -0
  3. package/dist/blocks/layout/action-bar.d.ts.map +1 -0
  4. package/dist/blocks/layout/action-bar.js +10 -0
  5. package/dist/blocks/layout/action-bar.js.map +1 -0
  6. package/dist/blocks/layout/confirm-action-sheet.d.ts +24 -0
  7. package/dist/blocks/layout/confirm-action-sheet.d.ts.map +1 -0
  8. package/dist/blocks/layout/confirm-action-sheet.js +41 -0
  9. package/dist/blocks/layout/confirm-action-sheet.js.map +1 -0
  10. package/dist/blocks/layout/index.d.ts +2 -0
  11. package/dist/blocks/layout/index.d.ts.map +1 -1
  12. package/dist/blocks/layout/index.js +2 -0
  13. package/dist/blocks/layout/index.js.map +1 -1
  14. package/dist/blocks/navigation/index.d.ts +1 -0
  15. package/dist/blocks/navigation/index.d.ts.map +1 -1
  16. package/dist/blocks/navigation/index.js +1 -0
  17. package/dist/blocks/navigation/index.js.map +1 -1
  18. package/dist/blocks/navigation/navigation-sidebar.d.ts +23 -0
  19. package/dist/blocks/navigation/navigation-sidebar.d.ts.map +1 -0
  20. package/dist/blocks/navigation/navigation-sidebar.js +11 -0
  21. package/dist/blocks/navigation/navigation-sidebar.js.map +1 -0
  22. package/dist/components/card/card.d.ts +81 -2
  23. package/dist/components/card/card.d.ts.map +1 -1
  24. package/dist/components/card/card.js +27 -6
  25. package/dist/components/card/card.js.map +1 -1
  26. package/dist/components/card/index.d.ts +1 -1
  27. package/dist/components/card/index.d.ts.map +1 -1
  28. package/dist/components/card/index.js +1 -1
  29. package/dist/components/card/index.js.map +1 -1
  30. package/dist/components/data-row/data-row.d.ts +69 -0
  31. package/dist/components/data-row/data-row.d.ts.map +1 -0
  32. package/dist/components/data-row/data-row.js +89 -0
  33. package/dist/components/data-row/data-row.js.map +1 -0
  34. package/dist/components/data-row/index.d.ts +2 -0
  35. package/dist/components/data-row/index.d.ts.map +1 -0
  36. package/dist/components/data-row/index.js +2 -0
  37. package/dist/components/data-row/index.js.map +1 -0
  38. package/dist/components/form-field/form-field.d.ts +124 -0
  39. package/dist/components/form-field/form-field.d.ts.map +1 -0
  40. package/dist/components/form-field/form-field.js +68 -0
  41. package/dist/components/form-field/form-field.js.map +1 -0
  42. package/dist/components/form-field/index.d.ts +2 -0
  43. package/dist/components/form-field/index.d.ts.map +1 -0
  44. package/dist/components/form-field/index.js +2 -0
  45. package/dist/components/form-field/index.js.map +1 -0
  46. package/dist/components/labeled-field/index.d.ts +2 -0
  47. package/dist/components/labeled-field/index.d.ts.map +1 -0
  48. package/dist/components/labeled-field/index.js +2 -0
  49. package/dist/components/labeled-field/index.js.map +1 -0
  50. package/dist/components/labeled-field/labeled-field.d.ts +19 -0
  51. package/dist/components/labeled-field/labeled-field.d.ts.map +1 -0
  52. package/dist/components/labeled-field/labeled-field.js +15 -0
  53. package/dist/components/labeled-field/labeled-field.js.map +1 -0
  54. package/dist/components/numeric-input/index.d.ts +2 -0
  55. package/dist/components/numeric-input/index.d.ts.map +1 -0
  56. package/dist/components/numeric-input/index.js +2 -0
  57. package/dist/components/numeric-input/index.js.map +1 -0
  58. package/dist/components/numeric-input/numeric-input.d.ts +119 -0
  59. package/dist/components/numeric-input/numeric-input.d.ts.map +1 -0
  60. package/dist/components/numeric-input/numeric-input.js +129 -0
  61. package/dist/components/numeric-input/numeric-input.js.map +1 -0
  62. package/dist/components/segmented-control/index.d.ts +2 -0
  63. package/dist/components/segmented-control/index.d.ts.map +1 -0
  64. package/dist/components/segmented-control/index.js +2 -0
  65. package/dist/components/segmented-control/index.js.map +1 -0
  66. package/dist/components/segmented-control/segmented-control.d.ts +281 -0
  67. package/dist/components/segmented-control/segmented-control.d.ts.map +1 -0
  68. package/dist/components/segmented-control/segmented-control.js +110 -0
  69. package/dist/components/segmented-control/segmented-control.js.map +1 -0
  70. package/dist/index.d.ts +7 -1
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +7 -1
  73. package/dist/index.js.map +1 -1
  74. package/dist/lib/index.d.ts +1 -0
  75. package/dist/lib/index.d.ts.map +1 -1
  76. package/dist/lib/index.js +1 -0
  77. package/dist/lib/index.js.map +1 -1
  78. package/dist/lib/theme-bridge.d.ts +29 -0
  79. package/dist/lib/theme-bridge.d.ts.map +1 -0
  80. package/dist/lib/theme-bridge.js +79 -0
  81. package/dist/lib/theme-bridge.js.map +1 -0
  82. package/package.json +3 -2
package/README.md ADDED
@@ -0,0 +1,1243 @@
1
+ # WhileUI Native
2
+
3
+ > **shadcn/ui for React Native** — Copy-paste components you own.
4
+
5
+ Beautiful, accessible, themeable React Native components built with [Uniwind](https://uniwind.dev) + Tailwind CSS v4. Inspired by [shadcn/ui](https://ui.shadcn.com/).
6
+
7
+ Current version: **0.2.0**
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @thewhileloop/whileui
13
+ # or
14
+ pnpm add @thewhileloop/whileui
15
+ ```
16
+
17
+ ### Required Peer Dependencies
18
+
19
+ ```bash
20
+ pnpm add react@^19.0.0 react-native@^0.81.0 uniwind@^1.0.0 tailwindcss@^4.0.0
21
+ pnpm add react-native-reanimated react-native-safe-area-context react-native-screens
22
+ ```
23
+
24
+ ### Portal Dependencies (Select, Popover, Tooltip, HoverCard)
25
+
26
+ ```bash
27
+ pnpm add @rn-primitives/portal @rn-primitives/hooks @rn-primitives/slot @rn-primitives/select @rn-primitives/popover @rn-primitives/tooltip @rn-primitives/hover-card
28
+ ```
29
+
30
+ ### Setup Uniwind (required)
31
+
32
+ 1. **metro.config.js** (wrap with withUniwindConfig):
33
+
34
+ ```js
35
+ const { withUniwindConfig } = require('uniwind/metro');
36
+
37
+ module.exports = withUniwindConfig({
38
+ cssEntryFile: './global.css',
39
+ })({
40
+ // your metro config
41
+ });
42
+ ```
43
+
44
+ > `withUniwindConfig` must be the outermost wrapper. `cssEntryFile` must be a relative path string.
45
+
46
+ 2. **global.css** at app root:
47
+
48
+ ```css
49
+ @import 'tailwindcss';
50
+ @import 'uniwind';
51
+
52
+ /* WhileUI Noir theme - copy this or create your own */
53
+ @layer theme {
54
+ :root {
55
+ @variant light {
56
+ --color-background: oklch(1 0 0);
57
+ --color-foreground: oklch(0.1316 0.0041 17.69);
58
+ --color-card: oklch(1 0 0);
59
+ --color-card-foreground: oklch(0.1316 0.0041 17.69);
60
+ --color-primary: oklch(0.1316 0.0041 17.69);
61
+ --color-primary-foreground: oklch(0.98 0 0);
62
+ --color-secondary: oklch(0.9598 0.0017 17.69);
63
+ --color-secondary-foreground: oklch(0.1316 0.0041 17.69);
64
+ --color-muted: oklch(0.9598 0.0017 17.69);
65
+ --color-muted-foreground: oklch(0.5415 0.0135 17.69);
66
+ --color-accent: oklch(0.9598 0.0017 17.69);
67
+ --color-accent-foreground: oklch(0.1316 0.0041 17.69);
68
+ --color-destructive: oklch(0.5 0.19 27);
69
+ --color-destructive-foreground: oklch(0.98 0 0);
70
+ --color-border: oklch(0.9039 0.0034 17.69);
71
+ --color-input: oklch(0.9039 0.0034 17.69);
72
+ --color-ring: oklch(0.1316 0.0041 17.69);
73
+ --color-success: oklch(0.59 0.16 145);
74
+ --color-success-foreground: oklch(0.98 0 0);
75
+ --color-warning: oklch(0.75 0.18 85);
76
+ --color-warning-foreground: oklch(0.1316 0.0041 17.69);
77
+ --color-info: oklch(0.65 0.15 245);
78
+ --color-info-foreground: oklch(0.98 0 0);
79
+ }
80
+
81
+ @variant dark {
82
+ --color-background: oklch(0.1316 0.0041 17.69);
83
+ --color-foreground: oklch(0.98 0 0);
84
+ --color-card: oklch(0.1316 0.0041 17.69);
85
+ --color-card-foreground: oklch(0.98 0 0);
86
+ --color-primary: oklch(0.98 0 0);
87
+ --color-primary-foreground: oklch(0.1316 0.0041 17.69);
88
+ --color-secondary: oklch(0.2104 0.0084 17.69);
89
+ --color-secondary-foreground: oklch(0.98 0 0);
90
+ --color-muted: oklch(0.2104 0.0084 17.69);
91
+ --color-muted-foreground: oklch(0.6961 0.0174 17.69);
92
+ --color-accent: oklch(0.2104 0.0084 17.69);
93
+ --color-accent-foreground: oklch(0.98 0 0);
94
+ --color-destructive: oklch(0.45 0.18 27);
95
+ --color-destructive-foreground: oklch(0.98 0 0);
96
+ --color-border: oklch(0.2104 0.0084 17.69);
97
+ --color-input: oklch(0.2104 0.0084 17.69);
98
+ --color-ring: oklch(0.8267 0.0206 17.69);
99
+ --color-success: oklch(0.59 0.16 145);
100
+ --color-success-foreground: oklch(0.98 0 0);
101
+ --color-warning: oklch(0.75 0.18 85);
102
+ --color-warning-foreground: oklch(0.1316 0.0041 17.69);
103
+ --color-info: oklch(0.65 0.15 245);
104
+ --color-info-foreground: oklch(0.98 0 0);
105
+ }
106
+ }
107
+ }
108
+ ```
109
+
110
+ 3. **App.tsx**:
111
+
112
+ ```tsx
113
+ import './global.css';
114
+ import { PortalHost } from '@thewhileloop/whileui';
115
+ import { Button, ButtonText } from '@thewhileloop/whileui';
116
+
117
+ export default function App() {
118
+ return (
119
+ <>
120
+ {/* Your app content */}
121
+ <PortalHost />
122
+ </>
123
+ );
124
+ }
125
+ ```
126
+
127
+ > **Note:** `<PortalHost />` must be added at the root of your app (as the last child) to enable portal-based components like Select, Popover, Tooltip, and HoverCard to render correctly.
128
+
129
+ ## Usage
130
+
131
+ ```tsx
132
+ import {
133
+ Button,
134
+ ButtonText,
135
+ Card,
136
+ CardHeader,
137
+ CardTitle,
138
+ CardContent,
139
+ Input,
140
+ Text,
141
+ } from '@thewhileloop/whileui';
142
+
143
+ function MyScreen() {
144
+ return (
145
+ <Card>
146
+ <CardHeader>
147
+ <CardTitle>Welcome</CardTitle>
148
+ </CardHeader>
149
+ <CardContent>
150
+ <Input placeholder="Email" />
151
+ <Button className="mt-4">
152
+ <ButtonText>Continue</ButtonText>
153
+ </Button>
154
+ </CardContent>
155
+ </Card>
156
+ );
157
+ }
158
+ ```
159
+
160
+ ## Philosophy
161
+
162
+ - **Copy-Paste Ownership** — Components live in _your_ project. No `node_modules` lock-in.
163
+ - **Beautiful by Default** — OKLCH color system, light/dark themes, polished out of the box.
164
+ - **Fully Customizable** — Every component uses `tv()` variants and accepts `className` overrides.
165
+ - **Accessible** — Proper ARIA roles, keyboard support, controlled/uncontrolled state.
166
+ - **Tree-Shakeable** — Only imports what you use. `sideEffects: false`.
167
+
168
+ ## Components
169
+
170
+ ### Primitives
171
+
172
+ | Component | Notes |
173
+ | ------------- | -------------------------------- |
174
+ | **Text** | Themed text with variant support |
175
+ | **View** | Themed view wrapper |
176
+ | **Pressable** | Themed pressable wrapper |
177
+
178
+ ### Form Controls
179
+
180
+ | Component | Variants | Notes |
181
+ | -------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------- |
182
+ | **Button** | default, destructive, outline, secondary, ghost, link | 4 sizes, ButtonText & ButtonIcon sub-components |
183
+ | **Input** | default, error | TextInput wrapper with themed styling |
184
+ | **NumericInput** | default, error | Numeric input with prefix/suffix slots, optional steppers, and compact size |
185
+ | **FormField** | default, compact | Compound API: FormField, FormLabel, FormControl, FormHint, FormMessage |
186
+ | **LabeledField** | default, compact | Field wrapper with label/helper/error plus left/right slots |
187
+ | **Textarea** | — | Multi-line text input |
188
+ | **Checkbox** | — | Controlled/uncontrolled, accessibility roles |
189
+ | **Switch** | — | Controlled/uncontrolled, accessibility roles |
190
+ | **RadioGroup** | — | RadioGroup + RadioGroupItem |
191
+ | **Select** | — | Uses `SelectOption` type `{value, label}`. Includes SelectGroup, SelectLabel, SelectSeparator |
192
+ | **Label** | — | Form field label |
193
+ | **SegmentedControl** | single select | SegmentedControl, SegmentedControlItem, SegmentedControlItemText with wrapping layout support |
194
+ | **Toggle** | default, outline | ToggleText sub-component |
195
+ | **ToggleGroup** | single, multiple | Group of toggle items |
196
+
197
+ ### Display
198
+
199
+ | Component | Variants | Notes |
200
+ | --------------- | ------------------------------------------------- | ------------------------------------------------------------------------------- |
201
+ | **Card** | padding (none, sm, default, lg), unstyled | Compound: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter |
202
+ | **Badge** | default, secondary, destructive, outline, success | BadgeText sub-component |
203
+ | **Alert** | default, destructive, success, warning | AlertTitle & AlertDescription |
204
+ | **Avatar** | sm, default, lg | AvatarImage + AvatarFallback |
205
+ | **DataRow** | default, compact | DataRow, DataRowLeft/Center/Right, DataRowLabel/Description/Value |
206
+ | **Separator** | horizontal, vertical | Themed divider |
207
+ | **Progress** | sm, default, lg | Value-based progress bar with accessibility |
208
+ | **Spinner** | sm, default, lg | ActivityIndicator wrapper |
209
+ | **Skeleton** | — | Loading placeholder |
210
+ | **AspectRatio** | — | Maintain aspect ratio container |
211
+
212
+ ### Layout
213
+
214
+ | Component | Notes |
215
+ | --------------- | ---------------------------------------- |
216
+ | **Tabs** | TabsList, TabsTrigger, TabsContent |
217
+ | **Accordion** | AccordionItem, AccordionTrigger, Content |
218
+ | **Collapsible** | CollapsibleTrigger, CollapsibleContent |
219
+
220
+ ### Overlays & Menus
221
+
222
+ | Component | Notes |
223
+ | ---------------- | ---------------------------------------------------- |
224
+ | **Dialog** | Modal dialog with Header, Footer, Title, Description |
225
+ | **AlertDialog** | Confirmation dialog with Action/Cancel buttons |
226
+ | **Popover** | Position-aware popover (requires PortalHost) |
227
+ | **Tooltip** | Position-aware tooltip (requires PortalHost) |
228
+ | **DropdownMenu** | DropdownMenuTrigger, Content, Item, Label, Separator |
229
+ | **ContextMenu** | Long-press context menu |
230
+ | **HoverCard** | Position-aware hover card (requires PortalHost) |
231
+ | **Menubar** | Horizontal menu bar |
232
+
233
+ ### Feedback
234
+
235
+ | Component | Notes |
236
+ | --------- | ---------------------------------------------- |
237
+ | **Toast** | ToastProvider, ToastContainer, useToast() hook |
238
+
239
+ ## Blocks (Pre-built Screens)
240
+
241
+ ### Auth
242
+
243
+ | Block | Description |
244
+ | ---------------------- | ------------------------------- |
245
+ | **SignInForm** | Email/password sign in |
246
+ | **SignUpForm** | Registration form |
247
+ | **ForgotPasswordForm** | Password reset request |
248
+ | **ResetPasswordForm** | Set new password |
249
+ | **VerifyEmailForm** | Email verification code input |
250
+ | **SocialConnections** | OAuth provider buttons |
251
+ | **UserMenu** | Profile dropdown for auth flows |
252
+
253
+ ### Navigation
254
+
255
+ | Block | Description |
256
+ | --------------------- | ------------------------------------------------- |
257
+ | **AppShell** | Layout shell with slots for navigation |
258
+ | **NavigationSidebar** | Sidebar nav with grouped sections and footer slot |
259
+ | **Header** | Top app bar with back/actions |
260
+ | **BottomNav** | Tab-style bottom navigation bar |
261
+ | **FloatingBottomNav** | Elevated bottom nav with safe area support |
262
+ | **TabBar** | Top tab bar with indicator |
263
+ | **DrawerMenu** | Drawer with sections and items |
264
+
265
+ ### Lists
266
+
267
+ | Block | Description |
268
+ | -------------------- | ------------------------------------ |
269
+ | **ListItem** | Title/subtitle row |
270
+ | **NotificationItem** | Notification row with metadata |
271
+ | **SwipeableItem** | Swipe actions (left/right) list item |
272
+
273
+ ### Commerce
274
+
275
+ | Block | Description |
276
+ | ------------------- | ------------------------------- |
277
+ | **ProductCard** | Product card with badge/media |
278
+ | **PricingCard** | Pricing tiers with feature list |
279
+ | **CheckoutSummary** | Cart summary with line items |
280
+
281
+ ### Profile & Settings
282
+
283
+ | Block | Description |
284
+ | ------------------- | ----------------------------------- |
285
+ | **ProfileHeader** | Profile header with stats |
286
+ | **AccountCard** | Account summary card |
287
+ | **SettingsSection** | Section header with optional action |
288
+ | **SettingsItem** | Row for toggles/links/settings |
289
+
290
+ ### Splash & States
291
+
292
+ | Block | Description |
293
+ | -------------------- | ------------------------------ |
294
+ | **SplashScreen** | Branded splash screen |
295
+ | **MinimalSplash** | Minimal monochrome splash |
296
+ | **BrandedSplash** | Splash with brand imagery |
297
+ | **OnboardingScreen** | Paged onboarding with slides |
298
+ | **LoadingScreen** | Full-screen loading state |
299
+ | **EmptyState** | Placeholder empty/content-less |
300
+ | **ErrorState** | Error message with action |
301
+ | **UserMenu** | Avatar dropdown with user info |
302
+
303
+ ### Navigation
304
+
305
+ | Block | Description |
306
+ | --------------------- | --------------------------------- |
307
+ | **NavigationSidebar** | Sidebar nav with grouped sections |
308
+ | **Header** | App header with title & actions |
309
+ | **BottomNav** | Bottom tab navigation |
310
+ | **FloatingBottomNav** | Floating bottom navigation |
311
+ | **TabBar** | Horizontal tab bar |
312
+ | **DrawerMenu** | Side drawer navigation |
313
+
314
+ ### Layout
315
+
316
+ | Block | Description |
317
+ | ---------------------- | ----------------------------------------------- |
318
+ | **AppShell** | Main app layout wrapper |
319
+ | **ActionBar** | Sticky bottom action row with safe-area padding |
320
+ | **ConfirmActionSheet** | Reusable destructive confirmation sheet |
321
+ | **EmptyState** | Empty content placeholder |
322
+ | **ErrorState** | Error display with retry |
323
+ | **LoadingScreen** | Full-screen loading indicator |
324
+ | **OnboardingScreen** | Onboarding flow screen |
325
+
326
+ ### Splash
327
+
328
+ | Block | Description |
329
+ | ----------------- | ------------------------------------------------ |
330
+ | **SplashScreen** | Animated app launch splash with fade/scale/slide |
331
+ | **MinimalSplash** | Preset: Simple fade animation |
332
+ | **BrandedSplash** | Preset: Scale animation with loading indicator |
333
+
334
+ ### Profile
335
+
336
+ | Block | Description |
337
+ | ------------------- | -------------------------- |
338
+ | **ProfileHeader** | User profile header |
339
+ | **AccountCard** | Account info card |
340
+ | **SettingsItem** | Settings list item |
341
+ | **SettingsSection** | Settings group with header |
342
+
343
+ ### Lists
344
+
345
+ | Block | Description |
346
+ | -------------------- | -------------------------------- |
347
+ | **ListItem** | Standard list item |
348
+ | **NotificationItem** | Notification list item |
349
+ | **SwipeableItem** | Swipeable list item with actions |
350
+
351
+ ### Commerce
352
+
353
+ | Block | Description |
354
+ | ------------------- | -------------------------- |
355
+ | **ProductCard** | Product display card |
356
+ | **PricingCard** | Pricing tier card |
357
+ | **CheckoutSummary** | Order summary for checkout |
358
+
359
+ ## Quick Start
360
+
361
+ ```bash
362
+ # Install dependencies
363
+ pnpm install
364
+
365
+ # Run the showcase app
366
+ cd apps/showcase
367
+ npx expo start
368
+ ```
369
+
370
+ ## Project Structure
371
+
372
+ ```
373
+ whileui/
374
+ ├── packages/
375
+ │ └── ui/
376
+ │ └── src/
377
+ │ ├── components/ # All components (copy these!)
378
+ │ │ ├── button/
379
+ │ │ ├── card/
380
+ │ │ ├── form-field/
381
+ │ │ ├── numeric-input/
382
+ │ │ ├── segmented-control/
383
+ │ │ ├── data-row/
384
+ │ │ ├── dialog/
385
+ │ │ └── ...
386
+ │ ├── blocks/ # Pre-built screens
387
+ │ │ ├── auth/
388
+ │ │ ├── navigation/
389
+ │ │ ├── layout/
390
+ │ │ ├── profile/
391
+ │ │ ├── lists/
392
+ │ │ └── commerce/
393
+ │ ├── lib/ # Utilities
394
+ │ │ ├── cn.ts # clsx + tailwind-merge
395
+ │ │ ├── tv.ts # tailwind-variants re-export
396
+ │ │ ├── font-context.ts
397
+ │ │ └── theme-bridge.ts
398
+ │ └── index.ts # Barrel export
399
+ ├── apps/
400
+ │ └── showcase/ # Expo demo app
401
+ │ ├── App.tsx # Component showcase
402
+ │ ├── global.css # Theme variables (OKLCH) — at app root!
403
+ │ └── metro.config.js # Uniwind + monorepo config
404
+ └── package.json # pnpm monorepo root
405
+ ```
406
+
407
+ ## Theming
408
+
409
+ Themes are defined in `global.css` using CSS variables with OKLCH colors:
410
+
411
+ ```css
412
+ @variant light {
413
+ --color-primary: oklch(0.6 0.2 160);
414
+ --color-background: oklch(1 0 0);
415
+ /* ... */
416
+ }
417
+
418
+ @variant dark {
419
+ --color-primary: oklch(0.6 0.2 160);
420
+ --color-background: oklch(0.145 0 0);
421
+ /* ... */
422
+ }
423
+ ```
424
+
425
+ ### Token Contract (Release Baseline)
426
+
427
+ The WhileUI token contract is strict for cross-app reuse. Define these in **every** theme variant (`@variant light`, `@variant dark`, and custom variants):
428
+
429
+ - Required core tokens: `background`, `foreground`, `card`, `card-foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `muted`, `muted-foreground`, `accent`, `accent-foreground`, `destructive`, `destructive-foreground`, `border`, `input`, `ring`
430
+ - Optional status tokens: `success`, `success-foreground`, `warning`, `warning-foreground`, `info`, `info-foreground`
431
+ - Optional scale tokens: spacing (`--spacing`, `--spacing-*`), typography (`--text-*`, `--leading-*`, `--tracking-*`), radius (`--radius-*`), elevation (`--shadow-*`)
432
+
433
+ Minimal contract example:
434
+
435
+ ```css
436
+ @layer theme {
437
+ :root {
438
+ @variant light {
439
+ --color-background: oklch(1 0 0);
440
+ --color-foreground: oklch(0.15 0 0);
441
+ --color-card: oklch(1 0 0);
442
+ --color-card-foreground: oklch(0.15 0 0);
443
+ --color-primary: oklch(0.2 0 0);
444
+ --color-primary-foreground: oklch(0.98 0 0);
445
+ --color-secondary: oklch(0.95 0 0);
446
+ --color-secondary-foreground: oklch(0.15 0 0);
447
+ --color-muted: oklch(0.95 0 0);
448
+ --color-muted-foreground: oklch(0.45 0 0);
449
+ --color-accent: oklch(0.9 0.05 180);
450
+ --color-accent-foreground: oklch(0.15 0 0);
451
+ --color-destructive: oklch(0.58 0.2 26);
452
+ --color-destructive-foreground: oklch(0.98 0 0);
453
+ --color-border: oklch(0.9 0 0);
454
+ --color-input: oklch(0.92 0 0);
455
+ --color-ring: oklch(0.22 0 0);
456
+ }
457
+ }
458
+ }
459
+ ```
460
+
461
+ Switch themes at runtime via Uniwind:
462
+
463
+ ```tsx
464
+ import { Uniwind } from 'uniwind';
465
+
466
+ Uniwind.setTheme('dark'); // or 'light' or 'system'
467
+ ```
468
+
469
+ Or use the first-party ThemeBridge helper with optional persistence:
470
+
471
+ ```tsx
472
+ import { useThemeBridge, type ThemeBridgeAdapter } from '@thewhileloop/whileui';
473
+
474
+ const adapter: ThemeBridgeAdapter = {
475
+ loadThemeMode: async () => 'system',
476
+ saveThemeMode: async (mode) => {
477
+ await storage.setItem('theme-mode', mode);
478
+ },
479
+ };
480
+
481
+ const { mode, resolvedTheme, setMode, cycleMode } = useThemeBridge({ adapter });
482
+ ```
483
+
484
+ ## Using Components
485
+
486
+ Copy any component folder from `packages/ui/src/components/` into your project:
487
+
488
+ ```tsx
489
+ import { cn } from './lib/cn';
490
+ import { tv, type VariantProps } from './lib/tv';
491
+
492
+ // Use the component with className overrides
493
+ <Button className="mt-4">
494
+ <ButtonText>Get Started</ButtonText>
495
+ </Button>;
496
+ ```
497
+
498
+ ## Tech Stack
499
+
500
+ - **Styling**: [Uniwind](https://uniwind.dev) (free tier) + Tailwind CSS v4
501
+ - **Variants**: [tailwind-variants](https://www.tailwind-variants.org/) (`tv()`)
502
+ - **Merging**: [clsx](https://github.com/lukeed/clsx) + [tailwind-merge](https://github.com/dcastil/tailwind-merge)
503
+ - **Expo**: SDK 54
504
+ - **Monorepo**: pnpm + Turborepo
505
+
506
+ ## License
507
+
508
+ MIT
509
+
510
+ ---
511
+
512
+ # API Reference
513
+
514
+ ## Button
515
+
516
+ ```tsx
517
+ import { Button, ButtonText, ButtonIcon } from '@thewhileloop/whileui';
518
+
519
+ <Button variant="default" size="default" disabled={false} onPress={() => {}}>
520
+ <ButtonIcon>
521
+ <Icon />
522
+ </ButtonIcon>
523
+ <ButtonText>Click me</ButtonText>
524
+ <ButtonIcon position="right">
525
+ <Icon />
526
+ </ButtonIcon>
527
+ </Button>;
528
+ ```
529
+
530
+ | Prop | Type | Default | Description |
531
+ | --------- | ----------------------------------------------------------------------------- | ----------- | --------------------------- |
532
+ | variant | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'default'` | Button style variant |
533
+ | size | `'default' \| 'sm' \| 'lg' \| 'icon'` | `'default'` | Button size |
534
+ | disabled | `boolean` | `false` | Disable the button |
535
+ | className | `string` | — | Additional Tailwind classes |
536
+
537
+ ## Input
538
+
539
+ ```tsx
540
+ import { Input } from '@thewhileloop/whileui';
541
+
542
+ <Input placeholder="Email" variant="default" value={value} onChangeText={setValue} />;
543
+ ```
544
+
545
+ | Prop | Type | Default | Description |
546
+ | ----------- | ---------------------- | ----------- | ------------------------- |
547
+ | variant | `'default' \| 'error'` | `'default'` | Input style variant |
548
+ | placeholder | `string` | — | Placeholder text |
549
+ | editable | `boolean` | `true` | Whether input is editable |
550
+
551
+ ## NumericInput
552
+
553
+ ```tsx
554
+ import { NumericInput } from '@thewhileloop/whileui';
555
+
556
+ <NumericInput
557
+ value={amount}
558
+ onValueChange={setAmount}
559
+ prefix={<Text className="text-muted-foreground">$</Text>}
560
+ suffix={<Text className="text-muted-foreground">USD</Text>}
561
+ min={0}
562
+ step={0.5}
563
+ showSteppers
564
+ size="compact"
565
+ />;
566
+ ```
567
+
568
+ | Prop | Type | Default | Description |
569
+ | --------------- | --------------------------------- | ----------- | --------------------------------- |
570
+ | variant | `'default' \| 'error'` | `'default'` | Input style |
571
+ | size | `'default' \| 'compact'` | `'default'` | Density size |
572
+ | value | `number \| null` | — | Controlled numeric value |
573
+ | onValueChange | `(value: number \| null) => void` | — | Numeric value change callback |
574
+ | prefix / suffix | `ReactNode` | — | Left/right slots |
575
+ | showSteppers | `boolean` | `false` | Show decrement/increment controls |
576
+
577
+ ## FormField
578
+
579
+ ```tsx
580
+ import {
581
+ FormField,
582
+ FormLabel,
583
+ FormControl,
584
+ FormHint,
585
+ FormMessage,
586
+ Input,
587
+ } from '@thewhileloop/whileui';
588
+
589
+ <FormField required invalid={Boolean(error)}>
590
+ <FormLabel>Email</FormLabel>
591
+ <FormControl>
592
+ <Input placeholder="you@example.com" />
593
+ </FormControl>
594
+ {error ? <FormMessage>{error}</FormMessage> : <FormHint>We'll never share your email.</FormHint>}
595
+ </FormField>;
596
+ ```
597
+
598
+ ## LabeledField
599
+
600
+ ```tsx
601
+ import { LabeledField, LabeledFieldControl, Input } from '@thewhileloop/whileui';
602
+
603
+ <LabeledField
604
+ label="Username"
605
+ hint="3-20 characters"
606
+ leftSlot={<Icon />}
607
+ rightSlot={
608
+ <Button size="sm">
609
+ <ButtonText>Check</ButtonText>
610
+ </Button>
611
+ }
612
+ >
613
+ <LabeledFieldControl>
614
+ <Input className="border-0 bg-transparent px-0" />
615
+ </LabeledFieldControl>
616
+ </LabeledField>;
617
+ ```
618
+
619
+ ## SegmentedControl
620
+
621
+ ```tsx
622
+ import {
623
+ SegmentedControl,
624
+ SegmentedControlItem,
625
+ SegmentedControlItemText,
626
+ } from '@thewhileloop/whileui';
627
+
628
+ <SegmentedControl value={unit} onValueChange={setUnit} wrap>
629
+ <SegmentedControlItem value="metric">
630
+ <SegmentedControlItemText>Metric</SegmentedControlItemText>
631
+ </SegmentedControlItem>
632
+ <SegmentedControlItem value="imperial">
633
+ <SegmentedControlItemText>Imperial</SegmentedControlItemText>
634
+ </SegmentedControlItem>
635
+ </SegmentedControl>;
636
+ ```
637
+
638
+ ## DataRow
639
+
640
+ ```tsx
641
+ import {
642
+ DataRow,
643
+ DataRowLeft,
644
+ DataRowCenter,
645
+ DataRowRight,
646
+ DataRowLabel,
647
+ DataRowDescription,
648
+ DataRowValue,
649
+ Avatar,
650
+ AvatarFallback,
651
+ } from '@thewhileloop/whileui';
652
+
653
+ <DataRow>
654
+ <DataRowLeft>
655
+ <Avatar size="sm">
656
+ <AvatarFallback>JD</AvatarFallback>
657
+ </Avatar>
658
+ </DataRowLeft>
659
+ <DataRowCenter>
660
+ <DataRowLabel>Jane Doe</DataRowLabel>
661
+ <DataRowDescription>Product Designer</DataRowDescription>
662
+ </DataRowCenter>
663
+ <DataRowRight>
664
+ <DataRowValue>Owner</DataRowValue>
665
+ </DataRowRight>
666
+ </DataRow>;
667
+ ```
668
+
669
+ ## Card
670
+
671
+ ```tsx
672
+ import {
673
+ Card,
674
+ CardHeader,
675
+ CardTitle,
676
+ CardDescription,
677
+ CardContent,
678
+ CardFooter,
679
+ } from '@thewhileloop/whileui';
680
+
681
+ <Card padding="default">
682
+ <CardHeader>
683
+ <CardTitle>Title</CardTitle>
684
+ <CardDescription>Description</CardDescription>
685
+ </CardHeader>
686
+ <CardContent>{/* Content */}</CardContent>
687
+ <CardFooter>{/* Footer */}</CardFooter>
688
+ </Card>;
689
+
690
+ <Card unstyled padding="none" className="rounded-xl border border-border bg-card">
691
+ {/* advanced custom layouts */}
692
+ </Card>;
693
+ ```
694
+
695
+ | Prop | Type | Default | Description |
696
+ | -------- | ------------------------------------- | ----------- | ----------------------------------- |
697
+ | padding | `'none' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Card interior padding |
698
+ | unstyled | `boolean` | `false` | Remove built-in card surface styles |
699
+
700
+ ## Badge
701
+
702
+ ```tsx
703
+ import { Badge, BadgeText } from '@thewhileloop/whileui';
704
+
705
+ <Badge variant="default">
706
+ <BadgeText>New</BadgeText>
707
+ </Badge>;
708
+ ```
709
+
710
+ | Prop | Type | Default |
711
+ | ------- | -------------------------------------------------------------------------------------------- | ----------- |
712
+ | variant | `'default' \| 'secondary' \| 'destructive' \| 'outline' \| 'success' \| 'warning' \| 'info'` | `'default'` |
713
+
714
+ ## Alert
715
+
716
+ ```tsx
717
+ import { Alert, AlertTitle, AlertDescription } from '@thewhileloop/whileui';
718
+
719
+ <Alert variant="default">
720
+ <AlertTitle>Heads up!</AlertTitle>
721
+ <AlertDescription>Something happened.</AlertDescription>
722
+ </Alert>;
723
+ ```
724
+
725
+ | Prop | Type | Default |
726
+ | ------- | ------------------------------------------------------ | ----------- |
727
+ | variant | `'default' \| 'destructive' \| 'success' \| 'warning'` | `'default'` |
728
+
729
+ ## Dialog
730
+
731
+ ```tsx
732
+ import {
733
+ Dialog,
734
+ DialogTrigger,
735
+ DialogContent,
736
+ DialogHeader,
737
+ DialogTitle,
738
+ DialogDescription,
739
+ DialogFooter,
740
+ DialogClose,
741
+ } from '@thewhileloop/whileui';
742
+
743
+ <Dialog>
744
+ <DialogTrigger asChild>
745
+ <Button>
746
+ <ButtonText>Open</ButtonText>
747
+ </Button>
748
+ </DialogTrigger>
749
+ <DialogContent>
750
+ <DialogHeader>
751
+ <DialogTitle>Title</DialogTitle>
752
+ <DialogDescription>Description</DialogDescription>
753
+ </DialogHeader>
754
+ {/* Content */}
755
+ <DialogFooter>
756
+ <DialogClose asChild>
757
+ <Button>
758
+ <ButtonText>Close</ButtonText>
759
+ </Button>
760
+ </DialogClose>
761
+ </DialogFooter>
762
+ </DialogContent>
763
+ </Dialog>;
764
+ ```
765
+
766
+ ## AlertDialog
767
+
768
+ ```tsx
769
+ import {
770
+ AlertDialog,
771
+ AlertDialogTrigger,
772
+ AlertDialogContent,
773
+ AlertDialogHeader,
774
+ AlertDialogTitle,
775
+ AlertDialogDescription,
776
+ AlertDialogFooter,
777
+ AlertDialogAction,
778
+ AlertDialogCancel,
779
+ } from '@thewhileloop/whileui';
780
+
781
+ <AlertDialog>
782
+ <AlertDialogTrigger asChild>
783
+ <Button variant="destructive">
784
+ <ButtonText>Delete</ButtonText>
785
+ </Button>
786
+ </AlertDialogTrigger>
787
+ <AlertDialogContent>
788
+ <AlertDialogHeader>
789
+ <AlertDialogTitle>Are you sure?</AlertDialogTitle>
790
+ <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
791
+ </AlertDialogHeader>
792
+ <AlertDialogFooter>
793
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
794
+ <AlertDialogAction>Delete</AlertDialogAction>
795
+ </AlertDialogFooter>
796
+ </AlertDialogContent>
797
+ </AlertDialog>;
798
+ ```
799
+
800
+ ## Checkbox
801
+
802
+ ```tsx
803
+ import { Checkbox } from '@thewhileloop/whileui';
804
+
805
+ <Checkbox checked={checked} onCheckedChange={setChecked} />;
806
+ ```
807
+
808
+ | Prop | Type | Default |
809
+ | --------------- | ---------------------------- | ------- |
810
+ | checked | `boolean` | `false` |
811
+ | onCheckedChange | `(checked: boolean) => void` | — |
812
+ | disabled | `boolean` | `false` |
813
+
814
+ ## Switch
815
+
816
+ ```tsx
817
+ import { Switch } from '@thewhileloop/whileui';
818
+
819
+ <Switch checked={checked} onCheckedChange={setChecked} />;
820
+ ```
821
+
822
+ | Prop | Type | Default |
823
+ | --------------- | ---------------------------- | ------- |
824
+ | checked | `boolean` | `false` |
825
+ | onCheckedChange | `(checked: boolean) => void` | — |
826
+
827
+ ## RadioGroup
828
+
829
+ ```tsx
830
+ import { RadioGroup, RadioGroupItem } from '@thewhileloop/whileui';
831
+
832
+ <RadioGroup value={value} onValueChange={setValue}>
833
+ <RadioGroupItem value="option1" />
834
+ <RadioGroupItem value="option2" />
835
+ </RadioGroup>;
836
+ ```
837
+
838
+ ## Select
839
+
840
+ ```tsx
841
+ import {
842
+ Select,
843
+ SelectTrigger,
844
+ SelectValue,
845
+ SelectContent,
846
+ SelectItem,
847
+ } from '@thewhileloop/whileui';
848
+
849
+ <Select value={value} onValueChange={setValue}>
850
+ <SelectTrigger>
851
+ <SelectValue placeholder="Select..." />
852
+ </SelectTrigger>
853
+ <SelectContent>
854
+ <SelectItem label="Option 1" value="1" />
855
+ <SelectItem label="Option 2" value="2" />
856
+ </SelectContent>
857
+ </Select>;
858
+ ```
859
+
860
+ ## Tabs
861
+
862
+ ```tsx
863
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@thewhileloop/whileui';
864
+
865
+ <Tabs defaultValue="tab1">
866
+ <TabsList>
867
+ <TabsTrigger value="tab1">
868
+ <Text>Tab 1</Text>
869
+ </TabsTrigger>
870
+ <TabsTrigger value="tab2">
871
+ <Text>Tab 2</Text>
872
+ </TabsTrigger>
873
+ </TabsList>
874
+ <TabsContent value="tab1">{/* Content 1 */}</TabsContent>
875
+ <TabsContent value="tab2">{/* Content 2 */}</TabsContent>
876
+ </Tabs>;
877
+ ```
878
+
879
+ ## Accordion
880
+
881
+ ```tsx
882
+ import {
883
+ Accordion,
884
+ AccordionItem,
885
+ AccordionTrigger,
886
+ AccordionContent,
887
+ } from '@thewhileloop/whileui';
888
+
889
+ <Accordion type="single" collapsible>
890
+ <AccordionItem value="item1">
891
+ <AccordionTrigger>
892
+ <Text>Section 1</Text>
893
+ </AccordionTrigger>
894
+ <AccordionContent>
895
+ <Text>Content 1</Text>
896
+ </AccordionContent>
897
+ </AccordionItem>
898
+ </Accordion>;
899
+ ```
900
+
901
+ | Prop | Type | Default |
902
+ | ----------- | ------------------------ | ---------- |
903
+ | type | `'single' \| 'multiple'` | `'single'` |
904
+ | collapsible | `boolean` | `false` |
905
+
906
+ ## Avatar
907
+
908
+ ```tsx
909
+ import { Avatar, AvatarImage, AvatarFallback } from '@thewhileloop/whileui';
910
+
911
+ <Avatar size="default">
912
+ <AvatarImage src="https://..." />
913
+ <AvatarFallback>JD</AvatarFallback>
914
+ </Avatar>;
915
+ ```
916
+
917
+ | Prop | Type | Default |
918
+ | ---- | --------------------------- | ----------- |
919
+ | size | `'sm' \| 'default' \| 'lg'` | `'default'` |
920
+
921
+ ## Progress
922
+
923
+ ```tsx
924
+ import { Progress } from '@thewhileloop/whileui';
925
+
926
+ <Progress value={50} size="default" />;
927
+ ```
928
+
929
+ | Prop | Type | Default |
930
+ | ----- | --------------------------- | ----------- |
931
+ | value | `number` | `0` |
932
+ | size | `'sm' \| 'default' \| 'lg'` | `'default'` |
933
+
934
+ ## Toast
935
+
936
+ ```tsx
937
+ import { ToastProvider, ToastContainer, useToast } from '@thewhileloop/whileui';
938
+
939
+ // Wrap app
940
+ <ToastProvider>
941
+ <App />
942
+ <ToastContainer position="top" />
943
+ </ToastProvider>;
944
+
945
+ // Use in component
946
+ const { toast } = useToast();
947
+ toast({ title: 'Success', description: 'Saved!', variant: 'success' });
948
+ ```
949
+
950
+ | Toast Options | Type |
951
+ | ------------- | ----------------------------------------- |
952
+ | title | `string` |
953
+ | description | `string` |
954
+ | variant | `'default' \| 'success' \| 'destructive'` |
955
+ | duration | `number` (ms) |
956
+
957
+ ## Popover
958
+
959
+ ```tsx
960
+ import { Popover, PopoverTrigger, PopoverContent } from '@thewhileloop/whileui';
961
+
962
+ <Popover>
963
+ <PopoverTrigger asChild>
964
+ <Button>
965
+ <ButtonText>Open</ButtonText>
966
+ </Button>
967
+ </PopoverTrigger>
968
+ <PopoverContent>
969
+ <Text>Popover content</Text>
970
+ </PopoverContent>
971
+ </Popover>;
972
+ ```
973
+
974
+ ## DropdownMenu
975
+
976
+ ```tsx
977
+ import {
978
+ DropdownMenu,
979
+ DropdownMenuTrigger,
980
+ DropdownMenuContent,
981
+ DropdownMenuItem,
982
+ DropdownMenuLabel,
983
+ DropdownMenuSeparator,
984
+ } from '@thewhileloop/whileui';
985
+
986
+ <DropdownMenu>
987
+ <DropdownMenuTrigger asChild>
988
+ <Button>
989
+ <ButtonText>Menu</ButtonText>
990
+ </Button>
991
+ </DropdownMenuTrigger>
992
+ <DropdownMenuContent>
993
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
994
+ <DropdownMenuSeparator />
995
+ <DropdownMenuItem>
996
+ <Text>Edit</Text>
997
+ </DropdownMenuItem>
998
+ <DropdownMenuItem>
999
+ <Text>Delete</Text>
1000
+ </DropdownMenuItem>
1001
+ </DropdownMenuContent>
1002
+ </DropdownMenu>;
1003
+ ```
1004
+
1005
+ ---
1006
+
1007
+ # Blocks API
1008
+
1009
+ ## SignInForm
1010
+
1011
+ ```tsx
1012
+ import { SignInForm } from '@thewhileloop/whileui';
1013
+
1014
+ <SignInForm onSubmit={(email, password) => {}} onForgotPassword={() => {}} onSignUp={() => {}} />;
1015
+ ```
1016
+
1017
+ ## SignUpForm
1018
+
1019
+ ```tsx
1020
+ import { SignUpForm } from '@thewhileloop/whileui';
1021
+
1022
+ <SignUpForm onSubmit={(name, email, password) => {}} onSignIn={() => {}} />;
1023
+ ```
1024
+
1025
+ ## BottomNav
1026
+
1027
+ ```tsx
1028
+ import { BottomNav } from '@thewhileloop/whileui';
1029
+
1030
+ <BottomNav
1031
+ items={[
1032
+ { key: 'home', label: 'Home', icon: <Icon /> },
1033
+ { key: 'profile', label: 'Profile', icon: <Icon />, badge: 3 },
1034
+ ]}
1035
+ activeKey="home"
1036
+ onSelect={(key) => {}}
1037
+ />;
1038
+ ```
1039
+
1040
+ ## ActionBar
1041
+
1042
+ ```tsx
1043
+ import { ActionBar, Button, ButtonText } from '@thewhileloop/whileui';
1044
+
1045
+ <ActionBar>
1046
+ <Button variant="outline" className="flex-1">
1047
+ <ButtonText>Cancel</ButtonText>
1048
+ </Button>
1049
+ <Button className="flex-1">
1050
+ <ButtonText>Save</ButtonText>
1051
+ </Button>
1052
+ </ActionBar>;
1053
+ ```
1054
+
1055
+ ## ConfirmActionSheet
1056
+
1057
+ ```tsx
1058
+ import { ConfirmActionSheet } from '@thewhileloop/whileui';
1059
+
1060
+ <ConfirmActionSheet
1061
+ open={open}
1062
+ onOpenChange={setOpen}
1063
+ title="Delete project?"
1064
+ description="This action cannot be undone."
1065
+ confirmLabel="Delete"
1066
+ destructive
1067
+ onConfirm={() => deleteProject()}
1068
+ />;
1069
+ ```
1070
+
1071
+ ## NavigationSidebar
1072
+
1073
+ ```tsx
1074
+ import { NavigationSidebar } from '@thewhileloop/whileui';
1075
+
1076
+ <NavigationSidebar
1077
+ sections={[
1078
+ {
1079
+ title: 'Workspace',
1080
+ items: [
1081
+ { key: 'overview', label: 'Overview', icon: <Icon /> },
1082
+ { key: 'billing', label: 'Billing', icon: <Icon />, badge: 2 },
1083
+ ],
1084
+ },
1085
+ ]}
1086
+ activeKey="overview"
1087
+ onSelect={(key) => {}}
1088
+ header={<Text>Acme Inc.</Text>}
1089
+ footer={<Text className="text-xs text-muted-foreground">v1.0.0</Text>}
1090
+ />;
1091
+ ```
1092
+
1093
+ ## Header
1094
+
1095
+ ```tsx
1096
+ import { Header, HeaderBackButton } from '@thewhileloop/whileui';
1097
+
1098
+ <Header
1099
+ title="Settings"
1100
+ subtitle="Manage preferences"
1101
+ leftAction={<HeaderBackButton onPress={() => {}} />}
1102
+ rightActions={[{ key: 'search', icon: <Icon />, onPress: () => {} }]}
1103
+ />;
1104
+ ```
1105
+
1106
+ ## SplashScreen
1107
+
1108
+ ```tsx
1109
+ import { SplashScreen } from '@thewhileloop/whileui';
1110
+
1111
+ <SplashScreen
1112
+ logo={<MyLogo />}
1113
+ appName="MyApp"
1114
+ tagline="Your tagline"
1115
+ variant="scale"
1116
+ duration={1500}
1117
+ showLoading
1118
+ onAnimationComplete={() => {}}
1119
+ />;
1120
+ ```
1121
+
1122
+ | Prop | Type | Default |
1123
+ | ----------- | ------------------------------ | --------- |
1124
+ | variant | `'fade' \| 'scale' \| 'slide'` | `'scale'` |
1125
+ | duration | `number` | `800` |
1126
+ | showLoading | `boolean` | `false` |
1127
+
1128
+ ## EmptyState
1129
+
1130
+ ```tsx
1131
+ import { EmptyState } from '@thewhileloop/whileui';
1132
+
1133
+ <EmptyState
1134
+ icon={<Icon />}
1135
+ title="No items"
1136
+ description="Add your first item."
1137
+ action={{ label: 'Add Item', onPress: () => {} }}
1138
+ />;
1139
+ ```
1140
+
1141
+ ## ProfileHeader
1142
+
1143
+ ```tsx
1144
+ import { ProfileHeader } from '@thewhileloop/whileui';
1145
+
1146
+ <ProfileHeader
1147
+ name="John Doe"
1148
+ username="johndoe"
1149
+ bio="Designer & Developer"
1150
+ avatarFallback="JD"
1151
+ avatarUrl="https://..."
1152
+ verified
1153
+ stats={[
1154
+ { label: 'Followers', value: '1.2K' },
1155
+ { label: 'Following', value: 234 },
1156
+ ]}
1157
+ action={{ label: 'Edit Profile', onPress: () => {} }}
1158
+ />;
1159
+ ```
1160
+
1161
+ ## SettingsSection / SettingsItem
1162
+
1163
+ ```tsx
1164
+ import { SettingsSection, SettingsItem } from '@thewhileloop/whileui';
1165
+
1166
+ <SettingsSection title="Preferences">
1167
+ <SettingsItem
1168
+ icon={<Icon />}
1169
+ label="Notifications"
1170
+ type="toggle"
1171
+ toggleValue={enabled}
1172
+ onToggle={setEnabled}
1173
+ />
1174
+ <SettingsItem icon={<Icon />} label="Privacy" value="Public" onPress={() => {}} />
1175
+ <SettingsItem icon={<Icon />} label="Sign Out" type="action" destructive />
1176
+ </SettingsSection>;
1177
+ ```
1178
+
1179
+ ## ProductCard
1180
+
1181
+ ```tsx
1182
+ import { ProductCard } from '@thewhileloop/whileui';
1183
+
1184
+ <ProductCard
1185
+ title="Product Name"
1186
+ price="$99"
1187
+ originalPrice="$129"
1188
+ badge="-23%"
1189
+ rating={4.5}
1190
+ reviewCount={128}
1191
+ variant="vertical"
1192
+ onPress={() => {}}
1193
+ />;
1194
+ ```
1195
+
1196
+ | Prop | Type | Default |
1197
+ | ------- | ---------------------------- | ------------ |
1198
+ | variant | `'vertical' \| 'horizontal'` | `'vertical'` |
1199
+
1200
+ ## PricingCard
1201
+
1202
+ ```tsx
1203
+ import { PricingCard } from '@thewhileloop/whileui';
1204
+
1205
+ <PricingCard
1206
+ name="Pro"
1207
+ description="For teams"
1208
+ price="$29"
1209
+ period="/month"
1210
+ badge="Popular"
1211
+ highlighted
1212
+ features={[
1213
+ { label: 'Unlimited users', included: true },
1214
+ { label: 'Priority support', included: true },
1215
+ { label: 'Custom domain', included: false },
1216
+ ]}
1217
+ onPress={() => {}}
1218
+ />;
1219
+ ```
1220
+
1221
+ ## DrawerMenu
1222
+
1223
+ ```tsx
1224
+ import { DrawerMenu } from '@thewhileloop/whileui';
1225
+
1226
+ <DrawerMenu
1227
+ visible={open}
1228
+ onClose={() => setOpen(false)}
1229
+ sections={[
1230
+ {
1231
+ title: 'Menu',
1232
+ items: [
1233
+ { key: 'home', label: 'Home', icon: <Icon /> },
1234
+ { key: 'settings', label: 'Settings', icon: <Icon /> },
1235
+ ],
1236
+ },
1237
+ ]}
1238
+ activeKey="home"
1239
+ onSelect={(key) => {}}
1240
+ header={<View>...</View>}
1241
+ footer={<Text>v1.0</Text>}
1242
+ />;
1243
+ ```