@thewhileloop/whileui 4.2.3 → 4.4.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 +1271 -663
- package/dist/agent/catalog.json +1737 -0
- package/dist/agent/llms-components.txt +2808 -0
- package/dist/agent/llms-full.txt +2998 -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/layout/smart-input.js +2 -2
- 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/fab/fab.d.ts +12 -0
- package/dist/components/fab/fab.js +14 -2
- package/dist/components/fab/index.d.ts +1 -1
- package/dist/components/fab/index.js +1 -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.d.ts +5 -0
- package/dist/components/input/input.js +4 -4
- package/dist/components/numeric-input/numeric-input.d.ts +3 -0
- package/dist/components/numeric-input/numeric-input.js +4 -4
- package/dist/components/otp-input/otp-input.d.ts +8 -0
- package/dist/components/popover/popover.js +9 -1
- package/dist/components/pressable/pressable.d.ts +1 -0
- package/dist/components/segmented-control/segmented-control.d.ts +39 -1
- package/dist/components/segmented-control/segmented-control.js +29 -5
- package/dist/components/select/select.js +7 -1
- package/dist/components/surface/surface.d.ts +5 -0
- package/dist/components/textarea/textarea.js +3 -3
- package/dist/components/toast/toast.d.ts +10 -10
- package/dist/components/toast/toast.js +53 -35
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/dismiss-keyboard-on-open.d.ts +16 -0
- package/dist/lib/dismiss-keyboard-on-open.js +22 -0
- package/dist/lib/frosted-surface.d.ts +3 -0
- package/dist/lib/recipes.d.ts +20 -0
- package/dist/lib/recipes.js +19 -2
- package/dist/lib/theme-bridge.d.ts +9 -0
- package/dist/lib/theme-bridge.js +3 -1
- 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
|
| ------------------- | ---------------------- | ----------------------------------- |
|
|
@@ -525,6 +477,10 @@ const icons = useIconColors();
|
|
|
525
477
|
`FABGroup` stacks secondary actions above one. Sizes start at 48dp, so a FAB never
|
|
526
478
|
needs `hitSlop`.
|
|
527
479
|
|
|
480
|
+
A floating FAB covers whatever scrolls under it, so a list behind one ends with room for
|
|
481
|
+
it: `contentContainerStyle={{ paddingBottom: fabClearance() }}`, passing the FAB's `size`
|
|
482
|
+
when it is not the default.
|
|
483
|
+
|
|
528
484
|
## Layout Primitives (Stack, Row)
|
|
529
485
|
|
|
530
486
|
Use `Stack` for vertical layouts, `Row` for horizontal layouts. Both support `gap`, `align`, and `justify` variants.
|
|
@@ -545,11 +501,17 @@ import { Stack, Row } from '@thewhileloop/whileui';
|
|
|
545
501
|
</Stack>;
|
|
546
502
|
```
|
|
547
503
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
|
551
|
-
|
|
|
552
|
-
|
|
|
504
|
+
<!-- api:Row -->
|
|
505
|
+
|
|
506
|
+
| Prop | Type | Default | Description |
|
|
507
|
+
| --------- | ------------------------------------------------------------------- | ---------- | ----------- |
|
|
508
|
+
| className | `string` | — | |
|
|
509
|
+
| gap | `'none' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | |
|
|
510
|
+
| align | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | `'center'` | |
|
|
511
|
+
| justify | `'start' \| 'center' \| 'end' \| 'between' \| 'around' \| 'evenly'` | `'start'` | |
|
|
512
|
+
|
|
513
|
+
Also accepts the props of `ViewProps`.
|
|
514
|
+
<!-- /api:Row -->
|
|
553
515
|
|
|
554
516
|
### Container
|
|
555
517
|
|
|
@@ -561,11 +523,16 @@ controls drift apart.
|
|
|
561
523
|
<Container size="lg">{children}</Container>
|
|
562
524
|
```
|
|
563
525
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
|
567
|
-
|
|
|
568
|
-
|
|
|
526
|
+
<!-- api:Container -->
|
|
527
|
+
|
|
528
|
+
| Prop | Type | Default | Description |
|
|
529
|
+
| --------- | ---------------------------------------- | ------- | ----------- |
|
|
530
|
+
| className | `string` | — | |
|
|
531
|
+
| size | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | |
|
|
532
|
+
| fill | `boolean` | `false` | |
|
|
533
|
+
|
|
534
|
+
Also accepts the props of `ViewProps`.
|
|
535
|
+
<!-- /api:Container -->
|
|
569
536
|
|
|
570
537
|
No platform branch, and none is needed: every size is wider than any phone, so on iOS
|
|
571
538
|
and Android the cap never engages and it renders as a plain full-width `View`. The same
|
|
@@ -630,142 +597,66 @@ Blocks with `loading` support: ProductCard, ListItem, NotificationItem, MetricCa
|
|
|
630
597
|
|
|
631
598
|
Block props: see TypeScript interfaces in `packages/ui/src/blocks`.
|
|
632
599
|
|
|
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
600
|
## Theming
|
|
684
601
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
@variant light {
|
|
689
|
-
--color-primary: oklch(0.6 0.2 160);
|
|
690
|
-
--color-background: oklch(1 0 0);
|
|
691
|
-
/* ... */
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
@variant dark {
|
|
695
|
-
--color-primary: oklch(0.6 0.2 160);
|
|
696
|
-
--color-background: oklch(0.145 0 0);
|
|
697
|
-
/* ... */
|
|
698
|
-
}
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
### Strict Theme Token Contract
|
|
702
|
-
|
|
703
|
-
The WhileUI token contract is strict for cross-app reuse. Define these in **every** theme variant (`@variant light`, `@variant dark`, and custom variants):
|
|
704
|
-
|
|
705
|
-
- Required core tokens: `background`, `foreground`, `card`, `card-foreground`, `popover`, `popover-foreground`, `primary`, `primary-foreground`, `secondary`, `secondary-foreground`, `muted`, `muted-foreground`, `accent`, `accent-foreground`, `destructive`, `destructive-foreground`, `border`, `input`, `ring`
|
|
706
|
-
- Optional status tokens: `success`, `success-foreground`, `warning`, `warning-foreground`, `info`, `info-foreground`
|
|
707
|
-
- Optional effect tokens: `overlay`, `overlay-strong`, `surface-elevated`, `surface-border`, `surface-highlight`, `surface-translucent`, `surface-translucent-border`, `state-hover`, `state-pressed`, `state-disabled`
|
|
708
|
-
- Optional visual tokens, generated from a preset: `--ui-blur-intensity-subtle`, `--ui-blur-intensity-medium`, `--ui-blur-intensity-strong`, `--ui-blur-saturation-pct`, `--ui-frosted-highlight-height`, `--ui-frosted-backdrop-blur-intensity`, `--ui-frosted-backdrop-blur-scale`, `--ui-frosted-android-tint-alpha-scale`, `--ui-frosted-android-experimental-blur`, `--ui-drawer-frosted-inset`, `--ui-drawer-frosted-radius`, `--ui-drawer-content-top-padding`
|
|
709
|
-
- Optional scale tokens: spacing (`--spacing`, `--spacing-*`), typography (`--text-*`, `--leading-*`, `--tracking-*`), radius (`--radius-*`), elevation (`--shadow-*`)
|
|
602
|
+
A theme is a set of CSS variables in `global.css`, in OKLCH, with a `light` and a `dark`
|
|
603
|
+
variant. Every component reads the same contract, so a theme must define **every** token
|
|
604
|
+
in both modes: a missing one silently falls back to an unstyled value at runtime.
|
|
710
605
|
|
|
711
|
-
|
|
606
|
+
### Generate a theme
|
|
712
607
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
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:
|
|
608
|
+
`themeFromSeeds` builds a complete light and dark theme from three colours, and
|
|
609
|
+
`themePresetToCss` writes every token, including the hex fallbacks React Native APIs need,
|
|
610
|
+
ready to paste into `global.css`:
|
|
742
611
|
|
|
743
|
-
```
|
|
744
|
-
import {
|
|
612
|
+
```ts
|
|
613
|
+
import { checkThemeContrast, themeFromSeeds, themePresetToCss } from '@thewhileloop/whileui/tokens';
|
|
614
|
+
|
|
615
|
+
const theme = themeFromSeeds({
|
|
616
|
+
name: 'studio',
|
|
617
|
+
description: 'Bone paper and burnt amber',
|
|
618
|
+
light: { background: '#f0ede9', foreground: '#13161c', primary: '#b65400' },
|
|
619
|
+
// optional: `dark` seeds (otherwise a dark page with the brand lifted to read on it),
|
|
620
|
+
// `overrides` for any base colour, `visual` for radius and density tokens
|
|
621
|
+
});
|
|
745
622
|
|
|
746
|
-
|
|
623
|
+
checkThemeContrast(theme.light).filter((c) => !c.pass);
|
|
624
|
+
// [{ pair: 'primary on background', ratio: 4.23, min: 4.5, pass: false }, …]
|
|
625
|
+
const css = themePresetToCss(theme); // paste into global.css
|
|
747
626
|
```
|
|
748
627
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
628
|
+
Seeds are kept as given; the colours it derives (muted text, text on each fill, status
|
|
629
|
+
colours) are tuned to WCAG AA. `checkThemeContrast` checks any palette, hand-written ones
|
|
630
|
+
included: every text pairing at 4.5:1 and the focus ring at 3:1. A failing seed is
|
|
631
|
+
reported, never changed, because a brand colour is a brand decision. The built-in presets,
|
|
632
|
+
`WHILEUI_PRESET_SOFT` and `WHILEUI_PRESET_ATELIER`, go through `themePresetToCss` the same
|
|
633
|
+
way.
|
|
752
634
|
|
|
753
|
-
|
|
635
|
+
Switch at runtime with `Uniwind.setTheme('dark')` (or `'light'`, `'system'`), or let
|
|
636
|
+
`useThemeBridge` hold the choice. It returns the chosen `mode`, the `resolvedTheme` on
|
|
637
|
+
screen, `setMode`, `toggleTheme` and `cycleMode`. A sun/moon button calls `toggleTheme`,
|
|
638
|
+
which always flips what is on screen; `cycleMode` steps light → dark → system and suits
|
|
639
|
+
a control that shows all three, because a step into `system` that resolves to the
|
|
640
|
+
current theme looks like a dead tap. Pass an `adapter` to keep the choice across launches:
|
|
754
641
|
|
|
755
|
-
```
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
642
|
+
```tsx
|
|
643
|
+
const { resolvedTheme, toggleTheme } = useThemeBridge({
|
|
644
|
+
adapter: {
|
|
645
|
+
loadThemeMode: () => storage.getItem('theme') as ThemeMode | null,
|
|
646
|
+
saveThemeMode: (mode) => storage.setItem('theme', mode),
|
|
647
|
+
},
|
|
648
|
+
});
|
|
762
649
|
```
|
|
763
650
|
|
|
764
|
-
|
|
651
|
+
A named extra theme is registered with `extraThemes: ['name']` in `metro.config.js` and
|
|
652
|
+
defined with `@variant name`. The full token list, with each default, is in `theme-contract.ts`,
|
|
653
|
+
and agents can read it from the MCP server's `get_theme_tokens`.
|
|
765
654
|
|
|
766
|
-
|
|
655
|
+
### Frosted surfaces
|
|
767
656
|
|
|
768
|
-
|
|
657
|
+
Cards and overlays take `frosted`, `blurIntensity` and `blurTintToken`
|
|
658
|
+
(`'surfaceElevated' | 'surfaceTranslucent' | 'card' | 'popover'`). Without a blur view
|
|
659
|
+
they tint only; register `expo-blur` once for real blur:
|
|
769
660
|
|
|
770
661
|
```tsx
|
|
771
662
|
import { BlurView } from 'expo-blur';
|
|
@@ -774,62 +665,40 @@ import { registerFrostedBlurView } from '@thewhileloop/whileui';
|
|
|
774
665
|
registerFrostedBlurView(BlurView);
|
|
775
666
|
```
|
|
776
667
|
|
|
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:
|
|
668
|
+
For a frosted look across a whole theme, make `surface-elevated`, `surface-border` and
|
|
669
|
+
`surface-highlight` translucent, in your light and dark variants or in a separate theme.
|
|
670
|
+
The tuning tokens:
|
|
786
671
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
672
|
+
| Token | Default | Effect |
|
|
673
|
+
| ------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------- |
|
|
674
|
+
| `--ui-blur-intensity-subtle` / `-medium` / `-strong` | 18 / 26 / 34 | The three blur presets |
|
|
675
|
+
| `--ui-frosted-android-tint-alpha-scale` | 1.18 | Android needs denser tint; `1` matches iOS and web |
|
|
676
|
+
| `--ui-frosted-android-experimental-blur` | 1 | Uses `expo-blur`'s experimental Android blur; `0` turns it off |
|
|
677
|
+
| `--ui-frosted-highlight-height` | 2 | Top sheen in px; `0` removes it |
|
|
678
|
+
| `--ui-blur-saturation-pct` | 145 | Web saturation multiplier |
|
|
679
|
+
| `--ui-frosted-backdrop-blur-intensity`, `-scale` | 22, 0.72 | Backdrop blur, and its ratio when a component overrides blur |
|
|
680
|
+
| `--ui-drawer-frosted-inset`, `-radius`, `--ui-drawer-content-top-padding` | 10, 34, 0 | Frosted drawer shell |
|
|
790
681
|
|
|
791
|
-
|
|
682
|
+
### Colours for React Native APIs
|
|
792
683
|
|
|
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.
|
|
684
|
+
Icons, `ActivityIndicator` and other native APIs need hex or rgb, not OKLCH.
|
|
685
|
+
`useThemeColors()` returns the whole palette as React Native colours, falling back to the
|
|
686
|
+
`--app-color-*` tokens a generated theme includes; `useIconColors()` is the subset icons
|
|
687
|
+
use, with `muted` mapped to `mutedForeground`.
|
|
806
688
|
|
|
807
689
|
```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
|
|
690
|
+
const icons = useIconColors();
|
|
691
|
+
<Feather name="heart" size={20} color={icons.muted} />;
|
|
819
692
|
```
|
|
820
693
|
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
Input, Textarea, NumericInput, SmartInput, Spinner, and LoadingScreen default to theme colors when you omit `placeholderTextColor` or `spinnerColor`.
|
|
694
|
+
Input, Textarea, NumericInput, SmartInput, Spinner and LoadingScreen pick theme colours
|
|
695
|
+
when you omit `placeholderTextColor` or `spinnerColor`.
|
|
825
696
|
|
|
826
697
|
### Interaction and motion
|
|
827
698
|
|
|
828
699
|
Press opacity, disabled opacity, motion durations, the responsive spring, and the drawer
|
|
829
700
|
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.
|
|
701
|
+
variables, so a theme does not change them.
|
|
833
702
|
|
|
834
703
|
For a custom control, read that table:
|
|
835
704
|
|
|
@@ -853,118 +722,6 @@ motion.durations.normal;
|
|
|
853
722
|
motion.springs.responsive;
|
|
854
723
|
```
|
|
855
724
|
|
|
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
725
|
## Focus rings
|
|
969
726
|
|
|
970
727
|
Every interactive component draws a themed focus ring on web. It uses `:focus-visible`,
|
|
@@ -992,14 +749,6 @@ supplies a themed replacement in the same class. The one exemption is a visually
|
|
|
992
749
|
input, where a visible proxy shows the focus instead — `OTPInput` does this with its
|
|
993
750
|
blinking cursor.
|
|
994
751
|
|
|
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
752
|
# API Reference
|
|
1004
753
|
|
|
1005
754
|
## Pressable
|
|
@@ -1022,12 +771,17 @@ import { Pressable, Text } from '@thewhileloop/whileui';
|
|
|
1022
771
|
`style` contracts. Feedback changes opacity without changing layout. Native press state
|
|
1023
772
|
comes directly from React Native; web adds a motion-policy-aware opacity transition.
|
|
1024
773
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
|
1028
|
-
|
|
|
1029
|
-
|
|
|
1030
|
-
|
|
|
774
|
+
<!-- api:Pressable -->
|
|
775
|
+
|
|
776
|
+
| Prop | Type | Default | Description |
|
|
777
|
+
| ---------------- | ----------------- | ----------- | --------------------------------------------------------------------------- |
|
|
778
|
+
| className | `string` | — | Additional Tailwind classes. |
|
|
779
|
+
| feedback | `PressedVariant` | `'none'` | Semantic press feedback. `none` preserves a plain React Native Pressable. |
|
|
780
|
+
| disabledFeedback | `DisabledVariant` | `'default'` | Opacity used while the control is unavailable. |
|
|
781
|
+
| loading | `boolean` | `false` | Loading controls are busy and non-interactive, just like disabled controls. |
|
|
782
|
+
|
|
783
|
+
Also accepts the props of React Native's `PressableProps`.
|
|
784
|
+
<!-- /api:Pressable -->
|
|
1031
785
|
|
|
1032
786
|
## Button
|
|
1033
787
|
|
|
@@ -1046,13 +800,20 @@ import { Button, ButtonText, ButtonIcon } from '@thewhileloop/whileui';
|
|
|
1046
800
|
</Button>;
|
|
1047
801
|
```
|
|
1048
802
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
|
1052
|
-
|
|
|
1053
|
-
|
|
|
1054
|
-
|
|
|
1055
|
-
|
|
|
803
|
+
<!-- api:Button -->
|
|
804
|
+
|
|
805
|
+
| Prop | Type | Default | Description |
|
|
806
|
+
| ---------------- | ----------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------- |
|
|
807
|
+
| children | `React.ReactNode` | — | |
|
|
808
|
+
| className | `string` | — | Additional Tailwind classes. |
|
|
809
|
+
| disabled | `boolean` | `false` | Disable the button. |
|
|
810
|
+
| loading | `boolean` | `false` | Shows a centered spinner, exposes busy state, and prevents interaction. |
|
|
811
|
+
| disabledFeedback | `DisabledVariant` | — | Opacity used while the control is unavailable. |
|
|
812
|
+
| size | `'sm' \| 'lg' \| 'default' \| 'icon'` | `'default'` | |
|
|
813
|
+
| variant | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'default'` | |
|
|
814
|
+
|
|
815
|
+
Also accepts the props of `PressableProps`.
|
|
816
|
+
<!-- /api:Button -->
|
|
1056
817
|
|
|
1057
818
|
## Text
|
|
1058
819
|
|
|
@@ -1060,28 +821,51 @@ import { Button, ButtonText, ButtonIcon } from '@thewhileloop/whileui';
|
|
|
1060
821
|
classes are unnecessary for normal hierarchy. `heading` also sets
|
|
1061
822
|
`accessibilityRole="header"` unless the caller supplies another role.
|
|
1062
823
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
|
1066
|
-
|
|
|
824
|
+
<!-- api:Text -->
|
|
825
|
+
|
|
826
|
+
| Prop | Type | Default | Description |
|
|
827
|
+
| --------- | ---------------------------------------------------------------------------------------------------------- | ----------- | ----------- |
|
|
828
|
+
| className | `string` | — | |
|
|
829
|
+
| size | `'base' \| 'xs' \| 'sm' \| 'lg' \| 'xl' \| '2xl' \| '3xl'` | — | |
|
|
830
|
+
| variant | `'default' \| 'heading' \| 'title' \| 'subtitle' \| 'body' \| 'caption' \| 'label' \| 'eyebrow' \| 'code'` | `'default'` | |
|
|
831
|
+
|
|
832
|
+
Also accepts the props of React Native's `TextProps`.
|
|
833
|
+
<!-- /api:Text -->
|
|
1067
834
|
|
|
1068
835
|
## Input
|
|
1069
836
|
|
|
1070
837
|
```tsx
|
|
1071
|
-
import { Input } from '@thewhileloop/whileui';
|
|
838
|
+
import { Input, useIconColors } from '@thewhileloop/whileui';
|
|
839
|
+
import { Feather } from '@expo/vector-icons';
|
|
840
|
+
|
|
841
|
+
const icons = useIconColors();
|
|
1072
842
|
|
|
1073
843
|
<Input placeholder="Email" variant="default" value={value} onChangeText={setValue} />;
|
|
844
|
+
|
|
845
|
+
<Input
|
|
846
|
+
placeholder="Search"
|
|
847
|
+
prefix={<Feather name="search" size={16} color={icons.muted} aria-hidden />}
|
|
848
|
+
/>;
|
|
1074
849
|
```
|
|
1075
850
|
|
|
1076
|
-
On
|
|
1077
|
-
|
|
851
|
+
On iOS and Android, Input, Textarea, NumericInput and SmartInput draw the caret and
|
|
852
|
+
selection in the theme's primary colour; pass `selectionColor` (and `cursorColor` on
|
|
853
|
+
Android) to override it. On web, filled surfaces keep their themed background,
|
|
854
|
+
foreground and caret when the browser autofills them.
|
|
1078
855
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
|
1082
|
-
|
|
|
1083
|
-
|
|
|
1084
|
-
|
|
|
856
|
+
<!-- api:Input -->
|
|
857
|
+
|
|
858
|
+
| Prop | Type | Default | Description |
|
|
859
|
+
| -------------- | --------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
860
|
+
| aria-invalid | `boolean` | — | |
|
|
861
|
+
| className | `string` | — | |
|
|
862
|
+
| prefix | `React.ReactNode` | — | Shown before the text. A decorative icon here needs `aria-hidden`; screen readers otherwise stop on it as a separate element and read out its glyph. |
|
|
863
|
+
| suffix | `React.ReactNode` | — | Shown after the text: a unit, or a control such as a clear button. |
|
|
864
|
+
| inputClassName | `string` | — | |
|
|
865
|
+
| variant | `'default' \| 'error' \| 'plain'` | `'default'` | |
|
|
866
|
+
|
|
867
|
+
Also accepts the props of `TextInputProps`.
|
|
868
|
+
<!-- /api:Input -->
|
|
1085
869
|
|
|
1086
870
|
## NumericInput
|
|
1087
871
|
|
|
@@ -1100,15 +884,28 @@ import { NumericInput } from '@thewhileloop/whileui';
|
|
|
1100
884
|
/>;
|
|
1101
885
|
```
|
|
1102
886
|
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
|
1106
|
-
|
|
|
1107
|
-
|
|
|
1108
|
-
|
|
|
1109
|
-
|
|
|
1110
|
-
| prefix
|
|
1111
|
-
|
|
|
887
|
+
<!-- api:NumericInput -->
|
|
888
|
+
|
|
889
|
+
| Prop | Type | Default | Description |
|
|
890
|
+
| -------------- | --------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
891
|
+
| aria-invalid | `boolean` | — | |
|
|
892
|
+
| className | `string` | — | |
|
|
893
|
+
| inputClassName | `string` | — | |
|
|
894
|
+
| prefix | `React.ReactNode` | — | |
|
|
895
|
+
| suffix | `React.ReactNode` | — | |
|
|
896
|
+
| value | `number \| null` | — | Controlled numeric value. |
|
|
897
|
+
| defaultValue | `number` | — | |
|
|
898
|
+
| onValueChange | `(value: number \| null) => void` | — | Numeric value change callback. |
|
|
899
|
+
| min | `number` | — | |
|
|
900
|
+
| max | `number` | — | |
|
|
901
|
+
| step | `number` | `1` | |
|
|
902
|
+
| showSteppers | `boolean` | `false` | Show decrement/increment controls. |
|
|
903
|
+
| unstyled | `boolean` | `false` | Drop the field shell (border, background, radius, padding) for embedding inside another field shell like LabeledField. |
|
|
904
|
+
| size | `'default' \| 'compact'` | `'default'` | |
|
|
905
|
+
| variant | `'default' \| 'error'` | `'default'` | |
|
|
906
|
+
|
|
907
|
+
Also accepts the props of `TextInputProps`.
|
|
908
|
+
<!-- /api:NumericInput -->
|
|
1112
909
|
|
|
1113
910
|
## OTPInput
|
|
1114
911
|
|
|
@@ -1124,16 +921,23 @@ import { OTPInput } from '@thewhileloop/whileui';
|
|
|
1124
921
|
/>;
|
|
1125
922
|
```
|
|
1126
923
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
|
1130
|
-
|
|
|
1131
|
-
|
|
|
1132
|
-
|
|
|
1133
|
-
|
|
|
1134
|
-
|
|
|
1135
|
-
|
|
|
1136
|
-
|
|
|
924
|
+
<!-- api:OTPInput -->
|
|
925
|
+
|
|
926
|
+
| Prop | Type | Default | Description |
|
|
927
|
+
| ------------- | ------------------------- | ----------- | ---------------------------------- |
|
|
928
|
+
| length | `number` | `6` | Number of digit cells. |
|
|
929
|
+
| value | `string` | — | Controlled value. |
|
|
930
|
+
| onValueChange | `(value: string) => void` | — | Called on each digit change. |
|
|
931
|
+
| onComplete | `(code: string) => void` | — | Called when all digits are filled. |
|
|
932
|
+
| variant | `'default' \| 'error'` | `'default'` | Error variant triggers shake. |
|
|
933
|
+
| size | `'default' \| 'compact'` | `'default'` | Cell size. |
|
|
934
|
+
| secure | `boolean` | `false` | Mask digits with dots. |
|
|
935
|
+
| disabled | `boolean` | `false` | |
|
|
936
|
+
| autoFocus | `boolean` | `false` | Focus first cell on mount. |
|
|
937
|
+
| className | `string` | — | |
|
|
938
|
+
|
|
939
|
+
Also accepts the props of `ViewProps`.
|
|
940
|
+
<!-- /api:OTPInput -->
|
|
1137
941
|
|
|
1138
942
|
## FormField
|
|
1139
943
|
|
|
@@ -1161,6 +965,10 @@ import {
|
|
|
1161
965
|
custom controls; labels, hints, messages, density, and required indicators remain part
|
|
1162
966
|
of the compound field rather than each input.
|
|
1163
967
|
|
|
968
|
+
`FormLabel` names the `Input`, `Textarea` or `NumericInput` beside it for assistive
|
|
969
|
+
technology (`aria-labelledby` on web and Android; `useFormFieldState().labelId` gives a
|
|
970
|
+
custom control the same id). A label passed to the control itself wins.
|
|
971
|
+
|
|
1164
972
|
## Surface
|
|
1165
973
|
|
|
1166
974
|
```tsx
|
|
@@ -1171,14 +979,22 @@ import { Surface } from '@thewhileloop/whileui';
|
|
|
1171
979
|
</Surface>;
|
|
1172
980
|
```
|
|
1173
981
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
|
1177
|
-
|
|
|
1178
|
-
|
|
|
1179
|
-
|
|
|
1180
|
-
|
|
|
1181
|
-
|
|
|
982
|
+
<!-- api:Surface -->
|
|
983
|
+
|
|
984
|
+
| Prop | Type | Default | Description |
|
|
985
|
+
| ------------- | ------------------------------ | ------------------- | ------------------------------------ |
|
|
986
|
+
| level | `SurfaceLevel` | `'base'` | Semantic background and depth tier. |
|
|
987
|
+
| padding | `SurfacePaddingSize` | `'default'` | Token-backed interior spacing. |
|
|
988
|
+
| radius | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'xl'` | Token-backed corner radius. |
|
|
989
|
+
| bordered | `boolean` | `true` | Draw the theme hairline border. |
|
|
990
|
+
| shadow | `ShadowTier` | — | Override the level's depth. |
|
|
991
|
+
| className | `string` | — | |
|
|
992
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
993
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
994
|
+
| blurTintToken | `FrostedTintToken` | `LEVEL_TINT[level]` | Tint token used for frosted mode. |
|
|
995
|
+
|
|
996
|
+
Also accepts the props of `ViewProps`.
|
|
997
|
+
<!-- /api:Surface -->
|
|
1182
998
|
|
|
1183
999
|
## LabeledField
|
|
1184
1000
|
|
|
@@ -1220,9 +1036,24 @@ import {
|
|
|
1220
1036
|
</SegmentedControl>;
|
|
1221
1037
|
```
|
|
1222
1038
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1039
|
+
When the labels do not fit a phone, `wrap="scroll"` keeps them on one row at full width
|
|
1040
|
+
and scrolls sideways, instead of wrapping or truncating them.
|
|
1041
|
+
|
|
1042
|
+
<!-- api:SegmentedControl -->
|
|
1043
|
+
|
|
1044
|
+
| Prop | Type | Default | Description |
|
|
1045
|
+
| ------------- | ------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1046
|
+
| value | `string` | — | |
|
|
1047
|
+
| defaultValue | `string` | `''` | |
|
|
1048
|
+
| onValueChange | `(value: string) => void` | — | |
|
|
1049
|
+
| variant | `'default' \| 'pill'` | `'default'` | Pill = rounded-full items. |
|
|
1050
|
+
| wrap | `boolean \| 'auto' \| 'scroll'` | `'auto'` | `true` wraps segments onto more rows; `false` keeps one row and shares its width. `'scroll'` keeps one row with each segment as wide as its label and scrolls sideways when they do not fit — for labels a phone cannot show in full. `'auto'` wraps above three segments. |
|
|
1051
|
+
| size | `'default' \| 'compact'` | `'default'` | |
|
|
1052
|
+
| disabled | `boolean` | `false` | |
|
|
1053
|
+
| className | `string` | — | |
|
|
1054
|
+
|
|
1055
|
+
Also accepts the props of `ViewProps`.
|
|
1056
|
+
<!-- /api:SegmentedControl -->
|
|
1226
1057
|
|
|
1227
1058
|
## DataRow
|
|
1228
1059
|
|
|
@@ -1281,13 +1112,20 @@ import {
|
|
|
1281
1112
|
</Card>;
|
|
1282
1113
|
```
|
|
1283
1114
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
|
1287
|
-
|
|
|
1288
|
-
|
|
|
1289
|
-
|
|
|
1290
|
-
|
|
|
1115
|
+
<!-- api:Card -->
|
|
1116
|
+
|
|
1117
|
+
| Prop | Type | Default | Description |
|
|
1118
|
+
| ------------- | ------------------------------------- | ----------- | ------------------------------------------------------- |
|
|
1119
|
+
| shadow | `ShadowTier` | `'sm'` | Elevation tier; 'sm' by default, 'none' for flat cards. |
|
|
1120
|
+
| className | `string` | — | |
|
|
1121
|
+
| padding | `'none' \| 'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
1122
|
+
| unstyled | `boolean` | `false` | |
|
|
1123
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1124
|
+
| blurIntensity | `number` | — | Override blur amount directly. |
|
|
1125
|
+
| blurTintToken | `FrostedTintToken` | — | Tint token used for frosted mode. |
|
|
1126
|
+
|
|
1127
|
+
Also accepts the props of `ViewProps`.
|
|
1128
|
+
<!-- /api:Card -->
|
|
1291
1129
|
|
|
1292
1130
|
## Badge
|
|
1293
1131
|
|
|
@@ -1299,9 +1137,15 @@ import { Badge, BadgeText } from '@thewhileloop/whileui';
|
|
|
1299
1137
|
</Badge>;
|
|
1300
1138
|
```
|
|
1301
1139
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
|
1140
|
+
<!-- api:Badge -->
|
|
1141
|
+
|
|
1142
|
+
| Prop | Type | Default | Description |
|
|
1143
|
+
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ----------- |
|
|
1144
|
+
| className | `string` | — | |
|
|
1145
|
+
| variant | `'default' \| 'soft' \| 'destructive' \| 'outline' \| 'secondary' \| 'success' \| 'warning' \| 'info' \| 'soft-secondary' \| 'soft-destructive' \| 'soft-success' \| 'soft-warning' \| 'soft-info'` | `'default'` | |
|
|
1146
|
+
|
|
1147
|
+
Also accepts the props of `ViewProps`.
|
|
1148
|
+
<!-- /api:Badge -->
|
|
1305
1149
|
|
|
1306
1150
|
## Alert
|
|
1307
1151
|
|
|
@@ -1312,11 +1156,24 @@ import { Alert, AlertTitle, AlertDescription } from '@thewhileloop/whileui';
|
|
|
1312
1156
|
<AlertTitle>Heads up!</AlertTitle>
|
|
1313
1157
|
<AlertDescription>Something happened.</AlertDescription>
|
|
1314
1158
|
</Alert>;
|
|
1159
|
+
|
|
1160
|
+
// Shown after a failed submit: also spoken on iOS when it appears.
|
|
1161
|
+
<Alert variant="destructive" announce>
|
|
1162
|
+
<AlertTitle>Could not save</AlertTitle>
|
|
1163
|
+
<AlertDescription>Check your connection and try again.</AlertDescription>
|
|
1164
|
+
</Alert>;
|
|
1315
1165
|
```
|
|
1316
1166
|
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
|
1167
|
+
<!-- api:Alert -->
|
|
1168
|
+
|
|
1169
|
+
| Prop | Type | Default | Description |
|
|
1170
|
+
| --------- | ---------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1171
|
+
| 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. |
|
|
1172
|
+
| className | `string` | — | |
|
|
1173
|
+
| variant | `'default' \| 'destructive' \| 'success' \| 'warning' \| 'info'` | `'default'` | |
|
|
1174
|
+
|
|
1175
|
+
Also accepts the props of `ViewProps`.
|
|
1176
|
+
<!-- /api:Alert -->
|
|
1320
1177
|
|
|
1321
1178
|
## Dialog
|
|
1322
1179
|
|
|
@@ -1357,11 +1214,17 @@ import {
|
|
|
1357
1214
|
|
|
1358
1215
|
`DialogContent` supports frosted props:
|
|
1359
1216
|
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
|
1363
|
-
|
|
|
1364
|
-
|
|
|
1217
|
+
<!-- api:DialogContent -->
|
|
1218
|
+
|
|
1219
|
+
| Prop | Type | Default | Description |
|
|
1220
|
+
| ------------- | ------------------ | ---------------------- | ------------------------------------ |
|
|
1221
|
+
| className | `string` | — | |
|
|
1222
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1223
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1224
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1225
|
+
|
|
1226
|
+
Also accepts the props of `ViewProps`.
|
|
1227
|
+
<!-- /api:DialogContent -->
|
|
1365
1228
|
|
|
1366
1229
|
## AlertDialog
|
|
1367
1230
|
|
|
@@ -1399,12 +1262,18 @@ import {
|
|
|
1399
1262
|
|
|
1400
1263
|
`AlertDialogContent` supports the same frosted props as `DialogContent`.
|
|
1401
1264
|
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
|
1405
|
-
|
|
|
1406
|
-
|
|
|
1407
|
-
|
|
|
1265
|
+
<!-- api:AlertDialogContent -->
|
|
1266
|
+
|
|
1267
|
+
| Prop | Type | Default | Description |
|
|
1268
|
+
| ------------- | ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1269
|
+
| className | `string` | — | |
|
|
1270
|
+
| 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`). |
|
|
1271
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1272
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1273
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1274
|
+
|
|
1275
|
+
Also accepts the props of `ViewProps`.
|
|
1276
|
+
<!-- /api:AlertDialogContent -->
|
|
1408
1277
|
|
|
1409
1278
|
### React Native: stacked modals
|
|
1410
1279
|
|
|
@@ -1423,11 +1292,18 @@ import { Checkbox } from '@thewhileloop/whileui';
|
|
|
1423
1292
|
<Checkbox checked={checked} onCheckedChange={setChecked} />;
|
|
1424
1293
|
```
|
|
1425
1294
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
|
1429
|
-
|
|
|
1430
|
-
|
|
|
1295
|
+
<!-- api:Checkbox -->
|
|
1296
|
+
|
|
1297
|
+
| Prop | Type | Default | Description |
|
|
1298
|
+
| --------------- | ---------------------------- | ------- | ----------- |
|
|
1299
|
+
| checked | `boolean` | — | |
|
|
1300
|
+
| defaultChecked | `boolean` | `false` | |
|
|
1301
|
+
| onCheckedChange | `(checked: boolean) => void` | — | |
|
|
1302
|
+
| disabled | `boolean` | `false` | |
|
|
1303
|
+
| className | `string` | — | |
|
|
1304
|
+
|
|
1305
|
+
Also accepts the props of `ViewProps`.
|
|
1306
|
+
<!-- /api:Checkbox -->
|
|
1431
1307
|
|
|
1432
1308
|
## Switch
|
|
1433
1309
|
|
|
@@ -1437,10 +1313,18 @@ import { Switch } from '@thewhileloop/whileui';
|
|
|
1437
1313
|
<Switch checked={checked} onCheckedChange={setChecked} />;
|
|
1438
1314
|
```
|
|
1439
1315
|
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
|
1443
|
-
|
|
|
1316
|
+
<!-- api:Switch -->
|
|
1317
|
+
|
|
1318
|
+
| Prop | Type | Default | Description |
|
|
1319
|
+
| --------------- | ---------------------------- | ------- | ----------- |
|
|
1320
|
+
| checked | `boolean` | — | |
|
|
1321
|
+
| defaultChecked | `boolean` | `false` | |
|
|
1322
|
+
| onCheckedChange | `(checked: boolean) => void` | — | |
|
|
1323
|
+
| disabled | `boolean` | `false` | |
|
|
1324
|
+
| className | `string` | — | |
|
|
1325
|
+
|
|
1326
|
+
Also accepts the props of `ViewProps`.
|
|
1327
|
+
<!-- /api:Switch -->
|
|
1444
1328
|
|
|
1445
1329
|
## RadioGroup
|
|
1446
1330
|
|
|
@@ -1481,13 +1365,25 @@ selected values stay on one line and truncate without displacing the trigger che
|
|
|
1481
1365
|
For popover presentation on web, the options panel matches the trigger width, caps
|
|
1482
1366
|
itself to the collision-available viewport, and wraps long option labels inside it.
|
|
1483
1367
|
|
|
1368
|
+
Opening a Select, or a Popover, closes the on-screen keyboard first, so options opened
|
|
1369
|
+
from a form are never hidden behind it. Both draw in a portal, where the platform would
|
|
1370
|
+
otherwise leave the keyboard up; the Modal-hosted overlays close it on their own.
|
|
1371
|
+
|
|
1484
1372
|
`SelectContent` supports optional frosted props:
|
|
1485
1373
|
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
|
1489
|
-
|
|
|
1490
|
-
|
|
|
1374
|
+
<!-- api:SelectContent -->
|
|
1375
|
+
|
|
1376
|
+
| Prop | Type | Default | Description |
|
|
1377
|
+
| ------------- | ------------------------------------------------------------------ | ------------- | ------------------------------------ |
|
|
1378
|
+
| className | `string` | — | |
|
|
1379
|
+
| portalHost | `string` | — | |
|
|
1380
|
+
| insets | `{ top?: number; bottom?: number; left?: number; right?: number }` | — | |
|
|
1381
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1382
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
1383
|
+
| blurTintToken | `FrostedTintToken` | `'popover'` | Tint token used for frosted mode. |
|
|
1384
|
+
|
|
1385
|
+
Also accepts the props of the underlying primitive.
|
|
1386
|
+
<!-- /api:SelectContent -->
|
|
1491
1387
|
|
|
1492
1388
|
`SelectContent` keeps its listbox inside the safe area by default; pass `insets` only to
|
|
1493
1389
|
change that margin. A list taller than the screen scrolls inside the listbox on every
|
|
@@ -1536,10 +1432,17 @@ import {
|
|
|
1536
1432
|
</Accordion>;
|
|
1537
1433
|
```
|
|
1538
1434
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
|
1542
|
-
|
|
|
1435
|
+
<!-- api:Accordion -->
|
|
1436
|
+
|
|
1437
|
+
| Prop | Type | Default | Description |
|
|
1438
|
+
| ------------ | -------------------- | ---------- | ----------- |
|
|
1439
|
+
| type | `AccordionType` | `'single'` | |
|
|
1440
|
+
| defaultValue | `string \| string[]` | — | |
|
|
1441
|
+
| collapsible | `boolean` | `true` | |
|
|
1442
|
+
| className | `string` | — | |
|
|
1443
|
+
|
|
1444
|
+
Also accepts the props of `ViewProps`.
|
|
1445
|
+
<!-- /api:Accordion -->
|
|
1543
1446
|
|
|
1544
1447
|
## Avatar
|
|
1545
1448
|
|
|
@@ -1552,9 +1455,15 @@ import { Avatar, AvatarImage, AvatarFallback } from '@thewhileloop/whileui';
|
|
|
1552
1455
|
</Avatar>;
|
|
1553
1456
|
```
|
|
1554
1457
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
|
1458
|
+
<!-- api:Avatar -->
|
|
1459
|
+
|
|
1460
|
+
| Prop | Type | Default | Description |
|
|
1461
|
+
| --------- | ----------------------------------- | ----------- | ----------- |
|
|
1462
|
+
| className | `string` | — | |
|
|
1463
|
+
| size | `'sm' \| 'lg' \| 'xl' \| 'default'` | `'default'` | |
|
|
1464
|
+
|
|
1465
|
+
Also accepts the props of `ViewProps`.
|
|
1466
|
+
<!-- /api:Avatar -->
|
|
1558
1467
|
|
|
1559
1468
|
`AvatarImage` keeps `AvatarFallback` visible while loading or after an error. Its
|
|
1560
1469
|
`onLoadingStatusChange` callback reports `'loading'`, `'loaded'`, and `'error'`, including
|
|
@@ -1568,10 +1477,17 @@ import { Progress } from '@thewhileloop/whileui';
|
|
|
1568
1477
|
<Progress value={50} size="default" />;
|
|
1569
1478
|
```
|
|
1570
1479
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
|
1574
|
-
|
|
|
1480
|
+
<!-- api:Progress -->
|
|
1481
|
+
|
|
1482
|
+
| Prop | Type | Default | Description |
|
|
1483
|
+
| ------------------ | --------------------------- | ----------- | ----------- |
|
|
1484
|
+
| value | `number` | `0` | |
|
|
1485
|
+
| indicatorClassName | `string` | — | |
|
|
1486
|
+
| className | `string` | — | |
|
|
1487
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
1488
|
+
|
|
1489
|
+
Also accepts the props of `ViewProps`.
|
|
1490
|
+
<!-- /api:Progress -->
|
|
1575
1491
|
|
|
1576
1492
|
## Toast
|
|
1577
1493
|
|
|
@@ -1599,6 +1515,11 @@ toast({ title: 'Success', description: 'Saved!', variant: 'success' });
|
|
|
1599
1515
|
Each toast owns its duration from the moment it appears; adding or dismissing another
|
|
1600
1516
|
toast does not restart timers already in progress.
|
|
1601
1517
|
|
|
1518
|
+
Every variant sits on the theme's `popover` surface; `success` and `destructive` mark the
|
|
1519
|
+
toast with a status dot and tinted border rather than a solid fill. The stack is capped
|
|
1520
|
+
at `max-w-md` and centred, which never engages on a phone. A toast is announced politely
|
|
1521
|
+
to screen readers without taking focus, and its slide-in follows the motion policy.
|
|
1522
|
+
|
|
1602
1523
|
## Popover
|
|
1603
1524
|
|
|
1604
1525
|
```tsx
|
|
@@ -1618,11 +1539,19 @@ import { Popover, PopoverTrigger, PopoverContent } from '@thewhileloop/whileui';
|
|
|
1618
1539
|
|
|
1619
1540
|
`PopoverContent` supports frosted props:
|
|
1620
1541
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
|
1624
|
-
|
|
|
1625
|
-
|
|
|
1542
|
+
<!-- api:PopoverContent -->
|
|
1543
|
+
|
|
1544
|
+
| Prop | Type | Default | Description |
|
|
1545
|
+
| ------------- | ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------- |
|
|
1546
|
+
| className | `string` | — | |
|
|
1547
|
+
| portalHost | `string` | — | |
|
|
1548
|
+
| insets | `Partial<OverlayInsets>` | — | Collision padding. Defaults to safe area plus a margin so the panel never sits flush against an edge. |
|
|
1549
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1550
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
1551
|
+
| blurTintToken | `FrostedTintToken` | `'popover'` | Tint token used for frosted mode. |
|
|
1552
|
+
|
|
1553
|
+
Also accepts the props of the underlying primitive.
|
|
1554
|
+
<!-- /api:PopoverContent -->
|
|
1626
1555
|
|
|
1627
1556
|
## DropdownMenu
|
|
1628
1557
|
|
|
@@ -1710,7 +1639,7 @@ announces as a heading to assistive technology — as do `DropdownMenuLabel` and
|
|
|
1710
1639
|
## SignInForm
|
|
1711
1640
|
|
|
1712
1641
|
Copy `sign-in-form.tsx` and its sibling `auth-form-footer.tsx` from
|
|
1713
|
-
`apps/showcase/templates/auth/`, then:
|
|
1642
|
+
`apps/showcase/catalog/templates/auth/`, then:
|
|
1714
1643
|
|
|
1715
1644
|
```tsx
|
|
1716
1645
|
import { SignInForm } from './templates/auth'; // or your path
|
|
@@ -1735,7 +1664,7 @@ import { SignInForm } from './templates/auth'; // or your path
|
|
|
1735
1664
|
## SignUpForm
|
|
1736
1665
|
|
|
1737
1666
|
Copy `sign-up-form.tsx` and its sibling `auth-form-footer.tsx` from
|
|
1738
|
-
`apps/showcase/templates/auth/`, then:
|
|
1667
|
+
`apps/showcase/catalog/templates/auth/`, then:
|
|
1739
1668
|
|
|
1740
1669
|
```tsx
|
|
1741
1670
|
import { SignUpForm } from './templates/auth'; // or your path
|
|
@@ -1789,14 +1718,20 @@ import { AppShell, PageSkeleton } from '@thewhileloop/whileui';
|
|
|
1789
1718
|
</AppShell>;
|
|
1790
1719
|
```
|
|
1791
1720
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
|
1795
|
-
|
|
|
1796
|
-
|
|
|
1797
|
-
|
|
|
1798
|
-
|
|
|
1799
|
-
|
|
|
1721
|
+
<!-- api:AppShell -->
|
|
1722
|
+
|
|
1723
|
+
| Prop | Type | Default | Description |
|
|
1724
|
+
| --------- | ----------------- | ------- | --------------------------------------------------------- |
|
|
1725
|
+
| header | `React.ReactNode` | — | Header slot. |
|
|
1726
|
+
| footer | `React.ReactNode` | — | Footer slot. |
|
|
1727
|
+
| bottomNav | `React.ReactNode` | — | Bottom navigation slot. |
|
|
1728
|
+
| safeArea | `boolean` | `true` | Wrap in SafeAreaView. |
|
|
1729
|
+
| loading | `boolean` | `false` | Keep shell mounted while swapping content with skeleton. |
|
|
1730
|
+
| skeleton | `React.ReactNode` | — | Optional custom loading placeholder for the content area. |
|
|
1731
|
+
| className | `string` | — | |
|
|
1732
|
+
|
|
1733
|
+
Also accepts the props of `ViewProps`.
|
|
1734
|
+
<!-- /api:AppShell -->
|
|
1800
1735
|
|
|
1801
1736
|
## ActionBar
|
|
1802
1737
|
|
|
@@ -1840,7 +1775,7 @@ import { SmartInput, Button, ButtonText } from '@thewhileloop/whileui';
|
|
|
1840
1775
|
onChangeText={setMessage}
|
|
1841
1776
|
placeholderExamples={['Log breakfast', 'Add a 30 minute walk', 'Set weight to 72 kg']}
|
|
1842
1777
|
leftSlot={
|
|
1843
|
-
<Button variant="ghost" size="icon">
|
|
1778
|
+
<Button variant="ghost" size="icon" accessibilityLabel="Add">
|
|
1844
1779
|
<Icon name="add" />
|
|
1845
1780
|
</Button>
|
|
1846
1781
|
}
|
|
@@ -1897,24 +1832,37 @@ const [value, setValue] = useState('');
|
|
|
1897
1832
|
onSuggestionPress={(text) => setValue(text)}
|
|
1898
1833
|
emptyTitle="How can I help?"
|
|
1899
1834
|
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>}
|
|
1835
|
+
leftSlot={<Button variant="ghost" size="icon" accessibilityLabel="Attach"><Icon name="paperclip" /></Button>}
|
|
1836
|
+
rightSlot={<Button size="icon" accessibilityLabel="Send" onPress={handleSend}><Icon name="send" /></Button>}
|
|
1902
1837
|
/>;
|
|
1903
1838
|
```
|
|
1904
1839
|
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
|
1908
|
-
|
|
|
1909
|
-
| `
|
|
1910
|
-
|
|
|
1911
|
-
|
|
|
1912
|
-
|
|
|
1913
|
-
| `
|
|
1914
|
-
| `
|
|
1915
|
-
|
|
|
1916
|
-
|
|
|
1917
|
-
|
|
|
1840
|
+
<!-- api:Chat -->
|
|
1841
|
+
|
|
1842
|
+
| Prop | Type | Default | Description |
|
|
1843
|
+
| ----------------------- | ------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------- |
|
|
1844
|
+
| messages (required) | `ChatMessage[]` | — | Messages to show, each `{ id, role, content, secondary?, contentSize? }`. |
|
|
1845
|
+
| value (required) | `string` | — | Input value. |
|
|
1846
|
+
| onChangeText (required) | `(text: string) => void` | — | Input change. |
|
|
1847
|
+
| onSend (required) | `() => void` | — | Send handler. |
|
|
1848
|
+
| placeholder | `string` | `'Message...'` | |
|
|
1849
|
+
| suggestions | `string[]` | `EMPTY_SUGGESTIONS` | Chips when empty. |
|
|
1850
|
+
| onSuggestionPress | `(text: string) => void` | — | |
|
|
1851
|
+
| leftSlot | `React.ReactNode` | — | |
|
|
1852
|
+
| rightSlot | `React.ReactNode` | — | |
|
|
1853
|
+
| loading | `boolean` | `false` | |
|
|
1854
|
+
| header | `React.ReactNode` | — | |
|
|
1855
|
+
| emptyTitle | `string` | — | |
|
|
1856
|
+
| emptyDescription | `string` | — | |
|
|
1857
|
+
| exampleMessage | `ChatMessage` | — | Example message shown in empty state so users see the bubble UI. |
|
|
1858
|
+
| renderMessage | `(message: ChatMessage) => React.ReactNode` | — | Custom message renderer (markdown, code, images). Falls back to ChatMessageBubble. |
|
|
1859
|
+
| loadingIndicator | `React.ReactNode` | — | Shown at bottom when loading (typing indicator). |
|
|
1860
|
+
| inputSafeArea | `boolean` | `true` | Passed to SmartInput. Use false when Chat is in a contained demo. |
|
|
1861
|
+
| keyboardVerticalOffset | `number` | `0` | Offset for KeyboardAvoidingView when Chat has a header (e.g. StatusBar + header height). |
|
|
1862
|
+
| className | `string` | — | |
|
|
1863
|
+
| messagesClassName | `string` | — | |
|
|
1864
|
+
|
|
1865
|
+
<!-- /api:Chat -->
|
|
1918
1866
|
|
|
1919
1867
|
`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
1868
|
|
|
@@ -1949,18 +1897,24 @@ import { SmartImage } from '@thewhileloop/whileui';
|
|
|
1949
1897
|
/>;
|
|
1950
1898
|
```
|
|
1951
1899
|
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
|
1955
|
-
|
|
|
1956
|
-
|
|
|
1957
|
-
|
|
|
1958
|
-
|
|
|
1959
|
-
|
|
|
1960
|
-
|
|
|
1961
|
-
|
|
|
1962
|
-
|
|
|
1963
|
-
|
|
|
1900
|
+
<!-- api:SmartImage -->
|
|
1901
|
+
|
|
1902
|
+
| Prop | Type | Default | Description |
|
|
1903
|
+
| ----------------- | -------------------------------------------------- | --------- | ----------- |
|
|
1904
|
+
| className | `string` | — | |
|
|
1905
|
+
| source (required) | `ImageProps['source']` | — | |
|
|
1906
|
+
| alt (required) | `string` | — | |
|
|
1907
|
+
| skeleton | `boolean` | `true` | |
|
|
1908
|
+
| skeletonClassName | `string` | — | |
|
|
1909
|
+
| transition | `number` | `200` | |
|
|
1910
|
+
| onLoad | `() => void` | — | |
|
|
1911
|
+
| onError | `() => void` | — | |
|
|
1912
|
+
| fallbackSource | `ImageProps['source']` | — | |
|
|
1913
|
+
| radius | `'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | |
|
|
1914
|
+
| objectFit | `'fill' \| 'cover' \| 'contain' \| 'scale-down'` | `'cover'` | |
|
|
1915
|
+
|
|
1916
|
+
Also accepts the props of `ViewProps`.
|
|
1917
|
+
<!-- /api:SmartImage -->
|
|
1964
1918
|
|
|
1965
1919
|
The skeleton returns whenever the primary image starts loading again. A failed primary
|
|
1966
1920
|
source switches to `fallbackSource` when supplied without re-entering a primary/fallback
|
|
@@ -2030,19 +1984,26 @@ const [time, setTime] = useState<string | null>(null);
|
|
|
2030
1984
|
/>;
|
|
2031
1985
|
```
|
|
2032
1986
|
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
|
2036
|
-
|
|
|
2037
|
-
| `
|
|
2038
|
-
|
|
|
2039
|
-
|
|
|
2040
|
-
|
|
|
2041
|
-
|
|
|
2042
|
-
|
|
|
2043
|
-
|
|
|
2044
|
-
|
|
|
2045
|
-
| `
|
|
1987
|
+
<!-- api:DatePickerModal -->
|
|
1988
|
+
|
|
1989
|
+
| Prop | Type | Default | Description |
|
|
1990
|
+
| ----------------------- | -------------------------------- | ---------------------- | --------------------------------------------------------- |
|
|
1991
|
+
| value | `string \| null` | — | Controlled: selected date in YYYY-MM-DD |
|
|
1992
|
+
| onValueChange | `(date: string \| null) => void` | — | Controlled: change handler |
|
|
1993
|
+
| open (required) | `boolean` | — | Modal open state |
|
|
1994
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | Modal open change handler |
|
|
1995
|
+
| minDate | `string` | — | Minimum selectable date YYYY-MM-DD |
|
|
1996
|
+
| maxDate | `string` | — | Maximum selectable date YYYY-MM-DD |
|
|
1997
|
+
| theme | `CalendarTheme` | — | Override calendar theme (RN color strings) |
|
|
1998
|
+
| trigger | `React.ReactNode` | — | Trigger element. If not provided, no trigger is rendered. |
|
|
1999
|
+
| title | `string` | `'Select date'` | Title shown in modal header |
|
|
2000
|
+
| confirmLabel | `string` | `'Done'` | Button label for confirm |
|
|
2001
|
+
| className | `string` | — | |
|
|
2002
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
2003
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
2004
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
2005
|
+
|
|
2006
|
+
<!-- /api:DatePickerModal -->
|
|
2046
2007
|
|
|
2047
2008
|
## ConfirmActionSheet
|
|
2048
2009
|
|
|
@@ -2060,11 +2021,27 @@ import { ConfirmActionSheet } from '@thewhileloop/whileui';
|
|
|
2060
2021
|
/>;
|
|
2061
2022
|
```
|
|
2062
2023
|
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
|
2066
|
-
|
|
|
2067
|
-
|
|
|
2024
|
+
<!-- api:ConfirmActionSheet -->
|
|
2025
|
+
|
|
2026
|
+
| Prop | Type | Default | Description |
|
|
2027
|
+
| ----------------------- | ---------------------------- | ---------------------- | ------------------------------------ |
|
|
2028
|
+
| open (required) | `boolean` | — | |
|
|
2029
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | |
|
|
2030
|
+
| title (required) | `string` | — | |
|
|
2031
|
+
| description | `string` | — | |
|
|
2032
|
+
| actions | `ConfirmActionSheetAction[]` | — | |
|
|
2033
|
+
| confirmLabel | `string` | `'Confirm'` | |
|
|
2034
|
+
| cancelLabel | `string` | `'Cancel'` | |
|
|
2035
|
+
| onConfirm | `() => void` | — | |
|
|
2036
|
+
| onCancel | `() => void` | — | |
|
|
2037
|
+
| destructive | `boolean` | `true` | |
|
|
2038
|
+
| className | `string` | — | |
|
|
2039
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
2040
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
2041
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
2042
|
+
|
|
2043
|
+
Also accepts the props of `ViewProps`.
|
|
2044
|
+
<!-- /api:ConfirmActionSheet -->
|
|
2068
2045
|
|
|
2069
2046
|
## Sheet
|
|
2070
2047
|
|
|
@@ -2094,15 +2071,20 @@ import {
|
|
|
2094
2071
|
</Sheet>;
|
|
2095
2072
|
```
|
|
2096
2073
|
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
|
2100
|
-
|
|
|
2101
|
-
|
|
|
2102
|
-
|
|
|
2103
|
-
|
|
|
2104
|
-
|
|
|
2105
|
-
|
|
|
2074
|
+
<!-- api:Sheet -->
|
|
2075
|
+
|
|
2076
|
+
| Prop | Type | Default | Description |
|
|
2077
|
+
| ----------------------- | ---------------------------- | ---------------------- | ----------------------------------------------------------- |
|
|
2078
|
+
| open (required) | `boolean` | — | |
|
|
2079
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | |
|
|
2080
|
+
| children (required) | `React.ReactNode` | — | |
|
|
2081
|
+
| maxHeight | `'half' \| 'full' \| number` | `'full'` | Max height: 'half' \| 'full' \| number (px). Default 'full' |
|
|
2082
|
+
| maxWidth | `number` | — | Web: max width. Default ~360 for mobile-first. |
|
|
2083
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
2084
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
2085
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
2086
|
+
|
|
2087
|
+
<!-- /api:Sheet -->
|
|
2106
2088
|
|
|
2107
2089
|
`SheetContent` is a `ScrollView` and accepts its props. It enables nested scrolling and
|
|
2108
2090
|
keeps taps on inputs while the keyboard is open (`keyboardShouldPersistTaps="handled"`).
|
|
@@ -2143,11 +2125,22 @@ import { SplashScreen } from '@thewhileloop/whileui';
|
|
|
2143
2125
|
/>;
|
|
2144
2126
|
```
|
|
2145
2127
|
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
|
2149
|
-
|
|
|
2150
|
-
|
|
|
2128
|
+
<!-- api:SplashScreen -->
|
|
2129
|
+
|
|
2130
|
+
| Prop | Type | Default | Description |
|
|
2131
|
+
| ------------------- | ------------------------------ | --------- | ----------------------------------------------- |
|
|
2132
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2133
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2134
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2135
|
+
| variant | `'fade' \| 'scale' \| 'slide'` | `'scale'` | Animation variant |
|
|
2136
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2137
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2138
|
+
| showLoading | `boolean` | `false` | Whether to show loading indicator |
|
|
2139
|
+
| loadingText | `string` | — | Loading text |
|
|
2140
|
+
| className | `string` | — | |
|
|
2141
|
+
|
|
2142
|
+
Also accepts the props of `ViewProps`.
|
|
2143
|
+
<!-- /api:SplashScreen -->
|
|
2151
2144
|
|
|
2152
2145
|
## ContentSkeleton
|
|
2153
2146
|
|
|
@@ -2161,10 +2154,16 @@ import { ContentSkeleton } from '@thewhileloop/whileui';
|
|
|
2161
2154
|
<ContentSkeleton variant="generic" />
|
|
2162
2155
|
```
|
|
2163
2156
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
|
2167
|
-
|
|
|
2157
|
+
<!-- api:ContentSkeleton -->
|
|
2158
|
+
|
|
2159
|
+
| Prop | Type | Default | Description |
|
|
2160
|
+
| --------- | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
|
|
2161
|
+
| variant | `'list' \| 'card' \| 'generic'` | `'list'` | Layout preset. `list` = avatar + lines rows, `card` = card with image/body, `generic` = simple page lines. |
|
|
2162
|
+
| rows | `number` | `4` | Number of list rows (list variant only). Default 4. |
|
|
2163
|
+
| className | `string` | — | |
|
|
2164
|
+
|
|
2165
|
+
Also accepts the props of `ViewProps`.
|
|
2166
|
+
<!-- /api:ContentSkeleton -->
|
|
2168
2167
|
|
|
2169
2168
|
## PageSkeleton
|
|
2170
2169
|
|
|
@@ -2183,14 +2182,19 @@ import { PageSkeleton } from '@thewhileloop/whileui';
|
|
|
2183
2182
|
<PageSkeleton variant="list" padding="none" className="flex-1" />
|
|
2184
2183
|
```
|
|
2185
2184
|
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
|
2189
|
-
|
|
|
2190
|
-
|
|
|
2191
|
-
| `
|
|
2192
|
-
| `
|
|
2193
|
-
|
|
|
2185
|
+
<!-- api:PageSkeleton -->
|
|
2186
|
+
|
|
2187
|
+
| Prop | Type | Default | Description |
|
|
2188
|
+
| ------------------ | ------------------------------------------ | ----------- | ------------------------------------------------------------------- |
|
|
2189
|
+
| variant (required) | `PageSkeletonVariant` | — | Layout preset. |
|
|
2190
|
+
| count | `number` | `3` | Rows/items for list (default 3) or settings (default 4) variant. |
|
|
2191
|
+
| padding | `PageSkeletonPadding` | `'default'` | Container padding. |
|
|
2192
|
+
| header | `ReactNode` | — | Optional header slot rendered above skeleton content. |
|
|
2193
|
+
| headerPlaceholder | `boolean \| PageSkeletonHeaderPlaceholder` | `false` | Optional placeholder header (when no real header is available yet). |
|
|
2194
|
+
| className | `string` | — | |
|
|
2195
|
+
|
|
2196
|
+
Also accepts the props of `ViewProps`.
|
|
2197
|
+
<!-- /api:PageSkeleton -->
|
|
2194
2198
|
|
|
2195
2199
|
## ScreenSkeleton
|
|
2196
2200
|
|
|
@@ -2203,13 +2207,19 @@ import { ScreenSkeleton } from '@thewhileloop/whileui';
|
|
|
2203
2207
|
<ScreenSkeleton variant="list" count={5} header={<Header title="Projects" />} />
|
|
2204
2208
|
```
|
|
2205
2209
|
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
|
2209
|
-
|
|
|
2210
|
-
|
|
|
2211
|
-
|
|
|
2212
|
-
|
|
|
2210
|
+
<!-- api:ScreenSkeleton -->
|
|
2211
|
+
|
|
2212
|
+
| Prop | Type | Default | Description |
|
|
2213
|
+
| ----------------- | ------------------------------------------ | ----------- | -------------------------------------- |
|
|
2214
|
+
| variant | `PageSkeletonVariant` | `'generic'` | Skeleton content preset. |
|
|
2215
|
+
| count | `number` | — | Rows/items for list/settings variants. |
|
|
2216
|
+
| padding | `PageSkeletonPadding` | `'default'` | Inner content padding. |
|
|
2217
|
+
| header | `ReactNode` | — | Real header slot. |
|
|
2218
|
+
| headerPlaceholder | `boolean \| PageSkeletonHeaderPlaceholder` | `'default'` | Placeholder header. |
|
|
2219
|
+
| className | `string` | — | |
|
|
2220
|
+
|
|
2221
|
+
Also accepts the props of `ViewProps`.
|
|
2222
|
+
<!-- /api:ScreenSkeleton -->
|
|
2213
2223
|
|
|
2214
2224
|
## ErrorBoundary
|
|
2215
2225
|
|
|
@@ -2223,10 +2233,15 @@ import { ErrorBoundary } from '@thewhileloop/whileui';
|
|
|
2223
2233
|
</ErrorBoundary>;
|
|
2224
2234
|
```
|
|
2225
2235
|
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
|
2229
|
-
|
|
|
2236
|
+
<!-- api:ErrorBoundary -->
|
|
2237
|
+
|
|
2238
|
+
| Prop | Type | Default | Description |
|
|
2239
|
+
| ------------------- | --------------------------------------------------------------- | ------- | -------------------------------------------------- |
|
|
2240
|
+
| children (required) | `ReactNode` | — | |
|
|
2241
|
+
| fallback | `ReactNode \| ((error: Error, reset: () => void) => ReactNode)` | — | Custom fallback. If not provided, uses ErrorState. |
|
|
2242
|
+
| onError | `(error: Error, errorInfo: ErrorInfo) => void` | — | Called when error is caught. |
|
|
2243
|
+
|
|
2244
|
+
<!-- /api:ErrorBoundary -->
|
|
2230
2245
|
|
|
2231
2246
|
## PullToRefreshScrollView
|
|
2232
2247
|
|
|
@@ -2240,11 +2255,17 @@ import { PullToRefreshScrollView } from '@thewhileloop/whileui';
|
|
|
2240
2255
|
</PullToRefreshScrollView>;
|
|
2241
2256
|
```
|
|
2242
2257
|
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
|
2246
|
-
|
|
|
2247
|
-
|
|
|
2258
|
+
<!-- api:PullToRefreshScrollView -->
|
|
2259
|
+
|
|
2260
|
+
| Prop | Type | Default | Description |
|
|
2261
|
+
| --------------------- | ----------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2262
|
+
| refreshing (required) | `boolean` | — | Whether refresh is in progress. |
|
|
2263
|
+
| 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. |
|
|
2264
|
+
| refreshColor | `string` | — | Override tint/colors. Defaults to theme primary. |
|
|
2265
|
+
| className | `string` | — | |
|
|
2266
|
+
|
|
2267
|
+
Also accepts the props of `ScrollViewProps`.
|
|
2268
|
+
<!-- /api:PullToRefreshScrollView -->
|
|
2248
2269
|
|
|
2249
2270
|
The scroll view owns the refresh gesture; don't pass `refreshControl`. It bounces even
|
|
2250
2271
|
when content is shorter than the screen, so a short list can still be pulled. Screen
|
|
@@ -2276,14 +2297,20 @@ import { LoadingState } from '@thewhileloop/whileui';
|
|
|
2276
2297
|
The in-flow counterpart of `EmptyState` and `ErrorState` for a section that is still
|
|
2277
2298
|
fetching. The spinner is named for assistive technology by `label` (default "Loading").
|
|
2278
2299
|
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
|
2282
|
-
|
|
|
2300
|
+
<!-- api:LoadingState -->
|
|
2301
|
+
|
|
2302
|
+
| Prop | Type | Default | Description |
|
|
2303
|
+
| --------- | --------------------------- | ----------- | ------------------------------------------------------------------------------ |
|
|
2304
|
+
| label | `string` | — | Read by assistive technology as the spinner's name; shown under it when given. |
|
|
2305
|
+
| size | `'sm' \| 'default' \| 'lg'` | `'default'` | |
|
|
2306
|
+
| className | `string` | — | |
|
|
2307
|
+
|
|
2308
|
+
Also accepts the props of `ViewProps`.
|
|
2309
|
+
<!-- /api:LoadingState -->
|
|
2283
2310
|
|
|
2284
2311
|
## ProfileHeader
|
|
2285
2312
|
|
|
2286
|
-
Copy from `apps/showcase/templates/profile/profile-header.tsx`, then:
|
|
2313
|
+
Copy from `apps/showcase/catalog/templates/profile/profile-header.tsx`, then:
|
|
2287
2314
|
|
|
2288
2315
|
```tsx
|
|
2289
2316
|
import { ProfileHeader } from './templates/profile'; // or your path
|
|
@@ -2305,7 +2332,7 @@ import { ProfileHeader } from './templates/profile'; // or your path
|
|
|
2305
2332
|
|
|
2306
2333
|
## SettingsSection / SettingsItem
|
|
2307
2334
|
|
|
2308
|
-
Copy from `apps/showcase/templates/profile/`, then:
|
|
2335
|
+
Copy from `apps/showcase/catalog/templates/profile/`, then:
|
|
2309
2336
|
|
|
2310
2337
|
```tsx
|
|
2311
2338
|
import { SettingsSection, SettingsItem } from './templates/profile'; // or your path
|
|
@@ -2340,10 +2367,25 @@ import { ProductCard } from '@thewhileloop/whileui';
|
|
|
2340
2367
|
/>;
|
|
2341
2368
|
```
|
|
2342
2369
|
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
|
2346
|
-
|
|
|
2370
|
+
<!-- api:ProductCard -->
|
|
2371
|
+
|
|
2372
|
+
| Prop | Type | Default | Description |
|
|
2373
|
+
| ---------------- | ---------------------------- | ------------ | ----------------------------------------------- |
|
|
2374
|
+
| imageUrl | `string` | — | |
|
|
2375
|
+
| title (required) | `string` | — | |
|
|
2376
|
+
| description | `string` | — | |
|
|
2377
|
+
| price (required) | `string` | — | |
|
|
2378
|
+
| originalPrice | `string` | — | |
|
|
2379
|
+
| badge | `string` | — | |
|
|
2380
|
+
| rating | `number` | — | |
|
|
2381
|
+
| reviewCount | `number` | — | |
|
|
2382
|
+
| inStock | `boolean` | `true` | |
|
|
2383
|
+
| variant | `'vertical' \| 'horizontal'` | `'vertical'` | |
|
|
2384
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2385
|
+
| className | `string` | — | |
|
|
2386
|
+
|
|
2387
|
+
Also accepts the props of `PressableProps`.
|
|
2388
|
+
<!-- /api:ProductCard -->
|
|
2347
2389
|
|
|
2348
2390
|
## PricingCard
|
|
2349
2391
|
|
|
@@ -2382,16 +2424,24 @@ import { SubscriptionCard } from '@thewhileloop/whileui';
|
|
|
2382
2424
|
/>;
|
|
2383
2425
|
```
|
|
2384
2426
|
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
|
2388
|
-
|
|
|
2389
|
-
|
|
|
2390
|
-
|
|
|
2391
|
-
| `
|
|
2392
|
-
|
|
|
2393
|
-
| `
|
|
2394
|
-
|
|
|
2427
|
+
<!-- api:SubscriptionCard -->
|
|
2428
|
+
|
|
2429
|
+
| Prop | Type | Default | Description |
|
|
2430
|
+
| ------------------- | ------------ | --------------- | ----------------------------------------------- |
|
|
2431
|
+
| planName (required) | `string` | — | Current plan label. |
|
|
2432
|
+
| price (required) | `string` | — | Plan price. |
|
|
2433
|
+
| period | `string` | `'/month'` | Billing period label. |
|
|
2434
|
+
| expiresAt | `string` | — | Renewal/expiry date text. |
|
|
2435
|
+
| isActive | `boolean` | `true` | Active/inactive badge state. |
|
|
2436
|
+
| onManage | `() => void` | — | Manage action. |
|
|
2437
|
+
| onUpgrade | `() => void` | — | Upgrade action. |
|
|
2438
|
+
| manageLabel | `string` | `'Manage plan'` | |
|
|
2439
|
+
| upgradeLabel | `string` | `'Upgrade'` | |
|
|
2440
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2441
|
+
| className | `string` | — | |
|
|
2442
|
+
|
|
2443
|
+
Also accepts the props of `ViewProps`.
|
|
2444
|
+
<!-- /api:SubscriptionCard -->
|
|
2395
2445
|
|
|
2396
2446
|
## FeatureGate
|
|
2397
2447
|
|
|
@@ -2416,12 +2466,18 @@ import { UsageBar } from '@thewhileloop/whileui';
|
|
|
2416
2466
|
<UsageBar label="AI generations" used={8} limit={20} />;
|
|
2417
2467
|
```
|
|
2418
2468
|
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
|
2422
|
-
|
|
|
2423
|
-
|
|
|
2424
|
-
|
|
|
2469
|
+
<!-- api:UsageBar -->
|
|
2470
|
+
|
|
2471
|
+
| Prop | Type | Default | Description |
|
|
2472
|
+
| ---------------- | ----------------- | ------- | ------------------------------- |
|
|
2473
|
+
| label (required) | `string` | — | Usage label. |
|
|
2474
|
+
| used (required) | `number` | — | Used amount. |
|
|
2475
|
+
| limit (required) | `number` | — | Quota limit. |
|
|
2476
|
+
| variant | `UsageBarVariant` | — | Optional explicit visual state. |
|
|
2477
|
+
| className | `string` | — | |
|
|
2478
|
+
|
|
2479
|
+
Also accepts the props of `ViewProps`.
|
|
2480
|
+
<!-- /api:UsageBar -->
|
|
2425
2481
|
|
|
2426
2482
|
## PlanToggle
|
|
2427
2483
|
|
|
@@ -2474,48 +2530,600 @@ import { DrawerMenu } from '@thewhileloop/whileui';
|
|
|
2474
2530
|
/>;
|
|
2475
2531
|
```
|
|
2476
2532
|
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
|
2480
|
-
|
|
|
2481
|
-
|
|
|
2533
|
+
<!-- api:DrawerMenu -->
|
|
2534
|
+
|
|
2535
|
+
| Prop | Type | Default | Description |
|
|
2536
|
+
| ------------------- | ----------------------- | ---------------------- | ----------------------------------------------------------- |
|
|
2537
|
+
| visible (required) | `boolean` | — | |
|
|
2538
|
+
| onClose (required) | `() => void` | — | |
|
|
2539
|
+
| sections (required) | `DrawerMenuSection[]` | — | |
|
|
2540
|
+
| activeKey | `string` | — | |
|
|
2541
|
+
| onSelect | `(key: string) => void` | — | |
|
|
2542
|
+
| header | `React.ReactNode` | — | |
|
|
2543
|
+
| footer | `React.ReactNode` | — | |
|
|
2544
|
+
| maxWidth | `number` | — | Max width in px. On web, defaults to 360 when not provided. |
|
|
2545
|
+
| className | `string` | — | |
|
|
2546
|
+
| style | `StyleProp<ViewStyle>` | — | |
|
|
2547
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
2548
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
2549
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
2550
|
+
|
|
2551
|
+
<!-- /api:DrawerMenu -->
|
|
2482
2552
|
|
|
2483
2553
|
Closes from the backdrop, an item, Android back or Escape on web. It is hosted in a modal
|
|
2484
2554
|
on every platform, so keyboard focus stays inside it while it is open.
|
|
2485
2555
|
|
|
2486
|
-
##
|
|
2556
|
+
## More components
|
|
2557
|
+
|
|
2558
|
+
The rest of the catalog: what each is for and the props its type declares.
|
|
2559
|
+
|
|
2560
|
+
### View
|
|
2561
|
+
|
|
2562
|
+
Themed view wrapper
|
|
2563
|
+
|
|
2564
|
+
```tsx
|
|
2565
|
+
import { View } from '@thewhileloop/whileui';
|
|
2566
|
+
```
|
|
2567
|
+
|
|
2568
|
+
<!-- api:View -->
|
|
2569
|
+
|
|
2570
|
+
| Prop | Type | Default | Description |
|
|
2571
|
+
| --------- | -------- | ------- | ----------- |
|
|
2572
|
+
| className | `string` | — | |
|
|
2573
|
+
|
|
2574
|
+
Also accepts the props of React Native's `ViewProps`.
|
|
2575
|
+
<!-- /api:View -->
|
|
2576
|
+
|
|
2577
|
+
### Stack
|
|
2578
|
+
|
|
2579
|
+
Vertical flex layout with gap
|
|
2580
|
+
|
|
2581
|
+
```tsx
|
|
2582
|
+
import { Stack } from '@thewhileloop/whileui';
|
|
2583
|
+
```
|
|
2584
|
+
|
|
2585
|
+
<!-- api:Stack -->
|
|
2586
|
+
|
|
2587
|
+
| Prop | Type | Default | Description |
|
|
2588
|
+
| --------- | ------------------------------------------------------------------- | ----------- | ----------- |
|
|
2589
|
+
| className | `string` | — | |
|
|
2590
|
+
| gap | `'none' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | |
|
|
2591
|
+
| align | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | `'stretch'` | |
|
|
2592
|
+
| justify | `'start' \| 'center' \| 'end' \| 'between' \| 'around' \| 'evenly'` | `'start'` | |
|
|
2593
|
+
|
|
2594
|
+
Also accepts the props of `ViewProps`.
|
|
2595
|
+
<!-- /api:Stack -->
|
|
2596
|
+
|
|
2597
|
+
### MotionProvider
|
|
2598
|
+
|
|
2599
|
+
System reduced motion, explicit preview modes, and deterministic testing
|
|
2600
|
+
|
|
2601
|
+
```tsx
|
|
2602
|
+
import { MotionProvider } from '@thewhileloop/whileui';
|
|
2603
|
+
```
|
|
2604
|
+
|
|
2605
|
+
<!-- api:MotionProvider -->
|
|
2606
|
+
|
|
2607
|
+
| Prop | Type | Default | Description |
|
|
2608
|
+
| ------------------- | ----------------- | ---------- | ----------- |
|
|
2609
|
+
| children (required) | `React.ReactNode` | — | |
|
|
2610
|
+
| mode | `MotionMode` | `'system'` | |
|
|
2611
|
+
|
|
2612
|
+
<!-- /api:MotionProvider -->
|
|
2613
|
+
|
|
2614
|
+
### Textarea
|
|
2615
|
+
|
|
2616
|
+
Multi-line text input
|
|
2487
2617
|
|
|
2488
|
-
|
|
2618
|
+
```tsx
|
|
2619
|
+
import { Textarea } from '@thewhileloop/whileui';
|
|
2620
|
+
```
|
|
2621
|
+
|
|
2622
|
+
<!-- api:Textarea -->
|
|
2623
|
+
|
|
2624
|
+
| Prop | Type | Default | Description |
|
|
2625
|
+
| ------------ | --------------------------- | ----------- | ----------- |
|
|
2626
|
+
| aria-invalid | `boolean` | — | |
|
|
2627
|
+
| className | `string` | — | |
|
|
2628
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
2629
|
+
|
|
2630
|
+
Also accepts the props of `TextInputProps`.
|
|
2631
|
+
<!-- /api:Textarea -->
|
|
2632
|
+
|
|
2633
|
+
### Label
|
|
2634
|
+
|
|
2635
|
+
Form field label
|
|
2636
|
+
|
|
2637
|
+
```tsx
|
|
2638
|
+
import { Label } from '@thewhileloop/whileui';
|
|
2639
|
+
```
|
|
2640
|
+
|
|
2641
|
+
<!-- api:Label -->
|
|
2642
|
+
|
|
2643
|
+
| Prop | Type | Default | Description |
|
|
2644
|
+
| --------- | -------- | ------- | ----------- |
|
|
2645
|
+
| className | `string` | — | |
|
|
2646
|
+
|
|
2647
|
+
Also accepts the props of `TextProps`.
|
|
2648
|
+
<!-- /api:Label -->
|
|
2649
|
+
|
|
2650
|
+
### Toggle
|
|
2651
|
+
|
|
2652
|
+
ToggleText sub-component
|
|
2653
|
+
|
|
2654
|
+
```tsx
|
|
2655
|
+
import { Toggle } from '@thewhileloop/whileui';
|
|
2656
|
+
```
|
|
2657
|
+
|
|
2658
|
+
<!-- api:Toggle -->
|
|
2659
|
+
|
|
2660
|
+
| Prop | Type | Default | Description |
|
|
2661
|
+
| --------------- | ------------------------------------------------ | ----------- | ----------- |
|
|
2662
|
+
| pressed | `boolean` | `false` | |
|
|
2663
|
+
| defaultPressed | `boolean` | `false` | |
|
|
2664
|
+
| onPressedChange | `(pressed: boolean) => void` | — | |
|
|
2665
|
+
| variant | `VariantProps<typeof toggleVariants>['variant']` | `'default'` | |
|
|
2666
|
+
| size | `VariantProps<typeof toggleVariants>['size']` | `'default'` | |
|
|
2667
|
+
| className | `string` | — | |
|
|
2668
|
+
| children | `React.ReactNode` | — | |
|
|
2669
|
+
|
|
2670
|
+
Also accepts the props of `PressableProps`.
|
|
2671
|
+
<!-- /api:Toggle -->
|
|
2672
|
+
|
|
2673
|
+
### ToggleGroup
|
|
2674
|
+
|
|
2675
|
+
Group of toggle items
|
|
2676
|
+
|
|
2677
|
+
```tsx
|
|
2678
|
+
import { ToggleGroup } from '@thewhileloop/whileui';
|
|
2679
|
+
```
|
|
2680
|
+
|
|
2681
|
+
<!-- api:ToggleGroup -->
|
|
2682
|
+
|
|
2683
|
+
| Prop | Type | Default | Description |
|
|
2684
|
+
| ------------- | ------------------------------------- | ----------- | ----------- |
|
|
2685
|
+
| type | `ToggleGroupType` | `'single'` | |
|
|
2686
|
+
| defaultValue | `string \| string[]` | — | |
|
|
2687
|
+
| value | `string \| string[]` | — | |
|
|
2688
|
+
| onValueChange | `(value: string \| string[]) => void` | — | |
|
|
2689
|
+
| variant | `'default' \| 'outline'` | `'default'` | |
|
|
2690
|
+
| size | `'default' \| 'sm' \| 'lg'` | `'default'` | |
|
|
2691
|
+
| className | `string` | — | |
|
|
2692
|
+
|
|
2693
|
+
Also accepts the props of `ViewProps`.
|
|
2694
|
+
<!-- /api:ToggleGroup -->
|
|
2695
|
+
|
|
2696
|
+
### Separator
|
|
2697
|
+
|
|
2698
|
+
Themed divider
|
|
2699
|
+
|
|
2700
|
+
```tsx
|
|
2701
|
+
import { Separator } from '@thewhileloop/whileui';
|
|
2702
|
+
```
|
|
2703
|
+
|
|
2704
|
+
<!-- api:Separator -->
|
|
2705
|
+
|
|
2706
|
+
| Prop | Type | Default | Description |
|
|
2707
|
+
| ----------- | ---------------------------- | -------------- | ----------- |
|
|
2708
|
+
| orientation | `'horizontal' \| 'vertical'` | `'horizontal'` | |
|
|
2709
|
+
| className | `string` | — | |
|
|
2710
|
+
|
|
2711
|
+
Also accepts the props of `ViewProps`.
|
|
2712
|
+
<!-- /api:Separator -->
|
|
2713
|
+
|
|
2714
|
+
### Spinner
|
|
2715
|
+
|
|
2716
|
+
ActivityIndicator wrapper
|
|
2717
|
+
|
|
2718
|
+
```tsx
|
|
2719
|
+
import { Spinner } from '@thewhileloop/whileui';
|
|
2720
|
+
```
|
|
2721
|
+
|
|
2722
|
+
<!-- api:Spinner -->
|
|
2723
|
+
|
|
2724
|
+
| Prop | Type | Default | Description |
|
|
2725
|
+
| --------- | --------------------------- | ----------- | ----------- |
|
|
2726
|
+
| className | `string` | — | |
|
|
2727
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
2728
|
+
|
|
2729
|
+
Also accepts the props of `ActivityIndicatorProps`.
|
|
2730
|
+
<!-- /api:Spinner -->
|
|
2731
|
+
|
|
2732
|
+
### Skeleton
|
|
2733
|
+
|
|
2734
|
+
Loading placeholder (pulse = opacity fade, shimmer = sweep)
|
|
2735
|
+
|
|
2736
|
+
```tsx
|
|
2737
|
+
import { Skeleton } from '@thewhileloop/whileui';
|
|
2738
|
+
```
|
|
2739
|
+
|
|
2740
|
+
<!-- api:Skeleton -->
|
|
2741
|
+
|
|
2742
|
+
| Prop | Type | Default | Description |
|
|
2743
|
+
| --------- | ---------------------- | --------- | ----------- |
|
|
2744
|
+
| className | `string` | — | |
|
|
2745
|
+
| variant | `'pulse' \| 'shimmer'` | `'pulse'` | |
|
|
2746
|
+
|
|
2747
|
+
Also accepts the props of `ViewProps`.
|
|
2748
|
+
<!-- /api:Skeleton -->
|
|
2749
|
+
|
|
2750
|
+
### AspectRatio
|
|
2751
|
+
|
|
2752
|
+
Maintain aspect ratio container
|
|
2753
|
+
|
|
2754
|
+
```tsx
|
|
2755
|
+
import { AspectRatio } from '@thewhileloop/whileui';
|
|
2756
|
+
```
|
|
2757
|
+
|
|
2758
|
+
<!-- api:AspectRatio -->
|
|
2759
|
+
|
|
2760
|
+
| Prop | Type | Default | Description |
|
|
2761
|
+
| --------- | -------- | ------- | ----------- |
|
|
2762
|
+
| ratio | `number` | `1` | |
|
|
2763
|
+
| className | `string` | — | |
|
|
2764
|
+
|
|
2765
|
+
Also accepts the props of `ViewProps`.
|
|
2766
|
+
<!-- /api:AspectRatio -->
|
|
2767
|
+
|
|
2768
|
+
### Collapsible
|
|
2769
|
+
|
|
2770
|
+
CollapsibleTrigger, CollapsibleContent
|
|
2771
|
+
|
|
2772
|
+
```tsx
|
|
2773
|
+
import { Collapsible } from '@thewhileloop/whileui';
|
|
2774
|
+
```
|
|
2775
|
+
|
|
2776
|
+
<!-- api:Collapsible -->
|
|
2777
|
+
|
|
2778
|
+
| Prop | Type | Default | Description |
|
|
2779
|
+
| ------------ | ------------------------- | ------- | ----------- |
|
|
2780
|
+
| defaultOpen | `boolean` | `false` | |
|
|
2781
|
+
| open | `boolean` | — | |
|
|
2782
|
+
| onOpenChange | `(open: boolean) => void` | — | |
|
|
2783
|
+
| disabled | `boolean` | `false` | |
|
|
2784
|
+
| className | `string` | — | |
|
|
2785
|
+
|
|
2786
|
+
Also accepts the props of `ViewProps`.
|
|
2787
|
+
<!-- /api:Collapsible -->
|
|
2788
|
+
|
|
2789
|
+
### Tooltip
|
|
2790
|
+
|
|
2791
|
+
Position-aware tooltip (requires PortalHost)
|
|
2792
|
+
|
|
2793
|
+
```tsx
|
|
2794
|
+
import { Tooltip } from '@thewhileloop/whileui';
|
|
2795
|
+
```
|
|
2796
|
+
|
|
2797
|
+
<!-- api:Tooltip -->
|
|
2798
|
+
|
|
2799
|
+
| Prop | Type | Default | Description |
|
|
2800
|
+
| ----------------------- | ------------------------- | ------- | ----------- |
|
|
2801
|
+
| onOpenChange | `(open: boolean) => void` | — | |
|
|
2802
|
+
| delayDuration | `number` | — | |
|
|
2803
|
+
| skipDelayDuration | `number` | — | |
|
|
2804
|
+
| disableHoverableContent | `boolean` | — | |
|
|
2805
|
+
| children (required) | `React.ReactNode` | — | |
|
|
2806
|
+
|
|
2807
|
+
<!-- /api:Tooltip -->
|
|
2808
|
+
|
|
2809
|
+
### FloatingBottomNav
|
|
2810
|
+
|
|
2811
|
+
Elevated bottom nav with safe area support
|
|
2812
|
+
|
|
2813
|
+
```tsx
|
|
2814
|
+
import { FloatingBottomNav } from '@thewhileloop/whileui';
|
|
2815
|
+
```
|
|
2816
|
+
|
|
2817
|
+
<!-- api:FloatingBottomNav -->
|
|
2818
|
+
|
|
2819
|
+
| Prop | Type | Default | Description |
|
|
2820
|
+
| -------------------- | ------------------------- | ------- | ----------- |
|
|
2821
|
+
| items (required) | `FloatingBottomNavItem[]` | — | |
|
|
2822
|
+
| activeKey (required) | `string` | — | |
|
|
2823
|
+
| onSelect (required) | `(key: string) => void` | — | |
|
|
2824
|
+
| className | `string` | — | |
|
|
2825
|
+
|
|
2826
|
+
Also accepts the props of `ViewProps`.
|
|
2827
|
+
<!-- /api:FloatingBottomNav -->
|
|
2828
|
+
|
|
2829
|
+
### TabBar
|
|
2830
|
+
|
|
2831
|
+
Top tab bar with indicator
|
|
2832
|
+
|
|
2833
|
+
```tsx
|
|
2834
|
+
import { TabBar } from '@thewhileloop/whileui';
|
|
2835
|
+
```
|
|
2836
|
+
|
|
2837
|
+
<!-- api:TabBar -->
|
|
2489
2838
|
|
|
2490
|
-
|
|
2839
|
+
| Prop | Type | Default | Description |
|
|
2840
|
+
| -------------------- | ------------------------------------- | ----------- | ----------- |
|
|
2841
|
+
| items (required) | `TabBarItem[]` | — | |
|
|
2842
|
+
| activeKey (required) | `string` | — | |
|
|
2843
|
+
| onSelect (required) | `(key: string) => void` | — | |
|
|
2844
|
+
| variant | `'default' \| 'pills' \| 'underline'` | `'default'` | |
|
|
2845
|
+
| scrollable | `boolean` | `false` | |
|
|
2846
|
+
| className | `string` | — | |
|
|
2491
2847
|
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
- [ ] **Slider** — single + range (two thumbs), step marks, labels, Reanimated gesture, haptic on snap
|
|
2495
|
-
- [ ] **Carousel** — Reanimated-powered, auto-play, pagination dots, snap-to-item
|
|
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
|
|
2848
|
+
Also accepts the props of `ViewProps`.
|
|
2849
|
+
<!-- /api:TabBar -->
|
|
2502
2850
|
|
|
2503
|
-
###
|
|
2851
|
+
### FormModalScreen
|
|
2504
2852
|
|
|
2505
|
-
|
|
2506
|
-
- [x] AppShell, Chat, FormModalScreen, CheckoutSummary (already had `loading`)
|
|
2507
|
-
- [ ] Add `loading` to remaining blocks: Header, BottomNav, DrawerMenu, TimelineFeed
|
|
2853
|
+
Modal scaffold for forms with loading states
|
|
2508
2854
|
|
|
2509
|
-
|
|
2855
|
+
```tsx
|
|
2856
|
+
import { FormModalScreen } from '@thewhileloop/whileui';
|
|
2857
|
+
```
|
|
2858
|
+
|
|
2859
|
+
<!-- api:FormModalScreen -->
|
|
2860
|
+
|
|
2861
|
+
| Prop | Type | Default | Description |
|
|
2862
|
+
| ------------------- | ----------------- | ------------- | ----------------------------------------------------------------------------------------- |
|
|
2863
|
+
| className | `string` | — | |
|
|
2864
|
+
| title (required) | `string` | — | |
|
|
2865
|
+
| subtitle | `string` | — | |
|
|
2866
|
+
| leftSlot | `React.ReactNode` | — | |
|
|
2867
|
+
| rightSlot | `React.ReactNode` | — | |
|
|
2868
|
+
| onClose | `() => void` | — | |
|
|
2869
|
+
| loading | `boolean` | `false` | |
|
|
2870
|
+
| saving | `boolean` | `false` | |
|
|
2871
|
+
| savingText | `string` | `'Saving...'` | |
|
|
2872
|
+
| loadingSpinnerColor | `string` | — | Hex color for spinner in loading state. ActivityIndicator requires hex. |
|
|
2873
|
+
| savingSpinnerColor | `string` | — | Hex color for spinner in saving banner. Defaults to platform (Android blue) when not set. |
|
|
2874
|
+
| scrollEnabled | `boolean` | `true` | |
|
|
2875
|
+
| children (required) | `React.ReactNode` | — | |
|
|
2876
|
+
|
|
2877
|
+
Also accepts the props of `ViewProps`.
|
|
2878
|
+
<!-- /api:FormModalScreen -->
|
|
2879
|
+
|
|
2880
|
+
### ErrorState
|
|
2881
|
+
|
|
2882
|
+
Error display with retry
|
|
2883
|
+
|
|
2884
|
+
```tsx
|
|
2885
|
+
import { ErrorState } from '@thewhileloop/whileui';
|
|
2886
|
+
```
|
|
2887
|
+
|
|
2888
|
+
<!-- api:ErrorState -->
|
|
2889
|
+
|
|
2890
|
+
| Prop | Type | Default | Description |
|
|
2891
|
+
| ----------- | ----------------- | ------------------------ | ----------- |
|
|
2892
|
+
| icon | `React.ReactNode` | — | |
|
|
2893
|
+
| title | `string` | `'Something went wrong'` | |
|
|
2894
|
+
| description | `string` | — | |
|
|
2895
|
+
| error | `Error \| string` | — | |
|
|
2896
|
+
| onRetry | `() => void` | — | |
|
|
2897
|
+
| retryLabel | `string` | `'Try again'` | |
|
|
2898
|
+
| className | `string` | — | |
|
|
2899
|
+
|
|
2900
|
+
Also accepts the props of `ViewProps`.
|
|
2901
|
+
<!-- /api:ErrorState -->
|
|
2902
|
+
|
|
2903
|
+
### LoadingScreen
|
|
2904
|
+
|
|
2905
|
+
Full-screen loading indicator
|
|
2906
|
+
|
|
2907
|
+
```tsx
|
|
2908
|
+
import { LoadingScreen } from '@thewhileloop/whileui';
|
|
2909
|
+
```
|
|
2910
|
+
|
|
2911
|
+
<!-- api:LoadingScreen -->
|
|
2912
|
+
|
|
2913
|
+
| Prop | Type | Default | Description |
|
|
2914
|
+
| ------------ | ---------------------- | ------- | ------------------------------------------------------ |
|
|
2915
|
+
| message | `string` | — | |
|
|
2916
|
+
| size | `'sm' \| 'md' \| 'lg'` | `'md'` | |
|
|
2917
|
+
| spinnerColor | `string` | — | Hex color for spinner. ActivityIndicator requires hex. |
|
|
2918
|
+
| className | `string` | — | |
|
|
2919
|
+
|
|
2920
|
+
Also accepts the props of `ViewProps`.
|
|
2921
|
+
<!-- /api:LoadingScreen -->
|
|
2922
|
+
|
|
2923
|
+
### MinimalSplash
|
|
2924
|
+
|
|
2925
|
+
Minimal monochrome splash
|
|
2926
|
+
|
|
2927
|
+
```tsx
|
|
2928
|
+
import { MinimalSplash } from '@thewhileloop/whileui';
|
|
2929
|
+
```
|
|
2930
|
+
|
|
2931
|
+
<!-- api:MinimalSplash -->
|
|
2932
|
+
|
|
2933
|
+
| Prop | Type | Default | Description |
|
|
2934
|
+
| ------------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
2935
|
+
| className | `string` | — | |
|
|
2936
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2937
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2938
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2939
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2940
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2941
|
+
| showLoading | `boolean` | `false` | Whether to show loading indicator |
|
|
2942
|
+
| loadingText | `string` | — | Loading text |
|
|
2943
|
+
|
|
2944
|
+
Also accepts the props of `SplashScreenProps`.
|
|
2945
|
+
<!-- /api:MinimalSplash -->
|
|
2946
|
+
|
|
2947
|
+
### BrandedSplash
|
|
2948
|
+
|
|
2949
|
+
Splash with brand imagery
|
|
2950
|
+
|
|
2951
|
+
```tsx
|
|
2952
|
+
import { BrandedSplash } from '@thewhileloop/whileui';
|
|
2953
|
+
```
|
|
2954
|
+
|
|
2955
|
+
<!-- api:BrandedSplash -->
|
|
2956
|
+
|
|
2957
|
+
| Prop | Type | Default | Description |
|
|
2958
|
+
| ------------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
2959
|
+
| className | `string` | — | |
|
|
2960
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2961
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2962
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2963
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2964
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2965
|
+
| loadingText | `string` | — | Loading text |
|
|
2966
|
+
|
|
2967
|
+
Also accepts the props of `SplashScreenProps`.
|
|
2968
|
+
<!-- /api:BrandedSplash -->
|
|
2969
|
+
|
|
2970
|
+
### ChatMessageBubble
|
|
2971
|
+
|
|
2972
|
+
One message in a `Chat`, rendered on its own when a screen composes the conversation itself.
|
|
2973
|
+
|
|
2974
|
+
```tsx
|
|
2975
|
+
import { ChatMessageBubble } from '@thewhileloop/whileui';
|
|
2976
|
+
```
|
|
2977
|
+
|
|
2978
|
+
<!-- api:ChatMessageBubble -->
|
|
2979
|
+
|
|
2980
|
+
| Prop | Type | Default | Description |
|
|
2981
|
+
| ------------------ | ------------- | ------- | --------------------------------------------------------------- |
|
|
2982
|
+
| message (required) | `ChatMessage` | — | The message to render; its `role` decides alignment and colour. |
|
|
2983
|
+
| contentClassName | `string` | — | Classes for the bubble's content wrapper. |
|
|
2984
|
+
|
|
2985
|
+
<!-- /api:ChatMessageBubble -->
|
|
2986
|
+
|
|
2987
|
+
### ChatSuggestions
|
|
2988
|
+
|
|
2989
|
+
Suggestion chips when empty
|
|
2990
|
+
|
|
2991
|
+
```tsx
|
|
2992
|
+
import { ChatSuggestions } from '@thewhileloop/whileui';
|
|
2993
|
+
```
|
|
2994
|
+
|
|
2995
|
+
<!-- api:ChatSuggestions -->
|
|
2996
|
+
|
|
2997
|
+
| Prop | Type | Default | Description |
|
|
2998
|
+
| ---------------------- | ------------------------ | ------------------- | ----------- |
|
|
2999
|
+
| suggestions (required) | `string[]` | `EMPTY_SUGGESTIONS` | |
|
|
3000
|
+
| onSelect (required) | `(text: string) => void` | — | |
|
|
3001
|
+
| className | `string` | — | |
|
|
3002
|
+
|
|
3003
|
+
<!-- /api:ChatSuggestions -->
|
|
3004
|
+
|
|
3005
|
+
### ListItem
|
|
3006
|
+
|
|
3007
|
+
Title/subtitle row
|
|
3008
|
+
|
|
3009
|
+
```tsx
|
|
3010
|
+
import { ListItem } from '@thewhileloop/whileui';
|
|
3011
|
+
```
|
|
3012
|
+
|
|
3013
|
+
<!-- api:ListItem -->
|
|
3014
|
+
|
|
3015
|
+
| Prop | Type | Default | Description |
|
|
3016
|
+
| ---------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
3017
|
+
| icon | `React.ReactNode` | — | |
|
|
3018
|
+
| title (required) | `string` | — | |
|
|
3019
|
+
| subtitle | `string` | — | |
|
|
3020
|
+
| description | `string` | — | |
|
|
3021
|
+
| rightIcon | `React.ReactNode` | — | |
|
|
3022
|
+
| rightText | `string` | — | |
|
|
3023
|
+
| action | `React.ReactNode` | — | |
|
|
3024
|
+
| showBorder | `boolean` | `true` | |
|
|
3025
|
+
| compact | `boolean` | `false` | |
|
|
3026
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
3027
|
+
| className | `string` | — | |
|
|
3028
|
+
|
|
3029
|
+
Also accepts the props of `PressableProps`.
|
|
3030
|
+
<!-- /api:ListItem -->
|
|
3031
|
+
|
|
3032
|
+
### NotificationItem
|
|
3033
|
+
|
|
3034
|
+
Notification row with metadata
|
|
3035
|
+
|
|
3036
|
+
```tsx
|
|
3037
|
+
import { NotificationItem } from '@thewhileloop/whileui';
|
|
3038
|
+
```
|
|
3039
|
+
|
|
3040
|
+
<!-- api:NotificationItem -->
|
|
3041
|
+
|
|
3042
|
+
| Prop | Type | Default | Description |
|
|
3043
|
+
| ------------------ | ----------------- | ------- | ----------------------------------------------- |
|
|
3044
|
+
| icon | `React.ReactNode` | — | |
|
|
3045
|
+
| avatarUrl | `string` | — | |
|
|
3046
|
+
| avatarFallback | `string` | — | |
|
|
3047
|
+
| title (required) | `string` | — | |
|
|
3048
|
+
| message (required) | `string` | — | |
|
|
3049
|
+
| time (required) | `string` | — | |
|
|
3050
|
+
| read | `boolean` | `false` | |
|
|
3051
|
+
| showDot | `boolean` | `true` | |
|
|
3052
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
3053
|
+
| className | `string` | — | |
|
|
3054
|
+
|
|
3055
|
+
Also accepts the props of `PressableProps`.
|
|
3056
|
+
<!-- /api:NotificationItem -->
|
|
3057
|
+
|
|
3058
|
+
### TimelineFeed
|
|
3059
|
+
|
|
3060
|
+
Vertical feed with connecting lines
|
|
3061
|
+
|
|
3062
|
+
```tsx
|
|
3063
|
+
import { TimelineFeed } from '@thewhileloop/whileui';
|
|
3064
|
+
```
|
|
3065
|
+
|
|
3066
|
+
<!-- api:TimelineFeed -->
|
|
3067
|
+
|
|
3068
|
+
| Prop | Type | Default | Description |
|
|
3069
|
+
| ---------------- | ---------------- | ------- | ----------- |
|
|
3070
|
+
| className | `string` | — | |
|
|
3071
|
+
| items (required) | `TimelineItem[]` | — | |
|
|
3072
|
+
| showConnector | `boolean` | `true` | |
|
|
3073
|
+
|
|
3074
|
+
Also accepts the props of `ViewProps`.
|
|
3075
|
+
<!-- /api:TimelineFeed -->
|
|
3076
|
+
|
|
3077
|
+
### CheckoutSummary
|
|
3078
|
+
|
|
3079
|
+
Cart summary with line items
|
|
3080
|
+
|
|
3081
|
+
```tsx
|
|
3082
|
+
import { CheckoutSummary } from '@thewhileloop/whileui';
|
|
3083
|
+
```
|
|
3084
|
+
|
|
3085
|
+
<!-- api:CheckoutSummary -->
|
|
3086
|
+
|
|
3087
|
+
| Prop | Type | Default | Description |
|
|
3088
|
+
| ------------------- | -------------------- | ------------ | ----------- |
|
|
3089
|
+
| items (required) | `CheckoutLineItem[]` | — | |
|
|
3090
|
+
| subtotal (required) | `string` | — | |
|
|
3091
|
+
| shipping | `string` | — | |
|
|
3092
|
+
| tax | `string` | — | |
|
|
3093
|
+
| discount | `string` | — | |
|
|
3094
|
+
| total (required) | `string` | — | |
|
|
3095
|
+
| buttonLabel | `string` | `'Checkout'` | |
|
|
3096
|
+
| onCheckout | `() => void` | — | |
|
|
3097
|
+
| loading | `boolean` | `false` | |
|
|
3098
|
+
| className | `string` | — | |
|
|
3099
|
+
|
|
3100
|
+
Also accepts the props of `ViewProps`.
|
|
3101
|
+
<!-- /api:CheckoutSummary -->
|
|
3102
|
+
|
|
3103
|
+
### MetricCard
|
|
3104
|
+
|
|
3105
|
+
Stats/progress card for dashboards
|
|
3106
|
+
|
|
3107
|
+
```tsx
|
|
3108
|
+
import { MetricCard } from '@thewhileloop/whileui';
|
|
3109
|
+
```
|
|
2510
3110
|
|
|
2511
|
-
|
|
2512
|
-
- [ ] Add `## API` sections for blocks: FloatingBottomNav, TabBar, FormModalScreen, ErrorState, LoadingScreen, ListItem, NotificationItem, MetricCard, TimelineFeed
|
|
3111
|
+
<!-- api:MetricCard -->
|
|
2513
3112
|
|
|
2514
|
-
|
|
3113
|
+
| Prop | Type | Default | Description |
|
|
3114
|
+
| ---------------- | ------------------------------------ | ----------- | ----------------------------------------------- |
|
|
3115
|
+
| className | `string` | — | |
|
|
3116
|
+
| label (required) | `string` | — | |
|
|
3117
|
+
| value (required) | `string \| number` | — | |
|
|
3118
|
+
| subtitle | `string` | — | |
|
|
3119
|
+
| progress | `number` | — | |
|
|
3120
|
+
| segments | `{ value: number; color: string }[]` | — | |
|
|
3121
|
+
| icon | `React.ReactNode` | — | |
|
|
3122
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
3123
|
+
| variant | `'default' \| 'outlined'` | `'default'` | |
|
|
2515
3124
|
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
- [ ] Automated visual regression tests (screenshot comparison)
|
|
3125
|
+
Also accepts the props of `ViewProps`.
|
|
3126
|
+
<!-- /api:MetricCard -->
|
|
2519
3127
|
|
|
2520
3128
|
## License
|
|
2521
3129
|
|