@thewhileloop/whileui 4.2.3 → 4.3.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 +1235 -663
- package/dist/agent/catalog.json +1737 -0
- package/dist/agent/llms-components.txt +2787 -0
- package/dist/agent/llms-full.txt +2962 -0
- package/dist/agent/llms.txt +152 -0
- package/dist/blocks/chat/chat.d.ts +10 -2
- package/dist/blocks/chat/chat.js +1 -1
- package/dist/blocks/chat/index.d.ts +1 -1
- package/dist/blocks/commerce/feature-gate.js +2 -2
- package/dist/blocks/commerce/metric-card.js +1 -1
- package/dist/blocks/commerce/subscription-card.d.ts +7 -0
- package/dist/blocks/commerce/usage-bar.d.ts +4 -0
- package/dist/blocks/layout/app-shell.d.ts +4 -0
- package/dist/blocks/layout/empty-state.js +1 -1
- package/dist/blocks/layout/error-state.js +1 -1
- package/dist/blocks/layout/page-skeleton.d.ts +1 -0
- package/dist/blocks/layout/pull-to-refresh-scroll-view.d.ts +1 -0
- package/dist/blocks/layout/screen-skeleton.d.ts +5 -0
- package/dist/blocks/lists/list-item.js +1 -1
- package/dist/blocks/lists/notification-item.js +1 -1
- package/dist/blocks/media/smart-image.js +4 -2
- package/dist/blocks/navigation/bottom-nav.js +1 -1
- package/dist/blocks/navigation/drawer-menu.js +1 -1
- package/dist/blocks/navigation/floating-bottom-nav.js +3 -1
- package/dist/blocks/navigation/tab-bar.js +1 -1
- package/dist/components/alert/alert.d.ts +7 -0
- package/dist/components/alert/alert.js +23 -4
- package/dist/components/button/button.d.ts +3 -0
- package/dist/components/button/button.js +2 -1
- package/dist/components/form-field/form-field.d.ts +18 -0
- package/dist/components/form-field/form-field.js +25 -4
- package/dist/components/input/input.js +2 -2
- package/dist/components/numeric-input/numeric-input.d.ts +3 -0
- package/dist/components/numeric-input/numeric-input.js +2 -2
- package/dist/components/otp-input/otp-input.d.ts +8 -0
- package/dist/components/pressable/pressable.d.ts +1 -0
- package/dist/components/segmented-control/segmented-control.d.ts +1 -0
- package/dist/components/surface/surface.d.ts +5 -0
- package/dist/components/textarea/textarea.js +2 -2
- package/dist/components/toast/toast.d.ts +10 -10
- package/dist/components/toast/toast.js +53 -35
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/frosted-surface.d.ts +3 -0
- package/dist/lib/theme-contract.js +12 -12
- package/dist/lib/theme-presets.js +9 -9
- package/dist/lib/theme-seeds.d.ts +40 -0
- package/dist/lib/theme-seeds.js +143 -0
- package/dist/metro.cjs +5 -4
- package/dist/tokens.d.ts +1 -0
- package/dist/tokens.js +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
# WhileUI Native
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Accessible, themeable React Native components for iOS, Android and the web.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Components and blocks built with [Uniwind](https://uniwind.dev) + Tailwind CSS v4. Every
|
|
6
|
+
one takes `className` and exports its `tv()` variants for extension, and the look comes
|
|
7
|
+
from design tokens in your `global.css`, so a new theme needs no component changes. Auth
|
|
8
|
+
and profile screens are copy-paste templates in
|
|
9
|
+
[`apps/showcase/catalog/templates`](apps/showcase/catalog/templates) rather than package
|
|
10
|
+
exports: copy them and make them yours.
|
|
6
11
|
|
|
7
|
-
**Requirements:** React 19.3+, React Native 0.88+, Expo 58+ (if using Expo).
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
production targets — iOS and Android set the interaction baseline. See
|
|
11
|
-
[ROADMAP.md](./ROADMAP.md) for the product boundary and extraction rules, and
|
|
12
|
-
[PLAN.md](./PLAN.md) for the supported stack and production contracts.
|
|
12
|
+
**Requirements:** React 19.3+, React Native 0.88+, Expo 58+ (if using Expo). See
|
|
13
|
+
[ROADMAP.md](./ROADMAP.md) for what the package owns and [PLAN.md](./PLAN.md) for the
|
|
14
|
+
supported stack.
|
|
13
15
|
|
|
14
16
|
## See it in action
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
The same app code on an iPhone simulator, an Android emulator and the web, running the
|
|
19
|
+
same tour side by side.
|
|
20
|
+
|
|
21
|
+
**The catalog** — every component and block, in light and dark
|
|
22
|
+
([`apps/showcase/catalog`](apps/showcase/catalog)):
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<img src="./docs/assets/whileui-catalog.gif" width="760" alt="The WhileUI component catalog running side by side on iPhone, Android and web: scrolling the components, switching from light to dark theme, and opening the Controls tab" />
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
**Clayhouse** — a made-up ceramics studio app built only from WhileUI, with its own theme
|
|
29
|
+
and fonts ([`apps/showcase/clayhouse`](apps/showcase/clayhouse)):
|
|
20
30
|
|
|
21
31
|
<p align="center">
|
|
22
|
-
<img src="./docs/assets/whileui-
|
|
23
|
-
<img src="./docs/assets/whileui-showcase-atelier.gif" width="300" alt="WhileUI Atelier mobile theme demonstrating compact controls, authentication forms, Select, and composable chat" />
|
|
32
|
+
<img src="./docs/assets/whileui-clayhouse.gif" width="760" alt="Clayhouse, a made-up ceramics studio app, running side by side on iPhone, Android and web: a live kiln temperature and firing curve, the glaze library, switching to night mode, and the member screen" />
|
|
24
33
|
</p>
|
|
25
34
|
|
|
26
35
|
## Installation
|
|
@@ -57,75 +66,19 @@ module.exports = withWhileUIMetro(config, {
|
|
|
57
66
|
> `expo-font` and `@expo/vector-icons` on the real `react-native`. Without that, Uniwind's
|
|
58
67
|
> import rewrite leaves every icon glyph silently missing on iOS and Android while web
|
|
59
68
|
> renders fine. Pass `exemptFromUniwindRewrite: ['pkg']` for any other package that
|
|
60
|
-
> imports a
|
|
69
|
+
> imports a `react-native` export Uniwind's wrapper lacks. It must be the outermost wrapper.
|
|
61
70
|
|
|
62
|
-
2. **global.css** at app root
|
|
71
|
+
2. **global.css** at app root, imported from `App.tsx`:
|
|
63
72
|
|
|
64
73
|
```css
|
|
65
74
|
@import 'tailwindcss';
|
|
66
75
|
@import 'uniwind';
|
|
67
76
|
|
|
68
|
-
/* Required when
|
|
69
|
-
|
|
70
|
-
component renders unstyled. */
|
|
77
|
+
/* Required when installed from npm: Tailwind does not scan node_modules, so without
|
|
78
|
+
this line every component renders unstyled. */
|
|
71
79
|
@source './node_modules/@thewhileloop/whileui/dist';
|
|
72
80
|
|
|
73
|
-
/*
|
|
74
|
-
@layer theme {
|
|
75
|
-
:root {
|
|
76
|
-
@variant light {
|
|
77
|
-
--color-background: oklch(1 0 0);
|
|
78
|
-
--color-foreground: oklch(0.1316 0.0041 17.69);
|
|
79
|
-
--color-card: oklch(1 0 0);
|
|
80
|
-
--color-card-foreground: oklch(0.1316 0.0041 17.69);
|
|
81
|
-
--color-primary: oklch(0.1316 0.0041 17.69);
|
|
82
|
-
--color-primary-foreground: oklch(0.98 0 0);
|
|
83
|
-
--color-secondary: oklch(0.9598 0.0017 17.69);
|
|
84
|
-
--color-secondary-foreground: oklch(0.1316 0.0041 17.69);
|
|
85
|
-
--color-muted: oklch(0.9598 0.0017 17.69);
|
|
86
|
-
--color-muted-foreground: oklch(0.5415 0.0135 17.69);
|
|
87
|
-
--color-accent: oklch(0.9598 0.0017 17.69);
|
|
88
|
-
--color-accent-foreground: oklch(0.1316 0.0041 17.69);
|
|
89
|
-
--color-destructive: oklch(0.5 0.19 27);
|
|
90
|
-
--color-destructive-foreground: oklch(0.98 0 0);
|
|
91
|
-
--color-border: oklch(0.9039 0.0034 17.69);
|
|
92
|
-
--color-input: oklch(0.9039 0.0034 17.69);
|
|
93
|
-
--color-ring: oklch(0.1316 0.0041 17.69);
|
|
94
|
-
--color-success: oklch(0.59 0.16 145);
|
|
95
|
-
--color-success-foreground: oklch(0.98 0 0);
|
|
96
|
-
--color-warning: oklch(0.75 0.18 85);
|
|
97
|
-
--color-warning-foreground: oklch(0.1316 0.0041 17.69);
|
|
98
|
-
--color-info: oklch(0.65 0.15 245);
|
|
99
|
-
--color-info-foreground: oklch(0.98 0 0);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
@variant dark {
|
|
103
|
-
--color-background: oklch(0.1316 0.0041 17.69);
|
|
104
|
-
--color-foreground: oklch(0.98 0 0);
|
|
105
|
-
--color-card: oklch(0.1316 0.0041 17.69);
|
|
106
|
-
--color-card-foreground: oklch(0.98 0 0);
|
|
107
|
-
--color-primary: oklch(0.98 0 0);
|
|
108
|
-
--color-primary-foreground: oklch(0.1316 0.0041 17.69);
|
|
109
|
-
--color-secondary: oklch(0.2104 0.0084 17.69);
|
|
110
|
-
--color-secondary-foreground: oklch(0.98 0 0);
|
|
111
|
-
--color-muted: oklch(0.2104 0.0084 17.69);
|
|
112
|
-
--color-muted-foreground: oklch(0.6961 0.0174 17.69);
|
|
113
|
-
--color-accent: oklch(0.2104 0.0084 17.69);
|
|
114
|
-
--color-accent-foreground: oklch(0.98 0 0);
|
|
115
|
-
--color-destructive: oklch(0.45 0.18 27);
|
|
116
|
-
--color-destructive-foreground: oklch(0.98 0 0);
|
|
117
|
-
--color-border: oklch(0.2104 0.0084 17.69);
|
|
118
|
-
--color-input: oklch(0.2104 0.0084 17.69);
|
|
119
|
-
--color-ring: oklch(0.8267 0.0206 17.69);
|
|
120
|
-
--color-success: oklch(0.59 0.16 145);
|
|
121
|
-
--color-success-foreground: oklch(0.98 0 0);
|
|
122
|
-
--color-warning: oklch(0.75 0.18 85);
|
|
123
|
-
--color-warning-foreground: oklch(0.1316 0.0041 17.69);
|
|
124
|
-
--color-info: oklch(0.65 0.15 245);
|
|
125
|
-
--color-info-foreground: oklch(0.98 0 0);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
81
|
+
/* Then your theme: generate it (Theming, below) rather than writing tokens by hand. */
|
|
129
82
|
```
|
|
130
83
|
|
|
131
84
|
3. **App.tsx**:
|
|
@@ -145,13 +98,11 @@ export default function App() {
|
|
|
145
98
|
}
|
|
146
99
|
```
|
|
147
100
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`portalHost` on their content only to target a `PortalHost` you placed yourself, such as
|
|
154
|
-
inside a navigation-presented modal screen that WhileUI does not host.
|
|
101
|
+
`PortalHost` (last child of the root) is where Select, Popover, Tooltip and HoverCard
|
|
102
|
+
render. Inside a Dialog, Sheet, menu or picker they use that overlay's own layer
|
|
103
|
+
automatically, and Android back or Escape closes the innermost layer first. Pass
|
|
104
|
+
`portalHost` only to target a host you placed yourself, such as in a modal screen your
|
|
105
|
+
navigator presents.
|
|
155
106
|
|
|
156
107
|
### Vite + React Native Web + WhileUI setup
|
|
157
108
|
|
|
@@ -215,36 +166,37 @@ function MyScreen() {
|
|
|
215
166
|
}
|
|
216
167
|
```
|
|
217
168
|
|
|
218
|
-
##
|
|
169
|
+
## AI agents
|
|
219
170
|
|
|
220
|
-
|
|
171
|
+
Every release ships its docs for agents inside the package, so an agent reads the docs for
|
|
172
|
+
the version you have installed, not whatever a website currently shows:
|
|
221
173
|
|
|
222
|
-
-
|
|
223
|
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
174
|
+
- `node_modules/@thewhileloop/whileui/dist/agent/llms.txt` — rules, composition grammar
|
|
175
|
+
and every component in one page. `llms-components.txt` adds each API section;
|
|
176
|
+
`llms-full.txt` is this README.
|
|
177
|
+
- [`@thewhileloop/whileui-mcp`](packages/mcp/README.md) serves the same catalog over MCP
|
|
178
|
+
(`list_components`, `get_component_docs`, `get_theme_tokens`, `get_guide`):
|
|
226
179
|
|
|
227
|
-
|
|
180
|
+
```bash
|
|
181
|
+
claude mcp add whileui -- npx -y @thewhileloop/whileui-mcp
|
|
182
|
+
```
|
|
228
183
|
|
|
229
|
-
-
|
|
230
|
-
|
|
184
|
+
- A [skill](skills/whileui/SKILL.md) for Claude Code, Cursor, Codex and other agents
|
|
185
|
+
that load skills: setup traps, how to compose and theme, and how to check a screen. It
|
|
186
|
+
points at the installed docs rather than copying them, and the build fails if it names
|
|
187
|
+
an API, MCP tool or file that no longer exists. Install it with
|
|
188
|
+
`npx skills add whileloophq/whileui`, or copy `skills/whileui` into your agent's skills
|
|
189
|
+
folder (for Claude Code, `.claude/skills/`).
|
|
231
190
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
## Philosophy
|
|
235
|
-
|
|
236
|
-
- **Copy-Paste Ownership** — Components live in _your_ project. No `node_modules` lock-in.
|
|
237
|
-
- **Beautiful by Default** — OKLCH color system, light/dark themes, polished out of the box.
|
|
238
|
-
- **Fully Customizable** — Every component uses `tv()` variants and accepts `className` overrides.
|
|
239
|
-
- **Accessible** — Proper ARIA roles, keyboard support, controlled/uncontrolled state.
|
|
240
|
-
- **Tree-Shakeable** — Only imports what you use. `sideEffects: false`.
|
|
191
|
+
The docs and catalog are generated from this README at build time, and the build fails if
|
|
192
|
+
a component named here is not exported.
|
|
241
193
|
|
|
242
194
|
## Quick Reference (AI / Code Generation)
|
|
243
195
|
|
|
244
196
|
- **Full-screen:** `AppShell` + `Header` in `header` + `BottomNav` in `bottomNav` + content in `children`
|
|
245
197
|
- **No-jump loading:** keep `AppShell` mounted, set `loading` + `skeleton` (e.g., `PageSkeleton`/`ScreenSkeleton`)
|
|
246
198
|
- **Layout:** `Stack` (vertical), `Row` (horizontal) — both support `gap`, `align`, `justify`
|
|
247
|
-
- **Auth callbacks:** Auth templates use objects: `onSubmit({ email, password })`, `onSubmit({ firstName, lastName, email, password })`, etc. Copy templates from `apps/showcase/templates/auth/`.
|
|
199
|
+
- **Auth callbacks:** Auth templates use objects: `onSubmit({ email, password })`, `onSubmit({ firstName, lastName, email, password })`, etc. Copy templates from `apps/showcase/catalog/templates/auth/`.
|
|
248
200
|
- **PortalHost:** Add `<PortalHost />` at app root for Select, Popover, Tooltip, HoverCard.
|
|
249
201
|
Inside any WhileUI overlay they render into that overlay's layer without configuration.
|
|
250
202
|
- **Motion:** Wrap the root in `<MotionProvider>`; use `useMotionPolicy()` for semantic
|
|
@@ -255,7 +207,7 @@ function MyScreen() {
|
|
|
255
207
|
`@source './node_modules/@thewhileloop/whileui/dist';` or every component renders unstyled.
|
|
256
208
|
- **States:** `LoadingState`, `EmptyState` and `ErrorState` share one shape; `ErrorState`
|
|
257
209
|
takes `onRetry`. `Text variant="eyebrow"` is the muted section label above a title.
|
|
258
|
-
- **Reference:** Block props in `packages/ui/src/blocks` (core) and `apps/showcase/templates/` (auth, profile); flow patterns in README "Flow Patterns" section.
|
|
210
|
+
- **Reference:** Block props in `packages/ui/src/blocks` (core) and `apps/showcase/catalog/templates/` (auth, profile); flow patterns in README "Flow Patterns" section.
|
|
259
211
|
|
|
260
212
|
### Composition grammar
|
|
261
213
|
|
|
@@ -360,7 +312,7 @@ function MyScreen() {
|
|
|
360
312
|
|
|
361
313
|
### Auth (Copy from showcase templates)
|
|
362
314
|
|
|
363
|
-
Copy from `apps/showcase/templates/auth/`:
|
|
315
|
+
Copy from `apps/showcase/catalog/templates/auth/`:
|
|
364
316
|
|
|
365
317
|
| Block | File | Description |
|
|
366
318
|
| ---------------------- | -------------------------- | ------------------------------------- |
|
|
@@ -418,7 +370,7 @@ Copy from `apps/showcase/templates/auth/`:
|
|
|
418
370
|
|
|
419
371
|
### Profile & Settings (Copy from showcase templates)
|
|
420
372
|
|
|
421
|
-
Copy from `apps/showcase/templates/profile/`:
|
|
373
|
+
Copy from `apps/showcase/catalog/templates/profile/`:
|
|
422
374
|
|
|
423
375
|
| Block | File | Description |
|
|
424
376
|
| ------------------- | ---------------------- | ----------------------------------- |
|
|
@@ -545,11 +497,17 @@ import { Stack, Row } from '@thewhileloop/whileui';
|
|
|
545
497
|
</Stack>;
|
|
546
498
|
```
|
|
547
499
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
|
551
|
-
|
|
|
552
|
-
|
|
|
500
|
+
<!-- api:Row -->
|
|
501
|
+
|
|
502
|
+
| Prop | Type | Default | Description |
|
|
503
|
+
| --------- | ------------------------------------------------------------------- | ---------- | ----------- |
|
|
504
|
+
| className | `string` | — | |
|
|
505
|
+
| gap | `'none' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | |
|
|
506
|
+
| align | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | `'center'` | |
|
|
507
|
+
| justify | `'start' \| 'center' \| 'end' \| 'between' \| 'around' \| 'evenly'` | `'start'` | |
|
|
508
|
+
|
|
509
|
+
Also accepts the props of `ViewProps`.
|
|
510
|
+
<!-- /api:Row -->
|
|
553
511
|
|
|
554
512
|
### Container
|
|
555
513
|
|
|
@@ -561,11 +519,16 @@ controls drift apart.
|
|
|
561
519
|
<Container size="lg">{children}</Container>
|
|
562
520
|
```
|
|
563
521
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
|
567
|
-
|
|
|
568
|
-
|
|
|
522
|
+
<!-- api:Container -->
|
|
523
|
+
|
|
524
|
+
| Prop | Type | Default | Description |
|
|
525
|
+
| --------- | ---------------------------------------- | ------- | ----------- |
|
|
526
|
+
| className | `string` | — | |
|
|
527
|
+
| size | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | |
|
|
528
|
+
| fill | `boolean` | `false` | |
|
|
529
|
+
|
|
530
|
+
Also accepts the props of `ViewProps`.
|
|
531
|
+
<!-- /api:Container -->
|
|
569
532
|
|
|
570
533
|
No platform branch, and none is needed: every size is wider than any phone, so on iOS
|
|
571
534
|
and Android the cap never engages and it renders as a plain full-width `View`. The same
|
|
@@ -630,142 +593,51 @@ Blocks with `loading` support: ProductCard, ListItem, NotificationItem, MetricCa
|
|
|
630
593
|
|
|
631
594
|
Block props: see TypeScript interfaces in `packages/ui/src/blocks`.
|
|
632
595
|
|
|
633
|
-
## Quick Start
|
|
634
|
-
|
|
635
|
-
```bash
|
|
636
|
-
bun install
|
|
637
|
-
cd apps/showcase
|
|
638
|
-
bun run dev
|
|
639
|
-
# Or: npx expo start --web
|
|
640
|
-
```
|
|
641
|
-
|
|
642
|
-
## Project Structure
|
|
643
|
-
|
|
644
|
-
```
|
|
645
|
-
whileui/
|
|
646
|
-
├── packages/
|
|
647
|
-
│ └── ui/
|
|
648
|
-
│ └── src/
|
|
649
|
-
│ ├── components/ # All components (copy these!)
|
|
650
|
-
│ │ ├── button/
|
|
651
|
-
│ │ ├── card/
|
|
652
|
-
│ │ ├── form-field/
|
|
653
|
-
│ │ ├── numeric-input/
|
|
654
|
-
│ │ ├── segmented-control/
|
|
655
|
-
│ │ ├── data-row/
|
|
656
|
-
│ │ ├── dialog/
|
|
657
|
-
│ │ └── ...
|
|
658
|
-
│ ├── blocks/ # Pre-built screens (core only)
|
|
659
|
-
│ │ ├── chat/
|
|
660
|
-
│ │ ├── navigation/
|
|
661
|
-
│ │ ├── layout/
|
|
662
|
-
│ │ ├── lists/
|
|
663
|
-
│ │ ├── commerce/
|
|
664
|
-
│ │ ├── splash/
|
|
665
|
-
│ │ └── media/
|
|
666
|
-
│ ├── lib/ # Utilities
|
|
667
|
-
│ │ ├── cn.ts # clsx + tailwind-merge
|
|
668
|
-
│ │ ├── tv.ts # tailwind-variants re-export
|
|
669
|
-
│ │ ├── font-context.ts
|
|
670
|
-
│ │ └── theme-bridge.ts
|
|
671
|
-
│ └── index.ts # Barrel export
|
|
672
|
-
├── apps/
|
|
673
|
-
│ └── showcase/ # Expo demo app
|
|
674
|
-
│ ├── templates/ # Copy-paste templates (auth, profile)
|
|
675
|
-
│ │ ├── auth/
|
|
676
|
-
│ │ └── profile/
|
|
677
|
-
│ ├── App.tsx # Component showcase
|
|
678
|
-
│ ├── global.css # Theme variables (OKLCH) — at app root!
|
|
679
|
-
│ └── metro.config.js # Uniwind + monorepo config
|
|
680
|
-
└── package.json # bun monorepo root
|
|
681
|
-
```
|
|
682
|
-
|
|
683
596
|
## Theming
|
|
684
597
|
|
|
685
|
-
|
|
598
|
+
A theme is a set of CSS variables in `global.css`, in OKLCH, with a `light` and a `dark`
|
|
599
|
+
variant. Every component reads the same contract, so a theme must define **every** token
|
|
600
|
+
in both modes: a missing one silently falls back to an unstyled value at runtime.
|
|
686
601
|
|
|
687
|
-
|
|
688
|
-
@variant light {
|
|
689
|
-
--color-primary: oklch(0.6 0.2 160);
|
|
690
|
-
--color-background: oklch(1 0 0);
|
|
691
|
-
/* ... */
|
|
692
|
-
}
|
|
602
|
+
### Generate a theme
|
|
693
603
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
/* ... */
|
|
698
|
-
}
|
|
699
|
-
```
|
|
604
|
+
`themeFromSeeds` builds a complete light and dark theme from three colours, and
|
|
605
|
+
`themePresetToCss` writes every token, including the hex fallbacks React Native APIs need,
|
|
606
|
+
ready to paste into `global.css`:
|
|
700
607
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
Minimal contract example:
|
|
712
|
-
|
|
713
|
-
```css
|
|
714
|
-
@layer theme {
|
|
715
|
-
:root {
|
|
716
|
-
@variant light {
|
|
717
|
-
--color-background: oklch(1 0 0);
|
|
718
|
-
--color-foreground: oklch(0.15 0 0);
|
|
719
|
-
--color-card: oklch(1 0 0);
|
|
720
|
-
--color-card-foreground: oklch(0.15 0 0);
|
|
721
|
-
--color-popover: oklch(1 0 0);
|
|
722
|
-
--color-popover-foreground: oklch(0.15 0 0);
|
|
723
|
-
--color-primary: oklch(0.2 0 0);
|
|
724
|
-
--color-primary-foreground: oklch(0.98 0 0);
|
|
725
|
-
--color-secondary: oklch(0.95 0 0);
|
|
726
|
-
--color-secondary-foreground: oklch(0.15 0 0);
|
|
727
|
-
--color-muted: oklch(0.95 0 0);
|
|
728
|
-
--color-muted-foreground: oklch(0.45 0 0);
|
|
729
|
-
--color-accent: oklch(0.9 0.05 180);
|
|
730
|
-
--color-accent-foreground: oklch(0.15 0 0);
|
|
731
|
-
--color-destructive: oklch(0.58 0.2 26);
|
|
732
|
-
--color-destructive-foreground: oklch(0.98 0 0);
|
|
733
|
-
--color-border: oklch(0.9 0 0);
|
|
734
|
-
--color-input: oklch(0.92 0 0);
|
|
735
|
-
--color-ring: oklch(0.22 0 0);
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
Switch themes at runtime via Uniwind:
|
|
742
|
-
|
|
743
|
-
```tsx
|
|
744
|
-
import { Uniwind } from 'uniwind';
|
|
608
|
+
```ts
|
|
609
|
+
import { checkThemeContrast, themeFromSeeds, themePresetToCss } from '@thewhileloop/whileui/tokens';
|
|
610
|
+
|
|
611
|
+
const theme = themeFromSeeds({
|
|
612
|
+
name: 'studio',
|
|
613
|
+
description: 'Bone paper and burnt amber',
|
|
614
|
+
light: { background: '#f0ede9', foreground: '#13161c', primary: '#b65400' },
|
|
615
|
+
// optional: `dark` seeds (otherwise a dark page with the brand lifted to read on it),
|
|
616
|
+
// `overrides` for any base colour, `visual` for radius and density tokens
|
|
617
|
+
});
|
|
745
618
|
|
|
746
|
-
|
|
619
|
+
checkThemeContrast(theme.light).filter((c) => !c.pass);
|
|
620
|
+
// [{ pair: 'primary on background', ratio: 4.23, min: 4.5, pass: false }, …]
|
|
621
|
+
const css = themePresetToCss(theme); // paste into global.css
|
|
747
622
|
```
|
|
748
623
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
624
|
+
Seeds are kept as given; the colours it derives (muted text, text on each fill, status
|
|
625
|
+
colours) are tuned to WCAG AA. `checkThemeContrast` checks any palette, hand-written ones
|
|
626
|
+
included: every text pairing at 4.5:1 and the focus ring at 3:1. A failing seed is
|
|
627
|
+
reported, never changed, because a brand colour is a brand decision. The built-in presets,
|
|
628
|
+
`WHILEUI_PRESET_SOFT` and `WHILEUI_PRESET_ATELIER`, go through `themePresetToCss` the same
|
|
629
|
+
way.
|
|
752
630
|
|
|
753
|
-
|
|
631
|
+
Switch at runtime with `Uniwind.setTheme('dark')` (or `'light'`, `'system'`). A named
|
|
632
|
+
extra theme is registered with `extraThemes: ['name']` in `metro.config.js` and defined
|
|
633
|
+
with `@variant name`. The full token list, with each default, is in `theme-contract.ts`,
|
|
634
|
+
and agents can read it from the MCP server's `get_theme_tokens`.
|
|
754
635
|
|
|
755
|
-
|
|
756
|
-
@variant light {
|
|
757
|
-
/* ... other tokens ... */
|
|
758
|
-
--color-surface-elevated: oklch(0.98 0.01 95 / 0.4);
|
|
759
|
-
--color-surface-border: oklch(1 0 0 / 0.25);
|
|
760
|
-
--color-surface-highlight: oklch(1 0 0 / 0.4);
|
|
761
|
-
}
|
|
762
|
-
```
|
|
636
|
+
### Frosted surfaces
|
|
763
637
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
**Android note:** frosted panels usually need denser tint than iOS/web. WhileUI now scales frosted tint alpha on Android with `--ui-frosted-android-tint-alpha-scale` (default `1.18`). Set it to `1` to match other platforms exactly, or raise it if your Android surfaces still feel washed out.
|
|
638
|
+
Cards and overlays take `frosted`, `blurIntensity` and `blurTintToken`
|
|
639
|
+
(`'surfaceElevated' | 'surfaceTranslucent' | 'card' | 'popover'`). Without a blur view
|
|
640
|
+
they tint only; register `expo-blur` once for real blur:
|
|
769
641
|
|
|
770
642
|
```tsx
|
|
771
643
|
import { BlurView } from 'expo-blur';
|
|
@@ -774,62 +646,40 @@ import { registerFrostedBlurView } from '@thewhileloop/whileui';
|
|
|
774
646
|
registerFrostedBlurView(BlurView);
|
|
775
647
|
```
|
|
776
648
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
- `frosted?: boolean`
|
|
782
|
-
- `blurIntensity?: number`
|
|
783
|
-
- `blurTintToken?: 'surfaceElevated' | 'surfaceTranslucent' | 'card' | 'popover'`
|
|
784
|
-
|
|
785
|
-
Default blur presets map to CSS visual tokens:
|
|
649
|
+
For a frosted look across a whole theme, make `surface-elevated`, `surface-border` and
|
|
650
|
+
`surface-highlight` translucent, in your light and dark variants or in a separate theme.
|
|
651
|
+
The tuning tokens:
|
|
786
652
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
653
|
+
| Token | Default | Effect |
|
|
654
|
+
| ------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------- |
|
|
655
|
+
| `--ui-blur-intensity-subtle` / `-medium` / `-strong` | 18 / 26 / 34 | The three blur presets |
|
|
656
|
+
| `--ui-frosted-android-tint-alpha-scale` | 1.18 | Android needs denser tint; `1` matches iOS and web |
|
|
657
|
+
| `--ui-frosted-android-experimental-blur` | 1 | Uses `expo-blur`'s experimental Android blur; `0` turns it off |
|
|
658
|
+
| `--ui-frosted-highlight-height` | 2 | Top sheen in px; `0` removes it |
|
|
659
|
+
| `--ui-blur-saturation-pct` | 145 | Web saturation multiplier |
|
|
660
|
+
| `--ui-frosted-backdrop-blur-intensity`, `-scale` | 22, 0.72 | Backdrop blur, and its ratio when a component overrides blur |
|
|
661
|
+
| `--ui-drawer-frosted-inset`, `-radius`, `--ui-drawer-content-top-padding` | 10, 34, 0 | Frosted drawer shell |
|
|
790
662
|
|
|
791
|
-
|
|
663
|
+
### Colours for React Native APIs
|
|
792
664
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
-
|
|
796
|
-
|
|
797
|
-
- `--ui-frosted-android-tint-alpha-scale` (Android-only alpha multiplier applied to frosted tint and backdrop layers)
|
|
798
|
-
- `--ui-frosted-android-experimental-blur` (`1` enables `expo-blur` Android experimental path)
|
|
799
|
-
- `--ui-drawer-frosted-inset` (floating inset for frosted drawer shells)
|
|
800
|
-
- `--ui-drawer-frosted-radius` (drawer corner radius in px)
|
|
801
|
-
- `--ui-drawer-content-top-padding` (drawer content top spacing baseline)
|
|
802
|
-
|
|
803
|
-
### Theme Colors for RN Primitives
|
|
804
|
-
|
|
805
|
-
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.
|
|
665
|
+
Icons, `ActivityIndicator` and other native APIs need hex or rgb, not OKLCH.
|
|
666
|
+
`useThemeColors()` returns the whole palette as React Native colours, falling back to the
|
|
667
|
+
`--app-color-*` tokens a generated theme includes; `useIconColors()` is the subset icons
|
|
668
|
+
use, with `muted` mapped to `mutedForeground`.
|
|
806
669
|
|
|
807
670
|
```tsx
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
// Full palette (core semantic colors + status + overlay/effect tokens)
|
|
812
|
-
const colors = useThemeColors();
|
|
813
|
-
|
|
814
|
-
// Shorthand for icons: foreground, muted, primary, primaryForeground, accent, destructive
|
|
815
|
-
const iconColors = useIconColors();
|
|
816
|
-
|
|
817
|
-
<Feather name="heart" size={20} color={iconColors.muted} />
|
|
818
|
-
<Spinner color={colors.foreground} /> // Spinner defaults to this when color not passed
|
|
671
|
+
const icons = useIconColors();
|
|
672
|
+
<Feather name="heart" size={20} color={icons.muted} />;
|
|
819
673
|
```
|
|
820
674
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
Input, Textarea, NumericInput, SmartInput, Spinner, and LoadingScreen default to theme colors when you omit `placeholderTextColor` or `spinnerColor`.
|
|
675
|
+
Input, Textarea, NumericInput, SmartInput, Spinner and LoadingScreen pick theme colours
|
|
676
|
+
when you omit `placeholderTextColor` or `spinnerColor`.
|
|
825
677
|
|
|
826
678
|
### Interaction and motion
|
|
827
679
|
|
|
828
680
|
Press opacity, disabled opacity, motion durations, the responsive spring, and the drawer
|
|
829
681
|
open and close durations are package constants on `INTERACTION_TOKENS`. They are not CSS
|
|
830
|
-
variables
|
|
831
|
-
`--ui-disabled-*`, `--ui-inactive-opacity`, `--ui-motion-*`, or `--ui-drawer-open-duration`
|
|
832
|
-
/ `--ui-drawer-close-duration` declarations from a theme: nothing reads them.
|
|
682
|
+
variables, so a theme does not change them.
|
|
833
683
|
|
|
834
684
|
For a custom control, read that table:
|
|
835
685
|
|
|
@@ -853,118 +703,6 @@ motion.durations.normal;
|
|
|
853
703
|
motion.springs.responsive;
|
|
854
704
|
```
|
|
855
705
|
|
|
856
|
-
Optional RN fallback tokens (hex/rgb/hsl/named):
|
|
857
|
-
|
|
858
|
-
```css
|
|
859
|
-
@layer theme {
|
|
860
|
-
:root {
|
|
861
|
-
@variant light {
|
|
862
|
-
--app-color-primary: #000000;
|
|
863
|
-
--app-color-primary-foreground: #ffffff;
|
|
864
|
-
--app-color-foreground: #000000;
|
|
865
|
-
--app-color-background: #ffffff;
|
|
866
|
-
--app-color-card: #ffffff;
|
|
867
|
-
--app-color-card-foreground: #000000;
|
|
868
|
-
--app-color-popover: #ffffff;
|
|
869
|
-
--app-color-popover-foreground: #000000;
|
|
870
|
-
--app-color-secondary: #f5f5f5;
|
|
871
|
-
--app-color-secondary-foreground: #171717;
|
|
872
|
-
--app-color-muted: #f5f5f5;
|
|
873
|
-
--app-color-muted-foreground: #737373;
|
|
874
|
-
--app-color-border: #e5e5e5;
|
|
875
|
-
--app-color-input: #e5e5e5;
|
|
876
|
-
--app-color-ring: #94a3b8;
|
|
877
|
-
--app-color-accent: #22c55e;
|
|
878
|
-
--app-color-accent-foreground: #0a0a0a;
|
|
879
|
-
--app-color-destructive: #dc2626;
|
|
880
|
-
--app-color-destructive-foreground: #ffffff;
|
|
881
|
-
--app-color-success: #16a34a;
|
|
882
|
-
--app-color-success-foreground: #ffffff;
|
|
883
|
-
--app-color-warning: #f59e0b;
|
|
884
|
-
--app-color-warning-foreground: #111827;
|
|
885
|
-
--app-color-info: #3b82f6;
|
|
886
|
-
--app-color-info-foreground: #ffffff;
|
|
887
|
-
--app-color-overlay: rgba(0, 0, 0, 0.4);
|
|
888
|
-
--app-color-overlay-strong: rgba(0, 0, 0, 0.55);
|
|
889
|
-
--app-color-surface-elevated: #ffffff;
|
|
890
|
-
--app-color-surface-translucent: rgba(255, 255, 255, 0.5);
|
|
891
|
-
--app-color-surface-translucent-border: rgba(255, 255, 255, 0.18);
|
|
892
|
-
--app-color-surface-border: rgba(255, 255, 255, 0.3);
|
|
893
|
-
--app-color-surface-highlight: rgba(255, 255, 255, 0.3);
|
|
894
|
-
--app-color-state-hover: rgba(0, 0, 0, 0.05);
|
|
895
|
-
--app-color-state-pressed: rgba(0, 0, 0, 0.12);
|
|
896
|
-
--app-color-state-disabled: rgba(0, 0, 0, 0.4);
|
|
897
|
-
}
|
|
898
|
-
@variant dark {
|
|
899
|
-
--app-color-primary: #ffffff;
|
|
900
|
-
--app-color-primary-foreground: #000000;
|
|
901
|
-
--app-color-foreground: #ffffff;
|
|
902
|
-
--app-color-background: #000000;
|
|
903
|
-
--app-color-card: #0f0f10;
|
|
904
|
-
--app-color-card-foreground: #ffffff;
|
|
905
|
-
--app-color-popover: #121214;
|
|
906
|
-
--app-color-popover-foreground: #ffffff;
|
|
907
|
-
--app-color-secondary: #2e2e2e;
|
|
908
|
-
--app-color-secondary-foreground: #ffffff;
|
|
909
|
-
--app-color-muted: #2e2e2e;
|
|
910
|
-
--app-color-muted-foreground: #999999;
|
|
911
|
-
--app-color-border: #3d3d3d;
|
|
912
|
-
--app-color-input: #3d3d3d;
|
|
913
|
-
--app-color-ring: #7c889a;
|
|
914
|
-
--app-color-accent: #22c55e;
|
|
915
|
-
--app-color-accent-foreground: #0a0a0a;
|
|
916
|
-
--app-color-destructive: #dc2626;
|
|
917
|
-
--app-color-destructive-foreground: #ffffff;
|
|
918
|
-
--app-color-success: #22c55e;
|
|
919
|
-
--app-color-success-foreground: #0a0a0a;
|
|
920
|
-
--app-color-warning: #fbbf24;
|
|
921
|
-
--app-color-warning-foreground: #111827;
|
|
922
|
-
--app-color-info: #60a5fa;
|
|
923
|
-
--app-color-info-foreground: #111827;
|
|
924
|
-
--app-color-overlay: rgba(0, 0, 0, 0.5);
|
|
925
|
-
--app-color-overlay-strong: rgba(0, 0, 0, 0.7);
|
|
926
|
-
--app-color-surface-elevated: #111315;
|
|
927
|
-
--app-color-surface-translucent: rgba(17, 19, 21, 0.45);
|
|
928
|
-
--app-color-surface-translucent-border: rgba(255, 255, 255, 0.18);
|
|
929
|
-
--app-color-surface-border: rgba(255, 255, 255, 0.18);
|
|
930
|
-
--app-color-surface-highlight: rgba(255, 255, 255, 0.18);
|
|
931
|
-
--app-color-state-hover: rgba(255, 255, 255, 0.08);
|
|
932
|
-
--app-color-state-pressed: rgba(255, 255, 255, 0.15);
|
|
933
|
-
--app-color-state-disabled: rgba(255, 255, 255, 0.4);
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
```
|
|
938
|
-
|
|
939
|
-
Or use the first-party theme bridge with optional persistence. The hook owns theme
|
|
940
|
-
state and exposes event-driven setters:
|
|
941
|
-
|
|
942
|
-
```tsx
|
|
943
|
-
import { useThemeBridge, type ThemeBridgeAdapter } from '@thewhileloop/whileui';
|
|
944
|
-
|
|
945
|
-
const adapter: ThemeBridgeAdapter = {
|
|
946
|
-
loadThemeMode: async () => 'system',
|
|
947
|
-
saveThemeMode: async (mode) => {
|
|
948
|
-
await storage.setItem('theme-mode', mode);
|
|
949
|
-
},
|
|
950
|
-
};
|
|
951
|
-
|
|
952
|
-
const { mode, resolvedTheme, setMode, cycleMode } = useThemeBridge({ adapter });
|
|
953
|
-
```
|
|
954
|
-
|
|
955
|
-
The bridge loads a new adapter before saving into it. If a user changes the mode while
|
|
956
|
-
storage is still loading, that explicit choice wins over the late persisted value.
|
|
957
|
-
|
|
958
|
-
When another store already owns the mode, mount the controlled bridge once near the
|
|
959
|
-
application root. Change `mode` in the owning event handler; the bridge applies and
|
|
960
|
-
persists it without mirroring state back through a callback effect.
|
|
961
|
-
|
|
962
|
-
```tsx
|
|
963
|
-
import { ThemeBridge } from '@thewhileloop/whileui';
|
|
964
|
-
|
|
965
|
-
<ThemeBridge mode={mode} adapter={adapter} />;
|
|
966
|
-
```
|
|
967
|
-
|
|
968
706
|
## Focus rings
|
|
969
707
|
|
|
970
708
|
Every interactive component draws a themed focus ring on web. It uses `:focus-visible`,
|
|
@@ -992,14 +730,6 @@ supplies a themed replacement in the same class. The one exemption is a visually
|
|
|
992
730
|
input, where a visible proxy shows the focus instead — `OTPInput` does this with its
|
|
993
731
|
blinking cursor.
|
|
994
732
|
|
|
995
|
-
## Tech Stack
|
|
996
|
-
|
|
997
|
-
- **Styling**: [Uniwind](https://uniwind.dev) (free tier) + Tailwind CSS v4
|
|
998
|
-
- **Variants**: [tailwind-variants](https://www.tailwind-variants.org/) (`tv()`)
|
|
999
|
-
- **Merging**: [clsx](https://github.com/lukeed/clsx) + [tailwind-merge](https://github.com/dcastil/tailwind-merge)
|
|
1000
|
-
- **Expo**: SDK 54
|
|
1001
|
-
- **Monorepo**: bun + Turborepo
|
|
1002
|
-
|
|
1003
733
|
# API Reference
|
|
1004
734
|
|
|
1005
735
|
## Pressable
|
|
@@ -1022,12 +752,17 @@ import { Pressable, Text } from '@thewhileloop/whileui';
|
|
|
1022
752
|
`style` contracts. Feedback changes opacity without changing layout. Native press state
|
|
1023
753
|
comes directly from React Native; web adds a motion-policy-aware opacity transition.
|
|
1024
754
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
|
1028
|
-
|
|
|
1029
|
-
|
|
|
1030
|
-
|
|
|
755
|
+
<!-- api:Pressable -->
|
|
756
|
+
|
|
757
|
+
| Prop | Type | Default | Description |
|
|
758
|
+
| ---------------- | ----------------- | ----------- | --------------------------------------------------------------------------- |
|
|
759
|
+
| className | `string` | — | Additional Tailwind classes. |
|
|
760
|
+
| feedback | `PressedVariant` | `'none'` | Semantic press feedback. `none` preserves a plain React Native Pressable. |
|
|
761
|
+
| disabledFeedback | `DisabledVariant` | `'default'` | Opacity used while the control is unavailable. |
|
|
762
|
+
| loading | `boolean` | `false` | Loading controls are busy and non-interactive, just like disabled controls. |
|
|
763
|
+
|
|
764
|
+
Also accepts the props of React Native's `PressableProps`.
|
|
765
|
+
<!-- /api:Pressable -->
|
|
1031
766
|
|
|
1032
767
|
## Button
|
|
1033
768
|
|
|
@@ -1046,13 +781,20 @@ import { Button, ButtonText, ButtonIcon } from '@thewhileloop/whileui';
|
|
|
1046
781
|
</Button>;
|
|
1047
782
|
```
|
|
1048
783
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
|
1052
|
-
|
|
|
1053
|
-
|
|
|
1054
|
-
|
|
|
1055
|
-
|
|
|
784
|
+
<!-- api:Button -->
|
|
785
|
+
|
|
786
|
+
| Prop | Type | Default | Description |
|
|
787
|
+
| ---------------- | ----------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------- |
|
|
788
|
+
| children | `React.ReactNode` | — | |
|
|
789
|
+
| className | `string` | — | Additional Tailwind classes. |
|
|
790
|
+
| disabled | `boolean` | `false` | Disable the button. |
|
|
791
|
+
| loading | `boolean` | `false` | Shows a centered spinner, exposes busy state, and prevents interaction. |
|
|
792
|
+
| disabledFeedback | `DisabledVariant` | — | Opacity used while the control is unavailable. |
|
|
793
|
+
| size | `'sm' \| 'lg' \| 'default' \| 'icon'` | `'default'` | |
|
|
794
|
+
| variant | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'default'` | |
|
|
795
|
+
|
|
796
|
+
Also accepts the props of `PressableProps`.
|
|
797
|
+
<!-- /api:Button -->
|
|
1056
798
|
|
|
1057
799
|
## Text
|
|
1058
800
|
|
|
@@ -1060,10 +802,16 @@ import { Button, ButtonText, ButtonIcon } from '@thewhileloop/whileui';
|
|
|
1060
802
|
classes are unnecessary for normal hierarchy. `heading` also sets
|
|
1061
803
|
`accessibilityRole="header"` unless the caller supplies another role.
|
|
1062
804
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
|
1066
|
-
|
|
|
805
|
+
<!-- api:Text -->
|
|
806
|
+
|
|
807
|
+
| Prop | Type | Default | Description |
|
|
808
|
+
| --------- | ---------------------------------------------------------------------------------------------------------- | ----------- | ----------- |
|
|
809
|
+
| className | `string` | — | |
|
|
810
|
+
| size | `'base' \| 'xs' \| 'sm' \| 'lg' \| 'xl' \| '2xl' \| '3xl'` | — | |
|
|
811
|
+
| variant | `'default' \| 'heading' \| 'title' \| 'subtitle' \| 'body' \| 'caption' \| 'label' \| 'eyebrow' \| 'code'` | `'default'` | |
|
|
812
|
+
|
|
813
|
+
Also accepts the props of React Native's `TextProps`.
|
|
814
|
+
<!-- /api:Text -->
|
|
1067
815
|
|
|
1068
816
|
## Input
|
|
1069
817
|
|
|
@@ -1076,12 +824,19 @@ import { Input } from '@thewhileloop/whileui';
|
|
|
1076
824
|
On web, filled Input, Textarea, NumericInput, and SmartInput surfaces preserve their
|
|
1077
825
|
themed background, foreground, and caret when the browser autofills them.
|
|
1078
826
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
|
1082
|
-
|
|
|
1083
|
-
|
|
|
1084
|
-
|
|
|
827
|
+
<!-- api:Input -->
|
|
828
|
+
|
|
829
|
+
| Prop | Type | Default | Description |
|
|
830
|
+
| -------------- | --------------------------------- | ----------- | ----------- |
|
|
831
|
+
| aria-invalid | `boolean` | — | |
|
|
832
|
+
| className | `string` | — | |
|
|
833
|
+
| prefix | `React.ReactNode` | — | |
|
|
834
|
+
| suffix | `React.ReactNode` | — | |
|
|
835
|
+
| inputClassName | `string` | — | |
|
|
836
|
+
| variant | `'default' \| 'error' \| 'plain'` | `'default'` | |
|
|
837
|
+
|
|
838
|
+
Also accepts the props of `TextInputProps`.
|
|
839
|
+
<!-- /api:Input -->
|
|
1085
840
|
|
|
1086
841
|
## NumericInput
|
|
1087
842
|
|
|
@@ -1100,15 +855,28 @@ import { NumericInput } from '@thewhileloop/whileui';
|
|
|
1100
855
|
/>;
|
|
1101
856
|
```
|
|
1102
857
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
|
1106
|
-
|
|
|
1107
|
-
|
|
|
1108
|
-
|
|
|
1109
|
-
|
|
|
1110
|
-
| prefix
|
|
1111
|
-
|
|
|
858
|
+
<!-- api:NumericInput -->
|
|
859
|
+
|
|
860
|
+
| Prop | Type | Default | Description |
|
|
861
|
+
| -------------- | --------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
862
|
+
| aria-invalid | `boolean` | — | |
|
|
863
|
+
| className | `string` | — | |
|
|
864
|
+
| inputClassName | `string` | — | |
|
|
865
|
+
| prefix | `React.ReactNode` | — | |
|
|
866
|
+
| suffix | `React.ReactNode` | — | |
|
|
867
|
+
| value | `number \| null` | — | Controlled numeric value. |
|
|
868
|
+
| defaultValue | `number` | — | |
|
|
869
|
+
| onValueChange | `(value: number \| null) => void` | — | Numeric value change callback. |
|
|
870
|
+
| min | `number` | — | |
|
|
871
|
+
| max | `number` | — | |
|
|
872
|
+
| step | `number` | `1` | |
|
|
873
|
+
| showSteppers | `boolean` | `false` | Show decrement/increment controls. |
|
|
874
|
+
| unstyled | `boolean` | `false` | Drop the field shell (border, background, radius, padding) for embedding inside another field shell like LabeledField. |
|
|
875
|
+
| size | `'default' \| 'compact'` | `'default'` | |
|
|
876
|
+
| variant | `'default' \| 'error'` | `'default'` | |
|
|
877
|
+
|
|
878
|
+
Also accepts the props of `TextInputProps`.
|
|
879
|
+
<!-- /api:NumericInput -->
|
|
1112
880
|
|
|
1113
881
|
## OTPInput
|
|
1114
882
|
|
|
@@ -1124,16 +892,23 @@ import { OTPInput } from '@thewhileloop/whileui';
|
|
|
1124
892
|
/>;
|
|
1125
893
|
```
|
|
1126
894
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
|
1130
|
-
|
|
|
1131
|
-
|
|
|
1132
|
-
|
|
|
1133
|
-
|
|
|
1134
|
-
|
|
|
1135
|
-
|
|
|
1136
|
-
|
|
|
895
|
+
<!-- api:OTPInput -->
|
|
896
|
+
|
|
897
|
+
| Prop | Type | Default | Description |
|
|
898
|
+
| ------------- | ------------------------- | ----------- | ---------------------------------- |
|
|
899
|
+
| length | `number` | `6` | Number of digit cells. |
|
|
900
|
+
| value | `string` | — | Controlled value. |
|
|
901
|
+
| onValueChange | `(value: string) => void` | — | Called on each digit change. |
|
|
902
|
+
| onComplete | `(code: string) => void` | — | Called when all digits are filled. |
|
|
903
|
+
| variant | `'default' \| 'error'` | `'default'` | Error variant triggers shake. |
|
|
904
|
+
| size | `'default' \| 'compact'` | `'default'` | Cell size. |
|
|
905
|
+
| secure | `boolean` | `false` | Mask digits with dots. |
|
|
906
|
+
| disabled | `boolean` | `false` | |
|
|
907
|
+
| autoFocus | `boolean` | `false` | Focus first cell on mount. |
|
|
908
|
+
| className | `string` | — | |
|
|
909
|
+
|
|
910
|
+
Also accepts the props of `ViewProps`.
|
|
911
|
+
<!-- /api:OTPInput -->
|
|
1137
912
|
|
|
1138
913
|
## FormField
|
|
1139
914
|
|
|
@@ -1161,6 +936,10 @@ import {
|
|
|
1161
936
|
custom controls; labels, hints, messages, density, and required indicators remain part
|
|
1162
937
|
of the compound field rather than each input.
|
|
1163
938
|
|
|
939
|
+
`FormLabel` names the `Input`, `Textarea` or `NumericInput` beside it for assistive
|
|
940
|
+
technology (`aria-labelledby` on web and Android; `useFormFieldState().labelId` gives a
|
|
941
|
+
custom control the same id). A label passed to the control itself wins.
|
|
942
|
+
|
|
1164
943
|
## Surface
|
|
1165
944
|
|
|
1166
945
|
```tsx
|
|
@@ -1171,14 +950,22 @@ import { Surface } from '@thewhileloop/whileui';
|
|
|
1171
950
|
</Surface>;
|
|
1172
951
|
```
|
|
1173
952
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
|
1177
|
-
|
|
|
1178
|
-
|
|
|
1179
|
-
|
|
|
1180
|
-
|
|
|
1181
|
-
|
|
|
953
|
+
<!-- api:Surface -->
|
|
954
|
+
|
|
955
|
+
| Prop | Type | Default | Description |
|
|
956
|
+
| ------------- | ------------------------------ | ------------------- | ------------------------------------ |
|
|
957
|
+
| level | `SurfaceLevel` | `'base'` | Semantic background and depth tier. |
|
|
958
|
+
| padding | `SurfacePaddingSize` | `'default'` | Token-backed interior spacing. |
|
|
959
|
+
| radius | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'xl'` | Token-backed corner radius. |
|
|
960
|
+
| bordered | `boolean` | `true` | Draw the theme hairline border. |
|
|
961
|
+
| shadow | `ShadowTier` | — | Override the level's depth. |
|
|
962
|
+
| className | `string` | — | |
|
|
963
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
964
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
965
|
+
| blurTintToken | `FrostedTintToken` | `LEVEL_TINT[level]` | Tint token used for frosted mode. |
|
|
966
|
+
|
|
967
|
+
Also accepts the props of `ViewProps`.
|
|
968
|
+
<!-- /api:Surface -->
|
|
1182
969
|
|
|
1183
970
|
## LabeledField
|
|
1184
971
|
|
|
@@ -1220,9 +1007,21 @@ import {
|
|
|
1220
1007
|
</SegmentedControl>;
|
|
1221
1008
|
```
|
|
1222
1009
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
|
1010
|
+
<!-- api:SegmentedControl -->
|
|
1011
|
+
|
|
1012
|
+
| Prop | Type | Default | Description |
|
|
1013
|
+
| ------------- | ------------------------- | ----------- | -------------------------- |
|
|
1014
|
+
| value | `string` | — | |
|
|
1015
|
+
| defaultValue | `string` | `''` | |
|
|
1016
|
+
| onValueChange | `(value: string) => void` | — | |
|
|
1017
|
+
| variant | `'default' \| 'pill'` | `'default'` | Pill = rounded-full items. |
|
|
1018
|
+
| wrap | `boolean \| 'auto'` | `true` | |
|
|
1019
|
+
| size | `'default' \| 'compact'` | `'default'` | |
|
|
1020
|
+
| disabled | `boolean` | `false` | |
|
|
1021
|
+
| className | `string` | — | |
|
|
1022
|
+
|
|
1023
|
+
Also accepts the props of `ViewProps`.
|
|
1024
|
+
<!-- /api:SegmentedControl -->
|
|
1226
1025
|
|
|
1227
1026
|
## DataRow
|
|
1228
1027
|
|
|
@@ -1281,13 +1080,20 @@ import {
|
|
|
1281
1080
|
</Card>;
|
|
1282
1081
|
```
|
|
1283
1082
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
|
1287
|
-
|
|
|
1288
|
-
|
|
|
1289
|
-
|
|
|
1290
|
-
|
|
|
1083
|
+
<!-- api:Card -->
|
|
1084
|
+
|
|
1085
|
+
| Prop | Type | Default | Description |
|
|
1086
|
+
| ------------- | ------------------------------------- | ----------- | ------------------------------------------------------- |
|
|
1087
|
+
| shadow | `ShadowTier` | `'sm'` | Elevation tier; 'sm' by default, 'none' for flat cards. |
|
|
1088
|
+
| className | `string` | — | |
|
|
1089
|
+
| padding | `'none' \| 'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
1090
|
+
| unstyled | `boolean` | `false` | |
|
|
1091
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1092
|
+
| blurIntensity | `number` | — | Override blur amount directly. |
|
|
1093
|
+
| blurTintToken | `FrostedTintToken` | — | Tint token used for frosted mode. |
|
|
1094
|
+
|
|
1095
|
+
Also accepts the props of `ViewProps`.
|
|
1096
|
+
<!-- /api:Card -->
|
|
1291
1097
|
|
|
1292
1098
|
## Badge
|
|
1293
1099
|
|
|
@@ -1299,9 +1105,15 @@ import { Badge, BadgeText } from '@thewhileloop/whileui';
|
|
|
1299
1105
|
</Badge>;
|
|
1300
1106
|
```
|
|
1301
1107
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
|
1108
|
+
<!-- api:Badge -->
|
|
1109
|
+
|
|
1110
|
+
| Prop | Type | Default | Description |
|
|
1111
|
+
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ----------- |
|
|
1112
|
+
| className | `string` | — | |
|
|
1113
|
+
| variant | `'default' \| 'soft' \| 'destructive' \| 'outline' \| 'secondary' \| 'success' \| 'warning' \| 'info' \| 'soft-secondary' \| 'soft-destructive' \| 'soft-success' \| 'soft-warning' \| 'soft-info'` | `'default'` | |
|
|
1114
|
+
|
|
1115
|
+
Also accepts the props of `ViewProps`.
|
|
1116
|
+
<!-- /api:Badge -->
|
|
1305
1117
|
|
|
1306
1118
|
## Alert
|
|
1307
1119
|
|
|
@@ -1312,11 +1124,24 @@ import { Alert, AlertTitle, AlertDescription } from '@thewhileloop/whileui';
|
|
|
1312
1124
|
<AlertTitle>Heads up!</AlertTitle>
|
|
1313
1125
|
<AlertDescription>Something happened.</AlertDescription>
|
|
1314
1126
|
</Alert>;
|
|
1127
|
+
|
|
1128
|
+
// Shown after a failed submit: also spoken on iOS when it appears.
|
|
1129
|
+
<Alert variant="destructive" announce>
|
|
1130
|
+
<AlertTitle>Could not save</AlertTitle>
|
|
1131
|
+
<AlertDescription>Check your connection and try again.</AlertDescription>
|
|
1132
|
+
</Alert>;
|
|
1315
1133
|
```
|
|
1316
1134
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
|
1135
|
+
<!-- api:Alert -->
|
|
1136
|
+
|
|
1137
|
+
| Prop | Type | Default | Description |
|
|
1138
|
+
| --------- | ---------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1139
|
+
| announce | `boolean` | `false` | Speak the alert when it appears, for alerts shown in response to an action such as a failed submit. Android and the web already read it through the live region; iOS has none, so this announces its text once. Leave it off for alerts that are part of a screen's content, or every visit would read them aloud. |
|
|
1140
|
+
| className | `string` | — | |
|
|
1141
|
+
| variant | `'default' \| 'destructive' \| 'success' \| 'warning' \| 'info'` | `'default'` | |
|
|
1142
|
+
|
|
1143
|
+
Also accepts the props of `ViewProps`.
|
|
1144
|
+
<!-- /api:Alert -->
|
|
1320
1145
|
|
|
1321
1146
|
## Dialog
|
|
1322
1147
|
|
|
@@ -1357,11 +1182,17 @@ import {
|
|
|
1357
1182
|
|
|
1358
1183
|
`DialogContent` supports frosted props:
|
|
1359
1184
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
|
1363
|
-
|
|
|
1364
|
-
|
|
|
1185
|
+
<!-- api:DialogContent -->
|
|
1186
|
+
|
|
1187
|
+
| Prop | Type | Default | Description |
|
|
1188
|
+
| ------------- | ------------------ | ---------------------- | ------------------------------------ |
|
|
1189
|
+
| className | `string` | — | |
|
|
1190
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1191
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1192
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1193
|
+
|
|
1194
|
+
Also accepts the props of `ViewProps`.
|
|
1195
|
+
<!-- /api:DialogContent -->
|
|
1365
1196
|
|
|
1366
1197
|
## AlertDialog
|
|
1367
1198
|
|
|
@@ -1399,12 +1230,18 @@ import {
|
|
|
1399
1230
|
|
|
1400
1231
|
`AlertDialogContent` supports the same frosted props as `DialogContent`.
|
|
1401
1232
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
|
1405
|
-
|
|
|
1406
|
-
|
|
|
1407
|
-
|
|
|
1233
|
+
<!-- api:AlertDialogContent -->
|
|
1234
|
+
|
|
1235
|
+
| Prop | Type | Default | Description |
|
|
1236
|
+
| ------------- | ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1237
|
+
| className | `string` | — | |
|
|
1238
|
+
| presentation | `AlertDialogPresentation` | `'modal'` | `modal` — RN `Modal` + themed card (default). `native` — `Alert.alert` on iOS/Android (system chrome); ignored on web. `overlay` — same themed card as `modal`, but absolute fill inside the parent view (nest inside your `Modal`). |
|
|
1239
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1240
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1241
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1242
|
+
|
|
1243
|
+
Also accepts the props of `ViewProps`.
|
|
1244
|
+
<!-- /api:AlertDialogContent -->
|
|
1408
1245
|
|
|
1409
1246
|
### React Native: stacked modals
|
|
1410
1247
|
|
|
@@ -1423,11 +1260,18 @@ import { Checkbox } from '@thewhileloop/whileui';
|
|
|
1423
1260
|
<Checkbox checked={checked} onCheckedChange={setChecked} />;
|
|
1424
1261
|
```
|
|
1425
1262
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
|
1429
|
-
|
|
|
1430
|
-
|
|
|
1263
|
+
<!-- api:Checkbox -->
|
|
1264
|
+
|
|
1265
|
+
| Prop | Type | Default | Description |
|
|
1266
|
+
| --------------- | ---------------------------- | ------- | ----------- |
|
|
1267
|
+
| checked | `boolean` | — | |
|
|
1268
|
+
| defaultChecked | `boolean` | `false` | |
|
|
1269
|
+
| onCheckedChange | `(checked: boolean) => void` | — | |
|
|
1270
|
+
| disabled | `boolean` | `false` | |
|
|
1271
|
+
| className | `string` | — | |
|
|
1272
|
+
|
|
1273
|
+
Also accepts the props of `ViewProps`.
|
|
1274
|
+
<!-- /api:Checkbox -->
|
|
1431
1275
|
|
|
1432
1276
|
## Switch
|
|
1433
1277
|
|
|
@@ -1437,10 +1281,18 @@ import { Switch } from '@thewhileloop/whileui';
|
|
|
1437
1281
|
<Switch checked={checked} onCheckedChange={setChecked} />;
|
|
1438
1282
|
```
|
|
1439
1283
|
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
|
1443
|
-
|
|
|
1284
|
+
<!-- api:Switch -->
|
|
1285
|
+
|
|
1286
|
+
| Prop | Type | Default | Description |
|
|
1287
|
+
| --------------- | ---------------------------- | ------- | ----------- |
|
|
1288
|
+
| checked | `boolean` | — | |
|
|
1289
|
+
| defaultChecked | `boolean` | `false` | |
|
|
1290
|
+
| onCheckedChange | `(checked: boolean) => void` | — | |
|
|
1291
|
+
| disabled | `boolean` | `false` | |
|
|
1292
|
+
| className | `string` | — | |
|
|
1293
|
+
|
|
1294
|
+
Also accepts the props of `ViewProps`.
|
|
1295
|
+
<!-- /api:Switch -->
|
|
1444
1296
|
|
|
1445
1297
|
## RadioGroup
|
|
1446
1298
|
|
|
@@ -1483,11 +1335,19 @@ itself to the collision-available viewport, and wraps long option labels inside
|
|
|
1483
1335
|
|
|
1484
1336
|
`SelectContent` supports optional frosted props:
|
|
1485
1337
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
|
1489
|
-
|
|
|
1490
|
-
|
|
|
1338
|
+
<!-- api:SelectContent -->
|
|
1339
|
+
|
|
1340
|
+
| Prop | Type | Default | Description |
|
|
1341
|
+
| ------------- | ------------------------------------------------------------------ | ------------- | ------------------------------------ |
|
|
1342
|
+
| className | `string` | — | |
|
|
1343
|
+
| portalHost | `string` | — | |
|
|
1344
|
+
| insets | `{ top?: number; bottom?: number; left?: number; right?: number }` | — | |
|
|
1345
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1346
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
1347
|
+
| blurTintToken | `FrostedTintToken` | `'popover'` | Tint token used for frosted mode. |
|
|
1348
|
+
|
|
1349
|
+
Also accepts the props of the underlying primitive.
|
|
1350
|
+
<!-- /api:SelectContent -->
|
|
1491
1351
|
|
|
1492
1352
|
`SelectContent` keeps its listbox inside the safe area by default; pass `insets` only to
|
|
1493
1353
|
change that margin. A list taller than the screen scrolls inside the listbox on every
|
|
@@ -1536,10 +1396,17 @@ import {
|
|
|
1536
1396
|
</Accordion>;
|
|
1537
1397
|
```
|
|
1538
1398
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
|
1542
|
-
|
|
|
1399
|
+
<!-- api:Accordion -->
|
|
1400
|
+
|
|
1401
|
+
| Prop | Type | Default | Description |
|
|
1402
|
+
| ------------ | -------------------- | ---------- | ----------- |
|
|
1403
|
+
| type | `AccordionType` | `'single'` | |
|
|
1404
|
+
| defaultValue | `string \| string[]` | — | |
|
|
1405
|
+
| collapsible | `boolean` | `true` | |
|
|
1406
|
+
| className | `string` | — | |
|
|
1407
|
+
|
|
1408
|
+
Also accepts the props of `ViewProps`.
|
|
1409
|
+
<!-- /api:Accordion -->
|
|
1543
1410
|
|
|
1544
1411
|
## Avatar
|
|
1545
1412
|
|
|
@@ -1552,9 +1419,15 @@ import { Avatar, AvatarImage, AvatarFallback } from '@thewhileloop/whileui';
|
|
|
1552
1419
|
</Avatar>;
|
|
1553
1420
|
```
|
|
1554
1421
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
|
1422
|
+
<!-- api:Avatar -->
|
|
1423
|
+
|
|
1424
|
+
| Prop | Type | Default | Description |
|
|
1425
|
+
| --------- | ----------------------------------- | ----------- | ----------- |
|
|
1426
|
+
| className | `string` | — | |
|
|
1427
|
+
| size | `'sm' \| 'lg' \| 'xl' \| 'default'` | `'default'` | |
|
|
1428
|
+
|
|
1429
|
+
Also accepts the props of `ViewProps`.
|
|
1430
|
+
<!-- /api:Avatar -->
|
|
1558
1431
|
|
|
1559
1432
|
`AvatarImage` keeps `AvatarFallback` visible while loading or after an error. Its
|
|
1560
1433
|
`onLoadingStatusChange` callback reports `'loading'`, `'loaded'`, and `'error'`, including
|
|
@@ -1568,10 +1441,17 @@ import { Progress } from '@thewhileloop/whileui';
|
|
|
1568
1441
|
<Progress value={50} size="default" />;
|
|
1569
1442
|
```
|
|
1570
1443
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
|
1574
|
-
|
|
|
1444
|
+
<!-- api:Progress -->
|
|
1445
|
+
|
|
1446
|
+
| Prop | Type | Default | Description |
|
|
1447
|
+
| ------------------ | --------------------------- | ----------- | ----------- |
|
|
1448
|
+
| value | `number` | `0` | |
|
|
1449
|
+
| indicatorClassName | `string` | — | |
|
|
1450
|
+
| className | `string` | — | |
|
|
1451
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
1452
|
+
|
|
1453
|
+
Also accepts the props of `ViewProps`.
|
|
1454
|
+
<!-- /api:Progress -->
|
|
1575
1455
|
|
|
1576
1456
|
## Toast
|
|
1577
1457
|
|
|
@@ -1599,6 +1479,11 @@ toast({ title: 'Success', description: 'Saved!', variant: 'success' });
|
|
|
1599
1479
|
Each toast owns its duration from the moment it appears; adding or dismissing another
|
|
1600
1480
|
toast does not restart timers already in progress.
|
|
1601
1481
|
|
|
1482
|
+
Every variant sits on the theme's `popover` surface; `success` and `destructive` mark the
|
|
1483
|
+
toast with a status dot and tinted border rather than a solid fill. The stack is capped
|
|
1484
|
+
at `max-w-md` and centred, which never engages on a phone. A toast is announced politely
|
|
1485
|
+
to screen readers without taking focus, and its slide-in follows the motion policy.
|
|
1486
|
+
|
|
1602
1487
|
## Popover
|
|
1603
1488
|
|
|
1604
1489
|
```tsx
|
|
@@ -1618,11 +1503,19 @@ import { Popover, PopoverTrigger, PopoverContent } from '@thewhileloop/whileui';
|
|
|
1618
1503
|
|
|
1619
1504
|
`PopoverContent` supports frosted props:
|
|
1620
1505
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
|
1624
|
-
|
|
|
1625
|
-
|
|
|
1506
|
+
<!-- api:PopoverContent -->
|
|
1507
|
+
|
|
1508
|
+
| Prop | Type | Default | Description |
|
|
1509
|
+
| ------------- | ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------- |
|
|
1510
|
+
| className | `string` | — | |
|
|
1511
|
+
| portalHost | `string` | — | |
|
|
1512
|
+
| insets | `Partial<OverlayInsets>` | — | Collision padding. Defaults to safe area plus a margin so the panel never sits flush against an edge. |
|
|
1513
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1514
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
1515
|
+
| blurTintToken | `FrostedTintToken` | `'popover'` | Tint token used for frosted mode. |
|
|
1516
|
+
|
|
1517
|
+
Also accepts the props of the underlying primitive.
|
|
1518
|
+
<!-- /api:PopoverContent -->
|
|
1626
1519
|
|
|
1627
1520
|
## DropdownMenu
|
|
1628
1521
|
|
|
@@ -1710,7 +1603,7 @@ announces as a heading to assistive technology — as do `DropdownMenuLabel` and
|
|
|
1710
1603
|
## SignInForm
|
|
1711
1604
|
|
|
1712
1605
|
Copy `sign-in-form.tsx` and its sibling `auth-form-footer.tsx` from
|
|
1713
|
-
`apps/showcase/templates/auth/`, then:
|
|
1606
|
+
`apps/showcase/catalog/templates/auth/`, then:
|
|
1714
1607
|
|
|
1715
1608
|
```tsx
|
|
1716
1609
|
import { SignInForm } from './templates/auth'; // or your path
|
|
@@ -1735,7 +1628,7 @@ import { SignInForm } from './templates/auth'; // or your path
|
|
|
1735
1628
|
## SignUpForm
|
|
1736
1629
|
|
|
1737
1630
|
Copy `sign-up-form.tsx` and its sibling `auth-form-footer.tsx` from
|
|
1738
|
-
`apps/showcase/templates/auth/`, then:
|
|
1631
|
+
`apps/showcase/catalog/templates/auth/`, then:
|
|
1739
1632
|
|
|
1740
1633
|
```tsx
|
|
1741
1634
|
import { SignUpForm } from './templates/auth'; // or your path
|
|
@@ -1789,14 +1682,20 @@ import { AppShell, PageSkeleton } from '@thewhileloop/whileui';
|
|
|
1789
1682
|
</AppShell>;
|
|
1790
1683
|
```
|
|
1791
1684
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
|
1795
|
-
|
|
|
1796
|
-
|
|
|
1797
|
-
|
|
|
1798
|
-
|
|
|
1799
|
-
|
|
|
1685
|
+
<!-- api:AppShell -->
|
|
1686
|
+
|
|
1687
|
+
| Prop | Type | Default | Description |
|
|
1688
|
+
| --------- | ----------------- | ------- | --------------------------------------------------------- |
|
|
1689
|
+
| header | `React.ReactNode` | — | Header slot. |
|
|
1690
|
+
| footer | `React.ReactNode` | — | Footer slot. |
|
|
1691
|
+
| bottomNav | `React.ReactNode` | — | Bottom navigation slot. |
|
|
1692
|
+
| safeArea | `boolean` | `true` | Wrap in SafeAreaView. |
|
|
1693
|
+
| loading | `boolean` | `false` | Keep shell mounted while swapping content with skeleton. |
|
|
1694
|
+
| skeleton | `React.ReactNode` | — | Optional custom loading placeholder for the content area. |
|
|
1695
|
+
| className | `string` | — | |
|
|
1696
|
+
|
|
1697
|
+
Also accepts the props of `ViewProps`.
|
|
1698
|
+
<!-- /api:AppShell -->
|
|
1800
1699
|
|
|
1801
1700
|
## ActionBar
|
|
1802
1701
|
|
|
@@ -1840,7 +1739,7 @@ import { SmartInput, Button, ButtonText } from '@thewhileloop/whileui';
|
|
|
1840
1739
|
onChangeText={setMessage}
|
|
1841
1740
|
placeholderExamples={['Log breakfast', 'Add a 30 minute walk', 'Set weight to 72 kg']}
|
|
1842
1741
|
leftSlot={
|
|
1843
|
-
<Button variant="ghost" size="icon">
|
|
1742
|
+
<Button variant="ghost" size="icon" accessibilityLabel="Add">
|
|
1844
1743
|
<Icon name="add" />
|
|
1845
1744
|
</Button>
|
|
1846
1745
|
}
|
|
@@ -1897,24 +1796,37 @@ const [value, setValue] = useState('');
|
|
|
1897
1796
|
onSuggestionPress={(text) => setValue(text)}
|
|
1898
1797
|
emptyTitle="How can I help?"
|
|
1899
1798
|
emptyDescription="Ask anything."
|
|
1900
|
-
leftSlot={<Button variant="ghost" size="icon"><Icon name="paperclip" /></Button>}
|
|
1901
|
-
rightSlot={<Button size="icon" onPress={handleSend}><Icon name="send" /></Button>}
|
|
1799
|
+
leftSlot={<Button variant="ghost" size="icon" accessibilityLabel="Attach"><Icon name="paperclip" /></Button>}
|
|
1800
|
+
rightSlot={<Button size="icon" accessibilityLabel="Send" onPress={handleSend}><Icon name="send" /></Button>}
|
|
1902
1801
|
/>;
|
|
1903
1802
|
```
|
|
1904
1803
|
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
|
1908
|
-
|
|
|
1909
|
-
| `
|
|
1910
|
-
|
|
|
1911
|
-
|
|
|
1912
|
-
|
|
|
1913
|
-
| `
|
|
1914
|
-
| `
|
|
1915
|
-
|
|
|
1916
|
-
|
|
|
1917
|
-
|
|
|
1804
|
+
<!-- api:Chat -->
|
|
1805
|
+
|
|
1806
|
+
| Prop | Type | Default | Description |
|
|
1807
|
+
| ----------------------- | ------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------- |
|
|
1808
|
+
| messages (required) | `ChatMessage[]` | — | Messages to show, each `{ id, role, content, secondary?, contentSize? }`. |
|
|
1809
|
+
| value (required) | `string` | — | Input value. |
|
|
1810
|
+
| onChangeText (required) | `(text: string) => void` | — | Input change. |
|
|
1811
|
+
| onSend (required) | `() => void` | — | Send handler. |
|
|
1812
|
+
| placeholder | `string` | `'Message...'` | |
|
|
1813
|
+
| suggestions | `string[]` | `EMPTY_SUGGESTIONS` | Chips when empty. |
|
|
1814
|
+
| onSuggestionPress | `(text: string) => void` | — | |
|
|
1815
|
+
| leftSlot | `React.ReactNode` | — | |
|
|
1816
|
+
| rightSlot | `React.ReactNode` | — | |
|
|
1817
|
+
| loading | `boolean` | `false` | |
|
|
1818
|
+
| header | `React.ReactNode` | — | |
|
|
1819
|
+
| emptyTitle | `string` | — | |
|
|
1820
|
+
| emptyDescription | `string` | — | |
|
|
1821
|
+
| exampleMessage | `ChatMessage` | — | Example message shown in empty state so users see the bubble UI. |
|
|
1822
|
+
| renderMessage | `(message: ChatMessage) => React.ReactNode` | — | Custom message renderer (markdown, code, images). Falls back to ChatMessageBubble. |
|
|
1823
|
+
| loadingIndicator | `React.ReactNode` | — | Shown at bottom when loading (typing indicator). |
|
|
1824
|
+
| inputSafeArea | `boolean` | `true` | Passed to SmartInput. Use false when Chat is in a contained demo. |
|
|
1825
|
+
| keyboardVerticalOffset | `number` | `0` | Offset for KeyboardAvoidingView when Chat has a header (e.g. StatusBar + header height). |
|
|
1826
|
+
| className | `string` | — | |
|
|
1827
|
+
| messagesClassName | `string` | — | |
|
|
1828
|
+
|
|
1829
|
+
<!-- /api:Chat -->
|
|
1918
1830
|
|
|
1919
1831
|
`ChatConversation` extends React Native `ScrollViewProps`, forwards its ref, and adds `contentContainerClassName` to style the constrained inner column. Caller `onScroll` and `onContentSizeChange` callbacks are preserved.
|
|
1920
1832
|
|
|
@@ -1949,18 +1861,24 @@ import { SmartImage } from '@thewhileloop/whileui';
|
|
|
1949
1861
|
/>;
|
|
1950
1862
|
```
|
|
1951
1863
|
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
|
1955
|
-
|
|
|
1956
|
-
|
|
|
1957
|
-
|
|
|
1958
|
-
|
|
|
1959
|
-
|
|
|
1960
|
-
|
|
|
1961
|
-
|
|
|
1962
|
-
|
|
|
1963
|
-
|
|
|
1864
|
+
<!-- api:SmartImage -->
|
|
1865
|
+
|
|
1866
|
+
| Prop | Type | Default | Description |
|
|
1867
|
+
| ----------------- | -------------------------------------------------- | --------- | ----------- |
|
|
1868
|
+
| className | `string` | — | |
|
|
1869
|
+
| source (required) | `ImageProps['source']` | — | |
|
|
1870
|
+
| alt (required) | `string` | — | |
|
|
1871
|
+
| skeleton | `boolean` | `true` | |
|
|
1872
|
+
| skeletonClassName | `string` | — | |
|
|
1873
|
+
| transition | `number` | `200` | |
|
|
1874
|
+
| onLoad | `() => void` | — | |
|
|
1875
|
+
| onError | `() => void` | — | |
|
|
1876
|
+
| fallbackSource | `ImageProps['source']` | — | |
|
|
1877
|
+
| radius | `'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | |
|
|
1878
|
+
| objectFit | `'fill' \| 'cover' \| 'contain' \| 'scale-down'` | `'cover'` | |
|
|
1879
|
+
|
|
1880
|
+
Also accepts the props of `ViewProps`.
|
|
1881
|
+
<!-- /api:SmartImage -->
|
|
1964
1882
|
|
|
1965
1883
|
The skeleton returns whenever the primary image starts loading again. A failed primary
|
|
1966
1884
|
source switches to `fallbackSource` when supplied without re-entering a primary/fallback
|
|
@@ -2030,19 +1948,26 @@ const [time, setTime] = useState<string | null>(null);
|
|
|
2030
1948
|
/>;
|
|
2031
1949
|
```
|
|
2032
1950
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
|
2036
|
-
|
|
|
2037
|
-
| `
|
|
2038
|
-
|
|
|
2039
|
-
|
|
|
2040
|
-
|
|
|
2041
|
-
|
|
|
2042
|
-
|
|
|
2043
|
-
|
|
|
2044
|
-
|
|
|
2045
|
-
| `
|
|
1951
|
+
<!-- api:DatePickerModal -->
|
|
1952
|
+
|
|
1953
|
+
| Prop | Type | Default | Description |
|
|
1954
|
+
| ----------------------- | -------------------------------- | ---------------------- | --------------------------------------------------------- |
|
|
1955
|
+
| value | `string \| null` | — | Controlled: selected date in YYYY-MM-DD |
|
|
1956
|
+
| onValueChange | `(date: string \| null) => void` | — | Controlled: change handler |
|
|
1957
|
+
| open (required) | `boolean` | — | Modal open state |
|
|
1958
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | Modal open change handler |
|
|
1959
|
+
| minDate | `string` | — | Minimum selectable date YYYY-MM-DD |
|
|
1960
|
+
| maxDate | `string` | — | Maximum selectable date YYYY-MM-DD |
|
|
1961
|
+
| theme | `CalendarTheme` | — | Override calendar theme (RN color strings) |
|
|
1962
|
+
| trigger | `React.ReactNode` | — | Trigger element. If not provided, no trigger is rendered. |
|
|
1963
|
+
| title | `string` | `'Select date'` | Title shown in modal header |
|
|
1964
|
+
| confirmLabel | `string` | `'Done'` | Button label for confirm |
|
|
1965
|
+
| className | `string` | — | |
|
|
1966
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1967
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1968
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1969
|
+
|
|
1970
|
+
<!-- /api:DatePickerModal -->
|
|
2046
1971
|
|
|
2047
1972
|
## ConfirmActionSheet
|
|
2048
1973
|
|
|
@@ -2060,11 +1985,27 @@ import { ConfirmActionSheet } from '@thewhileloop/whileui';
|
|
|
2060
1985
|
/>;
|
|
2061
1986
|
```
|
|
2062
1987
|
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
|
2066
|
-
|
|
|
2067
|
-
|
|
|
1988
|
+
<!-- api:ConfirmActionSheet -->
|
|
1989
|
+
|
|
1990
|
+
| Prop | Type | Default | Description |
|
|
1991
|
+
| ----------------------- | ---------------------------- | ---------------------- | ------------------------------------ |
|
|
1992
|
+
| open (required) | `boolean` | — | |
|
|
1993
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | |
|
|
1994
|
+
| title (required) | `string` | — | |
|
|
1995
|
+
| description | `string` | — | |
|
|
1996
|
+
| actions | `ConfirmActionSheetAction[]` | — | |
|
|
1997
|
+
| confirmLabel | `string` | `'Confirm'` | |
|
|
1998
|
+
| cancelLabel | `string` | `'Cancel'` | |
|
|
1999
|
+
| onConfirm | `() => void` | — | |
|
|
2000
|
+
| onCancel | `() => void` | — | |
|
|
2001
|
+
| destructive | `boolean` | `true` | |
|
|
2002
|
+
| className | `string` | — | |
|
|
2003
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
2004
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
2005
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
2006
|
+
|
|
2007
|
+
Also accepts the props of `ViewProps`.
|
|
2008
|
+
<!-- /api:ConfirmActionSheet -->
|
|
2068
2009
|
|
|
2069
2010
|
## Sheet
|
|
2070
2011
|
|
|
@@ -2094,15 +2035,20 @@ import {
|
|
|
2094
2035
|
</Sheet>;
|
|
2095
2036
|
```
|
|
2096
2037
|
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
|
2100
|
-
|
|
|
2101
|
-
|
|
|
2102
|
-
|
|
|
2103
|
-
|
|
|
2104
|
-
|
|
|
2105
|
-
|
|
|
2038
|
+
<!-- api:Sheet -->
|
|
2039
|
+
|
|
2040
|
+
| Prop | Type | Default | Description |
|
|
2041
|
+
| ----------------------- | ---------------------------- | ---------------------- | ----------------------------------------------------------- |
|
|
2042
|
+
| open (required) | `boolean` | — | |
|
|
2043
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | |
|
|
2044
|
+
| children (required) | `React.ReactNode` | — | |
|
|
2045
|
+
| maxHeight | `'half' \| 'full' \| number` | `'full'` | Max height: 'half' \| 'full' \| number (px). Default 'full' |
|
|
2046
|
+
| maxWidth | `number` | — | Web: max width. Default ~360 for mobile-first. |
|
|
2047
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
2048
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
2049
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
2050
|
+
|
|
2051
|
+
<!-- /api:Sheet -->
|
|
2106
2052
|
|
|
2107
2053
|
`SheetContent` is a `ScrollView` and accepts its props. It enables nested scrolling and
|
|
2108
2054
|
keeps taps on inputs while the keyboard is open (`keyboardShouldPersistTaps="handled"`).
|
|
@@ -2143,11 +2089,22 @@ import { SplashScreen } from '@thewhileloop/whileui';
|
|
|
2143
2089
|
/>;
|
|
2144
2090
|
```
|
|
2145
2091
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
|
2149
|
-
|
|
|
2150
|
-
|
|
|
2092
|
+
<!-- api:SplashScreen -->
|
|
2093
|
+
|
|
2094
|
+
| Prop | Type | Default | Description |
|
|
2095
|
+
| ------------------- | ------------------------------ | --------- | ----------------------------------------------- |
|
|
2096
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2097
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2098
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2099
|
+
| variant | `'fade' \| 'scale' \| 'slide'` | `'scale'` | Animation variant |
|
|
2100
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2101
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2102
|
+
| showLoading | `boolean` | `false` | Whether to show loading indicator |
|
|
2103
|
+
| loadingText | `string` | — | Loading text |
|
|
2104
|
+
| className | `string` | — | |
|
|
2105
|
+
|
|
2106
|
+
Also accepts the props of `ViewProps`.
|
|
2107
|
+
<!-- /api:SplashScreen -->
|
|
2151
2108
|
|
|
2152
2109
|
## ContentSkeleton
|
|
2153
2110
|
|
|
@@ -2161,10 +2118,16 @@ import { ContentSkeleton } from '@thewhileloop/whileui';
|
|
|
2161
2118
|
<ContentSkeleton variant="generic" />
|
|
2162
2119
|
```
|
|
2163
2120
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
|
2167
|
-
|
|
|
2121
|
+
<!-- api:ContentSkeleton -->
|
|
2122
|
+
|
|
2123
|
+
| Prop | Type | Default | Description |
|
|
2124
|
+
| --------- | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
|
|
2125
|
+
| variant | `'list' \| 'card' \| 'generic'` | `'list'` | Layout preset. `list` = avatar + lines rows, `card` = card with image/body, `generic` = simple page lines. |
|
|
2126
|
+
| rows | `number` | `4` | Number of list rows (list variant only). Default 4. |
|
|
2127
|
+
| className | `string` | — | |
|
|
2128
|
+
|
|
2129
|
+
Also accepts the props of `ViewProps`.
|
|
2130
|
+
<!-- /api:ContentSkeleton -->
|
|
2168
2131
|
|
|
2169
2132
|
## PageSkeleton
|
|
2170
2133
|
|
|
@@ -2183,14 +2146,19 @@ import { PageSkeleton } from '@thewhileloop/whileui';
|
|
|
2183
2146
|
<PageSkeleton variant="list" padding="none" className="flex-1" />
|
|
2184
2147
|
```
|
|
2185
2148
|
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
|
2189
|
-
|
|
|
2190
|
-
|
|
|
2191
|
-
| `
|
|
2192
|
-
| `
|
|
2193
|
-
|
|
|
2149
|
+
<!-- api:PageSkeleton -->
|
|
2150
|
+
|
|
2151
|
+
| Prop | Type | Default | Description |
|
|
2152
|
+
| ------------------ | ------------------------------------------ | ----------- | ------------------------------------------------------------------- |
|
|
2153
|
+
| variant (required) | `PageSkeletonVariant` | — | Layout preset. |
|
|
2154
|
+
| count | `number` | `3` | Rows/items for list (default 3) or settings (default 4) variant. |
|
|
2155
|
+
| padding | `PageSkeletonPadding` | `'default'` | Container padding. |
|
|
2156
|
+
| header | `ReactNode` | — | Optional header slot rendered above skeleton content. |
|
|
2157
|
+
| headerPlaceholder | `boolean \| PageSkeletonHeaderPlaceholder` | `false` | Optional placeholder header (when no real header is available yet). |
|
|
2158
|
+
| className | `string` | — | |
|
|
2159
|
+
|
|
2160
|
+
Also accepts the props of `ViewProps`.
|
|
2161
|
+
<!-- /api:PageSkeleton -->
|
|
2194
2162
|
|
|
2195
2163
|
## ScreenSkeleton
|
|
2196
2164
|
|
|
@@ -2203,13 +2171,19 @@ import { ScreenSkeleton } from '@thewhileloop/whileui';
|
|
|
2203
2171
|
<ScreenSkeleton variant="list" count={5} header={<Header title="Projects" />} />
|
|
2204
2172
|
```
|
|
2205
2173
|
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
|
2209
|
-
|
|
|
2210
|
-
|
|
|
2211
|
-
|
|
|
2212
|
-
|
|
|
2174
|
+
<!-- api:ScreenSkeleton -->
|
|
2175
|
+
|
|
2176
|
+
| Prop | Type | Default | Description |
|
|
2177
|
+
| ----------------- | ------------------------------------------ | ----------- | -------------------------------------- |
|
|
2178
|
+
| variant | `PageSkeletonVariant` | `'generic'` | Skeleton content preset. |
|
|
2179
|
+
| count | `number` | — | Rows/items for list/settings variants. |
|
|
2180
|
+
| padding | `PageSkeletonPadding` | `'default'` | Inner content padding. |
|
|
2181
|
+
| header | `ReactNode` | — | Real header slot. |
|
|
2182
|
+
| headerPlaceholder | `boolean \| PageSkeletonHeaderPlaceholder` | `'default'` | Placeholder header. |
|
|
2183
|
+
| className | `string` | — | |
|
|
2184
|
+
|
|
2185
|
+
Also accepts the props of `ViewProps`.
|
|
2186
|
+
<!-- /api:ScreenSkeleton -->
|
|
2213
2187
|
|
|
2214
2188
|
## ErrorBoundary
|
|
2215
2189
|
|
|
@@ -2223,10 +2197,15 @@ import { ErrorBoundary } from '@thewhileloop/whileui';
|
|
|
2223
2197
|
</ErrorBoundary>;
|
|
2224
2198
|
```
|
|
2225
2199
|
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
|
2229
|
-
|
|
|
2200
|
+
<!-- api:ErrorBoundary -->
|
|
2201
|
+
|
|
2202
|
+
| Prop | Type | Default | Description |
|
|
2203
|
+
| ------------------- | --------------------------------------------------------------- | ------- | -------------------------------------------------- |
|
|
2204
|
+
| children (required) | `ReactNode` | — | |
|
|
2205
|
+
| fallback | `ReactNode \| ((error: Error, reset: () => void) => ReactNode)` | — | Custom fallback. If not provided, uses ErrorState. |
|
|
2206
|
+
| onError | `(error: Error, errorInfo: ErrorInfo) => void` | — | Called when error is caught. |
|
|
2207
|
+
|
|
2208
|
+
<!-- /api:ErrorBoundary -->
|
|
2230
2209
|
|
|
2231
2210
|
## PullToRefreshScrollView
|
|
2232
2211
|
|
|
@@ -2240,11 +2219,17 @@ import { PullToRefreshScrollView } from '@thewhileloop/whileui';
|
|
|
2240
2219
|
</PullToRefreshScrollView>;
|
|
2241
2220
|
```
|
|
2242
2221
|
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
|
2246
|
-
|
|
|
2247
|
-
|
|
|
2222
|
+
<!-- api:PullToRefreshScrollView -->
|
|
2223
|
+
|
|
2224
|
+
| Prop | Type | Default | Description |
|
|
2225
|
+
| --------------------- | ----------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2226
|
+
| refreshing (required) | `boolean` | — | Whether refresh is in progress. |
|
|
2227
|
+
| onRefresh (required) | `() => void \| Promise<void>` | — | Refreshing is almost always async, so the signature says so. Nothing here awaits it — the spinner is driven by `refreshing`, which the owner controls — so a handler that can reject must catch its own errors. |
|
|
2228
|
+
| refreshColor | `string` | — | Override tint/colors. Defaults to theme primary. |
|
|
2229
|
+
| className | `string` | — | |
|
|
2230
|
+
|
|
2231
|
+
Also accepts the props of `ScrollViewProps`.
|
|
2232
|
+
<!-- /api:PullToRefreshScrollView -->
|
|
2248
2233
|
|
|
2249
2234
|
The scroll view owns the refresh gesture; don't pass `refreshControl`. It bounces even
|
|
2250
2235
|
when content is shorter than the screen, so a short list can still be pulled. Screen
|
|
@@ -2276,14 +2261,20 @@ import { LoadingState } from '@thewhileloop/whileui';
|
|
|
2276
2261
|
The in-flow counterpart of `EmptyState` and `ErrorState` for a section that is still
|
|
2277
2262
|
fetching. The spinner is named for assistive technology by `label` (default "Loading").
|
|
2278
2263
|
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
|
2282
|
-
|
|
|
2264
|
+
<!-- api:LoadingState -->
|
|
2265
|
+
|
|
2266
|
+
| Prop | Type | Default | Description |
|
|
2267
|
+
| --------- | --------------------------- | ----------- | ------------------------------------------------------------------------------ |
|
|
2268
|
+
| label | `string` | — | Read by assistive technology as the spinner's name; shown under it when given. |
|
|
2269
|
+
| size | `'sm' \| 'default' \| 'lg'` | `'default'` | |
|
|
2270
|
+
| className | `string` | — | |
|
|
2271
|
+
|
|
2272
|
+
Also accepts the props of `ViewProps`.
|
|
2273
|
+
<!-- /api:LoadingState -->
|
|
2283
2274
|
|
|
2284
2275
|
## ProfileHeader
|
|
2285
2276
|
|
|
2286
|
-
Copy from `apps/showcase/templates/profile/profile-header.tsx`, then:
|
|
2277
|
+
Copy from `apps/showcase/catalog/templates/profile/profile-header.tsx`, then:
|
|
2287
2278
|
|
|
2288
2279
|
```tsx
|
|
2289
2280
|
import { ProfileHeader } from './templates/profile'; // or your path
|
|
@@ -2305,7 +2296,7 @@ import { ProfileHeader } from './templates/profile'; // or your path
|
|
|
2305
2296
|
|
|
2306
2297
|
## SettingsSection / SettingsItem
|
|
2307
2298
|
|
|
2308
|
-
Copy from `apps/showcase/templates/profile/`, then:
|
|
2299
|
+
Copy from `apps/showcase/catalog/templates/profile/`, then:
|
|
2309
2300
|
|
|
2310
2301
|
```tsx
|
|
2311
2302
|
import { SettingsSection, SettingsItem } from './templates/profile'; // or your path
|
|
@@ -2340,10 +2331,25 @@ import { ProductCard } from '@thewhileloop/whileui';
|
|
|
2340
2331
|
/>;
|
|
2341
2332
|
```
|
|
2342
2333
|
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
|
2346
|
-
|
|
|
2334
|
+
<!-- api:ProductCard -->
|
|
2335
|
+
|
|
2336
|
+
| Prop | Type | Default | Description |
|
|
2337
|
+
| ---------------- | ---------------------------- | ------------ | ----------------------------------------------- |
|
|
2338
|
+
| imageUrl | `string` | — | |
|
|
2339
|
+
| title (required) | `string` | — | |
|
|
2340
|
+
| description | `string` | — | |
|
|
2341
|
+
| price (required) | `string` | — | |
|
|
2342
|
+
| originalPrice | `string` | — | |
|
|
2343
|
+
| badge | `string` | — | |
|
|
2344
|
+
| rating | `number` | — | |
|
|
2345
|
+
| reviewCount | `number` | — | |
|
|
2346
|
+
| inStock | `boolean` | `true` | |
|
|
2347
|
+
| variant | `'vertical' \| 'horizontal'` | `'vertical'` | |
|
|
2348
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2349
|
+
| className | `string` | — | |
|
|
2350
|
+
|
|
2351
|
+
Also accepts the props of `PressableProps`.
|
|
2352
|
+
<!-- /api:ProductCard -->
|
|
2347
2353
|
|
|
2348
2354
|
## PricingCard
|
|
2349
2355
|
|
|
@@ -2382,16 +2388,24 @@ import { SubscriptionCard } from '@thewhileloop/whileui';
|
|
|
2382
2388
|
/>;
|
|
2383
2389
|
```
|
|
2384
2390
|
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
|
2388
|
-
|
|
|
2389
|
-
|
|
|
2390
|
-
|
|
|
2391
|
-
| `
|
|
2392
|
-
|
|
|
2393
|
-
| `
|
|
2394
|
-
|
|
|
2391
|
+
<!-- api:SubscriptionCard -->
|
|
2392
|
+
|
|
2393
|
+
| Prop | Type | Default | Description |
|
|
2394
|
+
| ------------------- | ------------ | --------------- | ----------------------------------------------- |
|
|
2395
|
+
| planName (required) | `string` | — | Current plan label. |
|
|
2396
|
+
| price (required) | `string` | — | Plan price. |
|
|
2397
|
+
| period | `string` | `'/month'` | Billing period label. |
|
|
2398
|
+
| expiresAt | `string` | — | Renewal/expiry date text. |
|
|
2399
|
+
| isActive | `boolean` | `true` | Active/inactive badge state. |
|
|
2400
|
+
| onManage | `() => void` | — | Manage action. |
|
|
2401
|
+
| onUpgrade | `() => void` | — | Upgrade action. |
|
|
2402
|
+
| manageLabel | `string` | `'Manage plan'` | |
|
|
2403
|
+
| upgradeLabel | `string` | `'Upgrade'` | |
|
|
2404
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2405
|
+
| className | `string` | — | |
|
|
2406
|
+
|
|
2407
|
+
Also accepts the props of `ViewProps`.
|
|
2408
|
+
<!-- /api:SubscriptionCard -->
|
|
2395
2409
|
|
|
2396
2410
|
## FeatureGate
|
|
2397
2411
|
|
|
@@ -2416,12 +2430,18 @@ import { UsageBar } from '@thewhileloop/whileui';
|
|
|
2416
2430
|
<UsageBar label="AI generations" used={8} limit={20} />;
|
|
2417
2431
|
```
|
|
2418
2432
|
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
|
2422
|
-
|
|
|
2423
|
-
|
|
|
2424
|
-
|
|
|
2433
|
+
<!-- api:UsageBar -->
|
|
2434
|
+
|
|
2435
|
+
| Prop | Type | Default | Description |
|
|
2436
|
+
| ---------------- | ----------------- | ------- | ------------------------------- |
|
|
2437
|
+
| label (required) | `string` | — | Usage label. |
|
|
2438
|
+
| used (required) | `number` | — | Used amount. |
|
|
2439
|
+
| limit (required) | `number` | — | Quota limit. |
|
|
2440
|
+
| variant | `UsageBarVariant` | — | Optional explicit visual state. |
|
|
2441
|
+
| className | `string` | — | |
|
|
2442
|
+
|
|
2443
|
+
Also accepts the props of `ViewProps`.
|
|
2444
|
+
<!-- /api:UsageBar -->
|
|
2425
2445
|
|
|
2426
2446
|
## PlanToggle
|
|
2427
2447
|
|
|
@@ -2474,48 +2494,600 @@ import { DrawerMenu } from '@thewhileloop/whileui';
|
|
|
2474
2494
|
/>;
|
|
2475
2495
|
```
|
|
2476
2496
|
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
|
2480
|
-
|
|
|
2481
|
-
|
|
|
2497
|
+
<!-- api:DrawerMenu -->
|
|
2498
|
+
|
|
2499
|
+
| Prop | Type | Default | Description |
|
|
2500
|
+
| ------------------- | ----------------------- | ---------------------- | ----------------------------------------------------------- |
|
|
2501
|
+
| visible (required) | `boolean` | — | |
|
|
2502
|
+
| onClose (required) | `() => void` | — | |
|
|
2503
|
+
| sections (required) | `DrawerMenuSection[]` | — | |
|
|
2504
|
+
| activeKey | `string` | — | |
|
|
2505
|
+
| onSelect | `(key: string) => void` | — | |
|
|
2506
|
+
| header | `React.ReactNode` | — | |
|
|
2507
|
+
| footer | `React.ReactNode` | — | |
|
|
2508
|
+
| maxWidth | `number` | — | Max width in px. On web, defaults to 360 when not provided. |
|
|
2509
|
+
| className | `string` | — | |
|
|
2510
|
+
| style | `StyleProp<ViewStyle>` | — | |
|
|
2511
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
2512
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
2513
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
2514
|
+
|
|
2515
|
+
<!-- /api:DrawerMenu -->
|
|
2482
2516
|
|
|
2483
2517
|
Closes from the backdrop, an item, Android back or Escape on web. It is hosted in a modal
|
|
2484
2518
|
on every platform, so keyboard focus stays inside it while it is open.
|
|
2485
2519
|
|
|
2486
|
-
##
|
|
2520
|
+
## More components
|
|
2521
|
+
|
|
2522
|
+
The rest of the catalog: what each is for and the props its type declares.
|
|
2523
|
+
|
|
2524
|
+
### View
|
|
2525
|
+
|
|
2526
|
+
Themed view wrapper
|
|
2527
|
+
|
|
2528
|
+
```tsx
|
|
2529
|
+
import { View } from '@thewhileloop/whileui';
|
|
2530
|
+
```
|
|
2531
|
+
|
|
2532
|
+
<!-- api:View -->
|
|
2533
|
+
|
|
2534
|
+
| Prop | Type | Default | Description |
|
|
2535
|
+
| --------- | -------- | ------- | ----------- |
|
|
2536
|
+
| className | `string` | — | |
|
|
2537
|
+
|
|
2538
|
+
Also accepts the props of React Native's `ViewProps`.
|
|
2539
|
+
<!-- /api:View -->
|
|
2540
|
+
|
|
2541
|
+
### Stack
|
|
2487
2542
|
|
|
2488
|
-
|
|
2543
|
+
Vertical flex layout with gap
|
|
2489
2544
|
|
|
2490
|
-
|
|
2545
|
+
```tsx
|
|
2546
|
+
import { Stack } from '@thewhileloop/whileui';
|
|
2547
|
+
```
|
|
2548
|
+
|
|
2549
|
+
<!-- api:Stack -->
|
|
2550
|
+
|
|
2551
|
+
| Prop | Type | Default | Description |
|
|
2552
|
+
| --------- | ------------------------------------------------------------------- | ----------- | ----------- |
|
|
2553
|
+
| className | `string` | — | |
|
|
2554
|
+
| gap | `'none' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | |
|
|
2555
|
+
| align | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | `'stretch'` | |
|
|
2556
|
+
| justify | `'start' \| 'center' \| 'end' \| 'between' \| 'around' \| 'evenly'` | `'start'` | |
|
|
2557
|
+
|
|
2558
|
+
Also accepts the props of `ViewProps`.
|
|
2559
|
+
<!-- /api:Stack -->
|
|
2560
|
+
|
|
2561
|
+
### MotionProvider
|
|
2562
|
+
|
|
2563
|
+
System reduced motion, explicit preview modes, and deterministic testing
|
|
2564
|
+
|
|
2565
|
+
```tsx
|
|
2566
|
+
import { MotionProvider } from '@thewhileloop/whileui';
|
|
2567
|
+
```
|
|
2568
|
+
|
|
2569
|
+
<!-- api:MotionProvider -->
|
|
2491
2570
|
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
- [ ] **FAB (Floating Action Button)** — expandable action menu, Reanimated spring, auto-hide on scroll
|
|
2497
|
-
- [ ] **Combobox** — searchable select with keyboard navigation, empty state, async loading
|
|
2498
|
-
- [ ] **Data Table** — sortable headers, skeleton loading rows, row actions, responsive stacking
|
|
2499
|
-
- [ ] **Banner** — dismissible info/warning/success/destructive bar with icon + action, auto-dismiss timer
|
|
2500
|
-
- [ ] **Pagination** — compact (dots) + expanded (numbers) variants, edge-aware ellipsis, 44px touch targets
|
|
2501
|
-
- [ ] **Inline Calendar** — standalone calendar view reusing DatePicker logic, range selection
|
|
2571
|
+
| Prop | Type | Default | Description |
|
|
2572
|
+
| ------------------- | ----------------- | ---------- | ----------- |
|
|
2573
|
+
| children (required) | `React.ReactNode` | — | |
|
|
2574
|
+
| mode | `MotionMode` | `'system'` | |
|
|
2502
2575
|
|
|
2503
|
-
|
|
2576
|
+
<!-- /api:MotionProvider -->
|
|
2504
2577
|
|
|
2505
|
-
|
|
2506
|
-
- [x] AppShell, Chat, FormModalScreen, CheckoutSummary (already had `loading`)
|
|
2507
|
-
- [ ] Add `loading` to remaining blocks: Header, BottomNav, DrawerMenu, TimelineFeed
|
|
2578
|
+
### Textarea
|
|
2508
2579
|
|
|
2509
|
-
|
|
2580
|
+
Multi-line text input
|
|
2581
|
+
|
|
2582
|
+
```tsx
|
|
2583
|
+
import { Textarea } from '@thewhileloop/whileui';
|
|
2584
|
+
```
|
|
2585
|
+
|
|
2586
|
+
<!-- api:Textarea -->
|
|
2587
|
+
|
|
2588
|
+
| Prop | Type | Default | Description |
|
|
2589
|
+
| ------------ | --------------------------- | ----------- | ----------- |
|
|
2590
|
+
| aria-invalid | `boolean` | — | |
|
|
2591
|
+
| className | `string` | — | |
|
|
2592
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
2593
|
+
|
|
2594
|
+
Also accepts the props of `TextInputProps`.
|
|
2595
|
+
<!-- /api:Textarea -->
|
|
2596
|
+
|
|
2597
|
+
### Label
|
|
2598
|
+
|
|
2599
|
+
Form field label
|
|
2600
|
+
|
|
2601
|
+
```tsx
|
|
2602
|
+
import { Label } from '@thewhileloop/whileui';
|
|
2603
|
+
```
|
|
2604
|
+
|
|
2605
|
+
<!-- api:Label -->
|
|
2606
|
+
|
|
2607
|
+
| Prop | Type | Default | Description |
|
|
2608
|
+
| --------- | -------- | ------- | ----------- |
|
|
2609
|
+
| className | `string` | — | |
|
|
2610
|
+
|
|
2611
|
+
Also accepts the props of `TextProps`.
|
|
2612
|
+
<!-- /api:Label -->
|
|
2613
|
+
|
|
2614
|
+
### Toggle
|
|
2615
|
+
|
|
2616
|
+
ToggleText sub-component
|
|
2617
|
+
|
|
2618
|
+
```tsx
|
|
2619
|
+
import { Toggle } from '@thewhileloop/whileui';
|
|
2620
|
+
```
|
|
2621
|
+
|
|
2622
|
+
<!-- api:Toggle -->
|
|
2623
|
+
|
|
2624
|
+
| Prop | Type | Default | Description |
|
|
2625
|
+
| --------------- | ------------------------------------------------ | ----------- | ----------- |
|
|
2626
|
+
| pressed | `boolean` | `false` | |
|
|
2627
|
+
| defaultPressed | `boolean` | `false` | |
|
|
2628
|
+
| onPressedChange | `(pressed: boolean) => void` | — | |
|
|
2629
|
+
| variant | `VariantProps<typeof toggleVariants>['variant']` | `'default'` | |
|
|
2630
|
+
| size | `VariantProps<typeof toggleVariants>['size']` | `'default'` | |
|
|
2631
|
+
| className | `string` | — | |
|
|
2632
|
+
| children | `React.ReactNode` | — | |
|
|
2633
|
+
|
|
2634
|
+
Also accepts the props of `PressableProps`.
|
|
2635
|
+
<!-- /api:Toggle -->
|
|
2636
|
+
|
|
2637
|
+
### ToggleGroup
|
|
2638
|
+
|
|
2639
|
+
Group of toggle items
|
|
2640
|
+
|
|
2641
|
+
```tsx
|
|
2642
|
+
import { ToggleGroup } from '@thewhileloop/whileui';
|
|
2643
|
+
```
|
|
2644
|
+
|
|
2645
|
+
<!-- api:ToggleGroup -->
|
|
2646
|
+
|
|
2647
|
+
| Prop | Type | Default | Description |
|
|
2648
|
+
| ------------- | ------------------------------------- | ----------- | ----------- |
|
|
2649
|
+
| type | `ToggleGroupType` | `'single'` | |
|
|
2650
|
+
| defaultValue | `string \| string[]` | — | |
|
|
2651
|
+
| value | `string \| string[]` | — | |
|
|
2652
|
+
| onValueChange | `(value: string \| string[]) => void` | — | |
|
|
2653
|
+
| variant | `'default' \| 'outline'` | `'default'` | |
|
|
2654
|
+
| size | `'default' \| 'sm' \| 'lg'` | `'default'` | |
|
|
2655
|
+
| className | `string` | — | |
|
|
2656
|
+
|
|
2657
|
+
Also accepts the props of `ViewProps`.
|
|
2658
|
+
<!-- /api:ToggleGroup -->
|
|
2659
|
+
|
|
2660
|
+
### Separator
|
|
2661
|
+
|
|
2662
|
+
Themed divider
|
|
2663
|
+
|
|
2664
|
+
```tsx
|
|
2665
|
+
import { Separator } from '@thewhileloop/whileui';
|
|
2666
|
+
```
|
|
2667
|
+
|
|
2668
|
+
<!-- api:Separator -->
|
|
2669
|
+
|
|
2670
|
+
| Prop | Type | Default | Description |
|
|
2671
|
+
| ----------- | ---------------------------- | -------------- | ----------- |
|
|
2672
|
+
| orientation | `'horizontal' \| 'vertical'` | `'horizontal'` | |
|
|
2673
|
+
| className | `string` | — | |
|
|
2674
|
+
|
|
2675
|
+
Also accepts the props of `ViewProps`.
|
|
2676
|
+
<!-- /api:Separator -->
|
|
2677
|
+
|
|
2678
|
+
### Spinner
|
|
2679
|
+
|
|
2680
|
+
ActivityIndicator wrapper
|
|
2681
|
+
|
|
2682
|
+
```tsx
|
|
2683
|
+
import { Spinner } from '@thewhileloop/whileui';
|
|
2684
|
+
```
|
|
2685
|
+
|
|
2686
|
+
<!-- api:Spinner -->
|
|
2687
|
+
|
|
2688
|
+
| Prop | Type | Default | Description |
|
|
2689
|
+
| --------- | --------------------------- | ----------- | ----------- |
|
|
2690
|
+
| className | `string` | — | |
|
|
2691
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
2692
|
+
|
|
2693
|
+
Also accepts the props of `ActivityIndicatorProps`.
|
|
2694
|
+
<!-- /api:Spinner -->
|
|
2695
|
+
|
|
2696
|
+
### Skeleton
|
|
2697
|
+
|
|
2698
|
+
Loading placeholder (pulse = opacity fade, shimmer = sweep)
|
|
2699
|
+
|
|
2700
|
+
```tsx
|
|
2701
|
+
import { Skeleton } from '@thewhileloop/whileui';
|
|
2702
|
+
```
|
|
2703
|
+
|
|
2704
|
+
<!-- api:Skeleton -->
|
|
2705
|
+
|
|
2706
|
+
| Prop | Type | Default | Description |
|
|
2707
|
+
| --------- | ---------------------- | --------- | ----------- |
|
|
2708
|
+
| className | `string` | — | |
|
|
2709
|
+
| variant | `'pulse' \| 'shimmer'` | `'pulse'` | |
|
|
2710
|
+
|
|
2711
|
+
Also accepts the props of `ViewProps`.
|
|
2712
|
+
<!-- /api:Skeleton -->
|
|
2713
|
+
|
|
2714
|
+
### AspectRatio
|
|
2715
|
+
|
|
2716
|
+
Maintain aspect ratio container
|
|
2717
|
+
|
|
2718
|
+
```tsx
|
|
2719
|
+
import { AspectRatio } from '@thewhileloop/whileui';
|
|
2720
|
+
```
|
|
2721
|
+
|
|
2722
|
+
<!-- api:AspectRatio -->
|
|
2723
|
+
|
|
2724
|
+
| Prop | Type | Default | Description |
|
|
2725
|
+
| --------- | -------- | ------- | ----------- |
|
|
2726
|
+
| ratio | `number` | `1` | |
|
|
2727
|
+
| className | `string` | — | |
|
|
2728
|
+
|
|
2729
|
+
Also accepts the props of `ViewProps`.
|
|
2730
|
+
<!-- /api:AspectRatio -->
|
|
2731
|
+
|
|
2732
|
+
### Collapsible
|
|
2733
|
+
|
|
2734
|
+
CollapsibleTrigger, CollapsibleContent
|
|
2735
|
+
|
|
2736
|
+
```tsx
|
|
2737
|
+
import { Collapsible } from '@thewhileloop/whileui';
|
|
2738
|
+
```
|
|
2739
|
+
|
|
2740
|
+
<!-- api:Collapsible -->
|
|
2741
|
+
|
|
2742
|
+
| Prop | Type | Default | Description |
|
|
2743
|
+
| ------------ | ------------------------- | ------- | ----------- |
|
|
2744
|
+
| defaultOpen | `boolean` | `false` | |
|
|
2745
|
+
| open | `boolean` | — | |
|
|
2746
|
+
| onOpenChange | `(open: boolean) => void` | — | |
|
|
2747
|
+
| disabled | `boolean` | `false` | |
|
|
2748
|
+
| className | `string` | — | |
|
|
2749
|
+
|
|
2750
|
+
Also accepts the props of `ViewProps`.
|
|
2751
|
+
<!-- /api:Collapsible -->
|
|
2752
|
+
|
|
2753
|
+
### Tooltip
|
|
2754
|
+
|
|
2755
|
+
Position-aware tooltip (requires PortalHost)
|
|
2756
|
+
|
|
2757
|
+
```tsx
|
|
2758
|
+
import { Tooltip } from '@thewhileloop/whileui';
|
|
2759
|
+
```
|
|
2760
|
+
|
|
2761
|
+
<!-- api:Tooltip -->
|
|
2762
|
+
|
|
2763
|
+
| Prop | Type | Default | Description |
|
|
2764
|
+
| ----------------------- | ------------------------- | ------- | ----------- |
|
|
2765
|
+
| onOpenChange | `(open: boolean) => void` | — | |
|
|
2766
|
+
| delayDuration | `number` | — | |
|
|
2767
|
+
| skipDelayDuration | `number` | — | |
|
|
2768
|
+
| disableHoverableContent | `boolean` | — | |
|
|
2769
|
+
| children (required) | `React.ReactNode` | — | |
|
|
2770
|
+
|
|
2771
|
+
<!-- /api:Tooltip -->
|
|
2772
|
+
|
|
2773
|
+
### FloatingBottomNav
|
|
2774
|
+
|
|
2775
|
+
Elevated bottom nav with safe area support
|
|
2776
|
+
|
|
2777
|
+
```tsx
|
|
2778
|
+
import { FloatingBottomNav } from '@thewhileloop/whileui';
|
|
2779
|
+
```
|
|
2780
|
+
|
|
2781
|
+
<!-- api:FloatingBottomNav -->
|
|
2782
|
+
|
|
2783
|
+
| Prop | Type | Default | Description |
|
|
2784
|
+
| -------------------- | ------------------------- | ------- | ----------- |
|
|
2785
|
+
| items (required) | `FloatingBottomNavItem[]` | — | |
|
|
2786
|
+
| activeKey (required) | `string` | — | |
|
|
2787
|
+
| onSelect (required) | `(key: string) => void` | — | |
|
|
2788
|
+
| className | `string` | — | |
|
|
2789
|
+
|
|
2790
|
+
Also accepts the props of `ViewProps`.
|
|
2791
|
+
<!-- /api:FloatingBottomNav -->
|
|
2792
|
+
|
|
2793
|
+
### TabBar
|
|
2794
|
+
|
|
2795
|
+
Top tab bar with indicator
|
|
2796
|
+
|
|
2797
|
+
```tsx
|
|
2798
|
+
import { TabBar } from '@thewhileloop/whileui';
|
|
2799
|
+
```
|
|
2800
|
+
|
|
2801
|
+
<!-- api:TabBar -->
|
|
2802
|
+
|
|
2803
|
+
| Prop | Type | Default | Description |
|
|
2804
|
+
| -------------------- | ------------------------------------- | ----------- | ----------- |
|
|
2805
|
+
| items (required) | `TabBarItem[]` | — | |
|
|
2806
|
+
| activeKey (required) | `string` | — | |
|
|
2807
|
+
| onSelect (required) | `(key: string) => void` | — | |
|
|
2808
|
+
| variant | `'default' \| 'pills' \| 'underline'` | `'default'` | |
|
|
2809
|
+
| scrollable | `boolean` | `false` | |
|
|
2810
|
+
| className | `string` | — | |
|
|
2811
|
+
|
|
2812
|
+
Also accepts the props of `ViewProps`.
|
|
2813
|
+
<!-- /api:TabBar -->
|
|
2814
|
+
|
|
2815
|
+
### FormModalScreen
|
|
2816
|
+
|
|
2817
|
+
Modal scaffold for forms with loading states
|
|
2818
|
+
|
|
2819
|
+
```tsx
|
|
2820
|
+
import { FormModalScreen } from '@thewhileloop/whileui';
|
|
2821
|
+
```
|
|
2822
|
+
|
|
2823
|
+
<!-- api:FormModalScreen -->
|
|
2824
|
+
|
|
2825
|
+
| Prop | Type | Default | Description |
|
|
2826
|
+
| ------------------- | ----------------- | ------------- | ----------------------------------------------------------------------------------------- |
|
|
2827
|
+
| className | `string` | — | |
|
|
2828
|
+
| title (required) | `string` | — | |
|
|
2829
|
+
| subtitle | `string` | — | |
|
|
2830
|
+
| leftSlot | `React.ReactNode` | — | |
|
|
2831
|
+
| rightSlot | `React.ReactNode` | — | |
|
|
2832
|
+
| onClose | `() => void` | — | |
|
|
2833
|
+
| loading | `boolean` | `false` | |
|
|
2834
|
+
| saving | `boolean` | `false` | |
|
|
2835
|
+
| savingText | `string` | `'Saving...'` | |
|
|
2836
|
+
| loadingSpinnerColor | `string` | — | Hex color for spinner in loading state. ActivityIndicator requires hex. |
|
|
2837
|
+
| savingSpinnerColor | `string` | — | Hex color for spinner in saving banner. Defaults to platform (Android blue) when not set. |
|
|
2838
|
+
| scrollEnabled | `boolean` | `true` | |
|
|
2839
|
+
| children (required) | `React.ReactNode` | — | |
|
|
2840
|
+
|
|
2841
|
+
Also accepts the props of `ViewProps`.
|
|
2842
|
+
<!-- /api:FormModalScreen -->
|
|
2843
|
+
|
|
2844
|
+
### ErrorState
|
|
2845
|
+
|
|
2846
|
+
Error display with retry
|
|
2847
|
+
|
|
2848
|
+
```tsx
|
|
2849
|
+
import { ErrorState } from '@thewhileloop/whileui';
|
|
2850
|
+
```
|
|
2851
|
+
|
|
2852
|
+
<!-- api:ErrorState -->
|
|
2853
|
+
|
|
2854
|
+
| Prop | Type | Default | Description |
|
|
2855
|
+
| ----------- | ----------------- | ------------------------ | ----------- |
|
|
2856
|
+
| icon | `React.ReactNode` | — | |
|
|
2857
|
+
| title | `string` | `'Something went wrong'` | |
|
|
2858
|
+
| description | `string` | — | |
|
|
2859
|
+
| error | `Error \| string` | — | |
|
|
2860
|
+
| onRetry | `() => void` | — | |
|
|
2861
|
+
| retryLabel | `string` | `'Try again'` | |
|
|
2862
|
+
| className | `string` | — | |
|
|
2863
|
+
|
|
2864
|
+
Also accepts the props of `ViewProps`.
|
|
2865
|
+
<!-- /api:ErrorState -->
|
|
2866
|
+
|
|
2867
|
+
### LoadingScreen
|
|
2868
|
+
|
|
2869
|
+
Full-screen loading indicator
|
|
2870
|
+
|
|
2871
|
+
```tsx
|
|
2872
|
+
import { LoadingScreen } from '@thewhileloop/whileui';
|
|
2873
|
+
```
|
|
2874
|
+
|
|
2875
|
+
<!-- api:LoadingScreen -->
|
|
2876
|
+
|
|
2877
|
+
| Prop | Type | Default | Description |
|
|
2878
|
+
| ------------ | ---------------------- | ------- | ------------------------------------------------------ |
|
|
2879
|
+
| message | `string` | — | |
|
|
2880
|
+
| size | `'sm' \| 'md' \| 'lg'` | `'md'` | |
|
|
2881
|
+
| spinnerColor | `string` | — | Hex color for spinner. ActivityIndicator requires hex. |
|
|
2882
|
+
| className | `string` | — | |
|
|
2883
|
+
|
|
2884
|
+
Also accepts the props of `ViewProps`.
|
|
2885
|
+
<!-- /api:LoadingScreen -->
|
|
2886
|
+
|
|
2887
|
+
### MinimalSplash
|
|
2888
|
+
|
|
2889
|
+
Minimal monochrome splash
|
|
2890
|
+
|
|
2891
|
+
```tsx
|
|
2892
|
+
import { MinimalSplash } from '@thewhileloop/whileui';
|
|
2893
|
+
```
|
|
2894
|
+
|
|
2895
|
+
<!-- api:MinimalSplash -->
|
|
2896
|
+
|
|
2897
|
+
| Prop | Type | Default | Description |
|
|
2898
|
+
| ------------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
2899
|
+
| className | `string` | — | |
|
|
2900
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2901
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2902
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2903
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2904
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2905
|
+
| showLoading | `boolean` | `false` | Whether to show loading indicator |
|
|
2906
|
+
| loadingText | `string` | — | Loading text |
|
|
2907
|
+
|
|
2908
|
+
Also accepts the props of `SplashScreenProps`.
|
|
2909
|
+
<!-- /api:MinimalSplash -->
|
|
2910
|
+
|
|
2911
|
+
### BrandedSplash
|
|
2912
|
+
|
|
2913
|
+
Splash with brand imagery
|
|
2914
|
+
|
|
2915
|
+
```tsx
|
|
2916
|
+
import { BrandedSplash } from '@thewhileloop/whileui';
|
|
2917
|
+
```
|
|
2918
|
+
|
|
2919
|
+
<!-- api:BrandedSplash -->
|
|
2920
|
+
|
|
2921
|
+
| Prop | Type | Default | Description |
|
|
2922
|
+
| ------------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
2923
|
+
| className | `string` | — | |
|
|
2924
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2925
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2926
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2927
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2928
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2929
|
+
| loadingText | `string` | — | Loading text |
|
|
2930
|
+
|
|
2931
|
+
Also accepts the props of `SplashScreenProps`.
|
|
2932
|
+
<!-- /api:BrandedSplash -->
|
|
2933
|
+
|
|
2934
|
+
### ChatMessageBubble
|
|
2935
|
+
|
|
2936
|
+
One message in a `Chat`, rendered on its own when a screen composes the conversation itself.
|
|
2937
|
+
|
|
2938
|
+
```tsx
|
|
2939
|
+
import { ChatMessageBubble } from '@thewhileloop/whileui';
|
|
2940
|
+
```
|
|
2941
|
+
|
|
2942
|
+
<!-- api:ChatMessageBubble -->
|
|
2943
|
+
|
|
2944
|
+
| Prop | Type | Default | Description |
|
|
2945
|
+
| ------------------ | ------------- | ------- | --------------------------------------------------------------- |
|
|
2946
|
+
| message (required) | `ChatMessage` | — | The message to render; its `role` decides alignment and colour. |
|
|
2947
|
+
| contentClassName | `string` | — | Classes for the bubble's content wrapper. |
|
|
2948
|
+
|
|
2949
|
+
<!-- /api:ChatMessageBubble -->
|
|
2950
|
+
|
|
2951
|
+
### ChatSuggestions
|
|
2952
|
+
|
|
2953
|
+
Suggestion chips when empty
|
|
2954
|
+
|
|
2955
|
+
```tsx
|
|
2956
|
+
import { ChatSuggestions } from '@thewhileloop/whileui';
|
|
2957
|
+
```
|
|
2958
|
+
|
|
2959
|
+
<!-- api:ChatSuggestions -->
|
|
2960
|
+
|
|
2961
|
+
| Prop | Type | Default | Description |
|
|
2962
|
+
| ---------------------- | ------------------------ | ------------------- | ----------- |
|
|
2963
|
+
| suggestions (required) | `string[]` | `EMPTY_SUGGESTIONS` | |
|
|
2964
|
+
| onSelect (required) | `(text: string) => void` | — | |
|
|
2965
|
+
| className | `string` | — | |
|
|
2966
|
+
|
|
2967
|
+
<!-- /api:ChatSuggestions -->
|
|
2968
|
+
|
|
2969
|
+
### ListItem
|
|
2970
|
+
|
|
2971
|
+
Title/subtitle row
|
|
2972
|
+
|
|
2973
|
+
```tsx
|
|
2974
|
+
import { ListItem } from '@thewhileloop/whileui';
|
|
2975
|
+
```
|
|
2976
|
+
|
|
2977
|
+
<!-- api:ListItem -->
|
|
2978
|
+
|
|
2979
|
+
| Prop | Type | Default | Description |
|
|
2980
|
+
| ---------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
2981
|
+
| icon | `React.ReactNode` | — | |
|
|
2982
|
+
| title (required) | `string` | — | |
|
|
2983
|
+
| subtitle | `string` | — | |
|
|
2984
|
+
| description | `string` | — | |
|
|
2985
|
+
| rightIcon | `React.ReactNode` | — | |
|
|
2986
|
+
| rightText | `string` | — | |
|
|
2987
|
+
| action | `React.ReactNode` | — | |
|
|
2988
|
+
| showBorder | `boolean` | `true` | |
|
|
2989
|
+
| compact | `boolean` | `false` | |
|
|
2990
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2991
|
+
| className | `string` | — | |
|
|
2992
|
+
|
|
2993
|
+
Also accepts the props of `PressableProps`.
|
|
2994
|
+
<!-- /api:ListItem -->
|
|
2995
|
+
|
|
2996
|
+
### NotificationItem
|
|
2997
|
+
|
|
2998
|
+
Notification row with metadata
|
|
2999
|
+
|
|
3000
|
+
```tsx
|
|
3001
|
+
import { NotificationItem } from '@thewhileloop/whileui';
|
|
3002
|
+
```
|
|
3003
|
+
|
|
3004
|
+
<!-- api:NotificationItem -->
|
|
3005
|
+
|
|
3006
|
+
| Prop | Type | Default | Description |
|
|
3007
|
+
| ------------------ | ----------------- | ------- | ----------------------------------------------- |
|
|
3008
|
+
| icon | `React.ReactNode` | — | |
|
|
3009
|
+
| avatarUrl | `string` | — | |
|
|
3010
|
+
| avatarFallback | `string` | — | |
|
|
3011
|
+
| title (required) | `string` | — | |
|
|
3012
|
+
| message (required) | `string` | — | |
|
|
3013
|
+
| time (required) | `string` | — | |
|
|
3014
|
+
| read | `boolean` | `false` | |
|
|
3015
|
+
| showDot | `boolean` | `true` | |
|
|
3016
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
3017
|
+
| className | `string` | — | |
|
|
3018
|
+
|
|
3019
|
+
Also accepts the props of `PressableProps`.
|
|
3020
|
+
<!-- /api:NotificationItem -->
|
|
3021
|
+
|
|
3022
|
+
### TimelineFeed
|
|
3023
|
+
|
|
3024
|
+
Vertical feed with connecting lines
|
|
3025
|
+
|
|
3026
|
+
```tsx
|
|
3027
|
+
import { TimelineFeed } from '@thewhileloop/whileui';
|
|
3028
|
+
```
|
|
3029
|
+
|
|
3030
|
+
<!-- api:TimelineFeed -->
|
|
3031
|
+
|
|
3032
|
+
| Prop | Type | Default | Description |
|
|
3033
|
+
| ---------------- | ---------------- | ------- | ----------- |
|
|
3034
|
+
| className | `string` | — | |
|
|
3035
|
+
| items (required) | `TimelineItem[]` | — | |
|
|
3036
|
+
| showConnector | `boolean` | `true` | |
|
|
3037
|
+
|
|
3038
|
+
Also accepts the props of `ViewProps`.
|
|
3039
|
+
<!-- /api:TimelineFeed -->
|
|
3040
|
+
|
|
3041
|
+
### CheckoutSummary
|
|
3042
|
+
|
|
3043
|
+
Cart summary with line items
|
|
3044
|
+
|
|
3045
|
+
```tsx
|
|
3046
|
+
import { CheckoutSummary } from '@thewhileloop/whileui';
|
|
3047
|
+
```
|
|
3048
|
+
|
|
3049
|
+
<!-- api:CheckoutSummary -->
|
|
3050
|
+
|
|
3051
|
+
| Prop | Type | Default | Description |
|
|
3052
|
+
| ------------------- | -------------------- | ------------ | ----------- |
|
|
3053
|
+
| items (required) | `CheckoutLineItem[]` | — | |
|
|
3054
|
+
| subtotal (required) | `string` | — | |
|
|
3055
|
+
| shipping | `string` | — | |
|
|
3056
|
+
| tax | `string` | — | |
|
|
3057
|
+
| discount | `string` | — | |
|
|
3058
|
+
| total (required) | `string` | — | |
|
|
3059
|
+
| buttonLabel | `string` | `'Checkout'` | |
|
|
3060
|
+
| onCheckout | `() => void` | — | |
|
|
3061
|
+
| loading | `boolean` | `false` | |
|
|
3062
|
+
| className | `string` | — | |
|
|
3063
|
+
|
|
3064
|
+
Also accepts the props of `ViewProps`.
|
|
3065
|
+
<!-- /api:CheckoutSummary -->
|
|
3066
|
+
|
|
3067
|
+
### MetricCard
|
|
3068
|
+
|
|
3069
|
+
Stats/progress card for dashboards
|
|
3070
|
+
|
|
3071
|
+
```tsx
|
|
3072
|
+
import { MetricCard } from '@thewhileloop/whileui';
|
|
3073
|
+
```
|
|
2510
3074
|
|
|
2511
|
-
|
|
2512
|
-
- [ ] Add `## API` sections for blocks: FloatingBottomNav, TabBar, FormModalScreen, ErrorState, LoadingScreen, ListItem, NotificationItem, MetricCard, TimelineFeed
|
|
3075
|
+
<!-- api:MetricCard -->
|
|
2513
3076
|
|
|
2514
|
-
|
|
3077
|
+
| Prop | Type | Default | Description |
|
|
3078
|
+
| ---------------- | ------------------------------------ | ----------- | ----------------------------------------------- |
|
|
3079
|
+
| className | `string` | — | |
|
|
3080
|
+
| label (required) | `string` | — | |
|
|
3081
|
+
| value (required) | `string \| number` | — | |
|
|
3082
|
+
| subtitle | `string` | — | |
|
|
3083
|
+
| progress | `number` | — | |
|
|
3084
|
+
| segments | `{ value: number; color: string }[]` | — | |
|
|
3085
|
+
| icon | `React.ReactNode` | — | |
|
|
3086
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
3087
|
+
| variant | `'default' \| 'outlined'` | `'default'` | |
|
|
2515
3088
|
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
- [ ] Automated visual regression tests (screenshot comparison)
|
|
3089
|
+
Also accepts the props of `ViewProps`.
|
|
3090
|
+
<!-- /api:MetricCard -->
|
|
2519
3091
|
|
|
2520
3092
|
## License
|
|
2521
3093
|
|