@thewhileloop/whileui 1.1.1 → 1.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.
- package/README.md +492 -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 +3 -2
- package/dist/components/alert-dialog/alert-dialog.d.ts.map +1 -1
- package/dist/components/alert-dialog/alert-dialog.js +15 -4
- package/dist/components/alert-dialog/alert-dialog.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 +5 -0
- 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,8 @@ import {
|
|
|
901
1136
|
</AlertDialog>;
|
|
902
1137
|
```
|
|
903
1138
|
|
|
1139
|
+
`AlertDialogContent` supports the same frosted props as `DialogContent`.
|
|
1140
|
+
|
|
904
1141
|
## Checkbox
|
|
905
1142
|
|
|
906
1143
|
```tsx
|
|
@@ -954,13 +1191,21 @@ import {
|
|
|
954
1191
|
<SelectTrigger>
|
|
955
1192
|
<SelectValue placeholder="Select..." />
|
|
956
1193
|
</SelectTrigger>
|
|
957
|
-
<SelectContent>
|
|
1194
|
+
<SelectContent frosted blurIntensity={24} blurTintToken="surfaceTranslucent">
|
|
958
1195
|
<SelectItem label="Option 1" value="1" />
|
|
959
1196
|
<SelectItem label="Option 2" value="2" />
|
|
960
1197
|
</SelectContent>
|
|
961
1198
|
</Select>;
|
|
962
1199
|
```
|
|
963
1200
|
|
|
1201
|
+
`SelectContent` supports optional frosted props:
|
|
1202
|
+
|
|
1203
|
+
| Prop | Type | Default |
|
|
1204
|
+
| ------------- | ------------------------------------------------------------------ | ------------- |
|
|
1205
|
+
| frosted | `boolean` | `false` |
|
|
1206
|
+
| blurIntensity | `number` | subtle preset |
|
|
1207
|
+
| blurTintToken | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'popover'` |
|
|
1208
|
+
|
|
964
1209
|
## Tabs
|
|
965
1210
|
|
|
966
1211
|
```tsx
|
|
@@ -1075,6 +1320,14 @@ import { Popover, PopoverTrigger, PopoverContent } from '@thewhileloop/whileui';
|
|
|
1075
1320
|
</Popover>;
|
|
1076
1321
|
```
|
|
1077
1322
|
|
|
1323
|
+
`PopoverContent` supports frosted props:
|
|
1324
|
+
|
|
1325
|
+
| Prop | Type | Default |
|
|
1326
|
+
| ------------- | ------------------------------------------------------------------ | ------------- |
|
|
1327
|
+
| frosted | `boolean` | `false` |
|
|
1328
|
+
| blurIntensity | `number` | subtle preset |
|
|
1329
|
+
| blurTintToken | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'popover'` |
|
|
1330
|
+
|
|
1078
1331
|
## DropdownMenu
|
|
1079
1332
|
|
|
1080
1333
|
```tsx
|
|
@@ -1106,6 +1359,20 @@ import {
|
|
|
1106
1359
|
</DropdownMenu>;
|
|
1107
1360
|
```
|
|
1108
1361
|
|
|
1362
|
+
`DropdownMenuContent`, `ContextMenuContent`, and `MenubarContent` support the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1363
|
+
|
|
1364
|
+
## HoverCard
|
|
1365
|
+
|
|
1366
|
+
`HoverCardContent` supports the same frosted props as `PopoverContent` (default blur preset: subtle).
|
|
1367
|
+
|
|
1368
|
+
## ContextMenu
|
|
1369
|
+
|
|
1370
|
+
`ContextMenuContent` supports the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1371
|
+
|
|
1372
|
+
## Menubar
|
|
1373
|
+
|
|
1374
|
+
`MenubarContent` supports the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1375
|
+
|
|
1109
1376
|
---
|
|
1110
1377
|
|
|
1111
1378
|
# Blocks API
|
|
@@ -1173,6 +1440,32 @@ import { BottomNav } from '@thewhileloop/whileui';
|
|
|
1173
1440
|
/>;
|
|
1174
1441
|
```
|
|
1175
1442
|
|
|
1443
|
+
## AppShell
|
|
1444
|
+
|
|
1445
|
+
Layout shell for full-screen pages. Keep shell chrome mounted and swap only content via `loading` + `skeleton`.
|
|
1446
|
+
|
|
1447
|
+
```tsx
|
|
1448
|
+
import { AppShell, PageSkeleton } from '@thewhileloop/whileui';
|
|
1449
|
+
|
|
1450
|
+
<AppShell
|
|
1451
|
+
header={<Header title="Settings" />}
|
|
1452
|
+
bottomNav={<BottomNav items={[...]} activeKey="settings" onSelect={setTab} />}
|
|
1453
|
+
loading={loading}
|
|
1454
|
+
skeleton={<PageSkeleton variant="settings" headerPlaceholder />}
|
|
1455
|
+
>
|
|
1456
|
+
<ScrollView className="flex-1 p-4">{/* Content */}</ScrollView>
|
|
1457
|
+
</AppShell>;
|
|
1458
|
+
```
|
|
1459
|
+
|
|
1460
|
+
| Prop | Type | Default | Description |
|
|
1461
|
+
| ----------- | ----------- | ------- | --------------------------------------------------- |
|
|
1462
|
+
| `header` | `ReactNode` | — | Header slot |
|
|
1463
|
+
| `footer` | `ReactNode` | — | Footer slot |
|
|
1464
|
+
| `bottomNav` | `ReactNode` | — | Bottom navigation slot |
|
|
1465
|
+
| `safeArea` | `boolean` | `true` | Wrap in SafeAreaView |
|
|
1466
|
+
| `loading` | `boolean` | `false` | Show `skeleton` in content area, keep shell mounted |
|
|
1467
|
+
| `skeleton` | `ReactNode` | — | Content placeholder rendered when `loading=true` |
|
|
1468
|
+
|
|
1176
1469
|
## ActionBar
|
|
1177
1470
|
|
|
1178
1471
|
```tsx
|
|
@@ -1299,14 +1592,17 @@ const [range, setRange] = useState<DateRange | null>(null);
|
|
|
1299
1592
|
/>;
|
|
1300
1593
|
```
|
|
1301
1594
|
|
|
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`
|
|
1595
|
+
| Prop | Type | Description |
|
|
1596
|
+
| ----------------------- | ------------------------------------------------------------------ | -------------------------------------- |
|
|
1597
|
+
| `value` | `string \| null` / `DateRange \| null` | Selected date(s) YYYY-MM-DD |
|
|
1598
|
+
| `onValueChange` | `(date) => void` | Change handler |
|
|
1599
|
+
| `open` / `onOpenChange` | — | Modal state (modal variants) |
|
|
1600
|
+
| `trigger` | `ReactNode` | Custom trigger (modal variants) |
|
|
1601
|
+
| `minDate` / `maxDate` | `string` | YYYY-MM-DD bounds |
|
|
1602
|
+
| `theme` | `CalendarTheme` | Override calendar colors |
|
|
1603
|
+
| `frosted` | `boolean` | Enable frosted surface for modal panel |
|
|
1604
|
+
| `blurIntensity` | `number` | Override blur amount |
|
|
1605
|
+
| `blurTintToken` | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | Tint token for frosted panel |
|
|
1310
1606
|
|
|
1311
1607
|
## ConfirmActionSheet
|
|
1312
1608
|
|
|
@@ -1324,6 +1620,12 @@ import { ConfirmActionSheet } from '@thewhileloop/whileui';
|
|
|
1324
1620
|
/>;
|
|
1325
1621
|
```
|
|
1326
1622
|
|
|
1623
|
+
| Prop | Type | Default |
|
|
1624
|
+
| --------------- | ------------------------------------------------------------------ | ---------------------- |
|
|
1625
|
+
| `frosted` | `boolean` | `false` |
|
|
1626
|
+
| `blurIntensity` | `number` | medium preset |
|
|
1627
|
+
| `blurTintToken` | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'surfaceTranslucent'` |
|
|
1628
|
+
|
|
1327
1629
|
## Sheet
|
|
1328
1630
|
|
|
1329
1631
|
Bottom sheet modal with slide animation. Slots: `SheetHeader`, `SheetContent`, `SheetFooter`, `SheetClose`.
|
|
@@ -1352,12 +1654,15 @@ import {
|
|
|
1352
1654
|
</Sheet>;
|
|
1353
1655
|
```
|
|
1354
1656
|
|
|
1355
|
-
| Prop
|
|
1356
|
-
|
|
|
1357
|
-
| open
|
|
1358
|
-
| onOpenChange
|
|
1359
|
-
| maxHeight
|
|
1360
|
-
| maxWidth
|
|
1657
|
+
| Prop | Type | Default |
|
|
1658
|
+
| ------------- | ------------------------------------------------------------------ | ---------------------- |
|
|
1659
|
+
| open | `boolean` | — |
|
|
1660
|
+
| onOpenChange | `(open: boolean) => void` | — |
|
|
1661
|
+
| maxHeight | `'half' \| 'full' \| number` | `'full'` |
|
|
1662
|
+
| maxWidth | `number` | `360` |
|
|
1663
|
+
| frosted | `boolean` | `false` |
|
|
1664
|
+
| blurIntensity | `number` | medium preset |
|
|
1665
|
+
| blurTintToken | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'surfaceTranslucent'` |
|
|
1361
1666
|
|
|
1362
1667
|
## NavigationSidebar
|
|
1363
1668
|
|
|
@@ -1445,15 +1750,38 @@ import { PageSkeleton } from '@thewhileloop/whileui';
|
|
|
1445
1750
|
<PageSkeleton variant="settings" count={6} />
|
|
1446
1751
|
<PageSkeleton variant="card" />
|
|
1447
1752
|
<PageSkeleton variant="generic" />
|
|
1753
|
+
<PageSkeleton variant="dashboard" headerPlaceholder />
|
|
1754
|
+
<PageSkeleton variant="list" header={<Header title="Loading..." />} />
|
|
1448
1755
|
<PageSkeleton variant="list" padding="none" className="flex-1" />
|
|
1449
1756
|
```
|
|
1450
1757
|
|
|
1451
|
-
| Prop
|
|
1452
|
-
|
|
|
1453
|
-
| variant
|
|
1454
|
-
| count
|
|
1455
|
-
| padding
|
|
1456
|
-
|
|
|
1758
|
+
| Prop | Type | Default | Description |
|
|
1759
|
+
| ------------------- | ------------------------------------------------------------ | ---------------------- | --------------------------------------------- |
|
|
1760
|
+
| `variant` | `'dashboard' \| 'list' \| 'settings' \| 'card' \| 'generic'` | required | Layout preset |
|
|
1761
|
+
| `count` | `number` | 3 (list), 4 (settings) | Rows/items for list or settings variant |
|
|
1762
|
+
| `padding` | `'none' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Container padding |
|
|
1763
|
+
| `header` | `ReactNode` | — | Optional real header slot above content |
|
|
1764
|
+
| `headerPlaceholder` | `boolean \| 'compact' \| 'default'` | `false` | Skeleton header when real header is not ready |
|
|
1765
|
+
| `className` | `string` | — | Outer container classes |
|
|
1766
|
+
|
|
1767
|
+
## ScreenSkeleton
|
|
1768
|
+
|
|
1769
|
+
Convenience block for loading screens that need both header and content continuity.
|
|
1770
|
+
|
|
1771
|
+
```tsx
|
|
1772
|
+
import { ScreenSkeleton } from '@thewhileloop/whileui';
|
|
1773
|
+
|
|
1774
|
+
<ScreenSkeleton variant="dashboard" headerPlaceholder />
|
|
1775
|
+
<ScreenSkeleton variant="list" count={5} header={<Header title="Projects" />} />
|
|
1776
|
+
```
|
|
1777
|
+
|
|
1778
|
+
| Prop | Type | Default | Description |
|
|
1779
|
+
| ------------------- | ----------------------------------- | ----------- | ------------------------------------- |
|
|
1780
|
+
| `variant` | `PageSkeletonVariant` | `'generic'` | Skeleton content preset |
|
|
1781
|
+
| `count` | `number` | — | Rows/items for list/settings variants |
|
|
1782
|
+
| `padding` | `PageSkeletonPadding` | `'default'` | Inner content padding |
|
|
1783
|
+
| `header` | `ReactNode` | — | Real header slot |
|
|
1784
|
+
| `headerPlaceholder` | `boolean \| 'compact' \| 'default'` | `'default'` | Placeholder header |
|
|
1457
1785
|
|
|
1458
1786
|
## ErrorBoundary
|
|
1459
1787
|
|
|
@@ -1565,6 +1893,7 @@ import { ProductCard } from '@thewhileloop/whileui';
|
|
|
1565
1893
|
| Prop | Type | Default |
|
|
1566
1894
|
| ------- | ---------------------------- | ------------ |
|
|
1567
1895
|
| variant | `'vertical' \| 'horizontal'` | `'vertical'` |
|
|
1896
|
+
| loading | `boolean` | `false` |
|
|
1568
1897
|
|
|
1569
1898
|
## PricingCard
|
|
1570
1899
|
|
|
@@ -1587,6 +1916,90 @@ import { PricingCard } from '@thewhileloop/whileui';
|
|
|
1587
1916
|
/>;
|
|
1588
1917
|
```
|
|
1589
1918
|
|
|
1919
|
+
## SubscriptionCard
|
|
1920
|
+
|
|
1921
|
+
```tsx
|
|
1922
|
+
import { SubscriptionCard } from '@thewhileloop/whileui';
|
|
1923
|
+
|
|
1924
|
+
<SubscriptionCard
|
|
1925
|
+
planName="Pro"
|
|
1926
|
+
price="$29"
|
|
1927
|
+
period="/month"
|
|
1928
|
+
expiresAt="April 18, 2026"
|
|
1929
|
+
isActive
|
|
1930
|
+
onManage={() => {}}
|
|
1931
|
+
onUpgrade={() => {}}
|
|
1932
|
+
/>;
|
|
1933
|
+
```
|
|
1934
|
+
|
|
1935
|
+
| Prop | Type | Description |
|
|
1936
|
+
| ----------- | ------------ | --------------------------- |
|
|
1937
|
+
| `planName` | `string` | Current plan label |
|
|
1938
|
+
| `price` | `string` | Plan price |
|
|
1939
|
+
| `period` | `string` | Billing period label |
|
|
1940
|
+
| `expiresAt` | `string` | Renewal/expiry date text |
|
|
1941
|
+
| `isActive` | `boolean` | Active/inactive badge state |
|
|
1942
|
+
| `onManage` | `() => void` | Manage action |
|
|
1943
|
+
| `onUpgrade` | `() => void` | Upgrade action |
|
|
1944
|
+
| `loading` | `boolean` | Show skeleton placeholder |
|
|
1945
|
+
|
|
1946
|
+
## FeatureGate
|
|
1947
|
+
|
|
1948
|
+
```tsx
|
|
1949
|
+
import { FeatureGate } from '@thewhileloop/whileui';
|
|
1950
|
+
|
|
1951
|
+
<FeatureGate
|
|
1952
|
+
title="Advanced export is locked"
|
|
1953
|
+
description="Upgrade to unlock 4K export."
|
|
1954
|
+
buttonLabel="Upgrade"
|
|
1955
|
+
onUpgrade={() => {}}
|
|
1956
|
+
/>;
|
|
1957
|
+
```
|
|
1958
|
+
|
|
1959
|
+
Use `children` to show dimmed preview content with an overlay CTA.
|
|
1960
|
+
|
|
1961
|
+
## UsageBar
|
|
1962
|
+
|
|
1963
|
+
```tsx
|
|
1964
|
+
import { UsageBar } from '@thewhileloop/whileui';
|
|
1965
|
+
|
|
1966
|
+
<UsageBar label="AI generations" used={8} limit={20} />;
|
|
1967
|
+
```
|
|
1968
|
+
|
|
1969
|
+
| Prop | Type | Default | Description |
|
|
1970
|
+
| --------- | -------------------------------------- | ------- | ------------------------------ |
|
|
1971
|
+
| `label` | `string` | — | Usage label |
|
|
1972
|
+
| `used` | `number` | — | Used amount |
|
|
1973
|
+
| `limit` | `number` | — | Quota limit |
|
|
1974
|
+
| `variant` | `'default' \| 'warning' \| 'exceeded'` | auto | Optional explicit visual state |
|
|
1975
|
+
|
|
1976
|
+
## PlanToggle
|
|
1977
|
+
|
|
1978
|
+
```tsx
|
|
1979
|
+
import { PlanToggle } from '@thewhileloop/whileui';
|
|
1980
|
+
|
|
1981
|
+
<PlanToggle
|
|
1982
|
+
selected="monthly"
|
|
1983
|
+
monthlyLabel="Monthly"
|
|
1984
|
+
annualLabel="Annual"
|
|
1985
|
+
annualDiscount="Save 20%"
|
|
1986
|
+
onChange={(next) => {}}
|
|
1987
|
+
/>;
|
|
1988
|
+
```
|
|
1989
|
+
|
|
1990
|
+
## UpgradeBanner
|
|
1991
|
+
|
|
1992
|
+
```tsx
|
|
1993
|
+
import { UpgradeBanner } from '@thewhileloop/whileui';
|
|
1994
|
+
|
|
1995
|
+
<UpgradeBanner
|
|
1996
|
+
message="Unlock unlimited exports with Pro."
|
|
1997
|
+
actionLabel="See plans"
|
|
1998
|
+
onAction={() => {}}
|
|
1999
|
+
onDismiss={() => {}}
|
|
2000
|
+
/>;
|
|
2001
|
+
```
|
|
2002
|
+
|
|
1590
2003
|
## DrawerMenu
|
|
1591
2004
|
|
|
1592
2005
|
```tsx
|
|
@@ -1611,6 +2024,47 @@ import { DrawerMenu } from '@thewhileloop/whileui';
|
|
|
1611
2024
|
/>;
|
|
1612
2025
|
```
|
|
1613
2026
|
|
|
2027
|
+
| Prop | Type | Default |
|
|
2028
|
+
| --------------- | ------------------------------------------------------------------ | ---------------------- |
|
|
2029
|
+
| `frosted` | `boolean` | `false` |
|
|
2030
|
+
| `blurIntensity` | `number` | medium preset |
|
|
2031
|
+
| `blurTintToken` | `'surfaceElevated' \| 'surfaceTranslucent' \| 'card' \| 'popover'` | `'surfaceTranslucent'` |
|
|
2032
|
+
|
|
2033
|
+
## Roadmap
|
|
2034
|
+
|
|
2035
|
+
Tracked work items for future releases.
|
|
2036
|
+
|
|
2037
|
+
### New Components
|
|
2038
|
+
|
|
2039
|
+
- [ ] **Chip / Tag** — selectable, dismissible, multi-select with `tv()` variants, loading skeleton
|
|
2040
|
+
- [ ] **Rating / Stars** — interactive + read-only, half-star precision, swipe gesture, pairs with ProductCard
|
|
2041
|
+
- [ ] **Slider** — single + range (two thumbs), step marks, labels, Reanimated gesture, haptic on snap
|
|
2042
|
+
- [ ] **Carousel** — Reanimated-powered, auto-play, pagination dots, snap-to-item
|
|
2043
|
+
- [ ] **FAB (Floating Action Button)** — expandable action menu, Reanimated spring, auto-hide on scroll
|
|
2044
|
+
- [ ] **Combobox** — searchable select with keyboard navigation, empty state, async loading
|
|
2045
|
+
- [ ] **Data Table** — sortable headers, skeleton loading rows, row actions, responsive stacking
|
|
2046
|
+
- [ ] **Banner** — dismissible info/warning/success/destructive bar with icon + action, auto-dismiss timer
|
|
2047
|
+
- [ ] **Pagination** — compact (dots) + expanded (numbers) variants, edge-aware ellipsis, 44px touch targets
|
|
2048
|
+
- [ ] **Inline Calendar** — standalone calendar view reusing DatePicker logic, range selection
|
|
2049
|
+
|
|
2050
|
+
### Component-Level Loading (`loading` prop)
|
|
2051
|
+
|
|
2052
|
+
- [x] ProductCard, ListItem, NotificationItem, MetricCard, SubscriptionCard, PricingCard
|
|
2053
|
+
- [x] AppShell, Chat, FormModalScreen, CheckoutSummary (already had `loading`)
|
|
2054
|
+
- [ ] Add `loading` to remaining blocks: Header, BottomNav, DrawerMenu, TimelineFeed, SwipeableItem
|
|
2055
|
+
|
|
2056
|
+
### Documentation Gaps
|
|
2057
|
+
|
|
2058
|
+
- [ ] Add `## API` sections for: Textarea, Toggle, ToggleGroup, Label, Separator, Skeleton, AspectRatio, Collapsible, Text, View, Pressable
|
|
2059
|
+
- [ ] Add `## API` sections for blocks: FloatingBottomNav, TabBar, FormModalScreen, ErrorState, LoadingScreen, OnboardingScreen, ListItem, NotificationItem, MetricCard, SmartImage, TimelineFeed
|
|
2060
|
+
- [ ] Build `apps/site/` (docs website) with `registry.ts`, `demos.tsx`, `block-demos.tsx`, `props-data.ts`
|
|
2061
|
+
|
|
2062
|
+
### Infrastructure
|
|
2063
|
+
|
|
2064
|
+
- [ ] Publish to npm (`@thewhileloop/whileui`)
|
|
2065
|
+
- [ ] CI: typecheck + format check on PR
|
|
2066
|
+
- [ ] Automated visual regression tests (screenshot comparison)
|
|
2067
|
+
|
|
1614
2068
|
## License
|
|
1615
2069
|
|
|
1616
2070
|
MIT — [Source](https://github.com/whileloophq/whileui)
|