@thewhileloop/whileui 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +505 -38
- package/dist/blocks/chat/chat.d.ts.map +1 -1
- package/dist/blocks/chat/chat.js +15 -5
- package/dist/blocks/chat/chat.js.map +1 -1
- package/dist/blocks/commerce/feature-gate.d.ts +13 -0
- package/dist/blocks/commerce/feature-gate.d.ts.map +1 -0
- package/dist/blocks/commerce/feature-gate.js +13 -0
- package/dist/blocks/commerce/feature-gate.js.map +1 -0
- package/dist/blocks/commerce/index.d.ts +5 -0
- package/dist/blocks/commerce/index.d.ts.map +1 -1
- package/dist/blocks/commerce/index.js +5 -0
- package/dist/blocks/commerce/index.js.map +1 -1
- package/dist/blocks/commerce/metric-card.d.ts +3 -1
- package/dist/blocks/commerce/metric-card.d.ts.map +1 -1
- package/dist/blocks/commerce/metric-card.js +8 -1
- package/dist/blocks/commerce/metric-card.js.map +1 -1
- package/dist/blocks/commerce/plan-toggle.d.ts +12 -0
- package/dist/blocks/commerce/plan-toggle.d.ts.map +1 -0
- package/dist/blocks/commerce/plan-toggle.js +19 -0
- package/dist/blocks/commerce/plan-toggle.js.map +1 -0
- package/dist/blocks/commerce/pricing-card.d.ts +3 -1
- package/dist/blocks/commerce/pricing-card.d.ts.map +1 -1
- package/dist/blocks/commerce/pricing-card.js +9 -1
- package/dist/blocks/commerce/pricing-card.js.map +1 -1
- package/dist/blocks/commerce/product-card.d.ts +3 -1
- package/dist/blocks/commerce/product-card.d.ts.map +1 -1
- package/dist/blocks/commerce/product-card.js +16 -2
- package/dist/blocks/commerce/product-card.js.map +1 -1
- package/dist/blocks/commerce/subscription-card.d.ts +17 -0
- package/dist/blocks/commerce/subscription-card.d.ts.map +1 -0
- package/dist/blocks/commerce/subscription-card.js +18 -0
- package/dist/blocks/commerce/subscription-card.js.map +1 -0
- package/dist/blocks/commerce/upgrade-banner.d.ts +12 -0
- package/dist/blocks/commerce/upgrade-banner.d.ts.map +1 -0
- package/dist/blocks/commerce/upgrade-banner.js +9 -0
- package/dist/blocks/commerce/upgrade-banner.js.map +1 -0
- package/dist/blocks/commerce/usage-bar.d.ts +11 -0
- package/dist/blocks/commerce/usage-bar.d.ts.map +1 -0
- package/dist/blocks/commerce/usage-bar.js +33 -0
- package/dist/blocks/commerce/usage-bar.js.map +1 -0
- package/dist/blocks/datepicker/date-range-picker-modal.d.ts +3 -2
- package/dist/blocks/datepicker/date-range-picker-modal.d.ts.map +1 -1
- package/dist/blocks/datepicker/date-range-picker-modal.js +16 -3
- package/dist/blocks/datepicker/date-range-picker-modal.js.map +1 -1
- package/dist/blocks/datepicker/datepicker-inline.d.ts.map +1 -1
- package/dist/blocks/datepicker/datepicker-inline.js +3 -1
- package/dist/blocks/datepicker/datepicker-inline.js.map +1 -1
- package/dist/blocks/datepicker/datepicker-modal.d.ts +3 -2
- package/dist/blocks/datepicker/datepicker-modal.d.ts.map +1 -1
- package/dist/blocks/datepicker/datepicker-modal.js +16 -3
- package/dist/blocks/datepicker/datepicker-modal.js.map +1 -1
- package/dist/blocks/datepicker/use-calendar-theme.d.ts.map +1 -1
- package/dist/blocks/datepicker/use-calendar-theme.js +2 -1
- package/dist/blocks/datepicker/use-calendar-theme.js.map +1 -1
- package/dist/blocks/layout/app-shell.d.ts +5 -1
- package/dist/blocks/layout/app-shell.d.ts.map +1 -1
- package/dist/blocks/layout/app-shell.js +2 -2
- package/dist/blocks/layout/app-shell.js.map +1 -1
- package/dist/blocks/layout/confirm-action-sheet.d.ts +3 -2
- package/dist/blocks/layout/confirm-action-sheet.d.ts.map +1 -1
- package/dist/blocks/layout/confirm-action-sheet.js +23 -4
- package/dist/blocks/layout/confirm-action-sheet.js.map +1 -1
- package/dist/blocks/layout/form-modal-screen.d.ts.map +1 -1
- package/dist/blocks/layout/form-modal-screen.js +23 -1
- package/dist/blocks/layout/form-modal-screen.js.map +1 -1
- package/dist/blocks/layout/index.d.ts +2 -1
- package/dist/blocks/layout/index.d.ts.map +1 -1
- package/dist/blocks/layout/index.js +1 -0
- package/dist/blocks/layout/index.js.map +1 -1
- package/dist/blocks/layout/page-skeleton.d.ts +7 -1
- package/dist/blocks/layout/page-skeleton.d.ts.map +1 -1
- package/dist/blocks/layout/page-skeleton.js +7 -14
- package/dist/blocks/layout/page-skeleton.js.map +1 -1
- package/dist/blocks/layout/screen-skeleton.d.ts +16 -0
- package/dist/blocks/layout/screen-skeleton.d.ts.map +1 -0
- package/dist/blocks/layout/screen-skeleton.js +8 -0
- package/dist/blocks/layout/screen-skeleton.js.map +1 -0
- package/dist/blocks/layout/sheet.d.ts +3 -2
- package/dist/blocks/layout/sheet.d.ts.map +1 -1
- package/dist/blocks/layout/sheet.js +39 -6
- package/dist/blocks/layout/sheet.js.map +1 -1
- package/dist/blocks/layout/smart-input.d.ts.map +1 -1
- package/dist/blocks/layout/smart-input.js +8 -1
- package/dist/blocks/layout/smart-input.js.map +1 -1
- package/dist/blocks/lists/list-item.d.ts +3 -1
- package/dist/blocks/lists/list-item.d.ts.map +1 -1
- package/dist/blocks/lists/list-item.js +9 -1
- package/dist/blocks/lists/list-item.js.map +1 -1
- package/dist/blocks/lists/notification-item.d.ts +3 -1
- package/dist/blocks/lists/notification-item.d.ts.map +1 -1
- package/dist/blocks/lists/notification-item.js +10 -2
- package/dist/blocks/lists/notification-item.js.map +1 -1
- package/dist/blocks/lists/swipeable-item.d.ts.map +1 -1
- package/dist/blocks/lists/swipeable-item.js +5 -3
- package/dist/blocks/lists/swipeable-item.js.map +1 -1
- package/dist/blocks/navigation/bottom-nav.d.ts.map +1 -1
- package/dist/blocks/navigation/bottom-nav.js +9 -2
- package/dist/blocks/navigation/bottom-nav.js.map +1 -1
- package/dist/blocks/navigation/drawer-menu.d.ts +3 -2
- package/dist/blocks/navigation/drawer-menu.d.ts.map +1 -1
- package/dist/blocks/navigation/drawer-menu.js +88 -25
- package/dist/blocks/navigation/drawer-menu.js.map +1 -1
- package/dist/blocks/navigation/header.d.ts +1 -1
- package/dist/blocks/navigation/header.d.ts.map +1 -1
- package/dist/blocks/navigation/header.js +12 -3
- package/dist/blocks/navigation/header.js.map +1 -1
- package/dist/blocks/navigation/navigation-sidebar.d.ts.map +1 -1
- package/dist/blocks/navigation/navigation-sidebar.js +9 -2
- package/dist/blocks/navigation/navigation-sidebar.js.map +1 -1
- package/dist/blocks/navigation/tab-bar.d.ts.map +1 -1
- package/dist/blocks/navigation/tab-bar.js +8 -2
- package/dist/blocks/navigation/tab-bar.js.map +1 -1
- package/dist/components/accordion/accordion.d.ts +1 -1
- package/dist/components/accordion/accordion.d.ts.map +1 -1
- package/dist/components/accordion/accordion.js +10 -4
- package/dist/components/accordion/accordion.js.map +1 -1
- package/dist/components/alert/alert.js +4 -4
- package/dist/components/alert/alert.js.map +1 -1
- package/dist/components/alert-dialog/alert-dialog.d.ts +8 -4
- package/dist/components/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/dist/components/alert-dialog/alert-dialog.js +159 -14
- package/dist/components/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/components/alert-dialog/index.d.ts +1 -1
- package/dist/components/alert-dialog/index.d.ts.map +1 -1
- package/dist/components/alert-dialog/index.js.map +1 -1
- package/dist/components/badge/badge.js +6 -6
- package/dist/components/badge/badge.js.map +1 -1
- package/dist/components/button/button.d.ts.map +1 -1
- package/dist/components/button/button.js +11 -5
- package/dist/components/button/button.js.map +1 -1
- package/dist/components/card/card.d.ts +2 -1
- package/dist/components/card/card.d.ts.map +1 -1
- package/dist/components/card/card.js +12 -3
- package/dist/components/card/card.js.map +1 -1
- package/dist/components/checkbox/checkbox.d.ts.map +1 -1
- package/dist/components/checkbox/checkbox.js +7 -1
- package/dist/components/checkbox/checkbox.js.map +1 -1
- package/dist/components/context-menu/context-menu.d.ts +3 -2
- package/dist/components/context-menu/context-menu.d.ts.map +1 -1
- package/dist/components/context-menu/context-menu.js +12 -3
- package/dist/components/context-menu/context-menu.js.map +1 -1
- package/dist/components/data-row/data-row.d.ts.map +1 -1
- package/dist/components/data-row/data-row.js +8 -5
- package/dist/components/data-row/data-row.js.map +1 -1
- package/dist/components/dialog/dialog.d.ts +3 -2
- package/dist/components/dialog/dialog.d.ts.map +1 -1
- package/dist/components/dialog/dialog.js +14 -3
- package/dist/components/dialog/dialog.js.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.d.ts +3 -2
- package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.js +12 -3
- package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/form-field/form-field.d.ts.map +1 -1
- package/dist/components/form-field/form-field.js +6 -3
- package/dist/components/form-field/form-field.js.map +1 -1
- package/dist/components/hover-card/hover-card.d.ts +2 -1
- package/dist/components/hover-card/hover-card.d.ts.map +1 -1
- package/dist/components/hover-card/hover-card.js +17 -2
- package/dist/components/hover-card/hover-card.js.map +1 -1
- package/dist/components/input/input.d.ts.map +1 -1
- package/dist/components/input/input.js +3 -1
- package/dist/components/input/input.js.map +1 -1
- package/dist/components/labeled-field/labeled-field.d.ts.map +1 -1
- package/dist/components/labeled-field/labeled-field.js +3 -1
- package/dist/components/labeled-field/labeled-field.js.map +1 -1
- package/dist/components/menubar/menubar.d.ts +3 -2
- package/dist/components/menubar/menubar.d.ts.map +1 -1
- package/dist/components/menubar/menubar.js +12 -3
- package/dist/components/menubar/menubar.js.map +1 -1
- package/dist/components/numeric-input/numeric-input.d.ts.map +1 -1
- package/dist/components/numeric-input/numeric-input.js +15 -3
- package/dist/components/numeric-input/numeric-input.js.map +1 -1
- package/dist/components/otp-input/index.d.ts +2 -0
- package/dist/components/otp-input/index.d.ts.map +1 -0
- package/dist/components/otp-input/index.js +2 -0
- package/dist/components/otp-input/index.js.map +1 -0
- package/dist/components/otp-input/otp-input.d.ts +97 -0
- package/dist/components/otp-input/otp-input.d.ts.map +1 -0
- package/dist/components/otp-input/otp-input.js +88 -0
- package/dist/components/otp-input/otp-input.js.map +1 -0
- package/dist/components/popover/popover.d.ts +2 -1
- package/dist/components/popover/popover.d.ts.map +1 -1
- package/dist/components/popover/popover.js +16 -3
- package/dist/components/popover/popover.js.map +1 -1
- package/dist/components/segmented-control/segmented-control.d.ts +3 -3
- package/dist/components/segmented-control/segmented-control.d.ts.map +1 -1
- package/dist/components/segmented-control/segmented-control.js +19 -5
- package/dist/components/segmented-control/segmented-control.js.map +1 -1
- package/dist/components/select/select.d.ts +2 -1
- package/dist/components/select/select.d.ts.map +1 -1
- package/dist/components/select/select.js +30 -11
- package/dist/components/select/select.js.map +1 -1
- package/dist/components/skeleton/skeleton.d.ts.map +1 -1
- package/dist/components/skeleton/skeleton.js +6 -2
- package/dist/components/skeleton/skeleton.js.map +1 -1
- package/dist/components/switch/switch.d.ts.map +1 -1
- package/dist/components/switch/switch.js +7 -1
- package/dist/components/switch/switch.js.map +1 -1
- package/dist/components/tabs/tabs.d.ts +3 -1
- package/dist/components/tabs/tabs.d.ts.map +1 -1
- package/dist/components/tabs/tabs.js +9 -3
- package/dist/components/tabs/tabs.js.map +1 -1
- package/dist/components/textarea/textarea.d.ts +2 -2
- package/dist/components/textarea/textarea.d.ts.map +1 -1
- package/dist/components/textarea/textarea.js +19 -6
- package/dist/components/textarea/textarea.js.map +1 -1
- package/dist/components/toast/toast.d.ts.map +1 -1
- package/dist/components/toast/toast.js +12 -2
- package/dist/components/toast/toast.js.map +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/frosted-surface.d.ts +33 -0
- package/dist/lib/frosted-surface.d.ts.map +1 -0
- package/dist/lib/frosted-surface.js +181 -0
- package/dist/lib/frosted-surface.js.map +1 -0
- package/dist/lib/interaction-tokens.d.ts +26 -0
- package/dist/lib/interaction-tokens.d.ts.map +1 -0
- package/dist/lib/interaction-tokens.js +70 -0
- package/dist/lib/interaction-tokens.js.map +1 -0
- package/dist/lib/react-native-classname.d.ts +23 -0
- package/dist/lib/react-native-classname.d.ts.map +1 -0
- package/dist/lib/react-native-classname.js +2 -0
- package/dist/lib/react-native-classname.js.map +1 -0
- package/dist/lib/theme-colors.d.ts +33 -4
- package/dist/lib/theme-colors.d.ts.map +1 -1
- package/dist/lib/theme-colors.js +249 -37
- package/dist/lib/theme-colors.js.map +1 -1
- package/dist/lib/visual-tokens.d.ts +39 -0
- package/dist/lib/visual-tokens.d.ts.map +1 -0
- package/dist/lib/visual-tokens.js +91 -0
- package/dist/lib/visual-tokens.js.map +1 -0
- package/dist/vite.d.ts +47 -0
- package/dist/vite.d.ts.map +1 -0
- package/dist/vite.js +110 -0
- package/dist/vite.js.map +1 -0
- package/package.json +10 -1
package/README.md
CHANGED
|
@@ -116,6 +116,37 @@ export default function App() {
|
|
|
116
116
|
|
|
117
117
|
> **Note:** If you use Select, Popover, Tooltip, or HoverCard, add `<PortalHost />` at the root of your app (as the last child).
|
|
118
118
|
|
|
119
|
+
### Vite + React Native Web + WhileUI setup
|
|
120
|
+
|
|
121
|
+
Use the WhileUI Vite compatibility helper when your app includes portal-based primitives.
|
|
122
|
+
|
|
123
|
+
1. Install Vite + RN web basics:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
bun add react-dom react-native-web
|
|
127
|
+
bun add -d vite @vitejs/plugin-react
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
2. `vite.config.ts`:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { defineConfig } from 'vite';
|
|
134
|
+
import react from '@vitejs/plugin-react';
|
|
135
|
+
import { withWhileUIViteCompat } from '@thewhileloop/whileui/vite';
|
|
136
|
+
|
|
137
|
+
export default defineConfig(withWhileUIViteCompat({ plugins: [react()] }));
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
3. Keep `PortalHost` at app root when using Select/Popover/Tooltip/HoverCard.
|
|
141
|
+
|
|
142
|
+
Troubleshooting:
|
|
143
|
+
|
|
144
|
+
| Symptom | Likely cause | Fix |
|
|
145
|
+
| -------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
|
146
|
+
| `Unexpected token <` from `@rn-primitives/*/dist/*.mjs` | rn-primitives ships raw JSX in dist for some packages | Use `withWhileUIViteCompat(...)` in `vite.config.ts` |
|
|
147
|
+
| `className` rejected on RN primitives in consumer TS app | RN `className` augmentation not loaded from package entry | Import components from `@thewhileloop/whileui` (or side-effect import the package entry before use) |
|
|
148
|
+
| Theme token resolves differently on native/web | Missing `--app-color-*` fallback for non-RN-native values | Add `--app-color-*` fallbacks in `global.css` for any `oklch(...)` token used by native APIs |
|
|
149
|
+
|
|
119
150
|
## Usage
|
|
120
151
|
|
|
121
152
|
```tsx
|
|
@@ -152,7 +183,7 @@ function MyScreen() {
|
|
|
152
183
|
**Core package** exports:
|
|
153
184
|
|
|
154
185
|
- **Primitives** — Button, Input, Card, Text, etc.
|
|
155
|
-
- **Generic layout blocks** — EmptyState, ErrorState, LoadingScreen, ContentSkeleton, PageSkeleton
|
|
186
|
+
- **Generic layout blocks** — EmptyState, ErrorState, LoadingScreen, ContentSkeleton, PageSkeleton, ScreenSkeleton
|
|
156
187
|
- **Layout infrastructure** — FormModalScreen, ConfirmActionSheet, SmartInput, ActionBar
|
|
157
188
|
- **Navigation, Chat, Lists, Commerce, Media, DatePicker** — Blocks that rarely need app-specific customization
|
|
158
189
|
|
|
@@ -174,6 +205,7 @@ function MyScreen() {
|
|
|
174
205
|
## Quick Reference (AI / Code Generation)
|
|
175
206
|
|
|
176
207
|
- **Full-screen:** `AppShell` + `Header` in `header` + `BottomNav` in `bottomNav` + content in `children`
|
|
208
|
+
- **No-jump loading:** keep `AppShell` mounted, set `loading` + `skeleton` (e.g., `PageSkeleton`/`ScreenSkeleton`)
|
|
177
209
|
- **Layout:** `Stack` (vertical), `Row` (horizontal) — both support `gap`, `align`, `justify`
|
|
178
210
|
- **Auth callbacks:** Auth templates use objects: `onSubmit({ email, password })`, `onSubmit({ firstName, lastName, email, password })`, etc. Copy templates from `apps/showcase/templates/auth/`.
|
|
179
211
|
- **PortalHost:** Add `<PortalHost />` at app root for Select, Popover, Tooltip, HoverCard.
|
|
@@ -200,6 +232,7 @@ function MyScreen() {
|
|
|
200
232
|
| **Button** | default, destructive, outline, secondary, ghost, link | 4 sizes, ButtonText & ButtonIcon sub-components |
|
|
201
233
|
| **Input** | default, error | TextInput wrapper with themed styling |
|
|
202
234
|
| **NumericInput** | default, error | Numeric input with prefix/suffix slots, optional steppers, and compact size |
|
|
235
|
+
| **OTPInput** | default, error; default, compact | Verification code input with auto-focus, paste, secure mask, loading skeleton, error shake |
|
|
203
236
|
| **FormField** | default, compact | Compound API: FormField, FormLabel, FormControl, FormHint, FormMessage |
|
|
204
237
|
| **LabeledField** | default, compact | Field wrapper with label/helper/error plus left/right slots |
|
|
205
238
|
| **Textarea** | — | Multi-line text input |
|
|
@@ -292,6 +325,7 @@ Copy from `apps/showcase/templates/auth/`:
|
|
|
292
325
|
| **FormModalScreen** | Modal scaffold for forms with loading states |
|
|
293
326
|
| **ContentSkeleton** | Page/content placeholder with variants (list, card, generic) |
|
|
294
327
|
| **PageSkeleton** | Variant-based page layouts (dashboard, list, settings, card, generic) |
|
|
328
|
+
| **ScreenSkeleton** | Header slot/placeholder + PageSkeleton content in one block |
|
|
295
329
|
| **ErrorBoundary** | React ErrorBoundary that renders ErrorState by default |
|
|
296
330
|
| **EmptyState** | Empty content placeholder |
|
|
297
331
|
| **ErrorState** | Error display with retry |
|
|
@@ -333,12 +367,17 @@ Copy from `apps/showcase/templates/profile/`:
|
|
|
333
367
|
|
|
334
368
|
### Commerce
|
|
335
369
|
|
|
336
|
-
| Block
|
|
337
|
-
|
|
|
338
|
-
| **ProductCard**
|
|
339
|
-
| **PricingCard**
|
|
340
|
-
| **CheckoutSummary**
|
|
341
|
-
| **MetricCard**
|
|
370
|
+
| Block | Description |
|
|
371
|
+
| -------------------- | --------------------------------------------- |
|
|
372
|
+
| **ProductCard** | Product card with badge/media |
|
|
373
|
+
| **PricingCard** | Pricing tiers with feature list |
|
|
374
|
+
| **CheckoutSummary** | Cart summary with line items |
|
|
375
|
+
| **MetricCard** | Stats/progress card for dashboards |
|
|
376
|
+
| **SubscriptionCard** | Current plan card with manage/upgrade actions |
|
|
377
|
+
| **FeatureGate** | Locked feature state with upgrade CTA |
|
|
378
|
+
| **UsageBar** | Quota meter with warning/exceeded states |
|
|
379
|
+
| **PlanToggle** | Monthly vs annual plan switcher |
|
|
380
|
+
| **UpgradeBanner** | Inline upgrade banner with action/dismiss |
|
|
342
381
|
|
|
343
382
|
### Media
|
|
344
383
|
|
|
@@ -415,6 +454,22 @@ import { AppShell, Header, BottomNav, ScrollView } from '@thewhileloop/whileui';
|
|
|
415
454
|
| E-commerce | ProductCard list → CheckoutSummary + ActionBar |
|
|
416
455
|
| Chat | Chat + ChatSuggestions + SmartInput (attach, send). Extensible for images/tags |
|
|
417
456
|
| App shell | AppShell + Header + BottomNav + content |
|
|
457
|
+
| Loading | AppShell (`loading`) + PageSkeleton/ScreenSkeleton (keep header mounted) |
|
|
458
|
+
|
|
459
|
+
### Component-Level Loading
|
|
460
|
+
|
|
461
|
+
Many blocks accept a `loading` prop that renders a skeleton placeholder matching the component's own shape. No separate skeleton component needed — the block knows its own layout.
|
|
462
|
+
|
|
463
|
+
```tsx
|
|
464
|
+
<ProductCard loading title="" price="" />
|
|
465
|
+
<ListItem loading title="" />
|
|
466
|
+
<NotificationItem loading title="" message="" time="" />
|
|
467
|
+
<MetricCard loading label="" value="" />
|
|
468
|
+
<SubscriptionCard loading planName="" price="" />
|
|
469
|
+
<PricingCard loading name="" price="" features={[]} />
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Blocks with `loading` support: ProductCard, ListItem, NotificationItem, MetricCard, SubscriptionCard, PricingCard, AppShell, Chat, FormModalScreen, CheckoutSummary.
|
|
418
473
|
|
|
419
474
|
Block props: see TypeScript interfaces in `packages/ui/src/blocks`.
|
|
420
475
|
|
|
@@ -490,8 +545,10 @@ Themes are defined in `global.css` using CSS variables with OKLCH colors:
|
|
|
490
545
|
|
|
491
546
|
The WhileUI token contract is strict for cross-app reuse. Define these in **every** theme variant (`@variant light`, `@variant dark`, and custom variants):
|
|
492
547
|
|
|
493
|
-
- 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`
|
|
548
|
+
- Required core tokens: `background`, `foreground`, `card`, `card-foreground`, `popover`, `popover-foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `muted`, `muted-foreground`, `accent`, `accent-foreground`, `destructive`, `destructive-foreground`, `border`, `input`, `ring`
|
|
494
549
|
- Optional status tokens: `success`, `success-foreground`, `warning`, `warning-foreground`, `info`, `info-foreground`
|
|
550
|
+
- Optional effect tokens: `overlay`, `overlay-strong`, `surface-elevated`, `surface-border`, `surface-highlight`, `surface-translucent`, `surface-translucent-border`, `state-hover`, `state-pressed`, `state-disabled`
|
|
551
|
+
- Optional interaction/motion tokens: `--ui-press-opacity`, `--ui-press-opacity-strong`, `--ui-disabled-opacity`, `--ui-disabled-opacity-soft`, `--ui-disabled-opacity-subtle`, `--ui-inactive-opacity`, `--ui-motion-fast`, `--ui-motion-normal`, `--ui-motion-slow`, `--ui-drawer-open-duration`, `--ui-drawer-close-duration`, `--ui-blur-intensity-subtle`, `--ui-blur-intensity-medium`, `--ui-blur-intensity-strong`, `--ui-blur-saturation-pct`, `--ui-frosted-highlight-height`, `--ui-frosted-backdrop-blur-intensity`, `--ui-frosted-backdrop-blur-scale`, `--ui-frosted-android-experimental-blur`, `--ui-drawer-frosted-inset`, `--ui-drawer-frosted-radius`, `--ui-drawer-content-top-padding`
|
|
495
552
|
- Optional scale tokens: spacing (`--spacing`, `--spacing-*`), typography (`--text-*`, `--leading-*`, `--tracking-*`), radius (`--radius-*`), elevation (`--shadow-*`)
|
|
496
553
|
|
|
497
554
|
Minimal contract example:
|
|
@@ -504,6 +561,8 @@ Minimal contract example:
|
|
|
504
561
|
--color-foreground: oklch(0.15 0 0);
|
|
505
562
|
--color-card: oklch(1 0 0);
|
|
506
563
|
--color-card-foreground: oklch(0.15 0 0);
|
|
564
|
+
--color-popover: oklch(1 0 0);
|
|
565
|
+
--color-popover-foreground: oklch(0.15 0 0);
|
|
507
566
|
--color-primary: oklch(0.2 0 0);
|
|
508
567
|
--color-primary-foreground: oklch(0.98 0 0);
|
|
509
568
|
--color-secondary: oklch(0.95 0 0);
|
|
@@ -530,15 +589,66 @@ import { Uniwind } from 'uniwind';
|
|
|
530
589
|
Uniwind.setTheme('dark'); // or 'light' or 'system'
|
|
531
590
|
```
|
|
532
591
|
|
|
592
|
+
### Frosted / Translucent Theme
|
|
593
|
+
|
|
594
|
+
Some apps want a frosted or translucent look for floating panels (modals, sheets, toolbars). WhileUI stays neutral — no "glass" in core token names. Apps that want this effect opt in by overriding surface tokens in their theme.
|
|
595
|
+
|
|
596
|
+
**Option A — Override in existing light/dark:** In your `@variant light` and `@variant dark` blocks, set surface tokens to semi-transparent values:
|
|
597
|
+
|
|
598
|
+
```css
|
|
599
|
+
@variant light {
|
|
600
|
+
/* ... other tokens ... */
|
|
601
|
+
--color-surface-elevated: oklch(0.98 0.01 95 / 0.4);
|
|
602
|
+
--color-surface-border: oklch(1 0 0 / 0.25);
|
|
603
|
+
--color-surface-highlight: oklch(1 0 0 / 0.4);
|
|
604
|
+
}
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
**Option B — Separate frosted theme:** Register `extraThemes: ['frosted']` and define `:root.frosted` with the same structure as your base theme, but with translucent surface values. Then `Uniwind.setTheme('frosted')` when desired.
|
|
608
|
+
|
|
609
|
+
**Optional:** Add `expo-blur` and register its `BlurView` once at app startup for full frosted blur. For tint-only (no blur), translucent surface tokens are sufficient.
|
|
610
|
+
|
|
611
|
+
```tsx
|
|
612
|
+
import { BlurView } from 'expo-blur';
|
|
613
|
+
import { registerFrostedBlurView } from '@thewhileloop/whileui';
|
|
614
|
+
|
|
615
|
+
registerFrostedBlurView(BlurView);
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
Optional generic tokens: `surface-translucent`, `surface-translucent-border` — use them if you need a distinct token from `surface-elevated` for overlay panels.
|
|
619
|
+
|
|
620
|
+
Built-in overlays and cards support opt-in frosted mode via:
|
|
621
|
+
|
|
622
|
+
- `frosted?: boolean`
|
|
623
|
+
- `blurIntensity?: number`
|
|
624
|
+
- `blurTintToken?: 'surfaceElevated' | 'surfaceTranslucent' | 'card' | 'popover'`
|
|
625
|
+
|
|
626
|
+
Default blur presets map to CSS visual tokens:
|
|
627
|
+
|
|
628
|
+
- subtle: `--ui-blur-intensity-subtle` (default `18`)
|
|
629
|
+
- medium: `--ui-blur-intensity-medium` (default `22`)
|
|
630
|
+
- strong: `--ui-blur-intensity-strong` (default `28`)
|
|
631
|
+
|
|
632
|
+
Additional frosted tuning tokens:
|
|
633
|
+
|
|
634
|
+
- `--ui-blur-saturation-pct` (web fallback saturation multiplier)
|
|
635
|
+
- `--ui-frosted-highlight-height` (top highlight strip height in px, set `0` to disable hard top sheen)
|
|
636
|
+
- `--ui-frosted-backdrop-blur-intensity` (default backdrop blur amount)
|
|
637
|
+
- `--ui-frosted-backdrop-blur-scale` (ratio used when component blur is overridden)
|
|
638
|
+
- `--ui-frosted-android-experimental-blur` (`1` enables `expo-blur` Android experimental path)
|
|
639
|
+
- `--ui-drawer-frosted-inset` (floating inset for frosted drawer shells)
|
|
640
|
+
- `--ui-drawer-frosted-radius` (drawer corner radius in px)
|
|
641
|
+
- `--ui-drawer-content-top-padding` (drawer content top spacing baseline)
|
|
642
|
+
|
|
533
643
|
### Theme Colors for RN Primitives
|
|
534
644
|
|
|
535
|
-
Some React Native APIs require native color strings (hex/rgb/hsl/named). Use `useThemeColors` or `useIconColors` to read from your `global.css` theme. If your theme uses `oklch(...)`, add `--app-color-*`
|
|
645
|
+
Some React Native APIs require native color strings (hex/rgb/hsl/rgba/named). Use `useThemeColors` or `useIconColors` to read from your `global.css` theme. If your theme uses `oklch(...)`, add `--app-color-*` fallbacks — `useThemeColors` will use them when `--color-*` is not RN-native.
|
|
536
646
|
|
|
537
647
|
```tsx
|
|
538
648
|
import { useThemeColors, useIconColors } from '@thewhileloop/whileui';
|
|
539
649
|
import { Feather } from '@expo/vector-icons';
|
|
540
650
|
|
|
541
|
-
// Full palette (
|
|
651
|
+
// Full palette (core semantic colors + status + overlay/effect tokens)
|
|
542
652
|
const colors = useThemeColors();
|
|
543
653
|
|
|
544
654
|
// Shorthand for icons: foreground, muted, primary, primaryForeground, accent, destructive
|
|
@@ -548,11 +658,48 @@ const iconColors = useIconColors();
|
|
|
548
658
|
<Spinner color={colors.foreground} /> // Spinner defaults to this when color not passed
|
|
549
659
|
```
|
|
550
660
|
|
|
551
|
-
- **useThemeColors** / **useThemeTokens** — Returns RN-safe color strings (hex) for
|
|
661
|
+
- **useThemeColors** / **useThemeTokens** — Returns RN-safe color strings (hex/rgb/rgba) for semantic tokens (`background`, `card`, `popover`, `primary`, `secondary`, `muted`, `accent`, `destructive`, status colors) plus effect tokens (`overlay`, `overlayStrong`, `surfaceElevated`, `surfaceTranslucent`, `surfaceTranslucentBorder`, etc.). Falls back to `--app-color-*` when `--color-*` is missing/non-RN-native.
|
|
552
662
|
- **useIconColors** — Subset for icons. Maps `muted` → `mutedForeground` (readable on backgrounds).
|
|
553
663
|
|
|
554
664
|
Input, Textarea, NumericInput, SmartInput, Spinner, and LoadingScreen default to theme colors when you omit `placeholderTextColor` or `spinnerColor`.
|
|
555
665
|
|
|
666
|
+
### Interaction + Motion Tokens
|
|
667
|
+
|
|
668
|
+
WhileUI components also read optional `--ui-*` tokens for deeper control of press feedback, disabled states, and motion timing:
|
|
669
|
+
|
|
670
|
+
```css
|
|
671
|
+
@theme {
|
|
672
|
+
--ui-press-opacity: 0.72;
|
|
673
|
+
--ui-press-opacity-strong: 0.9;
|
|
674
|
+
--ui-disabled-opacity: 0.5;
|
|
675
|
+
--ui-disabled-opacity-soft: 0.6;
|
|
676
|
+
--ui-disabled-opacity-subtle: 0.4;
|
|
677
|
+
--ui-inactive-opacity: 0.5;
|
|
678
|
+
--ui-motion-fast: 160;
|
|
679
|
+
--ui-motion-normal: 220;
|
|
680
|
+
--ui-motion-slow: 300;
|
|
681
|
+
--ui-drawer-open-duration: 300;
|
|
682
|
+
--ui-drawer-close-duration: 220;
|
|
683
|
+
--ui-blur-intensity-subtle: 18;
|
|
684
|
+
--ui-blur-intensity-medium: 22;
|
|
685
|
+
--ui-blur-intensity-strong: 28;
|
|
686
|
+
--ui-blur-saturation-pct: 185;
|
|
687
|
+
--ui-frosted-highlight-height: 0;
|
|
688
|
+
--ui-frosted-backdrop-blur-intensity: 14;
|
|
689
|
+
--ui-frosted-backdrop-blur-scale: 0.55;
|
|
690
|
+
--ui-frosted-android-experimental-blur: 1;
|
|
691
|
+
--ui-drawer-frosted-inset: 0;
|
|
692
|
+
--ui-drawer-frosted-radius: 28;
|
|
693
|
+
--ui-drawer-content-top-padding: 0;
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
For custom components, use:
|
|
698
|
+
|
|
699
|
+
```tsx
|
|
700
|
+
import { useInteractionTokens, withInteractivePressableStyle } from '@thewhileloop/whileui';
|
|
701
|
+
```
|
|
702
|
+
|
|
556
703
|
Optional RN fallback tokens (hex/rgb/hsl/named):
|
|
557
704
|
|
|
558
705
|
```css
|
|
@@ -562,23 +709,75 @@ Optional RN fallback tokens (hex/rgb/hsl/named):
|
|
|
562
709
|
--app-color-primary: #000000;
|
|
563
710
|
--app-color-primary-foreground: #ffffff;
|
|
564
711
|
--app-color-foreground: #000000;
|
|
712
|
+
--app-color-background: #ffffff;
|
|
713
|
+
--app-color-card: #ffffff;
|
|
714
|
+
--app-color-card-foreground: #000000;
|
|
715
|
+
--app-color-popover: #ffffff;
|
|
716
|
+
--app-color-popover-foreground: #000000;
|
|
717
|
+
--app-color-secondary: #f5f5f5;
|
|
718
|
+
--app-color-secondary-foreground: #171717;
|
|
565
719
|
--app-color-muted: #f5f5f5;
|
|
566
720
|
--app-color-muted-foreground: #737373;
|
|
567
|
-
--app-color-background: #ffffff;
|
|
568
721
|
--app-color-border: #e5e5e5;
|
|
722
|
+
--app-color-input: #e5e5e5;
|
|
723
|
+
--app-color-ring: #94a3b8;
|
|
569
724
|
--app-color-accent: #22c55e;
|
|
725
|
+
--app-color-accent-foreground: #0a0a0a;
|
|
570
726
|
--app-color-destructive: #dc2626;
|
|
727
|
+
--app-color-destructive-foreground: #ffffff;
|
|
728
|
+
--app-color-success: #16a34a;
|
|
729
|
+
--app-color-success-foreground: #ffffff;
|
|
730
|
+
--app-color-warning: #f59e0b;
|
|
731
|
+
--app-color-warning-foreground: #111827;
|
|
732
|
+
--app-color-info: #3b82f6;
|
|
733
|
+
--app-color-info-foreground: #ffffff;
|
|
734
|
+
--app-color-overlay: rgba(0, 0, 0, 0.4);
|
|
735
|
+
--app-color-overlay-strong: rgba(0, 0, 0, 0.55);
|
|
736
|
+
--app-color-surface-elevated: #ffffff;
|
|
737
|
+
--app-color-surface-translucent: rgba(255, 255, 255, 0.5);
|
|
738
|
+
--app-color-surface-translucent-border: rgba(255, 255, 255, 0.18);
|
|
739
|
+
--app-color-surface-border: rgba(255, 255, 255, 0.3);
|
|
740
|
+
--app-color-surface-highlight: rgba(255, 255, 255, 0.3);
|
|
741
|
+
--app-color-state-hover: rgba(0, 0, 0, 0.05);
|
|
742
|
+
--app-color-state-pressed: rgba(0, 0, 0, 0.12);
|
|
743
|
+
--app-color-state-disabled: rgba(0, 0, 0, 0.4);
|
|
571
744
|
}
|
|
572
745
|
@variant dark {
|
|
573
746
|
--app-color-primary: #ffffff;
|
|
574
747
|
--app-color-primary-foreground: #000000;
|
|
575
748
|
--app-color-foreground: #ffffff;
|
|
749
|
+
--app-color-background: #000000;
|
|
750
|
+
--app-color-card: #0f0f10;
|
|
751
|
+
--app-color-card-foreground: #ffffff;
|
|
752
|
+
--app-color-popover: #121214;
|
|
753
|
+
--app-color-popover-foreground: #ffffff;
|
|
754
|
+
--app-color-secondary: #2e2e2e;
|
|
755
|
+
--app-color-secondary-foreground: #ffffff;
|
|
576
756
|
--app-color-muted: #2e2e2e;
|
|
577
757
|
--app-color-muted-foreground: #999999;
|
|
578
|
-
--app-color-background: #000000;
|
|
579
758
|
--app-color-border: #3d3d3d;
|
|
759
|
+
--app-color-input: #3d3d3d;
|
|
760
|
+
--app-color-ring: #7c889a;
|
|
580
761
|
--app-color-accent: #22c55e;
|
|
762
|
+
--app-color-accent-foreground: #0a0a0a;
|
|
581
763
|
--app-color-destructive: #dc2626;
|
|
764
|
+
--app-color-destructive-foreground: #ffffff;
|
|
765
|
+
--app-color-success: #22c55e;
|
|
766
|
+
--app-color-success-foreground: #0a0a0a;
|
|
767
|
+
--app-color-warning: #fbbf24;
|
|
768
|
+
--app-color-warning-foreground: #111827;
|
|
769
|
+
--app-color-info: #60a5fa;
|
|
770
|
+
--app-color-info-foreground: #111827;
|
|
771
|
+
--app-color-overlay: rgba(0, 0, 0, 0.5);
|
|
772
|
+
--app-color-overlay-strong: rgba(0, 0, 0, 0.7);
|
|
773
|
+
--app-color-surface-elevated: #111315;
|
|
774
|
+
--app-color-surface-translucent: rgba(17, 19, 21, 0.45);
|
|
775
|
+
--app-color-surface-translucent-border: rgba(255, 255, 255, 0.18);
|
|
776
|
+
--app-color-surface-border: rgba(255, 255, 255, 0.18);
|
|
777
|
+
--app-color-surface-highlight: rgba(255, 255, 255, 0.18);
|
|
778
|
+
--app-color-state-hover: rgba(255, 255, 255, 0.08);
|
|
779
|
+
--app-color-state-pressed: rgba(255, 255, 255, 0.15);
|
|
780
|
+
--app-color-state-disabled: rgba(255, 255, 255, 0.4);
|
|
582
781
|
}
|
|
583
782
|
}
|
|
584
783
|
}
|
|
@@ -674,6 +873,31 @@ import { NumericInput } from '@thewhileloop/whileui';
|
|
|
674
873
|
| prefix / suffix | `ReactNode` | — | Left/right slots |
|
|
675
874
|
| showSteppers | `boolean` | `false` | Show decrement/increment controls |
|
|
676
875
|
|
|
876
|
+
## OTPInput
|
|
877
|
+
|
|
878
|
+
```tsx
|
|
879
|
+
import { OTPInput } from '@thewhileloop/whileui';
|
|
880
|
+
|
|
881
|
+
<OTPInput
|
|
882
|
+
value={otp}
|
|
883
|
+
onValueChange={setOtp}
|
|
884
|
+
onComplete={(code) => verify(code)}
|
|
885
|
+
length={6}
|
|
886
|
+
variant="default"
|
|
887
|
+
/>;
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
| Prop | Type | Default | Description |
|
|
891
|
+
| ------------- | ------------------------- | ----------- | --------------------------------- |
|
|
892
|
+
| length | `number` | `6` | Number of digit cells |
|
|
893
|
+
| value | `string` | — | Controlled value |
|
|
894
|
+
| onValueChange | `(value: string) => void` | — | Called on each digit change |
|
|
895
|
+
| onComplete | `(code: string) => void` | — | Called when all digits are filled |
|
|
896
|
+
| variant | `'default' \| 'error'` | `'default'` | Error variant triggers shake |
|
|
897
|
+
| size | `'default' \| 'compact'` | `'default'` | Cell size |
|
|
898
|
+
| secure | `boolean` | `false` | Mask digits with dots |
|
|
899
|
+
| autoFocus | `boolean` | `false` | Focus first cell on mount |
|
|
900
|
+
|
|
677
901
|
## FormField
|
|
678
902
|
|
|
679
903
|
```tsx
|
|
@@ -796,10 +1020,13 @@ import {
|
|
|
796
1020
|
</Card>;
|
|
797
1021
|
```
|
|
798
1022
|
|
|
799
|
-
| Prop
|
|
800
|
-
|
|
|
801
|
-
| padding
|
|
802
|
-
| unstyled
|
|
1023
|
+
| Prop | Type | Default | Description |
|
|
1024
|
+
| ------------- | ------------------------------------------------------------------ | ------------ | ----------------------------------- |
|
|
1025
|
+
| padding | `'none' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Card interior padding |
|
|
1026
|
+
| unstyled | `boolean` | `false` | Remove built-in card surface styles |
|
|
1027
|
+
| frosted | `boolean` | `false` | Enables frosted tint + blur layer |
|
|
1028
|
+
| blurIntensity | `number` | token preset | Override blur amount directly |
|
|
1029
|
+
| blurTintToken | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'card'` | Tint token used for frosted mode |
|
|
803
1030
|
|
|
804
1031
|
## Badge
|
|
805
1032
|
|
|
@@ -867,6 +1094,14 @@ import {
|
|
|
867
1094
|
</Dialog>;
|
|
868
1095
|
```
|
|
869
1096
|
|
|
1097
|
+
`DialogContent` supports frosted props:
|
|
1098
|
+
|
|
1099
|
+
| Prop | Type | Default | Description |
|
|
1100
|
+
| ------------- | ------------------------------------------------------------------ | ---------------------- | -------------------------------- |
|
|
1101
|
+
| frosted | `boolean` | `false` | Enables frosted tint + blur |
|
|
1102
|
+
| blurIntensity | `number` | medium preset | Override blur amount |
|
|
1103
|
+
| blurTintToken | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'surfaceTranslucent'` | Tint token used for frosted mode |
|
|
1104
|
+
|
|
870
1105
|
## AlertDialog
|
|
871
1106
|
|
|
872
1107
|
```tsx
|
|
@@ -901,6 +1136,21 @@ import {
|
|
|
901
1136
|
</AlertDialog>;
|
|
902
1137
|
```
|
|
903
1138
|
|
|
1139
|
+
`AlertDialogContent` supports the same frosted props as `DialogContent`.
|
|
1140
|
+
|
|
1141
|
+
| Prop | Type | Default | Description |
|
|
1142
|
+
| ------------- | ------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1143
|
+
| presentation | `'modal' \| 'native'` | `'modal'` | **Native (iOS/Android):** `'modal'` uses `Modal` (with iOS `overFullScreen` to improve stacking). `'native'` uses `Alert.alert` with copy from `AlertDialogTitle`, `AlertDialogDescription`, `AlertDialogCancel`, and `AlertDialogAction` — reliable when another fullscreen `Modal` is already open. **Web:** `'native'` is ignored; the custom `Modal` UI is always used. |
|
|
1144
|
+
| frosted | `boolean` | `false` | Same as `DialogContent` |
|
|
1145
|
+
| blurIntensity | `number` | — | Same as `DialogContent` |
|
|
1146
|
+
| blurTintToken | `'surfaceElevated' \| …` | — | Same as `DialogContent` |
|
|
1147
|
+
|
|
1148
|
+
### React Native: stacked modals
|
|
1149
|
+
|
|
1150
|
+
If `AlertDialog` is a **sibling** of another fullscreen `Modal` (e.g. `<><Modal>…</Modal><AlertDialog>…</AlertDialog></>`), iOS often fails to show a second `Modal` on top, or taps feel dead, even though `open` is `true`. Prefer **`presentation="native"`** on `AlertDialogContent` in those flows so the system alert appears above the sheet. Alternatively, render the `AlertDialog` **inside** the visible `Modal` so both layers share one RN modal host.
|
|
1151
|
+
|
|
1152
|
+
**Manual QA (iOS + Android):** Open a fullscreen `Modal`, trigger a delete/confirm that uses `presentation="native"`, confirm the system alert is visible, **Cancel** closes without side effects, **Delete** runs the action handler and dismisses.
|
|
1153
|
+
|
|
904
1154
|
## Checkbox
|
|
905
1155
|
|
|
906
1156
|
```tsx
|
|
@@ -954,13 +1204,21 @@ import {
|
|
|
954
1204
|
<SelectTrigger>
|
|
955
1205
|
<SelectValue placeholder="Select..." />
|
|
956
1206
|
</SelectTrigger>
|
|
957
|
-
<SelectContent>
|
|
1207
|
+
<SelectContent frosted blurIntensity={24} blurTintToken="surfaceTranslucent">
|
|
958
1208
|
<SelectItem label="Option 1" value="1" />
|
|
959
1209
|
<SelectItem label="Option 2" value="2" />
|
|
960
1210
|
</SelectContent>
|
|
961
1211
|
</Select>;
|
|
962
1212
|
```
|
|
963
1213
|
|
|
1214
|
+
`SelectContent` supports optional frosted props:
|
|
1215
|
+
|
|
1216
|
+
| Prop | Type | Default |
|
|
1217
|
+
| ------------- | ------------------------------------------------------------------ | ------------- |
|
|
1218
|
+
| frosted | `boolean` | `false` |
|
|
1219
|
+
| blurIntensity | `number` | subtle preset |
|
|
1220
|
+
| blurTintToken | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'popover'` |
|
|
1221
|
+
|
|
964
1222
|
## Tabs
|
|
965
1223
|
|
|
966
1224
|
```tsx
|
|
@@ -1075,6 +1333,14 @@ import { Popover, PopoverTrigger, PopoverContent } from '@thewhileloop/whileui';
|
|
|
1075
1333
|
</Popover>;
|
|
1076
1334
|
```
|
|
1077
1335
|
|
|
1336
|
+
`PopoverContent` supports frosted props:
|
|
1337
|
+
|
|
1338
|
+
| Prop | Type | Default |
|
|
1339
|
+
| ------------- | ------------------------------------------------------------------ | ------------- |
|
|
1340
|
+
| frosted | `boolean` | `false` |
|
|
1341
|
+
| blurIntensity | `number` | subtle preset |
|
|
1342
|
+
| blurTintToken | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'popover'` |
|
|
1343
|
+
|
|
1078
1344
|
## DropdownMenu
|
|
1079
1345
|
|
|
1080
1346
|
```tsx
|
|
@@ -1106,6 +1372,20 @@ import {
|
|
|
1106
1372
|
</DropdownMenu>;
|
|
1107
1373
|
```
|
|
1108
1374
|
|
|
1375
|
+
`DropdownMenuContent`, `ContextMenuContent`, and `MenubarContent` support the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1376
|
+
|
|
1377
|
+
## HoverCard
|
|
1378
|
+
|
|
1379
|
+
`HoverCardContent` supports the same frosted props as `PopoverContent` (default blur preset: subtle).
|
|
1380
|
+
|
|
1381
|
+
## ContextMenu
|
|
1382
|
+
|
|
1383
|
+
`ContextMenuContent` supports the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1384
|
+
|
|
1385
|
+
## Menubar
|
|
1386
|
+
|
|
1387
|
+
`MenubarContent` supports the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1388
|
+
|
|
1109
1389
|
---
|
|
1110
1390
|
|
|
1111
1391
|
# Blocks API
|
|
@@ -1173,6 +1453,32 @@ import { BottomNav } from '@thewhileloop/whileui';
|
|
|
1173
1453
|
/>;
|
|
1174
1454
|
```
|
|
1175
1455
|
|
|
1456
|
+
## AppShell
|
|
1457
|
+
|
|
1458
|
+
Layout shell for full-screen pages. Keep shell chrome mounted and swap only content via `loading` + `skeleton`.
|
|
1459
|
+
|
|
1460
|
+
```tsx
|
|
1461
|
+
import { AppShell, PageSkeleton } from '@thewhileloop/whileui';
|
|
1462
|
+
|
|
1463
|
+
<AppShell
|
|
1464
|
+
header={<Header title="Settings" />}
|
|
1465
|
+
bottomNav={<BottomNav items={[...]} activeKey="settings" onSelect={setTab} />}
|
|
1466
|
+
loading={loading}
|
|
1467
|
+
skeleton={<PageSkeleton variant="settings" headerPlaceholder />}
|
|
1468
|
+
>
|
|
1469
|
+
<ScrollView className="flex-1 p-4">{/* Content */}</ScrollView>
|
|
1470
|
+
</AppShell>;
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
| Prop | Type | Default | Description |
|
|
1474
|
+
| ----------- | ----------- | ------- | --------------------------------------------------- |
|
|
1475
|
+
| `header` | `ReactNode` | — | Header slot |
|
|
1476
|
+
| `footer` | `ReactNode` | — | Footer slot |
|
|
1477
|
+
| `bottomNav` | `ReactNode` | — | Bottom navigation slot |
|
|
1478
|
+
| `safeArea` | `boolean` | `true` | Wrap in SafeAreaView |
|
|
1479
|
+
| `loading` | `boolean` | `false` | Show `skeleton` in content area, keep shell mounted |
|
|
1480
|
+
| `skeleton` | `ReactNode` | — | Content placeholder rendered when `loading=true` |
|
|
1481
|
+
|
|
1176
1482
|
## ActionBar
|
|
1177
1483
|
|
|
1178
1484
|
```tsx
|
|
@@ -1299,14 +1605,17 @@ const [range, setRange] = useState<DateRange | null>(null);
|
|
|
1299
1605
|
/>;
|
|
1300
1606
|
```
|
|
1301
1607
|
|
|
1302
|
-
| Prop | Type
|
|
1303
|
-
| ----------------------- |
|
|
1304
|
-
| `value` | `string \| null` / `DateRange \| null`
|
|
1305
|
-
| `onValueChange` | `(date) => void`
|
|
1306
|
-
| `open` / `onOpenChange` | —
|
|
1307
|
-
| `trigger` | `ReactNode`
|
|
1308
|
-
| `minDate` / `maxDate` | `string`
|
|
1309
|
-
| `theme` | `CalendarTheme`
|
|
1608
|
+
| Prop | Type | Description |
|
|
1609
|
+
| ----------------------- | ------------------------------------------------------------------ | -------------------------------------- |
|
|
1610
|
+
| `value` | `string \| null` / `DateRange \| null` | Selected date(s) YYYY-MM-DD |
|
|
1611
|
+
| `onValueChange` | `(date) => void` | Change handler |
|
|
1612
|
+
| `open` / `onOpenChange` | — | Modal state (modal variants) |
|
|
1613
|
+
| `trigger` | `ReactNode` | Custom trigger (modal variants) |
|
|
1614
|
+
| `minDate` / `maxDate` | `string` | YYYY-MM-DD bounds |
|
|
1615
|
+
| `theme` | `CalendarTheme` | Override calendar colors |
|
|
1616
|
+
| `frosted` | `boolean` | Enable frosted surface for modal panel |
|
|
1617
|
+
| `blurIntensity` | `number` | Override blur amount |
|
|
1618
|
+
| `blurTintToken` | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | Tint token for frosted panel |
|
|
1310
1619
|
|
|
1311
1620
|
## ConfirmActionSheet
|
|
1312
1621
|
|
|
@@ -1324,6 +1633,12 @@ import { ConfirmActionSheet } from '@thewhileloop/whileui';
|
|
|
1324
1633
|
/>;
|
|
1325
1634
|
```
|
|
1326
1635
|
|
|
1636
|
+
| Prop | Type | Default |
|
|
1637
|
+
| --------------- | ------------------------------------------------------------------ | ---------------------- |
|
|
1638
|
+
| `frosted` | `boolean` | `false` |
|
|
1639
|
+
| `blurIntensity` | `number` | medium preset |
|
|
1640
|
+
| `blurTintToken` | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'surfaceTranslucent'` |
|
|
1641
|
+
|
|
1327
1642
|
## Sheet
|
|
1328
1643
|
|
|
1329
1644
|
Bottom sheet modal with slide animation. Slots: `SheetHeader`, `SheetContent`, `SheetFooter`, `SheetClose`.
|
|
@@ -1352,12 +1667,15 @@ import {
|
|
|
1352
1667
|
</Sheet>;
|
|
1353
1668
|
```
|
|
1354
1669
|
|
|
1355
|
-
| Prop
|
|
1356
|
-
|
|
|
1357
|
-
| open
|
|
1358
|
-
| onOpenChange
|
|
1359
|
-
| maxHeight
|
|
1360
|
-
| maxWidth
|
|
1670
|
+
| Prop | Type | Default |
|
|
1671
|
+
| ------------- | ------------------------------------------------------------------ | ---------------------- |
|
|
1672
|
+
| open | `boolean` | — |
|
|
1673
|
+
| onOpenChange | `(open: boolean) => void` | — |
|
|
1674
|
+
| maxHeight | `'half' \| 'full' \| number` | `'full'` |
|
|
1675
|
+
| maxWidth | `number` | `360` |
|
|
1676
|
+
| frosted | `boolean` | `false` |
|
|
1677
|
+
| blurIntensity | `number` | medium preset |
|
|
1678
|
+
| blurTintToken | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'surfaceTranslucent'` |
|
|
1361
1679
|
|
|
1362
1680
|
## NavigationSidebar
|
|
1363
1681
|
|
|
@@ -1445,15 +1763,38 @@ import { PageSkeleton } from '@thewhileloop/whileui';
|
|
|
1445
1763
|
<PageSkeleton variant="settings" count={6} />
|
|
1446
1764
|
<PageSkeleton variant="card" />
|
|
1447
1765
|
<PageSkeleton variant="generic" />
|
|
1766
|
+
<PageSkeleton variant="dashboard" headerPlaceholder />
|
|
1767
|
+
<PageSkeleton variant="list" header={<Header title="Loading..." />} />
|
|
1448
1768
|
<PageSkeleton variant="list" padding="none" className="flex-1" />
|
|
1449
1769
|
```
|
|
1450
1770
|
|
|
1451
|
-
| Prop
|
|
1452
|
-
|
|
|
1453
|
-
| variant
|
|
1454
|
-
| count
|
|
1455
|
-
| padding
|
|
1456
|
-
|
|
|
1771
|
+
| Prop | Type | Default | Description |
|
|
1772
|
+
| ------------------- | ------------------------------------------------------------ | ---------------------- | --------------------------------------------- |
|
|
1773
|
+
| `variant` | `'dashboard' \| 'list' \| 'settings' \| 'card' \| 'generic'` | required | Layout preset |
|
|
1774
|
+
| `count` | `number` | 3 (list), 4 (settings) | Rows/items for list or settings variant |
|
|
1775
|
+
| `padding` | `'none' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Container padding |
|
|
1776
|
+
| `header` | `ReactNode` | — | Optional real header slot above content |
|
|
1777
|
+
| `headerPlaceholder` | `boolean \| 'compact' \| 'default'` | `false` | Skeleton header when real header is not ready |
|
|
1778
|
+
| `className` | `string` | — | Outer container classes |
|
|
1779
|
+
|
|
1780
|
+
## ScreenSkeleton
|
|
1781
|
+
|
|
1782
|
+
Convenience block for loading screens that need both header and content continuity.
|
|
1783
|
+
|
|
1784
|
+
```tsx
|
|
1785
|
+
import { ScreenSkeleton } from '@thewhileloop/whileui';
|
|
1786
|
+
|
|
1787
|
+
<ScreenSkeleton variant="dashboard" headerPlaceholder />
|
|
1788
|
+
<ScreenSkeleton variant="list" count={5} header={<Header title="Projects" />} />
|
|
1789
|
+
```
|
|
1790
|
+
|
|
1791
|
+
| Prop | Type | Default | Description |
|
|
1792
|
+
| ------------------- | ----------------------------------- | ----------- | ------------------------------------- |
|
|
1793
|
+
| `variant` | `PageSkeletonVariant` | `'generic'` | Skeleton content preset |
|
|
1794
|
+
| `count` | `number` | — | Rows/items for list/settings variants |
|
|
1795
|
+
| `padding` | `PageSkeletonPadding` | `'default'` | Inner content padding |
|
|
1796
|
+
| `header` | `ReactNode` | — | Real header slot |
|
|
1797
|
+
| `headerPlaceholder` | `boolean \| 'compact' \| 'default'` | `'default'` | Placeholder header |
|
|
1457
1798
|
|
|
1458
1799
|
## ErrorBoundary
|
|
1459
1800
|
|
|
@@ -1565,6 +1906,7 @@ import { ProductCard } from '@thewhileloop/whileui';
|
|
|
1565
1906
|
| Prop | Type | Default |
|
|
1566
1907
|
| ------- | ---------------------------- | ------------ |
|
|
1567
1908
|
| variant | `'vertical' \| 'horizontal'` | `'vertical'` |
|
|
1909
|
+
| loading | `boolean` | `false` |
|
|
1568
1910
|
|
|
1569
1911
|
## PricingCard
|
|
1570
1912
|
|
|
@@ -1587,6 +1929,90 @@ import { PricingCard } from '@thewhileloop/whileui';
|
|
|
1587
1929
|
/>;
|
|
1588
1930
|
```
|
|
1589
1931
|
|
|
1932
|
+
## SubscriptionCard
|
|
1933
|
+
|
|
1934
|
+
```tsx
|
|
1935
|
+
import { SubscriptionCard } from '@thewhileloop/whileui';
|
|
1936
|
+
|
|
1937
|
+
<SubscriptionCard
|
|
1938
|
+
planName="Pro"
|
|
1939
|
+
price="$29"
|
|
1940
|
+
period="/month"
|
|
1941
|
+
expiresAt="April 18, 2026"
|
|
1942
|
+
isActive
|
|
1943
|
+
onManage={() => {}}
|
|
1944
|
+
onUpgrade={() => {}}
|
|
1945
|
+
/>;
|
|
1946
|
+
```
|
|
1947
|
+
|
|
1948
|
+
| Prop | Type | Description |
|
|
1949
|
+
| ----------- | ------------ | --------------------------- |
|
|
1950
|
+
| `planName` | `string` | Current plan label |
|
|
1951
|
+
| `price` | `string` | Plan price |
|
|
1952
|
+
| `period` | `string` | Billing period label |
|
|
1953
|
+
| `expiresAt` | `string` | Renewal/expiry date text |
|
|
1954
|
+
| `isActive` | `boolean` | Active/inactive badge state |
|
|
1955
|
+
| `onManage` | `() => void` | Manage action |
|
|
1956
|
+
| `onUpgrade` | `() => void` | Upgrade action |
|
|
1957
|
+
| `loading` | `boolean` | Show skeleton placeholder |
|
|
1958
|
+
|
|
1959
|
+
## FeatureGate
|
|
1960
|
+
|
|
1961
|
+
```tsx
|
|
1962
|
+
import { FeatureGate } from '@thewhileloop/whileui';
|
|
1963
|
+
|
|
1964
|
+
<FeatureGate
|
|
1965
|
+
title="Advanced export is locked"
|
|
1966
|
+
description="Upgrade to unlock 4K export."
|
|
1967
|
+
buttonLabel="Upgrade"
|
|
1968
|
+
onUpgrade={() => {}}
|
|
1969
|
+
/>;
|
|
1970
|
+
```
|
|
1971
|
+
|
|
1972
|
+
Use `children` to show dimmed preview content with an overlay CTA.
|
|
1973
|
+
|
|
1974
|
+
## UsageBar
|
|
1975
|
+
|
|
1976
|
+
```tsx
|
|
1977
|
+
import { UsageBar } from '@thewhileloop/whileui';
|
|
1978
|
+
|
|
1979
|
+
<UsageBar label="AI generations" used={8} limit={20} />;
|
|
1980
|
+
```
|
|
1981
|
+
|
|
1982
|
+
| Prop | Type | Default | Description |
|
|
1983
|
+
| --------- | -------------------------------------- | ------- | ------------------------------ |
|
|
1984
|
+
| `label` | `string` | — | Usage label |
|
|
1985
|
+
| `used` | `number` | — | Used amount |
|
|
1986
|
+
| `limit` | `number` | — | Quota limit |
|
|
1987
|
+
| `variant` | `'default' \| 'warning' \| 'exceeded'` | auto | Optional explicit visual state |
|
|
1988
|
+
|
|
1989
|
+
## PlanToggle
|
|
1990
|
+
|
|
1991
|
+
```tsx
|
|
1992
|
+
import { PlanToggle } from '@thewhileloop/whileui';
|
|
1993
|
+
|
|
1994
|
+
<PlanToggle
|
|
1995
|
+
selected="monthly"
|
|
1996
|
+
monthlyLabel="Monthly"
|
|
1997
|
+
annualLabel="Annual"
|
|
1998
|
+
annualDiscount="Save 20%"
|
|
1999
|
+
onChange={(next) => {}}
|
|
2000
|
+
/>;
|
|
2001
|
+
```
|
|
2002
|
+
|
|
2003
|
+
## UpgradeBanner
|
|
2004
|
+
|
|
2005
|
+
```tsx
|
|
2006
|
+
import { UpgradeBanner } from '@thewhileloop/whileui';
|
|
2007
|
+
|
|
2008
|
+
<UpgradeBanner
|
|
2009
|
+
message="Unlock unlimited exports with Pro."
|
|
2010
|
+
actionLabel="See plans"
|
|
2011
|
+
onAction={() => {}}
|
|
2012
|
+
onDismiss={() => {}}
|
|
2013
|
+
/>;
|
|
2014
|
+
```
|
|
2015
|
+
|
|
1590
2016
|
## DrawerMenu
|
|
1591
2017
|
|
|
1592
2018
|
```tsx
|
|
@@ -1611,6 +2037,47 @@ import { DrawerMenu } from '@thewhileloop/whileui';
|
|
|
1611
2037
|
/>;
|
|
1612
2038
|
```
|
|
1613
2039
|
|
|
2040
|
+
| Prop | Type | Default |
|
|
2041
|
+
| --------------- | ------------------------------------------------------------------ | ---------------------- |
|
|
2042
|
+
| `frosted` | `boolean` | `false` |
|
|
2043
|
+
| `blurIntensity` | `number` | medium preset |
|
|
2044
|
+
| `blurTintToken` | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'surfaceTranslucent'` |
|
|
2045
|
+
|
|
2046
|
+
## Roadmap
|
|
2047
|
+
|
|
2048
|
+
Tracked work items for future releases.
|
|
2049
|
+
|
|
2050
|
+
### New Components
|
|
2051
|
+
|
|
2052
|
+
- [ ] **Chip / Tag** — selectable, dismissible, multi-select with `tv()` variants, loading skeleton
|
|
2053
|
+
- [ ] **Rating / Stars** — interactive + read-only, half-star precision, swipe gesture, pairs with ProductCard
|
|
2054
|
+
- [ ] **Slider** — single + range (two thumbs), step marks, labels, Reanimated gesture, haptic on snap
|
|
2055
|
+
- [ ] **Carousel** — Reanimated-powered, auto-play, pagination dots, snap-to-item
|
|
2056
|
+
- [ ] **FAB (Floating Action Button)** — expandable action menu, Reanimated spring, auto-hide on scroll
|
|
2057
|
+
- [ ] **Combobox** — searchable select with keyboard navigation, empty state, async loading
|
|
2058
|
+
- [ ] **Data Table** — sortable headers, skeleton loading rows, row actions, responsive stacking
|
|
2059
|
+
- [ ] **Banner** — dismissible info/warning/success/destructive bar with icon + action, auto-dismiss timer
|
|
2060
|
+
- [ ] **Pagination** — compact (dots) + expanded (numbers) variants, edge-aware ellipsis, 44px touch targets
|
|
2061
|
+
- [ ] **Inline Calendar** — standalone calendar view reusing DatePicker logic, range selection
|
|
2062
|
+
|
|
2063
|
+
### Component-Level Loading (`loading` prop)
|
|
2064
|
+
|
|
2065
|
+
- [x] ProductCard, ListItem, NotificationItem, MetricCard, SubscriptionCard, PricingCard
|
|
2066
|
+
- [x] AppShell, Chat, FormModalScreen, CheckoutSummary (already had `loading`)
|
|
2067
|
+
- [ ] Add `loading` to remaining blocks: Header, BottomNav, DrawerMenu, TimelineFeed, SwipeableItem
|
|
2068
|
+
|
|
2069
|
+
### Documentation Gaps
|
|
2070
|
+
|
|
2071
|
+
- [ ] Add `## API` sections for: Textarea, Toggle, ToggleGroup, Label, Separator, Skeleton, AspectRatio, Collapsible, Text, View, Pressable
|
|
2072
|
+
- [ ] Add `## API` sections for blocks: FloatingBottomNav, TabBar, FormModalScreen, ErrorState, LoadingScreen, OnboardingScreen, ListItem, NotificationItem, MetricCard, SmartImage, TimelineFeed
|
|
2073
|
+
- [ ] Build `apps/site/` (docs website) with `registry.ts`, `demos.tsx`, `block-demos.tsx`, `props-data.ts`
|
|
2074
|
+
|
|
2075
|
+
### Infrastructure
|
|
2076
|
+
|
|
2077
|
+
- [ ] Publish to npm (`@thewhileloop/whileui`)
|
|
2078
|
+
- [ ] CI: typecheck + format check on PR
|
|
2079
|
+
- [ ] Automated visual regression tests (screenshot comparison)
|
|
2080
|
+
|
|
1614
2081
|
## License
|
|
1615
2082
|
|
|
1616
2083
|
MIT — [Source](https://github.com/whileloophq/whileui)
|