@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.
Files changed (235) hide show
  1. package/README.md +492 -38
  2. package/dist/blocks/chat/chat.d.ts.map +1 -1
  3. package/dist/blocks/chat/chat.js +15 -5
  4. package/dist/blocks/chat/chat.js.map +1 -1
  5. package/dist/blocks/commerce/feature-gate.d.ts +13 -0
  6. package/dist/blocks/commerce/feature-gate.d.ts.map +1 -0
  7. package/dist/blocks/commerce/feature-gate.js +13 -0
  8. package/dist/blocks/commerce/feature-gate.js.map +1 -0
  9. package/dist/blocks/commerce/index.d.ts +5 -0
  10. package/dist/blocks/commerce/index.d.ts.map +1 -1
  11. package/dist/blocks/commerce/index.js +5 -0
  12. package/dist/blocks/commerce/index.js.map +1 -1
  13. package/dist/blocks/commerce/metric-card.d.ts +3 -1
  14. package/dist/blocks/commerce/metric-card.d.ts.map +1 -1
  15. package/dist/blocks/commerce/metric-card.js +8 -1
  16. package/dist/blocks/commerce/metric-card.js.map +1 -1
  17. package/dist/blocks/commerce/plan-toggle.d.ts +12 -0
  18. package/dist/blocks/commerce/plan-toggle.d.ts.map +1 -0
  19. package/dist/blocks/commerce/plan-toggle.js +19 -0
  20. package/dist/blocks/commerce/plan-toggle.js.map +1 -0
  21. package/dist/blocks/commerce/pricing-card.d.ts +3 -1
  22. package/dist/blocks/commerce/pricing-card.d.ts.map +1 -1
  23. package/dist/blocks/commerce/pricing-card.js +9 -1
  24. package/dist/blocks/commerce/pricing-card.js.map +1 -1
  25. package/dist/blocks/commerce/product-card.d.ts +3 -1
  26. package/dist/blocks/commerce/product-card.d.ts.map +1 -1
  27. package/dist/blocks/commerce/product-card.js +16 -2
  28. package/dist/blocks/commerce/product-card.js.map +1 -1
  29. package/dist/blocks/commerce/subscription-card.d.ts +17 -0
  30. package/dist/blocks/commerce/subscription-card.d.ts.map +1 -0
  31. package/dist/blocks/commerce/subscription-card.js +18 -0
  32. package/dist/blocks/commerce/subscription-card.js.map +1 -0
  33. package/dist/blocks/commerce/upgrade-banner.d.ts +12 -0
  34. package/dist/blocks/commerce/upgrade-banner.d.ts.map +1 -0
  35. package/dist/blocks/commerce/upgrade-banner.js +9 -0
  36. package/dist/blocks/commerce/upgrade-banner.js.map +1 -0
  37. package/dist/blocks/commerce/usage-bar.d.ts +11 -0
  38. package/dist/blocks/commerce/usage-bar.d.ts.map +1 -0
  39. package/dist/blocks/commerce/usage-bar.js +33 -0
  40. package/dist/blocks/commerce/usage-bar.js.map +1 -0
  41. package/dist/blocks/datepicker/date-range-picker-modal.d.ts +3 -2
  42. package/dist/blocks/datepicker/date-range-picker-modal.d.ts.map +1 -1
  43. package/dist/blocks/datepicker/date-range-picker-modal.js +16 -3
  44. package/dist/blocks/datepicker/date-range-picker-modal.js.map +1 -1
  45. package/dist/blocks/datepicker/datepicker-inline.d.ts.map +1 -1
  46. package/dist/blocks/datepicker/datepicker-inline.js +3 -1
  47. package/dist/blocks/datepicker/datepicker-inline.js.map +1 -1
  48. package/dist/blocks/datepicker/datepicker-modal.d.ts +3 -2
  49. package/dist/blocks/datepicker/datepicker-modal.d.ts.map +1 -1
  50. package/dist/blocks/datepicker/datepicker-modal.js +16 -3
  51. package/dist/blocks/datepicker/datepicker-modal.js.map +1 -1
  52. package/dist/blocks/datepicker/use-calendar-theme.d.ts.map +1 -1
  53. package/dist/blocks/datepicker/use-calendar-theme.js +2 -1
  54. package/dist/blocks/datepicker/use-calendar-theme.js.map +1 -1
  55. package/dist/blocks/layout/app-shell.d.ts +5 -1
  56. package/dist/blocks/layout/app-shell.d.ts.map +1 -1
  57. package/dist/blocks/layout/app-shell.js +2 -2
  58. package/dist/blocks/layout/app-shell.js.map +1 -1
  59. package/dist/blocks/layout/confirm-action-sheet.d.ts +3 -2
  60. package/dist/blocks/layout/confirm-action-sheet.d.ts.map +1 -1
  61. package/dist/blocks/layout/confirm-action-sheet.js +23 -4
  62. package/dist/blocks/layout/confirm-action-sheet.js.map +1 -1
  63. package/dist/blocks/layout/form-modal-screen.d.ts.map +1 -1
  64. package/dist/blocks/layout/form-modal-screen.js +23 -1
  65. package/dist/blocks/layout/form-modal-screen.js.map +1 -1
  66. package/dist/blocks/layout/index.d.ts +2 -1
  67. package/dist/blocks/layout/index.d.ts.map +1 -1
  68. package/dist/blocks/layout/index.js +1 -0
  69. package/dist/blocks/layout/index.js.map +1 -1
  70. package/dist/blocks/layout/page-skeleton.d.ts +7 -1
  71. package/dist/blocks/layout/page-skeleton.d.ts.map +1 -1
  72. package/dist/blocks/layout/page-skeleton.js +7 -14
  73. package/dist/blocks/layout/page-skeleton.js.map +1 -1
  74. package/dist/blocks/layout/screen-skeleton.d.ts +16 -0
  75. package/dist/blocks/layout/screen-skeleton.d.ts.map +1 -0
  76. package/dist/blocks/layout/screen-skeleton.js +8 -0
  77. package/dist/blocks/layout/screen-skeleton.js.map +1 -0
  78. package/dist/blocks/layout/sheet.d.ts +3 -2
  79. package/dist/blocks/layout/sheet.d.ts.map +1 -1
  80. package/dist/blocks/layout/sheet.js +39 -6
  81. package/dist/blocks/layout/sheet.js.map +1 -1
  82. package/dist/blocks/layout/smart-input.d.ts.map +1 -1
  83. package/dist/blocks/layout/smart-input.js +8 -1
  84. package/dist/blocks/layout/smart-input.js.map +1 -1
  85. package/dist/blocks/lists/list-item.d.ts +3 -1
  86. package/dist/blocks/lists/list-item.d.ts.map +1 -1
  87. package/dist/blocks/lists/list-item.js +9 -1
  88. package/dist/blocks/lists/list-item.js.map +1 -1
  89. package/dist/blocks/lists/notification-item.d.ts +3 -1
  90. package/dist/blocks/lists/notification-item.d.ts.map +1 -1
  91. package/dist/blocks/lists/notification-item.js +10 -2
  92. package/dist/blocks/lists/notification-item.js.map +1 -1
  93. package/dist/blocks/lists/swipeable-item.d.ts.map +1 -1
  94. package/dist/blocks/lists/swipeable-item.js +5 -3
  95. package/dist/blocks/lists/swipeable-item.js.map +1 -1
  96. package/dist/blocks/navigation/bottom-nav.d.ts.map +1 -1
  97. package/dist/blocks/navigation/bottom-nav.js +9 -2
  98. package/dist/blocks/navigation/bottom-nav.js.map +1 -1
  99. package/dist/blocks/navigation/drawer-menu.d.ts +3 -2
  100. package/dist/blocks/navigation/drawer-menu.d.ts.map +1 -1
  101. package/dist/blocks/navigation/drawer-menu.js +88 -25
  102. package/dist/blocks/navigation/drawer-menu.js.map +1 -1
  103. package/dist/blocks/navigation/header.d.ts +1 -1
  104. package/dist/blocks/navigation/header.d.ts.map +1 -1
  105. package/dist/blocks/navigation/header.js +12 -3
  106. package/dist/blocks/navigation/header.js.map +1 -1
  107. package/dist/blocks/navigation/navigation-sidebar.d.ts.map +1 -1
  108. package/dist/blocks/navigation/navigation-sidebar.js +9 -2
  109. package/dist/blocks/navigation/navigation-sidebar.js.map +1 -1
  110. package/dist/blocks/navigation/tab-bar.d.ts.map +1 -1
  111. package/dist/blocks/navigation/tab-bar.js +8 -2
  112. package/dist/blocks/navigation/tab-bar.js.map +1 -1
  113. package/dist/components/accordion/accordion.d.ts +1 -1
  114. package/dist/components/accordion/accordion.d.ts.map +1 -1
  115. package/dist/components/accordion/accordion.js +10 -4
  116. package/dist/components/accordion/accordion.js.map +1 -1
  117. package/dist/components/alert/alert.js +4 -4
  118. package/dist/components/alert/alert.js.map +1 -1
  119. package/dist/components/alert-dialog/alert-dialog.d.ts +3 -2
  120. package/dist/components/alert-dialog/alert-dialog.d.ts.map +1 -1
  121. package/dist/components/alert-dialog/alert-dialog.js +15 -4
  122. package/dist/components/alert-dialog/alert-dialog.js.map +1 -1
  123. package/dist/components/badge/badge.js +6 -6
  124. package/dist/components/badge/badge.js.map +1 -1
  125. package/dist/components/button/button.d.ts.map +1 -1
  126. package/dist/components/button/button.js +11 -5
  127. package/dist/components/button/button.js.map +1 -1
  128. package/dist/components/card/card.d.ts +2 -1
  129. package/dist/components/card/card.d.ts.map +1 -1
  130. package/dist/components/card/card.js +12 -3
  131. package/dist/components/card/card.js.map +1 -1
  132. package/dist/components/checkbox/checkbox.d.ts.map +1 -1
  133. package/dist/components/checkbox/checkbox.js +7 -1
  134. package/dist/components/checkbox/checkbox.js.map +1 -1
  135. package/dist/components/context-menu/context-menu.d.ts +3 -2
  136. package/dist/components/context-menu/context-menu.d.ts.map +1 -1
  137. package/dist/components/context-menu/context-menu.js +12 -3
  138. package/dist/components/context-menu/context-menu.js.map +1 -1
  139. package/dist/components/data-row/data-row.d.ts.map +1 -1
  140. package/dist/components/data-row/data-row.js +8 -5
  141. package/dist/components/data-row/data-row.js.map +1 -1
  142. package/dist/components/dialog/dialog.d.ts +3 -2
  143. package/dist/components/dialog/dialog.d.ts.map +1 -1
  144. package/dist/components/dialog/dialog.js +14 -3
  145. package/dist/components/dialog/dialog.js.map +1 -1
  146. package/dist/components/dropdown-menu/dropdown-menu.d.ts +3 -2
  147. package/dist/components/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  148. package/dist/components/dropdown-menu/dropdown-menu.js +12 -3
  149. package/dist/components/dropdown-menu/dropdown-menu.js.map +1 -1
  150. package/dist/components/form-field/form-field.d.ts.map +1 -1
  151. package/dist/components/form-field/form-field.js +6 -3
  152. package/dist/components/form-field/form-field.js.map +1 -1
  153. package/dist/components/hover-card/hover-card.d.ts +2 -1
  154. package/dist/components/hover-card/hover-card.d.ts.map +1 -1
  155. package/dist/components/hover-card/hover-card.js +17 -2
  156. package/dist/components/hover-card/hover-card.js.map +1 -1
  157. package/dist/components/input/input.d.ts.map +1 -1
  158. package/dist/components/input/input.js +3 -1
  159. package/dist/components/input/input.js.map +1 -1
  160. package/dist/components/labeled-field/labeled-field.d.ts.map +1 -1
  161. package/dist/components/labeled-field/labeled-field.js +3 -1
  162. package/dist/components/labeled-field/labeled-field.js.map +1 -1
  163. package/dist/components/menubar/menubar.d.ts +3 -2
  164. package/dist/components/menubar/menubar.d.ts.map +1 -1
  165. package/dist/components/menubar/menubar.js +12 -3
  166. package/dist/components/menubar/menubar.js.map +1 -1
  167. package/dist/components/numeric-input/numeric-input.d.ts.map +1 -1
  168. package/dist/components/numeric-input/numeric-input.js +15 -3
  169. package/dist/components/numeric-input/numeric-input.js.map +1 -1
  170. package/dist/components/otp-input/index.d.ts +2 -0
  171. package/dist/components/otp-input/index.d.ts.map +1 -0
  172. package/dist/components/otp-input/index.js +2 -0
  173. package/dist/components/otp-input/index.js.map +1 -0
  174. package/dist/components/otp-input/otp-input.d.ts +97 -0
  175. package/dist/components/otp-input/otp-input.d.ts.map +1 -0
  176. package/dist/components/otp-input/otp-input.js +88 -0
  177. package/dist/components/otp-input/otp-input.js.map +1 -0
  178. package/dist/components/popover/popover.d.ts +2 -1
  179. package/dist/components/popover/popover.d.ts.map +1 -1
  180. package/dist/components/popover/popover.js +16 -3
  181. package/dist/components/popover/popover.js.map +1 -1
  182. package/dist/components/segmented-control/segmented-control.d.ts +3 -3
  183. package/dist/components/segmented-control/segmented-control.d.ts.map +1 -1
  184. package/dist/components/segmented-control/segmented-control.js +19 -5
  185. package/dist/components/segmented-control/segmented-control.js.map +1 -1
  186. package/dist/components/select/select.d.ts +2 -1
  187. package/dist/components/select/select.d.ts.map +1 -1
  188. package/dist/components/select/select.js +30 -11
  189. package/dist/components/select/select.js.map +1 -1
  190. package/dist/components/skeleton/skeleton.d.ts.map +1 -1
  191. package/dist/components/skeleton/skeleton.js +6 -2
  192. package/dist/components/skeleton/skeleton.js.map +1 -1
  193. package/dist/components/switch/switch.d.ts.map +1 -1
  194. package/dist/components/switch/switch.js +7 -1
  195. package/dist/components/switch/switch.js.map +1 -1
  196. package/dist/components/tabs/tabs.d.ts +3 -1
  197. package/dist/components/tabs/tabs.d.ts.map +1 -1
  198. package/dist/components/tabs/tabs.js +9 -3
  199. package/dist/components/tabs/tabs.js.map +1 -1
  200. package/dist/components/textarea/textarea.d.ts +2 -2
  201. package/dist/components/textarea/textarea.d.ts.map +1 -1
  202. package/dist/components/textarea/textarea.js +19 -6
  203. package/dist/components/textarea/textarea.js.map +1 -1
  204. package/dist/components/toast/toast.d.ts.map +1 -1
  205. package/dist/components/toast/toast.js +12 -2
  206. package/dist/components/toast/toast.js.map +1 -1
  207. package/dist/index.d.ts +5 -0
  208. package/dist/index.d.ts.map +1 -1
  209. package/dist/index.js +5 -0
  210. package/dist/index.js.map +1 -1
  211. package/dist/lib/frosted-surface.d.ts +33 -0
  212. package/dist/lib/frosted-surface.d.ts.map +1 -0
  213. package/dist/lib/frosted-surface.js +181 -0
  214. package/dist/lib/frosted-surface.js.map +1 -0
  215. package/dist/lib/interaction-tokens.d.ts +26 -0
  216. package/dist/lib/interaction-tokens.d.ts.map +1 -0
  217. package/dist/lib/interaction-tokens.js +70 -0
  218. package/dist/lib/interaction-tokens.js.map +1 -0
  219. package/dist/lib/react-native-classname.d.ts +23 -0
  220. package/dist/lib/react-native-classname.d.ts.map +1 -0
  221. package/dist/lib/react-native-classname.js +2 -0
  222. package/dist/lib/react-native-classname.js.map +1 -0
  223. package/dist/lib/theme-colors.d.ts +33 -4
  224. package/dist/lib/theme-colors.d.ts.map +1 -1
  225. package/dist/lib/theme-colors.js +249 -37
  226. package/dist/lib/theme-colors.js.map +1 -1
  227. package/dist/lib/visual-tokens.d.ts +39 -0
  228. package/dist/lib/visual-tokens.d.ts.map +1 -0
  229. package/dist/lib/visual-tokens.js +91 -0
  230. package/dist/lib/visual-tokens.js.map +1 -0
  231. package/dist/vite.d.ts +47 -0
  232. package/dist/vite.d.ts.map +1 -0
  233. package/dist/vite.js +110 -0
  234. package/dist/vite.js.map +1 -0
  235. 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 | Description |
337
- | ------------------- | ---------------------------------- |
338
- | **ProductCard** | Product card with badge/media |
339
- | **PricingCard** | Pricing tiers with feature list |
340
- | **CheckoutSummary** | Cart summary with line items |
341
- | **MetricCard** | Stats/progress card for dashboards |
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-*` hex fallbacks — `useThemeColors` will use them when `--color-*` is not RN-native.
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 (primary, foreground, muted, background, border, accent, destructive, etc.)
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 all semantic tokens. Use for RefreshControl, LinearGradient, charts. Falls back to `--app-color-*` when `--color-*` is not RN-native (e.g. `oklch(...)`).
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 | Type | Default | Description |
800
- | -------- | ------------------------------------- | ----------- | ----------------------------------- |
801
- | padding | `'none' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Card interior padding |
802
- | unstyled | `boolean` | `false` | Remove built-in card surface styles |
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 | Description |
1303
- | ----------------------- | -------------------------------------- | ------------------------------- |
1304
- | `value` | `string \| null` / `DateRange \| null` | Selected date(s) YYYY-MM-DD |
1305
- | `onValueChange` | `(date) => void` | Change handler |
1306
- | `open` / `onOpenChange` | — | Modal state (modal variants) |
1307
- | `trigger` | `ReactNode` | Custom trigger (modal variants) |
1308
- | `minDate` / `maxDate` | `string` | YYYY-MM-DD bounds |
1309
- | `theme` | `CalendarTheme` | Override calendar colors |
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 | Type | Default |
1356
- | ------------ | ---------------------------- | -------- |
1357
- | open | `boolean` | — |
1358
- | onOpenChange | `(open: boolean) => void` | — |
1359
- | maxHeight | `'half' \| 'full' \| number` | `'full'` |
1360
- | maxWidth | `number` | `360` |
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 | Type | Default | Description |
1452
- | --------- | ------------------------------------------------------------ | ---------------------- | --------------------------------------- |
1453
- | variant | `'dashboard' \| 'list' \| 'settings' \| 'card' \| 'generic'` | required | Layout preset |
1454
- | count | `number` | 3 (list), 4 (settings) | Rows/items for list or settings variant |
1455
- | padding | `'none' \| 'sm' \| 'default' \| 'lg'` | `'default'` | Container padding |
1456
- | className | `string` | — | Outer container classes |
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)