@thewhileloop/whileui 0.1.1 → 0.1.2

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 (2) hide show
  1. package/README.md +998 -0
  2. package/package.json +3 -2
package/README.md ADDED
@@ -0,0 +1,998 @@
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.1.2**
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
+ | **Textarea** | — | Multi-line text input |
185
+ | **Checkbox** | — | Controlled/uncontrolled, accessibility roles |
186
+ | **Switch** | — | Controlled/uncontrolled, accessibility roles |
187
+ | **RadioGroup** | — | RadioGroup + RadioGroupItem |
188
+ | **Select** | — | Uses `SelectOption` type `{value, label}`. Includes SelectGroup, SelectLabel, SelectSeparator |
189
+ | **Label** | — | Form field label |
190
+ | **Toggle** | default, outline | ToggleText sub-component |
191
+ | **ToggleGroup** | single, multiple | Group of toggle items |
192
+
193
+ ### Display
194
+
195
+ | Component | Variants | Notes |
196
+ | --------------- | ------------------------------------------------- | ------------------------------------------------------------------------------- |
197
+ | **Card** | — | Compound: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter |
198
+ | **Badge** | default, secondary, destructive, outline, success | BadgeText sub-component |
199
+ | **Alert** | default, destructive, success, warning | AlertTitle & AlertDescription |
200
+ | **Avatar** | sm, default, lg | AvatarImage + AvatarFallback |
201
+ | **Separator** | horizontal, vertical | Themed divider |
202
+ | **Progress** | sm, default, lg | Value-based progress bar with accessibility |
203
+ | **Spinner** | sm, default, lg | ActivityIndicator wrapper |
204
+ | **Skeleton** | — | Loading placeholder |
205
+ | **AspectRatio** | — | Maintain aspect ratio container |
206
+
207
+ ### Layout
208
+
209
+ | Component | Notes |
210
+ | --------------- | ---------------------------------------- |
211
+ | **Tabs** | TabsList, TabsTrigger, TabsContent |
212
+ | **Accordion** | AccordionItem, AccordionTrigger, Content |
213
+ | **Collapsible** | CollapsibleTrigger, CollapsibleContent |
214
+
215
+ ### Overlays & Menus
216
+
217
+ | Component | Notes |
218
+ | ---------------- | ---------------------------------------------------- |
219
+ | **Dialog** | Modal dialog with Header, Footer, Title, Description |
220
+ | **AlertDialog** | Confirmation dialog with Action/Cancel buttons |
221
+ | **Popover** | Position-aware popover (requires PortalHost) |
222
+ | **Tooltip** | Position-aware tooltip (requires PortalHost) |
223
+ | **DropdownMenu** | DropdownMenuTrigger, Content, Item, Label, Separator |
224
+ | **ContextMenu** | Long-press context menu |
225
+ | **HoverCard** | Position-aware hover card (requires PortalHost) |
226
+ | **Menubar** | Horizontal menu bar |
227
+
228
+ ### Feedback
229
+
230
+ | Component | Notes |
231
+ | --------- | ---------------------------------------------- |
232
+ | **Toast** | ToastProvider, ToastContainer, useToast() hook |
233
+
234
+ ## Blocks (Pre-built Screens)
235
+
236
+ ### Auth
237
+
238
+ | Block | Description |
239
+ | ---------------------- | ------------------------------- |
240
+ | **SignInForm** | Email/password sign in |
241
+ | **SignUpForm** | Registration form |
242
+ | **ForgotPasswordForm** | Password reset request |
243
+ | **ResetPasswordForm** | Set new password |
244
+ | **VerifyEmailForm** | Email verification code input |
245
+ | **SocialConnections** | OAuth provider buttons |
246
+ | **UserMenu** | Profile dropdown for auth flows |
247
+
248
+ ### Navigation
249
+
250
+ | Block | Description |
251
+ | --------------------- | ------------------------------------------ |
252
+ | **AppShell** | Layout shell with slots for navigation |
253
+ | **Header** | Top app bar with back/actions |
254
+ | **BottomNav** | Tab-style bottom navigation bar |
255
+ | **FloatingBottomNav** | Elevated bottom nav with safe area support |
256
+ | **TabBar** | Top tab bar with indicator |
257
+ | **DrawerMenu** | Drawer with sections and items |
258
+
259
+ ### Lists
260
+
261
+ | Block | Description |
262
+ | -------------------- | ------------------------------------ |
263
+ | **ListItem** | Title/subtitle row |
264
+ | **NotificationItem** | Notification row with metadata |
265
+ | **SwipeableItem** | Swipe actions (left/right) list item |
266
+
267
+ ### Commerce
268
+
269
+ | Block | Description |
270
+ | ------------------- | ------------------------------- |
271
+ | **ProductCard** | Product card with badge/media |
272
+ | **PricingCard** | Pricing tiers with feature list |
273
+ | **CheckoutSummary** | Cart summary with line items |
274
+
275
+ ### Profile & Settings
276
+
277
+ | Block | Description |
278
+ | ------------------- | ----------------------------------- |
279
+ | **ProfileHeader** | Profile header with stats |
280
+ | **AccountCard** | Account summary card |
281
+ | **SettingsSection** | Section header with optional action |
282
+ | **SettingsItem** | Row for toggles/links/settings |
283
+
284
+ ### Splash & States
285
+
286
+ | Block | Description |
287
+ | -------------------- | ------------------------------ |
288
+ | **SplashScreen** | Branded splash screen |
289
+ | **MinimalSplash** | Minimal monochrome splash |
290
+ | **BrandedSplash** | Splash with brand imagery |
291
+ | **OnboardingScreen** | Paged onboarding with slides |
292
+ | **LoadingScreen** | Full-screen loading state |
293
+ | **EmptyState** | Placeholder empty/content-less |
294
+ | **ErrorState** | Error message with action |
295
+ | **UserMenu** | Avatar dropdown with user info |
296
+
297
+ ### Navigation
298
+
299
+ | Block | Description |
300
+ | --------------------- | ------------------------------- |
301
+ | **Header** | App header with title & actions |
302
+ | **BottomNav** | Bottom tab navigation |
303
+ | **FloatingBottomNav** | Floating bottom navigation |
304
+ | **TabBar** | Horizontal tab bar |
305
+ | **DrawerMenu** | Side drawer navigation |
306
+
307
+ ### Layout
308
+
309
+ | Block | Description |
310
+ | -------------------- | ----------------------------- |
311
+ | **AppShell** | Main app layout wrapper |
312
+ | **EmptyState** | Empty content placeholder |
313
+ | **ErrorState** | Error display with retry |
314
+ | **LoadingScreen** | Full-screen loading indicator |
315
+ | **OnboardingScreen** | Onboarding flow screen |
316
+
317
+ ### Splash
318
+
319
+ | Block | Description |
320
+ | ----------------- | ------------------------------------------------ |
321
+ | **SplashScreen** | Animated app launch splash with fade/scale/slide |
322
+ | **MinimalSplash** | Preset: Simple fade animation |
323
+ | **BrandedSplash** | Preset: Scale animation with loading indicator |
324
+
325
+ ### Profile
326
+
327
+ | Block | Description |
328
+ | ------------------- | -------------------------- |
329
+ | **ProfileHeader** | User profile header |
330
+ | **AccountCard** | Account info card |
331
+ | **SettingsItem** | Settings list item |
332
+ | **SettingsSection** | Settings group with header |
333
+
334
+ ### Lists
335
+
336
+ | Block | Description |
337
+ | -------------------- | -------------------------------- |
338
+ | **ListItem** | Standard list item |
339
+ | **NotificationItem** | Notification list item |
340
+ | **SwipeableItem** | Swipeable list item with actions |
341
+
342
+ ### Commerce
343
+
344
+ | Block | Description |
345
+ | ------------------- | -------------------------- |
346
+ | **ProductCard** | Product display card |
347
+ | **PricingCard** | Pricing tier card |
348
+ | **CheckoutSummary** | Order summary for checkout |
349
+
350
+ ## Quick Start
351
+
352
+ ```bash
353
+ # Install dependencies
354
+ pnpm install
355
+
356
+ # Run the showcase app
357
+ cd apps/showcase
358
+ npx expo start
359
+ ```
360
+
361
+ ## Project Structure
362
+
363
+ ```
364
+ whileui/
365
+ ├── packages/
366
+ │ └── ui/
367
+ │ └── src/
368
+ │ ├── components/ # All components (copy these!)
369
+ │ │ ├── button/
370
+ │ │ ├── card/
371
+ │ │ ├── dialog/
372
+ │ │ └── ...
373
+ │ ├── blocks/ # Pre-built screens
374
+ │ │ ├── auth/
375
+ │ │ ├── navigation/
376
+ │ │ ├── layout/
377
+ │ │ ├── profile/
378
+ │ │ ├── lists/
379
+ │ │ └── commerce/
380
+ │ ├── lib/ # Utilities
381
+ │ │ ├── cn.ts # clsx + tailwind-merge
382
+ │ │ ├── tv.ts # tailwind-variants re-export
383
+ │ │ └── font-context.ts
384
+ │ └── index.ts # Barrel export
385
+ ├── apps/
386
+ │ └── showcase/ # Expo demo app
387
+ │ ├── App.tsx # Component showcase
388
+ │ ├── global.css # Theme variables (OKLCH) — at app root!
389
+ │ └── metro.config.js # Uniwind + monorepo config
390
+ └── package.json # pnpm monorepo root
391
+ ```
392
+
393
+ ## Theming
394
+
395
+ Themes are defined in `global.css` using CSS variables with OKLCH colors:
396
+
397
+ ```css
398
+ @variant light {
399
+ --color-primary: oklch(0.6 0.2 160);
400
+ --color-background: oklch(1 0 0);
401
+ /* ... */
402
+ }
403
+
404
+ @variant dark {
405
+ --color-primary: oklch(0.6 0.2 160);
406
+ --color-background: oklch(0.145 0 0);
407
+ /* ... */
408
+ }
409
+ ```
410
+
411
+ Switch themes at runtime via Uniwind:
412
+
413
+ ```tsx
414
+ import { Uniwind } from 'uniwind';
415
+
416
+ Uniwind.setTheme('dark'); // or 'light' or 'system'
417
+ ```
418
+
419
+ ## Using Components
420
+
421
+ Copy any component folder from `packages/ui/src/components/` into your project:
422
+
423
+ ```tsx
424
+ import { cn } from './lib/cn';
425
+ import { tv, type VariantProps } from './lib/tv';
426
+
427
+ // Use the component with className overrides
428
+ <Button className="mt-4">
429
+ <ButtonText>Get Started</ButtonText>
430
+ </Button>;
431
+ ```
432
+
433
+ ## Tech Stack
434
+
435
+ - **Styling**: [Uniwind](https://uniwind.dev) (free tier) + Tailwind CSS v4
436
+ - **Variants**: [tailwind-variants](https://www.tailwind-variants.org/) (`tv()`)
437
+ - **Merging**: [clsx](https://github.com/lukeed/clsx) + [tailwind-merge](https://github.com/dcastil/tailwind-merge)
438
+ - **Expo**: SDK 54
439
+ - **Monorepo**: pnpm + Turborepo
440
+
441
+ ## License
442
+
443
+ MIT
444
+
445
+ ---
446
+
447
+ # API Reference
448
+
449
+ ## Button
450
+
451
+ ```tsx
452
+ import { Button, ButtonText, ButtonIcon } from '@thewhileloop/whileui';
453
+
454
+ <Button variant="default" size="default" disabled={false} onPress={() => {}}>
455
+ <ButtonIcon>
456
+ <Icon />
457
+ </ButtonIcon>
458
+ <ButtonText>Click me</ButtonText>
459
+ <ButtonIcon position="right">
460
+ <Icon />
461
+ </ButtonIcon>
462
+ </Button>;
463
+ ```
464
+
465
+ | Prop | Type | Default | Description |
466
+ | --------- | ----------------------------------------------------------------------------- | ----------- | --------------------------- |
467
+ | variant | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'default'` | Button style variant |
468
+ | size | `'default' \| 'sm' \| 'lg' \| 'icon'` | `'default'` | Button size |
469
+ | disabled | `boolean` | `false` | Disable the button |
470
+ | className | `string` | — | Additional Tailwind classes |
471
+
472
+ ## Input
473
+
474
+ ```tsx
475
+ import { Input } from '@thewhileloop/whileui';
476
+
477
+ <Input placeholder="Email" variant="default" value={value} onChangeText={setValue} />;
478
+ ```
479
+
480
+ | Prop | Type | Default | Description |
481
+ | ----------- | ---------------------- | ----------- | ------------------------- |
482
+ | variant | `'default' \| 'error'` | `'default'` | Input style variant |
483
+ | placeholder | `string` | — | Placeholder text |
484
+ | editable | `boolean` | `true` | Whether input is editable |
485
+
486
+ ## Card
487
+
488
+ ```tsx
489
+ import {
490
+ Card,
491
+ CardHeader,
492
+ CardTitle,
493
+ CardDescription,
494
+ CardContent,
495
+ CardFooter,
496
+ } from '@thewhileloop/whileui';
497
+
498
+ <Card>
499
+ <CardHeader>
500
+ <CardTitle>Title</CardTitle>
501
+ <CardDescription>Description</CardDescription>
502
+ </CardHeader>
503
+ <CardContent>{/* Content */}</CardContent>
504
+ <CardFooter>{/* Footer */}</CardFooter>
505
+ </Card>;
506
+ ```
507
+
508
+ ## Badge
509
+
510
+ ```tsx
511
+ import { Badge, BadgeText } from '@thewhileloop/whileui';
512
+
513
+ <Badge variant="default">
514
+ <BadgeText>New</BadgeText>
515
+ </Badge>;
516
+ ```
517
+
518
+ | Prop | Type | Default |
519
+ | ------- | -------------------------------------------------------------------------------------------- | ----------- |
520
+ | variant | `'default' \| 'secondary' \| 'destructive' \| 'outline' \| 'success' \| 'warning' \| 'info'` | `'default'` |
521
+
522
+ ## Alert
523
+
524
+ ```tsx
525
+ import { Alert, AlertTitle, AlertDescription } from '@thewhileloop/whileui';
526
+
527
+ <Alert variant="default">
528
+ <AlertTitle>Heads up!</AlertTitle>
529
+ <AlertDescription>Something happened.</AlertDescription>
530
+ </Alert>;
531
+ ```
532
+
533
+ | Prop | Type | Default |
534
+ | ------- | ------------------------------------------------------ | ----------- |
535
+ | variant | `'default' \| 'destructive' \| 'success' \| 'warning'` | `'default'` |
536
+
537
+ ## Dialog
538
+
539
+ ```tsx
540
+ import {
541
+ Dialog,
542
+ DialogTrigger,
543
+ DialogContent,
544
+ DialogHeader,
545
+ DialogTitle,
546
+ DialogDescription,
547
+ DialogFooter,
548
+ DialogClose,
549
+ } from '@thewhileloop/whileui';
550
+
551
+ <Dialog>
552
+ <DialogTrigger asChild>
553
+ <Button>
554
+ <ButtonText>Open</ButtonText>
555
+ </Button>
556
+ </DialogTrigger>
557
+ <DialogContent>
558
+ <DialogHeader>
559
+ <DialogTitle>Title</DialogTitle>
560
+ <DialogDescription>Description</DialogDescription>
561
+ </DialogHeader>
562
+ {/* Content */}
563
+ <DialogFooter>
564
+ <DialogClose asChild>
565
+ <Button>
566
+ <ButtonText>Close</ButtonText>
567
+ </Button>
568
+ </DialogClose>
569
+ </DialogFooter>
570
+ </DialogContent>
571
+ </Dialog>;
572
+ ```
573
+
574
+ ## AlertDialog
575
+
576
+ ```tsx
577
+ import {
578
+ AlertDialog,
579
+ AlertDialogTrigger,
580
+ AlertDialogContent,
581
+ AlertDialogHeader,
582
+ AlertDialogTitle,
583
+ AlertDialogDescription,
584
+ AlertDialogFooter,
585
+ AlertDialogAction,
586
+ AlertDialogCancel,
587
+ } from '@thewhileloop/whileui';
588
+
589
+ <AlertDialog>
590
+ <AlertDialogTrigger asChild>
591
+ <Button variant="destructive">
592
+ <ButtonText>Delete</ButtonText>
593
+ </Button>
594
+ </AlertDialogTrigger>
595
+ <AlertDialogContent>
596
+ <AlertDialogHeader>
597
+ <AlertDialogTitle>Are you sure?</AlertDialogTitle>
598
+ <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
599
+ </AlertDialogHeader>
600
+ <AlertDialogFooter>
601
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
602
+ <AlertDialogAction>Delete</AlertDialogAction>
603
+ </AlertDialogFooter>
604
+ </AlertDialogContent>
605
+ </AlertDialog>;
606
+ ```
607
+
608
+ ## Checkbox
609
+
610
+ ```tsx
611
+ import { Checkbox } from '@thewhileloop/whileui';
612
+
613
+ <Checkbox checked={checked} onCheckedChange={setChecked} />;
614
+ ```
615
+
616
+ | Prop | Type | Default |
617
+ | --------------- | ---------------------------- | ------- |
618
+ | checked | `boolean` | `false` |
619
+ | onCheckedChange | `(checked: boolean) => void` | — |
620
+ | disabled | `boolean` | `false` |
621
+
622
+ ## Switch
623
+
624
+ ```tsx
625
+ import { Switch } from '@thewhileloop/whileui';
626
+
627
+ <Switch checked={checked} onCheckedChange={setChecked} />;
628
+ ```
629
+
630
+ | Prop | Type | Default |
631
+ | --------------- | ---------------------------- | ------- |
632
+ | checked | `boolean` | `false` |
633
+ | onCheckedChange | `(checked: boolean) => void` | — |
634
+
635
+ ## RadioGroup
636
+
637
+ ```tsx
638
+ import { RadioGroup, RadioGroupItem } from '@thewhileloop/whileui';
639
+
640
+ <RadioGroup value={value} onValueChange={setValue}>
641
+ <RadioGroupItem value="option1" />
642
+ <RadioGroupItem value="option2" />
643
+ </RadioGroup>;
644
+ ```
645
+
646
+ ## Select
647
+
648
+ ```tsx
649
+ import {
650
+ Select,
651
+ SelectTrigger,
652
+ SelectValue,
653
+ SelectContent,
654
+ SelectItem,
655
+ } from '@thewhileloop/whileui';
656
+
657
+ <Select value={value} onValueChange={setValue}>
658
+ <SelectTrigger>
659
+ <SelectValue placeholder="Select..." />
660
+ </SelectTrigger>
661
+ <SelectContent>
662
+ <SelectItem label="Option 1" value="1" />
663
+ <SelectItem label="Option 2" value="2" />
664
+ </SelectContent>
665
+ </Select>;
666
+ ```
667
+
668
+ ## Tabs
669
+
670
+ ```tsx
671
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@thewhileloop/whileui';
672
+
673
+ <Tabs defaultValue="tab1">
674
+ <TabsList>
675
+ <TabsTrigger value="tab1">
676
+ <Text>Tab 1</Text>
677
+ </TabsTrigger>
678
+ <TabsTrigger value="tab2">
679
+ <Text>Tab 2</Text>
680
+ </TabsTrigger>
681
+ </TabsList>
682
+ <TabsContent value="tab1">{/* Content 1 */}</TabsContent>
683
+ <TabsContent value="tab2">{/* Content 2 */}</TabsContent>
684
+ </Tabs>;
685
+ ```
686
+
687
+ ## Accordion
688
+
689
+ ```tsx
690
+ import {
691
+ Accordion,
692
+ AccordionItem,
693
+ AccordionTrigger,
694
+ AccordionContent,
695
+ } from '@thewhileloop/whileui';
696
+
697
+ <Accordion type="single" collapsible>
698
+ <AccordionItem value="item1">
699
+ <AccordionTrigger>
700
+ <Text>Section 1</Text>
701
+ </AccordionTrigger>
702
+ <AccordionContent>
703
+ <Text>Content 1</Text>
704
+ </AccordionContent>
705
+ </AccordionItem>
706
+ </Accordion>;
707
+ ```
708
+
709
+ | Prop | Type | Default |
710
+ | ----------- | ------------------------ | ---------- |
711
+ | type | `'single' \| 'multiple'` | `'single'` |
712
+ | collapsible | `boolean` | `false` |
713
+
714
+ ## Avatar
715
+
716
+ ```tsx
717
+ import { Avatar, AvatarImage, AvatarFallback } from '@thewhileloop/whileui';
718
+
719
+ <Avatar size="default">
720
+ <AvatarImage src="https://..." />
721
+ <AvatarFallback>JD</AvatarFallback>
722
+ </Avatar>;
723
+ ```
724
+
725
+ | Prop | Type | Default |
726
+ | ---- | --------------------------- | ----------- |
727
+ | size | `'sm' \| 'default' \| 'lg'` | `'default'` |
728
+
729
+ ## Progress
730
+
731
+ ```tsx
732
+ import { Progress } from '@thewhileloop/whileui';
733
+
734
+ <Progress value={50} size="default" />;
735
+ ```
736
+
737
+ | Prop | Type | Default |
738
+ | ----- | --------------------------- | ----------- |
739
+ | value | `number` | `0` |
740
+ | size | `'sm' \| 'default' \| 'lg'` | `'default'` |
741
+
742
+ ## Toast
743
+
744
+ ```tsx
745
+ import { ToastProvider, ToastContainer, useToast } from '@thewhileloop/whileui';
746
+
747
+ // Wrap app
748
+ <ToastProvider>
749
+ <App />
750
+ <ToastContainer position="top" />
751
+ </ToastProvider>;
752
+
753
+ // Use in component
754
+ const { toast } = useToast();
755
+ toast({ title: 'Success', description: 'Saved!', variant: 'success' });
756
+ ```
757
+
758
+ | Toast Options | Type |
759
+ | ------------- | ----------------------------------------- |
760
+ | title | `string` |
761
+ | description | `string` |
762
+ | variant | `'default' \| 'success' \| 'destructive'` |
763
+ | duration | `number` (ms) |
764
+
765
+ ## Popover
766
+
767
+ ```tsx
768
+ import { Popover, PopoverTrigger, PopoverContent } from '@thewhileloop/whileui';
769
+
770
+ <Popover>
771
+ <PopoverTrigger asChild>
772
+ <Button>
773
+ <ButtonText>Open</ButtonText>
774
+ </Button>
775
+ </PopoverTrigger>
776
+ <PopoverContent>
777
+ <Text>Popover content</Text>
778
+ </PopoverContent>
779
+ </Popover>;
780
+ ```
781
+
782
+ ## DropdownMenu
783
+
784
+ ```tsx
785
+ import {
786
+ DropdownMenu,
787
+ DropdownMenuTrigger,
788
+ DropdownMenuContent,
789
+ DropdownMenuItem,
790
+ DropdownMenuLabel,
791
+ DropdownMenuSeparator,
792
+ } from '@thewhileloop/whileui';
793
+
794
+ <DropdownMenu>
795
+ <DropdownMenuTrigger asChild>
796
+ <Button>
797
+ <ButtonText>Menu</ButtonText>
798
+ </Button>
799
+ </DropdownMenuTrigger>
800
+ <DropdownMenuContent>
801
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
802
+ <DropdownMenuSeparator />
803
+ <DropdownMenuItem>
804
+ <Text>Edit</Text>
805
+ </DropdownMenuItem>
806
+ <DropdownMenuItem>
807
+ <Text>Delete</Text>
808
+ </DropdownMenuItem>
809
+ </DropdownMenuContent>
810
+ </DropdownMenu>;
811
+ ```
812
+
813
+ ---
814
+
815
+ # Blocks API
816
+
817
+ ## SignInForm
818
+
819
+ ```tsx
820
+ import { SignInForm } from '@thewhileloop/whileui';
821
+
822
+ <SignInForm onSubmit={(email, password) => {}} onForgotPassword={() => {}} onSignUp={() => {}} />;
823
+ ```
824
+
825
+ ## SignUpForm
826
+
827
+ ```tsx
828
+ import { SignUpForm } from '@thewhileloop/whileui';
829
+
830
+ <SignUpForm onSubmit={(name, email, password) => {}} onSignIn={() => {}} />;
831
+ ```
832
+
833
+ ## BottomNav
834
+
835
+ ```tsx
836
+ import { BottomNav } from '@thewhileloop/whileui';
837
+
838
+ <BottomNav
839
+ items={[
840
+ { key: 'home', label: 'Home', icon: <Icon /> },
841
+ { key: 'profile', label: 'Profile', icon: <Icon />, badge: 3 },
842
+ ]}
843
+ activeKey="home"
844
+ onSelect={(key) => {}}
845
+ />;
846
+ ```
847
+
848
+ ## Header
849
+
850
+ ```tsx
851
+ import { Header, HeaderBackButton } from '@thewhileloop/whileui';
852
+
853
+ <Header
854
+ title="Settings"
855
+ subtitle="Manage preferences"
856
+ leftAction={<HeaderBackButton onPress={() => {}} />}
857
+ rightActions={[{ key: 'search', icon: <Icon />, onPress: () => {} }]}
858
+ />;
859
+ ```
860
+
861
+ ## SplashScreen
862
+
863
+ ```tsx
864
+ import { SplashScreen } from '@thewhileloop/whileui';
865
+
866
+ <SplashScreen
867
+ logo={<MyLogo />}
868
+ appName="MyApp"
869
+ tagline="Your tagline"
870
+ variant="scale"
871
+ duration={1500}
872
+ showLoading
873
+ onAnimationComplete={() => {}}
874
+ />;
875
+ ```
876
+
877
+ | Prop | Type | Default |
878
+ | ----------- | ------------------------------ | --------- |
879
+ | variant | `'fade' \| 'scale' \| 'slide'` | `'scale'` |
880
+ | duration | `number` | `800` |
881
+ | showLoading | `boolean` | `false` |
882
+
883
+ ## EmptyState
884
+
885
+ ```tsx
886
+ import { EmptyState } from '@thewhileloop/whileui';
887
+
888
+ <EmptyState
889
+ icon={<Icon />}
890
+ title="No items"
891
+ description="Add your first item."
892
+ action={{ label: 'Add Item', onPress: () => {} }}
893
+ />;
894
+ ```
895
+
896
+ ## ProfileHeader
897
+
898
+ ```tsx
899
+ import { ProfileHeader } from '@thewhileloop/whileui';
900
+
901
+ <ProfileHeader
902
+ name="John Doe"
903
+ username="johndoe"
904
+ bio="Designer & Developer"
905
+ avatarFallback="JD"
906
+ avatarUrl="https://..."
907
+ verified
908
+ stats={[
909
+ { label: 'Followers', value: '1.2K' },
910
+ { label: 'Following', value: 234 },
911
+ ]}
912
+ action={{ label: 'Edit Profile', onPress: () => {} }}
913
+ />;
914
+ ```
915
+
916
+ ## SettingsSection / SettingsItem
917
+
918
+ ```tsx
919
+ import { SettingsSection, SettingsItem } from '@thewhileloop/whileui';
920
+
921
+ <SettingsSection title="Preferences">
922
+ <SettingsItem
923
+ icon={<Icon />}
924
+ label="Notifications"
925
+ type="toggle"
926
+ toggleValue={enabled}
927
+ onToggle={setEnabled}
928
+ />
929
+ <SettingsItem icon={<Icon />} label="Privacy" value="Public" onPress={() => {}} />
930
+ <SettingsItem icon={<Icon />} label="Sign Out" type="action" destructive />
931
+ </SettingsSection>;
932
+ ```
933
+
934
+ ## ProductCard
935
+
936
+ ```tsx
937
+ import { ProductCard } from '@thewhileloop/whileui';
938
+
939
+ <ProductCard
940
+ title="Product Name"
941
+ price="$99"
942
+ originalPrice="$129"
943
+ badge="-23%"
944
+ rating={4.5}
945
+ reviewCount={128}
946
+ variant="vertical"
947
+ onPress={() => {}}
948
+ />;
949
+ ```
950
+
951
+ | Prop | Type | Default |
952
+ | ------- | ---------------------------- | ------------ |
953
+ | variant | `'vertical' \| 'horizontal'` | `'vertical'` |
954
+
955
+ ## PricingCard
956
+
957
+ ```tsx
958
+ import { PricingCard } from '@thewhileloop/whileui';
959
+
960
+ <PricingCard
961
+ name="Pro"
962
+ description="For teams"
963
+ price="$29"
964
+ period="/month"
965
+ badge="Popular"
966
+ highlighted
967
+ features={[
968
+ { label: 'Unlimited users', included: true },
969
+ { label: 'Priority support', included: true },
970
+ { label: 'Custom domain', included: false },
971
+ ]}
972
+ onPress={() => {}}
973
+ />;
974
+ ```
975
+
976
+ ## DrawerMenu
977
+
978
+ ```tsx
979
+ import { DrawerMenu } from '@thewhileloop/whileui';
980
+
981
+ <DrawerMenu
982
+ visible={open}
983
+ onClose={() => setOpen(false)}
984
+ sections={[
985
+ {
986
+ title: 'Menu',
987
+ items: [
988
+ { key: 'home', label: 'Home', icon: <Icon /> },
989
+ { key: 'settings', label: 'Settings', icon: <Icon /> },
990
+ ],
991
+ },
992
+ ]}
993
+ activeKey="home"
994
+ onSelect={(key) => {}}
995
+ header={<View>...</View>}
996
+ footer={<Text>v1.0</Text>}
997
+ />;
998
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thewhileloop/whileui",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "WhileUI Native — shadcn/ui for React Native. Copy-paste components you own.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,7 +36,8 @@
36
36
  "./package.json": "./package.json"
37
37
  },
38
38
  "files": [
39
- "dist"
39
+ "dist",
40
+ "README.md"
40
41
  ],
41
42
  "sideEffects": false,
42
43
  "publishConfig": {