@thewhileloop/whileui 4.2.1 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1235 -663
- package/dist/agent/catalog.json +1737 -0
- package/dist/agent/llms-components.txt +2787 -0
- package/dist/agent/llms-full.txt +2962 -0
- package/dist/agent/llms.txt +152 -0
- package/dist/blocks/chat/chat.d.ts +10 -2
- package/dist/blocks/chat/chat.js +1 -1
- package/dist/blocks/chat/index.d.ts +1 -1
- package/dist/blocks/commerce/feature-gate.js +2 -2
- package/dist/blocks/commerce/metric-card.js +1 -1
- package/dist/blocks/commerce/subscription-card.d.ts +7 -0
- package/dist/blocks/commerce/usage-bar.d.ts +4 -0
- package/dist/blocks/layout/app-shell.d.ts +4 -0
- package/dist/blocks/layout/empty-state.js +1 -1
- package/dist/blocks/layout/error-state.js +1 -1
- package/dist/blocks/layout/page-skeleton.d.ts +1 -0
- package/dist/blocks/layout/pull-to-refresh-scroll-view.d.ts +1 -0
- package/dist/blocks/layout/screen-skeleton.d.ts +5 -0
- package/dist/blocks/layout/sheet.js +1 -1
- 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 +2 -2
- package/dist/blocks/navigation/floating-bottom-nav.js +3 -1
- package/dist/blocks/navigation/tab-bar.js +1 -1
- package/dist/components/alert/alert.d.ts +7 -0
- package/dist/components/alert/alert.js +23 -4
- package/dist/components/button/button.d.ts +3 -0
- package/dist/components/button/button.js +2 -1
- package/dist/components/form-field/form-field.d.ts +18 -0
- package/dist/components/form-field/form-field.js +25 -4
- package/dist/components/input/input.js +2 -2
- package/dist/components/numeric-input/numeric-input.d.ts +3 -0
- package/dist/components/numeric-input/numeric-input.js +2 -2
- package/dist/components/otp-input/otp-input.d.ts +8 -0
- package/dist/components/pressable/pressable.d.ts +1 -0
- package/dist/components/segmented-control/segmented-control.d.ts +1 -0
- package/dist/components/surface/surface.d.ts +5 -0
- package/dist/components/textarea/textarea.js +2 -2
- package/dist/components/toast/toast.d.ts +10 -10
- package/dist/components/toast/toast.js +53 -35
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/frosted-surface.d.ts +3 -0
- package/dist/lib/theme-colors.d.ts +4 -3
- package/dist/lib/theme-colors.js +26 -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/lib/visual-tokens.js +13 -0
- package/dist/metro.cjs +12 -5
- package/dist/tokens.d.ts +1 -0
- package/dist/tokens.js +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,2787 @@
|
|
|
1
|
+
# WhileUI 4.3.0
|
|
2
|
+
|
|
3
|
+
> React Native components and blocks for iOS, Android and web, styled with Uniwind and
|
|
4
|
+
> themed through a typed token contract. Package: `@thewhileloop/whileui`.
|
|
5
|
+
|
|
6
|
+
- **Text** (Primitives) — Themed text with variant support
|
|
7
|
+
|
|
8
|
+
## Text
|
|
9
|
+
|
|
10
|
+
`Text` maps semantic roles to the active visual typography tokens; custom font-size
|
|
11
|
+
classes are unnecessary for normal hierarchy. `heading` also sets
|
|
12
|
+
`accessibilityRole="header"` unless the caller supplies another role.
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Description |
|
|
16
|
+
| --------- | ---------------------------------------------------------------------------------------------------------- | ----------- | ----------- |
|
|
17
|
+
| className | `string` | — | |
|
|
18
|
+
| size | `'base' \| 'xs' \| 'sm' \| 'lg' \| 'xl' \| '2xl' \| '3xl'` | — | |
|
|
19
|
+
| variant | `'default' \| 'heading' \| 'title' \| 'subtitle' \| 'body' \| 'caption' \| 'label' \| 'eyebrow' \| 'code'` | `'default'` | |
|
|
20
|
+
|
|
21
|
+
Also accepts the props of React Native's `TextProps`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
- **Surface** (Primitives) — Token-backed base, raised, and overlay container
|
|
26
|
+
|
|
27
|
+
## Surface
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { Surface } from '@thewhileloop/whileui';
|
|
31
|
+
|
|
32
|
+
<Surface level="raised" padding="default">
|
|
33
|
+
{/* grouped content */}
|
|
34
|
+
</Surface>;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
| Prop | Type | Default | Description |
|
|
39
|
+
| ------------- | ------------------------------ | ------------------- | ------------------------------------ |
|
|
40
|
+
| level | `SurfaceLevel` | `'base'` | Semantic background and depth tier. |
|
|
41
|
+
| padding | `SurfacePaddingSize` | `'default'` | Token-backed interior spacing. |
|
|
42
|
+
| radius | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'xl'` | Token-backed corner radius. |
|
|
43
|
+
| bordered | `boolean` | `true` | Draw the theme hairline border. |
|
|
44
|
+
| shadow | `ShadowTier` | — | Override the level's depth. |
|
|
45
|
+
| className | `string` | — | |
|
|
46
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
47
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
48
|
+
| blurTintToken | `FrostedTintToken` | `LEVEL_TINT[level]` | Tint token used for frosted mode. |
|
|
49
|
+
|
|
50
|
+
Also accepts the props of `ViewProps`.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
- **View** (Primitives) — Themed view wrapper
|
|
55
|
+
|
|
56
|
+
### View
|
|
57
|
+
|
|
58
|
+
Themed view wrapper
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { View } from '@thewhileloop/whileui';
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
| Prop | Type | Default | Description |
|
|
66
|
+
| --------- | -------- | ------- | ----------- |
|
|
67
|
+
| className | `string` | — | |
|
|
68
|
+
|
|
69
|
+
Also accepts the props of React Native's `ViewProps`.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
- **Pressable** (Primitives) — Themed pressable wrapper
|
|
74
|
+
|
|
75
|
+
## Pressable
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { Pressable, Text } from '@thewhileloop/whileui';
|
|
79
|
+
|
|
80
|
+
<Pressable
|
|
81
|
+
accessibilityRole="button"
|
|
82
|
+
feedback="strong"
|
|
83
|
+
disabledFeedback="default"
|
|
84
|
+
loading={false}
|
|
85
|
+
onPress={() => {}}
|
|
86
|
+
>
|
|
87
|
+
<Text>Open</Text>
|
|
88
|
+
</Pressable>;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`Pressable` preserves React Native's event, ref, accessibility, and function-style
|
|
92
|
+
`style` contracts. Feedback changes opacity without changing layout. Native press state
|
|
93
|
+
comes directly from React Native; web adds a motion-policy-aware opacity transition.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
| Prop | Type | Default | Description |
|
|
97
|
+
| ---------------- | ----------------- | ----------- | --------------------------------------------------------------------------- |
|
|
98
|
+
| className | `string` | — | Additional Tailwind classes. |
|
|
99
|
+
| feedback | `PressedVariant` | `'none'` | Semantic press feedback. `none` preserves a plain React Native Pressable. |
|
|
100
|
+
| disabledFeedback | `DisabledVariant` | `'default'` | Opacity used while the control is unavailable. |
|
|
101
|
+
| loading | `boolean` | `false` | Loading controls are busy and non-interactive, just like disabled controls. |
|
|
102
|
+
|
|
103
|
+
Also accepts the props of React Native's `PressableProps`.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
- **Stack** (Primitives) — Vertical flex layout with gap
|
|
108
|
+
|
|
109
|
+
### Stack
|
|
110
|
+
|
|
111
|
+
Vertical flex layout with gap
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { Stack } from '@thewhileloop/whileui';
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
| Prop | Type | Default | Description |
|
|
119
|
+
| --------- | ------------------------------------------------------------------- | ----------- | ----------- |
|
|
120
|
+
| className | `string` | — | |
|
|
121
|
+
| gap | `'none' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | |
|
|
122
|
+
| align | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | `'stretch'` | |
|
|
123
|
+
| justify | `'start' \| 'center' \| 'end' \| 'between' \| 'around' \| 'evenly'` | `'start'` | |
|
|
124
|
+
|
|
125
|
+
Also accepts the props of `ViewProps`.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
- **Row** (Primitives) — Horizontal flex layout with gap
|
|
130
|
+
|
|
131
|
+
## Layout Primitives (Stack, Row)
|
|
132
|
+
|
|
133
|
+
Use `Stack` for vertical layouts, `Row` for horizontal layouts. Both support `gap`, `align`, and `justify` variants.
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { Stack, Row } from '@thewhileloop/whileui';
|
|
137
|
+
|
|
138
|
+
<Stack gap="lg">
|
|
139
|
+
<Text>Title</Text>
|
|
140
|
+
<Row gap="md" justify="between">
|
|
141
|
+
<Button>
|
|
142
|
+
<ButtonText>Cancel</ButtonText>
|
|
143
|
+
</Button>
|
|
144
|
+
<Button>
|
|
145
|
+
<ButtonText>Save</ButtonText>
|
|
146
|
+
</Button>
|
|
147
|
+
</Row>
|
|
148
|
+
</Stack>;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
| Prop | Type | Default | Description |
|
|
153
|
+
| --------- | ------------------------------------------------------------------- | ---------- | ----------- |
|
|
154
|
+
| className | `string` | — | |
|
|
155
|
+
| gap | `'none' \| 'xs' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| '2xl'` | `'md'` | |
|
|
156
|
+
| align | `'start' \| 'center' \| 'end' \| 'stretch' \| 'baseline'` | `'center'` | |
|
|
157
|
+
| justify | `'start' \| 'center' \| 'end' \| 'between' \| 'around' \| 'evenly'` | `'start'` | |
|
|
158
|
+
|
|
159
|
+
Also accepts the props of `ViewProps`.
|
|
160
|
+
|
|
161
|
+
### Container
|
|
162
|
+
|
|
163
|
+
Caps content width and centres it. Phone-first layouts stretch to the viewport, which
|
|
164
|
+
is right at 390px and wrong at 1600px — line lengths become unreadable and related
|
|
165
|
+
controls drift apart.
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<Container size="lg">{children}</Container>
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
| Prop | Type | Default | Description |
|
|
173
|
+
| --------- | ---------------------------------------- | ------- | ----------- |
|
|
174
|
+
| className | `string` | — | |
|
|
175
|
+
| size | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | |
|
|
176
|
+
| fill | `boolean` | `false` | |
|
|
177
|
+
|
|
178
|
+
Also accepts the props of `ViewProps`.
|
|
179
|
+
|
|
180
|
+
No platform branch, and none is needed: every size is wider than any phone, so on iOS
|
|
181
|
+
and Android the cap never engages and it renders as a plain full-width `View`. The same
|
|
182
|
+
component is a no-op on native and the whole fix on desktop.
|
|
183
|
+
|
|
184
|
+
For a scroll view, put the column on the content container so the scrollbar stays at
|
|
185
|
+
the window edge:
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
<ScrollView contentContainerClassName="w-full max-w-5xl self-center" />
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
- **MotionProvider** (Root Providers) — System reduced motion, explicit preview modes, and deterministic testing
|
|
194
|
+
|
|
195
|
+
### MotionProvider
|
|
196
|
+
|
|
197
|
+
System reduced motion, explicit preview modes, and deterministic testing
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
import { MotionProvider } from '@thewhileloop/whileui';
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
| Prop | Type | Default | Description |
|
|
205
|
+
| ------------------- | ----------------- | ---------- | ----------- |
|
|
206
|
+
| children (required) | `React.ReactNode` | — | |
|
|
207
|
+
| mode | `MotionMode` | `'system'` | |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
- **Button** (Form Controls) — 4 sizes, ButtonText & ButtonIcon sub-components
|
|
212
|
+
|
|
213
|
+
## Button
|
|
214
|
+
|
|
215
|
+
```tsx
|
|
216
|
+
import { Button, ButtonText, ButtonIcon } from '@thewhileloop/whileui';
|
|
217
|
+
|
|
218
|
+
<Button variant="default" size="default" disabled={false} loading={false} onPress={() => {}}>
|
|
219
|
+
<ButtonIcon>
|
|
220
|
+
<Icon />
|
|
221
|
+
</ButtonIcon>
|
|
222
|
+
<ButtonText>Click me</ButtonText>
|
|
223
|
+
{/* Placement is child order — a trailing ButtonIcon renders after the label. */}
|
|
224
|
+
<ButtonIcon>
|
|
225
|
+
<Icon />
|
|
226
|
+
</ButtonIcon>
|
|
227
|
+
</Button>;
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
| Prop | Type | Default | Description |
|
|
232
|
+
| ---------------- | ----------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------- |
|
|
233
|
+
| children | `React.ReactNode` | — | |
|
|
234
|
+
| className | `string` | — | Additional Tailwind classes. |
|
|
235
|
+
| disabled | `boolean` | `false` | Disable the button. |
|
|
236
|
+
| loading | `boolean` | `false` | Shows a centered spinner, exposes busy state, and prevents interaction. |
|
|
237
|
+
| disabledFeedback | `DisabledVariant` | — | Opacity used while the control is unavailable. |
|
|
238
|
+
| size | `'sm' \| 'lg' \| 'default' \| 'icon'` | `'default'` | |
|
|
239
|
+
| variant | `'default' \| 'destructive' \| 'outline' \| 'secondary' \| 'ghost' \| 'link'` | `'default'` | |
|
|
240
|
+
|
|
241
|
+
Also accepts the props of `PressableProps`.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
- **Input** (Form Controls) — TextInput wrapper with themed styling
|
|
246
|
+
|
|
247
|
+
## Input
|
|
248
|
+
|
|
249
|
+
```tsx
|
|
250
|
+
import { Input } from '@thewhileloop/whileui';
|
|
251
|
+
|
|
252
|
+
<Input placeholder="Email" variant="default" value={value} onChangeText={setValue} />;
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
On web, filled Input, Textarea, NumericInput, and SmartInput surfaces preserve their
|
|
256
|
+
themed background, foreground, and caret when the browser autofills them.
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
| Prop | Type | Default | Description |
|
|
260
|
+
| -------------- | --------------------------------- | ----------- | ----------- |
|
|
261
|
+
| aria-invalid | `boolean` | — | |
|
|
262
|
+
| className | `string` | — | |
|
|
263
|
+
| prefix | `React.ReactNode` | — | |
|
|
264
|
+
| suffix | `React.ReactNode` | — | |
|
|
265
|
+
| inputClassName | `string` | — | |
|
|
266
|
+
| variant | `'default' \| 'error' \| 'plain'` | `'default'` | |
|
|
267
|
+
|
|
268
|
+
Also accepts the props of `TextInputProps`.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
- **NumericInput** (Form Controls) — Numeric input with prefix/suffix slots, optional steppers, and compact size
|
|
273
|
+
|
|
274
|
+
## NumericInput
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
import { NumericInput } from '@thewhileloop/whileui';
|
|
278
|
+
|
|
279
|
+
<NumericInput
|
|
280
|
+
value={amount}
|
|
281
|
+
onValueChange={setAmount}
|
|
282
|
+
prefix={<Text className="text-muted-foreground">$</Text>}
|
|
283
|
+
suffix={<Text className="text-muted-foreground">USD</Text>}
|
|
284
|
+
min={0}
|
|
285
|
+
step={0.5}
|
|
286
|
+
showSteppers
|
|
287
|
+
size="compact"
|
|
288
|
+
/>;
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
| Prop | Type | Default | Description |
|
|
293
|
+
| -------------- | --------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
294
|
+
| aria-invalid | `boolean` | — | |
|
|
295
|
+
| className | `string` | — | |
|
|
296
|
+
| inputClassName | `string` | — | |
|
|
297
|
+
| prefix | `React.ReactNode` | — | |
|
|
298
|
+
| suffix | `React.ReactNode` | — | |
|
|
299
|
+
| value | `number \| null` | — | Controlled numeric value. |
|
|
300
|
+
| defaultValue | `number` | — | |
|
|
301
|
+
| onValueChange | `(value: number \| null) => void` | — | Numeric value change callback. |
|
|
302
|
+
| min | `number` | — | |
|
|
303
|
+
| max | `number` | — | |
|
|
304
|
+
| step | `number` | `1` | |
|
|
305
|
+
| showSteppers | `boolean` | `false` | Show decrement/increment controls. |
|
|
306
|
+
| unstyled | `boolean` | `false` | Drop the field shell (border, background, radius, padding) for embedding inside another field shell like LabeledField. |
|
|
307
|
+
| size | `'default' \| 'compact'` | `'default'` | |
|
|
308
|
+
| variant | `'default' \| 'error'` | `'default'` | |
|
|
309
|
+
|
|
310
|
+
Also accepts the props of `TextInputProps`.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
- **OTPInput** (Form Controls) — Verification code input with auto-focus, paste, secure mask, loading skeleton, error shake
|
|
315
|
+
|
|
316
|
+
## OTPInput
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
import { OTPInput } from '@thewhileloop/whileui';
|
|
320
|
+
|
|
321
|
+
<OTPInput
|
|
322
|
+
value={otp}
|
|
323
|
+
onValueChange={setOtp}
|
|
324
|
+
onComplete={(code) => verify(code)}
|
|
325
|
+
length={6}
|
|
326
|
+
variant="default"
|
|
327
|
+
/>;
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
| Prop | Type | Default | Description |
|
|
332
|
+
| ------------- | ------------------------- | ----------- | ---------------------------------- |
|
|
333
|
+
| length | `number` | `6` | Number of digit cells. |
|
|
334
|
+
| value | `string` | — | Controlled value. |
|
|
335
|
+
| onValueChange | `(value: string) => void` | — | Called on each digit change. |
|
|
336
|
+
| onComplete | `(code: string) => void` | — | Called when all digits are filled. |
|
|
337
|
+
| variant | `'default' \| 'error'` | `'default'` | Error variant triggers shake. |
|
|
338
|
+
| size | `'default' \| 'compact'` | `'default'` | Cell size. |
|
|
339
|
+
| secure | `boolean` | `false` | Mask digits with dots. |
|
|
340
|
+
| disabled | `boolean` | `false` | |
|
|
341
|
+
| autoFocus | `boolean` | `false` | Focus first cell on mount. |
|
|
342
|
+
| className | `string` | — | |
|
|
343
|
+
|
|
344
|
+
Also accepts the props of `ViewProps`.
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
- **FormField** (Form Controls) — Compound API: FormField, FormLabel, FormControl, FormHint, FormMessage
|
|
349
|
+
|
|
350
|
+
## FormField
|
|
351
|
+
|
|
352
|
+
```tsx
|
|
353
|
+
import {
|
|
354
|
+
FormField,
|
|
355
|
+
FormLabel,
|
|
356
|
+
FormControl,
|
|
357
|
+
FormHint,
|
|
358
|
+
FormMessage,
|
|
359
|
+
Input,
|
|
360
|
+
} from '@thewhileloop/whileui';
|
|
361
|
+
|
|
362
|
+
<FormField required invalid={Boolean(error)}>
|
|
363
|
+
<FormLabel>Email</FormLabel>
|
|
364
|
+
<FormControl>
|
|
365
|
+
<Input placeholder="you@example.com" />
|
|
366
|
+
</FormControl>
|
|
367
|
+
{error ? <FormMessage>{error}</FormMessage> : <FormHint>We'll never share your email.</FormHint>}
|
|
368
|
+
</FormField>;
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
`invalid` and `disabled` are inherited by `Input`, `Textarea`, `NumericInput`, and
|
|
372
|
+
`Select`. An explicit control prop wins. `useFormFieldState()` exposes the same state for
|
|
373
|
+
custom controls; labels, hints, messages, density, and required indicators remain part
|
|
374
|
+
of the compound field rather than each input.
|
|
375
|
+
|
|
376
|
+
`FormLabel` names the `Input`, `Textarea` or `NumericInput` beside it for assistive
|
|
377
|
+
technology (`aria-labelledby` on web and Android; `useFormFieldState().labelId` gives a
|
|
378
|
+
custom control the same id). A label passed to the control itself wins.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
- **LabeledField** (Form Controls) — Field wrapper with label/helper/error plus left/right slots
|
|
383
|
+
|
|
384
|
+
## LabeledField
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
import { LabeledField, LabeledFieldControl, Input } from '@thewhileloop/whileui';
|
|
388
|
+
|
|
389
|
+
<LabeledField
|
|
390
|
+
label="Username"
|
|
391
|
+
hint="3-20 characters"
|
|
392
|
+
leftSlot={<Icon />}
|
|
393
|
+
rightSlot={
|
|
394
|
+
<Button size="sm">
|
|
395
|
+
<ButtonText>Check</ButtonText>
|
|
396
|
+
</Button>
|
|
397
|
+
}
|
|
398
|
+
>
|
|
399
|
+
<LabeledFieldControl>
|
|
400
|
+
<Input className="border-0 bg-transparent px-0" />
|
|
401
|
+
</LabeledFieldControl>
|
|
402
|
+
</LabeledField>;
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
- **Textarea** (Form Controls) — Multi-line text input
|
|
408
|
+
|
|
409
|
+
### Textarea
|
|
410
|
+
|
|
411
|
+
Multi-line text input
|
|
412
|
+
|
|
413
|
+
```tsx
|
|
414
|
+
import { Textarea } from '@thewhileloop/whileui';
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
| Prop | Type | Default | Description |
|
|
419
|
+
| ------------ | --------------------------- | ----------- | ----------- |
|
|
420
|
+
| aria-invalid | `boolean` | — | |
|
|
421
|
+
| className | `string` | — | |
|
|
422
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
423
|
+
|
|
424
|
+
Also accepts the props of `TextInputProps`.
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
- **Checkbox** (Form Controls) — Controlled/uncontrolled, accessibility roles
|
|
429
|
+
|
|
430
|
+
## Checkbox
|
|
431
|
+
|
|
432
|
+
```tsx
|
|
433
|
+
import { Checkbox } from '@thewhileloop/whileui';
|
|
434
|
+
|
|
435
|
+
<Checkbox checked={checked} onCheckedChange={setChecked} />;
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
| Prop | Type | Default | Description |
|
|
440
|
+
| --------------- | ---------------------------- | ------- | ----------- |
|
|
441
|
+
| checked | `boolean` | — | |
|
|
442
|
+
| defaultChecked | `boolean` | `false` | |
|
|
443
|
+
| onCheckedChange | `(checked: boolean) => void` | — | |
|
|
444
|
+
| disabled | `boolean` | `false` | |
|
|
445
|
+
| className | `string` | — | |
|
|
446
|
+
|
|
447
|
+
Also accepts the props of `ViewProps`.
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
- **Switch** (Form Controls) — Controlled/uncontrolled, accessibility roles
|
|
452
|
+
|
|
453
|
+
## Switch
|
|
454
|
+
|
|
455
|
+
```tsx
|
|
456
|
+
import { Switch } from '@thewhileloop/whileui';
|
|
457
|
+
|
|
458
|
+
<Switch checked={checked} onCheckedChange={setChecked} />;
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
| Prop | Type | Default | Description |
|
|
463
|
+
| --------------- | ---------------------------- | ------- | ----------- |
|
|
464
|
+
| checked | `boolean` | — | |
|
|
465
|
+
| defaultChecked | `boolean` | `false` | |
|
|
466
|
+
| onCheckedChange | `(checked: boolean) => void` | — | |
|
|
467
|
+
| disabled | `boolean` | `false` | |
|
|
468
|
+
| className | `string` | — | |
|
|
469
|
+
|
|
470
|
+
Also accepts the props of `ViewProps`.
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
- **RadioGroup** (Form Controls) — RadioGroup + RadioGroupItem
|
|
475
|
+
|
|
476
|
+
## RadioGroup
|
|
477
|
+
|
|
478
|
+
```tsx
|
|
479
|
+
import { RadioGroup, RadioGroupItem } from '@thewhileloop/whileui';
|
|
480
|
+
|
|
481
|
+
<RadioGroup value={value} onValueChange={setValue}>
|
|
482
|
+
<RadioGroupItem value="option1" />
|
|
483
|
+
<RadioGroupItem value="option2" />
|
|
484
|
+
</RadioGroup>;
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
- **Select** (Form Controls) — Uses `SelectOption` type `{value, label}`. Includes SelectGroup, SelectLabel, SelectSeparator
|
|
490
|
+
|
|
491
|
+
## Select
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
import {
|
|
495
|
+
Select,
|
|
496
|
+
SelectTrigger,
|
|
497
|
+
SelectValue,
|
|
498
|
+
SelectContent,
|
|
499
|
+
SelectItem,
|
|
500
|
+
} from '@thewhileloop/whileui';
|
|
501
|
+
|
|
502
|
+
<Select value={value} onValueChange={setValue}>
|
|
503
|
+
<SelectTrigger>
|
|
504
|
+
<SelectValue placeholder="Select..." />
|
|
505
|
+
</SelectTrigger>
|
|
506
|
+
<SelectContent frosted blurIntensity={24} blurTintToken="surfaceTranslucent">
|
|
507
|
+
<SelectItem label="Option 1" value="1" />
|
|
508
|
+
<SelectItem label="Option 2" value="2" />
|
|
509
|
+
</SelectContent>
|
|
510
|
+
</Select>;
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Focused and pressed options use the subtle interaction-state tokens; the primary token
|
|
514
|
+
marks the selected indicator without replacing the option's foreground colour. Long
|
|
515
|
+
selected values stay on one line and truncate without displacing the trigger chevron.
|
|
516
|
+
For popover presentation on web, the options panel matches the trigger width, caps
|
|
517
|
+
itself to the collision-available viewport, and wraps long option labels inside it.
|
|
518
|
+
|
|
519
|
+
`SelectContent` supports optional frosted props:
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
| Prop | Type | Default | Description |
|
|
523
|
+
| ------------- | ------------------------------------------------------------------ | ------------- | ------------------------------------ |
|
|
524
|
+
| className | `string` | — | |
|
|
525
|
+
| portalHost | `string` | — | |
|
|
526
|
+
| insets | `{ top?: number; bottom?: number; left?: number; right?: number }` | — | |
|
|
527
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
528
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
529
|
+
| blurTintToken | `FrostedTintToken` | `'popover'` | Tint token used for frosted mode. |
|
|
530
|
+
|
|
531
|
+
Also accepts the props of the underlying primitive.
|
|
532
|
+
|
|
533
|
+
`SelectContent` keeps its listbox inside the safe area by default; pass `insets` only to
|
|
534
|
+
change that margin. A list taller than the screen scrolls inside the listbox on every
|
|
535
|
+
platform. Inside a Dialog, Sheet or other WhileUI overlay it renders in that
|
|
536
|
+
overlay's layer automatically. Option values must be non-empty strings: the web
|
|
537
|
+
primitive rejects `""`, so model "none" as an explicit value such as `'none'`.
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
- **Label** (Form Controls) — Form field label
|
|
542
|
+
|
|
543
|
+
### Label
|
|
544
|
+
|
|
545
|
+
Form field label
|
|
546
|
+
|
|
547
|
+
```tsx
|
|
548
|
+
import { Label } from '@thewhileloop/whileui';
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
| Prop | Type | Default | Description |
|
|
553
|
+
| --------- | -------- | ------- | ----------- |
|
|
554
|
+
| className | `string` | — | |
|
|
555
|
+
|
|
556
|
+
Also accepts the props of `TextProps`.
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
- **SegmentedControl** (Form Controls) — SegmentedControl, SegmentedControlItem, SegmentedControlItemText with wrapping layout support
|
|
561
|
+
|
|
562
|
+
## SegmentedControl
|
|
563
|
+
|
|
564
|
+
```tsx
|
|
565
|
+
import {
|
|
566
|
+
SegmentedControl,
|
|
567
|
+
SegmentedControlItem,
|
|
568
|
+
SegmentedControlItemText,
|
|
569
|
+
} from '@thewhileloop/whileui';
|
|
570
|
+
|
|
571
|
+
<SegmentedControl value={unit} onValueChange={setUnit} variant="pill" wrap>
|
|
572
|
+
<SegmentedControlItem value="metric">
|
|
573
|
+
<SegmentedControlItemText>Metric</SegmentedControlItemText>
|
|
574
|
+
</SegmentedControlItem>
|
|
575
|
+
<SegmentedControlItem value="imperial">
|
|
576
|
+
<SegmentedControlItemText>Imperial</SegmentedControlItemText>
|
|
577
|
+
</SegmentedControlItem>
|
|
578
|
+
</SegmentedControl>;
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
| Prop | Type | Default | Description |
|
|
583
|
+
| ------------- | ------------------------- | ----------- | -------------------------- |
|
|
584
|
+
| value | `string` | — | |
|
|
585
|
+
| defaultValue | `string` | `''` | |
|
|
586
|
+
| onValueChange | `(value: string) => void` | — | |
|
|
587
|
+
| variant | `'default' \| 'pill'` | `'default'` | Pill = rounded-full items. |
|
|
588
|
+
| wrap | `boolean \| 'auto'` | `true` | |
|
|
589
|
+
| size | `'default' \| 'compact'` | `'default'` | |
|
|
590
|
+
| disabled | `boolean` | `false` | |
|
|
591
|
+
| className | `string` | — | |
|
|
592
|
+
|
|
593
|
+
Also accepts the props of `ViewProps`.
|
|
594
|
+
|
|
595
|
+
---
|
|
596
|
+
|
|
597
|
+
- **Toggle** (Form Controls) — ToggleText sub-component
|
|
598
|
+
|
|
599
|
+
### Toggle
|
|
600
|
+
|
|
601
|
+
ToggleText sub-component
|
|
602
|
+
|
|
603
|
+
```tsx
|
|
604
|
+
import { Toggle } from '@thewhileloop/whileui';
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
| Prop | Type | Default | Description |
|
|
609
|
+
| --------------- | ------------------------------------------------ | ----------- | ----------- |
|
|
610
|
+
| pressed | `boolean` | `false` | |
|
|
611
|
+
| defaultPressed | `boolean` | `false` | |
|
|
612
|
+
| onPressedChange | `(pressed: boolean) => void` | — | |
|
|
613
|
+
| variant | `VariantProps<typeof toggleVariants>['variant']` | `'default'` | |
|
|
614
|
+
| size | `VariantProps<typeof toggleVariants>['size']` | `'default'` | |
|
|
615
|
+
| className | `string` | — | |
|
|
616
|
+
| children | `React.ReactNode` | — | |
|
|
617
|
+
|
|
618
|
+
Also accepts the props of `PressableProps`.
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
- **ToggleGroup** (Form Controls) — Group of toggle items
|
|
623
|
+
|
|
624
|
+
### ToggleGroup
|
|
625
|
+
|
|
626
|
+
Group of toggle items
|
|
627
|
+
|
|
628
|
+
```tsx
|
|
629
|
+
import { ToggleGroup } from '@thewhileloop/whileui';
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
| Prop | Type | Default | Description |
|
|
634
|
+
| ------------- | ------------------------------------- | ----------- | ----------- |
|
|
635
|
+
| type | `ToggleGroupType` | `'single'` | |
|
|
636
|
+
| defaultValue | `string \| string[]` | — | |
|
|
637
|
+
| value | `string \| string[]` | — | |
|
|
638
|
+
| onValueChange | `(value: string \| string[]) => void` | — | |
|
|
639
|
+
| variant | `'default' \| 'outline'` | `'default'` | |
|
|
640
|
+
| size | `'default' \| 'sm' \| 'lg'` | `'default'` | |
|
|
641
|
+
| className | `string` | — | |
|
|
642
|
+
|
|
643
|
+
Also accepts the props of `ViewProps`.
|
|
644
|
+
|
|
645
|
+
---
|
|
646
|
+
|
|
647
|
+
- **Card** (Display) — Compound: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter
|
|
648
|
+
|
|
649
|
+
## Card
|
|
650
|
+
|
|
651
|
+
```tsx
|
|
652
|
+
import {
|
|
653
|
+
Card,
|
|
654
|
+
CardHeader,
|
|
655
|
+
CardTitle,
|
|
656
|
+
CardDescription,
|
|
657
|
+
CardContent,
|
|
658
|
+
CardFooter,
|
|
659
|
+
} from '@thewhileloop/whileui';
|
|
660
|
+
|
|
661
|
+
<Card padding="default">
|
|
662
|
+
<CardHeader>
|
|
663
|
+
<CardTitle>Title</CardTitle>
|
|
664
|
+
<CardDescription>Description</CardDescription>
|
|
665
|
+
</CardHeader>
|
|
666
|
+
<CardContent>{/* Content */}</CardContent>
|
|
667
|
+
<CardFooter>{/* Footer */}</CardFooter>
|
|
668
|
+
</Card>;
|
|
669
|
+
|
|
670
|
+
<Card unstyled padding="none" className="rounded-xl border border-border bg-card">
|
|
671
|
+
{/* advanced custom layouts */}
|
|
672
|
+
</Card>;
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
| Prop | Type | Default | Description |
|
|
677
|
+
| ------------- | ------------------------------------- | ----------- | ------------------------------------------------------- |
|
|
678
|
+
| shadow | `ShadowTier` | `'sm'` | Elevation tier; 'sm' by default, 'none' for flat cards. |
|
|
679
|
+
| className | `string` | — | |
|
|
680
|
+
| padding | `'none' \| 'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
681
|
+
| unstyled | `boolean` | `false` | |
|
|
682
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
683
|
+
| blurIntensity | `number` | — | Override blur amount directly. |
|
|
684
|
+
| blurTintToken | `FrostedTintToken` | — | Tint token used for frosted mode. |
|
|
685
|
+
|
|
686
|
+
Also accepts the props of `ViewProps`.
|
|
687
|
+
|
|
688
|
+
---
|
|
689
|
+
|
|
690
|
+
- **Surface** (Display) — Shared radius, padding, border, shadow, and optional frosted treatment
|
|
691
|
+
|
|
692
|
+
Documented under Surface.
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
- **Badge** (Display) — BadgeText sub-component
|
|
697
|
+
|
|
698
|
+
## Badge
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
import { Badge, BadgeText } from '@thewhileloop/whileui';
|
|
702
|
+
|
|
703
|
+
<Badge variant="default">
|
|
704
|
+
<BadgeText>New</BadgeText>
|
|
705
|
+
</Badge>;
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
| Prop | Type | Default | Description |
|
|
710
|
+
| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ----------- |
|
|
711
|
+
| className | `string` | — | |
|
|
712
|
+
| variant | `'default' \| 'soft' \| 'destructive' \| 'outline' \| 'secondary' \| 'success' \| 'warning' \| 'info' \| 'soft-secondary' \| 'soft-destructive' \| 'soft-success' \| 'soft-warning' \| 'soft-info'` | `'default'` | |
|
|
713
|
+
|
|
714
|
+
Also accepts the props of `ViewProps`.
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
- **FAB** (Display) — Floating action button. `label` required; pass your own icon as children
|
|
719
|
+
|
|
720
|
+
### FAB
|
|
721
|
+
|
|
722
|
+
One primary action, raised above the content. It owns no icon dependency — pass the
|
|
723
|
+
glyph as children and colour it from `useIconColors()`, because icon libraries need a
|
|
724
|
+
concrete colour rather than a class.
|
|
725
|
+
|
|
726
|
+
```tsx
|
|
727
|
+
import { FAB, useIconColors } from '@thewhileloop/whileui';
|
|
728
|
+
import { Feather } from '@expo/vector-icons';
|
|
729
|
+
|
|
730
|
+
const icons = useIconColors();
|
|
731
|
+
|
|
732
|
+
<FAB label="New group" onPress={create}>
|
|
733
|
+
<Feather name="plus" size={24} color={icons.primaryForeground} />
|
|
734
|
+
</FAB>;
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
| Prop | Type | Default | Notes |
|
|
738
|
+
| ----------- | ------------------------------------------------ | ---------- | --------------------------------------- |
|
|
739
|
+
| `label` | `string` | — | Required. The accessible name |
|
|
740
|
+
| `variant` | `default \| secondary \| destructive \| surface` | `default` | |
|
|
741
|
+
| `size` | `sm \| default \| lg` | `default` | 48 / 56 / 64 — all clear touch minimums |
|
|
742
|
+
| `placement` | `floating \| floating-left \| inline` | `floating` | `inline` lets the caller position it |
|
|
743
|
+
|
|
744
|
+
`FABGroup` stacks secondary actions above one. Sizes start at 48dp, so a FAB never
|
|
745
|
+
needs `hitSlop`.
|
|
746
|
+
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
- **Alert** (Display) — AlertTitle & AlertDescription
|
|
750
|
+
|
|
751
|
+
## Alert
|
|
752
|
+
|
|
753
|
+
```tsx
|
|
754
|
+
import { Alert, AlertTitle, AlertDescription } from '@thewhileloop/whileui';
|
|
755
|
+
|
|
756
|
+
<Alert variant="default">
|
|
757
|
+
<AlertTitle>Heads up!</AlertTitle>
|
|
758
|
+
<AlertDescription>Something happened.</AlertDescription>
|
|
759
|
+
</Alert>;
|
|
760
|
+
|
|
761
|
+
// Shown after a failed submit: also spoken on iOS when it appears.
|
|
762
|
+
<Alert variant="destructive" announce>
|
|
763
|
+
<AlertTitle>Could not save</AlertTitle>
|
|
764
|
+
<AlertDescription>Check your connection and try again.</AlertDescription>
|
|
765
|
+
</Alert>;
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
|
|
769
|
+
| Prop | Type | Default | Description |
|
|
770
|
+
| --------- | ---------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
771
|
+
| 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. |
|
|
772
|
+
| className | `string` | — | |
|
|
773
|
+
| variant | `'default' \| 'destructive' \| 'success' \| 'warning' \| 'info'` | `'default'` | |
|
|
774
|
+
|
|
775
|
+
Also accepts the props of `ViewProps`.
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
- **Avatar** (Display) — AvatarImage + AvatarFallback
|
|
780
|
+
|
|
781
|
+
## Avatar
|
|
782
|
+
|
|
783
|
+
```tsx
|
|
784
|
+
import { Avatar, AvatarImage, AvatarFallback } from '@thewhileloop/whileui';
|
|
785
|
+
|
|
786
|
+
<Avatar size="default">
|
|
787
|
+
<AvatarImage source={{ uri: 'https://...' }} />
|
|
788
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
789
|
+
</Avatar>;
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
| Prop | Type | Default | Description |
|
|
794
|
+
| --------- | ----------------------------------- | ----------- | ----------- |
|
|
795
|
+
| className | `string` | — | |
|
|
796
|
+
| size | `'sm' \| 'lg' \| 'xl' \| 'default'` | `'default'` | |
|
|
797
|
+
|
|
798
|
+
Also accepts the props of `ViewProps`.
|
|
799
|
+
|
|
800
|
+
`AvatarImage` keeps `AvatarFallback` visible while loading or after an error. Its
|
|
801
|
+
`onLoadingStatusChange` callback reports `'loading'`, `'loaded'`, and `'error'`, including
|
|
802
|
+
when a changed source begins loading, while the standard image event callbacks still run.
|
|
803
|
+
|
|
804
|
+
---
|
|
805
|
+
|
|
806
|
+
- **DataRow** (Display) — DataRow, DataRowLeft/Center/Right, DataRowLabel/Description/Value
|
|
807
|
+
|
|
808
|
+
## DataRow
|
|
809
|
+
|
|
810
|
+
```tsx
|
|
811
|
+
import {
|
|
812
|
+
DataRow,
|
|
813
|
+
DataRowLeft,
|
|
814
|
+
DataRowCenter,
|
|
815
|
+
DataRowRight,
|
|
816
|
+
DataRowLabel,
|
|
817
|
+
DataRowDescription,
|
|
818
|
+
DataRowValue,
|
|
819
|
+
Avatar,
|
|
820
|
+
AvatarFallback,
|
|
821
|
+
} from '@thewhileloop/whileui';
|
|
822
|
+
|
|
823
|
+
<DataRow>
|
|
824
|
+
<DataRowLeft>
|
|
825
|
+
<Avatar size="sm">
|
|
826
|
+
<AvatarFallback>JD</AvatarFallback>
|
|
827
|
+
</Avatar>
|
|
828
|
+
</DataRowLeft>
|
|
829
|
+
<DataRowCenter>
|
|
830
|
+
<DataRowLabel>Jane Doe</DataRowLabel>
|
|
831
|
+
<DataRowDescription>Product Designer</DataRowDescription>
|
|
832
|
+
</DataRowCenter>
|
|
833
|
+
<DataRowRight>
|
|
834
|
+
<DataRowValue>Owner</DataRowValue>
|
|
835
|
+
</DataRowRight>
|
|
836
|
+
</DataRow>;
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
---
|
|
840
|
+
|
|
841
|
+
- **Separator** (Display) — Themed divider
|
|
842
|
+
|
|
843
|
+
### Separator
|
|
844
|
+
|
|
845
|
+
Themed divider
|
|
846
|
+
|
|
847
|
+
```tsx
|
|
848
|
+
import { Separator } from '@thewhileloop/whileui';
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
| Prop | Type | Default | Description |
|
|
853
|
+
| ----------- | ---------------------------- | -------------- | ----------- |
|
|
854
|
+
| orientation | `'horizontal' \| 'vertical'` | `'horizontal'` | |
|
|
855
|
+
| className | `string` | — | |
|
|
856
|
+
|
|
857
|
+
Also accepts the props of `ViewProps`.
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
- **Progress** (Display) — Value-based progress bar with accessibility
|
|
862
|
+
|
|
863
|
+
## Progress
|
|
864
|
+
|
|
865
|
+
```tsx
|
|
866
|
+
import { Progress } from '@thewhileloop/whileui';
|
|
867
|
+
|
|
868
|
+
<Progress value={50} size="default" />;
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
|
|
872
|
+
| Prop | Type | Default | Description |
|
|
873
|
+
| ------------------ | --------------------------- | ----------- | ----------- |
|
|
874
|
+
| value | `number` | `0` | |
|
|
875
|
+
| indicatorClassName | `string` | — | |
|
|
876
|
+
| className | `string` | — | |
|
|
877
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
878
|
+
|
|
879
|
+
Also accepts the props of `ViewProps`.
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
- **Spinner** (Display) — ActivityIndicator wrapper
|
|
884
|
+
|
|
885
|
+
### Spinner
|
|
886
|
+
|
|
887
|
+
ActivityIndicator wrapper
|
|
888
|
+
|
|
889
|
+
```tsx
|
|
890
|
+
import { Spinner } from '@thewhileloop/whileui';
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
|
|
894
|
+
| Prop | Type | Default | Description |
|
|
895
|
+
| --------- | --------------------------- | ----------- | ----------- |
|
|
896
|
+
| className | `string` | — | |
|
|
897
|
+
| size | `'sm' \| 'lg' \| 'default'` | `'default'` | |
|
|
898
|
+
|
|
899
|
+
Also accepts the props of `ActivityIndicatorProps`.
|
|
900
|
+
|
|
901
|
+
---
|
|
902
|
+
|
|
903
|
+
- **Skeleton** (Display) — Loading placeholder (pulse = opacity fade, shimmer = sweep)
|
|
904
|
+
|
|
905
|
+
### Skeleton
|
|
906
|
+
|
|
907
|
+
Loading placeholder (pulse = opacity fade, shimmer = sweep)
|
|
908
|
+
|
|
909
|
+
```tsx
|
|
910
|
+
import { Skeleton } from '@thewhileloop/whileui';
|
|
911
|
+
```
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
| Prop | Type | Default | Description |
|
|
915
|
+
| --------- | ---------------------- | --------- | ----------- |
|
|
916
|
+
| className | `string` | — | |
|
|
917
|
+
| variant | `'pulse' \| 'shimmer'` | `'pulse'` | |
|
|
918
|
+
|
|
919
|
+
Also accepts the props of `ViewProps`.
|
|
920
|
+
|
|
921
|
+
---
|
|
922
|
+
|
|
923
|
+
- **AspectRatio** (Display) — Maintain aspect ratio container
|
|
924
|
+
|
|
925
|
+
### AspectRatio
|
|
926
|
+
|
|
927
|
+
Maintain aspect ratio container
|
|
928
|
+
|
|
929
|
+
```tsx
|
|
930
|
+
import { AspectRatio } from '@thewhileloop/whileui';
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
|
|
934
|
+
| Prop | Type | Default | Description |
|
|
935
|
+
| --------- | -------- | ------- | ----------- |
|
|
936
|
+
| ratio | `number` | `1` | |
|
|
937
|
+
| className | `string` | — | |
|
|
938
|
+
|
|
939
|
+
Also accepts the props of `ViewProps`.
|
|
940
|
+
|
|
941
|
+
---
|
|
942
|
+
|
|
943
|
+
- **Tabs** (Layout) — TabsList, TabsTrigger, TabsContent
|
|
944
|
+
|
|
945
|
+
## Tabs
|
|
946
|
+
|
|
947
|
+
```tsx
|
|
948
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@thewhileloop/whileui';
|
|
949
|
+
|
|
950
|
+
<Tabs defaultValue="tab1">
|
|
951
|
+
<TabsList>
|
|
952
|
+
<TabsTrigger value="tab1">
|
|
953
|
+
<Text>Tab 1</Text>
|
|
954
|
+
</TabsTrigger>
|
|
955
|
+
<TabsTrigger value="tab2">
|
|
956
|
+
<Text>Tab 2</Text>
|
|
957
|
+
</TabsTrigger>
|
|
958
|
+
</TabsList>
|
|
959
|
+
<TabsContent value="tab1">{/* Content 1 */}</TabsContent>
|
|
960
|
+
<TabsContent value="tab2">{/* Content 2 */}</TabsContent>
|
|
961
|
+
</Tabs>;
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
---
|
|
965
|
+
|
|
966
|
+
- **Accordion** (Layout) — AccordionItem, AccordionTrigger, Content
|
|
967
|
+
|
|
968
|
+
## Accordion
|
|
969
|
+
|
|
970
|
+
```tsx
|
|
971
|
+
import {
|
|
972
|
+
Accordion,
|
|
973
|
+
AccordionItem,
|
|
974
|
+
AccordionTrigger,
|
|
975
|
+
AccordionContent,
|
|
976
|
+
} from '@thewhileloop/whileui';
|
|
977
|
+
|
|
978
|
+
<Accordion type="single" collapsible>
|
|
979
|
+
<AccordionItem value="item1">
|
|
980
|
+
<AccordionTrigger>
|
|
981
|
+
<Text>Section 1</Text>
|
|
982
|
+
</AccordionTrigger>
|
|
983
|
+
<AccordionContent>
|
|
984
|
+
<Text>Content 1</Text>
|
|
985
|
+
</AccordionContent>
|
|
986
|
+
</AccordionItem>
|
|
987
|
+
</Accordion>;
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
|
|
991
|
+
| Prop | Type | Default | Description |
|
|
992
|
+
| ------------ | -------------------- | ---------- | ----------- |
|
|
993
|
+
| type | `AccordionType` | `'single'` | |
|
|
994
|
+
| defaultValue | `string \| string[]` | — | |
|
|
995
|
+
| collapsible | `boolean` | `true` | |
|
|
996
|
+
| className | `string` | — | |
|
|
997
|
+
|
|
998
|
+
Also accepts the props of `ViewProps`.
|
|
999
|
+
|
|
1000
|
+
---
|
|
1001
|
+
|
|
1002
|
+
- **Collapsible** (Layout) — CollapsibleTrigger, CollapsibleContent
|
|
1003
|
+
|
|
1004
|
+
### Collapsible
|
|
1005
|
+
|
|
1006
|
+
CollapsibleTrigger, CollapsibleContent
|
|
1007
|
+
|
|
1008
|
+
```tsx
|
|
1009
|
+
import { Collapsible } from '@thewhileloop/whileui';
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
| Prop | Type | Default | Description |
|
|
1014
|
+
| ------------ | ------------------------- | ------- | ----------- |
|
|
1015
|
+
| defaultOpen | `boolean` | `false` | |
|
|
1016
|
+
| open | `boolean` | — | |
|
|
1017
|
+
| onOpenChange | `(open: boolean) => void` | — | |
|
|
1018
|
+
| disabled | `boolean` | `false` | |
|
|
1019
|
+
| className | `string` | — | |
|
|
1020
|
+
|
|
1021
|
+
Also accepts the props of `ViewProps`.
|
|
1022
|
+
|
|
1023
|
+
---
|
|
1024
|
+
|
|
1025
|
+
- **Container** (Layout) — Centred content column with a max width
|
|
1026
|
+
|
|
1027
|
+
### Container
|
|
1028
|
+
|
|
1029
|
+
Caps content width and centres it. Phone-first layouts stretch to the viewport, which
|
|
1030
|
+
is right at 390px and wrong at 1600px — line lengths become unreadable and related
|
|
1031
|
+
controls drift apart.
|
|
1032
|
+
|
|
1033
|
+
```tsx
|
|
1034
|
+
<Container size="lg">{children}</Container>
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
| Prop | Type | Default | Description |
|
|
1039
|
+
| --------- | ---------------------------------------- | ------- | ----------- |
|
|
1040
|
+
| className | `string` | — | |
|
|
1041
|
+
| size | `'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | |
|
|
1042
|
+
| fill | `boolean` | `false` | |
|
|
1043
|
+
|
|
1044
|
+
Also accepts the props of `ViewProps`.
|
|
1045
|
+
|
|
1046
|
+
No platform branch, and none is needed: every size is wider than any phone, so on iOS
|
|
1047
|
+
and Android the cap never engages and it renders as a plain full-width `View`. The same
|
|
1048
|
+
component is a no-op on native and the whole fix on desktop.
|
|
1049
|
+
|
|
1050
|
+
For a scroll view, put the column on the content container so the scrollbar stays at
|
|
1051
|
+
the window edge:
|
|
1052
|
+
|
|
1053
|
+
```tsx
|
|
1054
|
+
<ScrollView contentContainerClassName="w-full max-w-5xl self-center" />
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
---
|
|
1058
|
+
|
|
1059
|
+
- **Dialog** (Overlays & Menus) — Modal dialog with Header, Footer, Title, Description
|
|
1060
|
+
|
|
1061
|
+
## Dialog
|
|
1062
|
+
|
|
1063
|
+
```tsx
|
|
1064
|
+
import {
|
|
1065
|
+
Dialog,
|
|
1066
|
+
DialogTrigger,
|
|
1067
|
+
DialogContent,
|
|
1068
|
+
DialogHeader,
|
|
1069
|
+
DialogTitle,
|
|
1070
|
+
DialogDescription,
|
|
1071
|
+
DialogFooter,
|
|
1072
|
+
DialogClose,
|
|
1073
|
+
} from '@thewhileloop/whileui';
|
|
1074
|
+
|
|
1075
|
+
<Dialog>
|
|
1076
|
+
<DialogTrigger asChild>
|
|
1077
|
+
<Button>
|
|
1078
|
+
<ButtonText>Open</ButtonText>
|
|
1079
|
+
</Button>
|
|
1080
|
+
</DialogTrigger>
|
|
1081
|
+
<DialogContent>
|
|
1082
|
+
<DialogHeader>
|
|
1083
|
+
<DialogTitle>Title</DialogTitle>
|
|
1084
|
+
<DialogDescription>Description</DialogDescription>
|
|
1085
|
+
</DialogHeader>
|
|
1086
|
+
{/* Content */}
|
|
1087
|
+
<DialogFooter>
|
|
1088
|
+
<DialogClose asChild>
|
|
1089
|
+
<Button>
|
|
1090
|
+
<ButtonText>Close</ButtonText>
|
|
1091
|
+
</Button>
|
|
1092
|
+
</DialogClose>
|
|
1093
|
+
</DialogFooter>
|
|
1094
|
+
</DialogContent>
|
|
1095
|
+
</Dialog>;
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
`DialogContent` supports frosted props:
|
|
1099
|
+
|
|
1100
|
+
|
|
1101
|
+
| Prop | Type | Default | Description |
|
|
1102
|
+
| ------------- | ------------------ | ---------------------- | ------------------------------------ |
|
|
1103
|
+
| className | `string` | — | |
|
|
1104
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1105
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1106
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1107
|
+
|
|
1108
|
+
Also accepts the props of `ViewProps`.
|
|
1109
|
+
|
|
1110
|
+
---
|
|
1111
|
+
|
|
1112
|
+
- **AlertDialog** (Overlays & Menus) — Confirmation dialog with Action/Cancel buttons
|
|
1113
|
+
|
|
1114
|
+
## AlertDialog
|
|
1115
|
+
|
|
1116
|
+
```tsx
|
|
1117
|
+
import {
|
|
1118
|
+
AlertDialog,
|
|
1119
|
+
AlertDialogTrigger,
|
|
1120
|
+
AlertDialogContent,
|
|
1121
|
+
AlertDialogHeader,
|
|
1122
|
+
AlertDialogTitle,
|
|
1123
|
+
AlertDialogDescription,
|
|
1124
|
+
AlertDialogFooter,
|
|
1125
|
+
AlertDialogAction,
|
|
1126
|
+
AlertDialogCancel,
|
|
1127
|
+
} from '@thewhileloop/whileui';
|
|
1128
|
+
|
|
1129
|
+
<AlertDialog>
|
|
1130
|
+
<AlertDialogTrigger asChild>
|
|
1131
|
+
<Button variant="destructive">
|
|
1132
|
+
<ButtonText>Delete</ButtonText>
|
|
1133
|
+
</Button>
|
|
1134
|
+
</AlertDialogTrigger>
|
|
1135
|
+
<AlertDialogContent>
|
|
1136
|
+
<AlertDialogHeader>
|
|
1137
|
+
<AlertDialogTitle>Are you sure?</AlertDialogTitle>
|
|
1138
|
+
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
|
|
1139
|
+
</AlertDialogHeader>
|
|
1140
|
+
<AlertDialogFooter>
|
|
1141
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
1142
|
+
<AlertDialogAction>Delete</AlertDialogAction>
|
|
1143
|
+
</AlertDialogFooter>
|
|
1144
|
+
</AlertDialogContent>
|
|
1145
|
+
</AlertDialog>;
|
|
1146
|
+
```
|
|
1147
|
+
|
|
1148
|
+
`AlertDialogContent` supports the same frosted props as `DialogContent`.
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
| Prop | Type | Default | Description |
|
|
1152
|
+
| ------------- | ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
1153
|
+
| className | `string` | — | |
|
|
1154
|
+
| 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`). |
|
|
1155
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1156
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1157
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1158
|
+
|
|
1159
|
+
Also accepts the props of `ViewProps`.
|
|
1160
|
+
|
|
1161
|
+
### React Native: stacked modals
|
|
1162
|
+
|
|
1163
|
+
Render a confirmation **inside** the overlay it belongs to. An `AlertDialog` or `Dialog`
|
|
1164
|
+
nested in a `Sheet` opens above it, and Android back or Escape closes the confirmation
|
|
1165
|
+
before the sheet. If `AlertDialog` must be a **sibling** of another fullscreen `Modal`, iOS
|
|
1166
|
+
often fails to show a second `Modal` on top; use **`presentation="overlay"`** inside that
|
|
1167
|
+
`Modal`, or **`presentation="native"`** for a system `Alert.alert` when themed UI is not
|
|
1168
|
+
required.
|
|
1169
|
+
|
|
1170
|
+
---
|
|
1171
|
+
|
|
1172
|
+
- **Popover** (Overlays & Menus) — Position-aware popover (requires PortalHost)
|
|
1173
|
+
|
|
1174
|
+
## Popover
|
|
1175
|
+
|
|
1176
|
+
```tsx
|
|
1177
|
+
import { Popover, PopoverTrigger, PopoverContent } from '@thewhileloop/whileui';
|
|
1178
|
+
|
|
1179
|
+
<Popover>
|
|
1180
|
+
<PopoverTrigger asChild>
|
|
1181
|
+
<Button>
|
|
1182
|
+
<ButtonText>Open</ButtonText>
|
|
1183
|
+
</Button>
|
|
1184
|
+
</PopoverTrigger>
|
|
1185
|
+
<PopoverContent>
|
|
1186
|
+
<Text>Popover content</Text>
|
|
1187
|
+
</PopoverContent>
|
|
1188
|
+
</Popover>;
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
`PopoverContent` supports frosted props:
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
| Prop | Type | Default | Description |
|
|
1195
|
+
| ------------- | ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------- |
|
|
1196
|
+
| className | `string` | — | |
|
|
1197
|
+
| portalHost | `string` | — | |
|
|
1198
|
+
| insets | `Partial<OverlayInsets>` | — | Collision padding. Defaults to safe area plus a margin so the panel never sits flush against an edge. |
|
|
1199
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1200
|
+
| blurIntensity | `number` | subtle preset | Override blur amount directly. |
|
|
1201
|
+
| blurTintToken | `FrostedTintToken` | `'popover'` | Tint token used for frosted mode. |
|
|
1202
|
+
|
|
1203
|
+
Also accepts the props of the underlying primitive.
|
|
1204
|
+
|
|
1205
|
+
---
|
|
1206
|
+
|
|
1207
|
+
- **Tooltip** (Overlays & Menus) — Position-aware tooltip (requires PortalHost)
|
|
1208
|
+
|
|
1209
|
+
### Tooltip
|
|
1210
|
+
|
|
1211
|
+
Position-aware tooltip (requires PortalHost)
|
|
1212
|
+
|
|
1213
|
+
```tsx
|
|
1214
|
+
import { Tooltip } from '@thewhileloop/whileui';
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
|
|
1218
|
+
| Prop | Type | Default | Description |
|
|
1219
|
+
| ----------------------- | ------------------------- | ------- | ----------- |
|
|
1220
|
+
| onOpenChange | `(open: boolean) => void` | — | |
|
|
1221
|
+
| delayDuration | `number` | — | |
|
|
1222
|
+
| skipDelayDuration | `number` | — | |
|
|
1223
|
+
| disableHoverableContent | `boolean` | — | |
|
|
1224
|
+
| children (required) | `React.ReactNode` | — | |
|
|
1225
|
+
|
|
1226
|
+
---
|
|
1227
|
+
|
|
1228
|
+
- **DropdownMenu** (Overlays & Menus) — DropdownMenuTrigger, Content, Item, Label, Separator
|
|
1229
|
+
|
|
1230
|
+
## DropdownMenu
|
|
1231
|
+
|
|
1232
|
+
```tsx
|
|
1233
|
+
import {
|
|
1234
|
+
DropdownMenu,
|
|
1235
|
+
DropdownMenuTrigger,
|
|
1236
|
+
DropdownMenuContent,
|
|
1237
|
+
DropdownMenuItem,
|
|
1238
|
+
DropdownMenuLabel,
|
|
1239
|
+
DropdownMenuSeparator,
|
|
1240
|
+
} from '@thewhileloop/whileui';
|
|
1241
|
+
|
|
1242
|
+
<DropdownMenu>
|
|
1243
|
+
<DropdownMenuTrigger asChild>
|
|
1244
|
+
<Button>
|
|
1245
|
+
<ButtonText>Menu</ButtonText>
|
|
1246
|
+
</Button>
|
|
1247
|
+
</DropdownMenuTrigger>
|
|
1248
|
+
<DropdownMenuContent>
|
|
1249
|
+
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
|
1250
|
+
<DropdownMenuSeparator />
|
|
1251
|
+
<DropdownMenuItem>
|
|
1252
|
+
<Text>Edit</Text>
|
|
1253
|
+
</DropdownMenuItem>
|
|
1254
|
+
<DropdownMenuItem>
|
|
1255
|
+
<Text>Delete</Text>
|
|
1256
|
+
</DropdownMenuItem>
|
|
1257
|
+
</DropdownMenuContent>
|
|
1258
|
+
</DropdownMenu>;
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
`DropdownMenuContent`, `ContextMenuContent`, and `MenubarContent` support the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1262
|
+
|
|
1263
|
+
---
|
|
1264
|
+
|
|
1265
|
+
- **ContextMenu** (Overlays & Menus) — Long-press context menu
|
|
1266
|
+
|
|
1267
|
+
## ContextMenu
|
|
1268
|
+
|
|
1269
|
+
`ContextMenuContent` supports the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1270
|
+
|
|
1271
|
+
---
|
|
1272
|
+
|
|
1273
|
+
- **HoverCard** (Overlays & Menus) — Position-aware hover card (requires PortalHost)
|
|
1274
|
+
|
|
1275
|
+
## HoverCard
|
|
1276
|
+
|
|
1277
|
+
`HoverCardContent` supports the same frosted props as `PopoverContent` (default blur preset: subtle).
|
|
1278
|
+
|
|
1279
|
+
---
|
|
1280
|
+
|
|
1281
|
+
- **Menubar** (Overlays & Menus) — Horizontal menu bar
|
|
1282
|
+
|
|
1283
|
+
## Menubar
|
|
1284
|
+
|
|
1285
|
+
```tsx
|
|
1286
|
+
import {
|
|
1287
|
+
Menubar,
|
|
1288
|
+
MenubarMenu,
|
|
1289
|
+
MenubarTrigger,
|
|
1290
|
+
MenubarContent,
|
|
1291
|
+
MenubarLabel,
|
|
1292
|
+
MenubarItem,
|
|
1293
|
+
MenubarSeparator,
|
|
1294
|
+
} from '@thewhileloop/whileui';
|
|
1295
|
+
|
|
1296
|
+
<Menubar>
|
|
1297
|
+
<MenubarMenu>
|
|
1298
|
+
<MenubarTrigger>
|
|
1299
|
+
<Text className="text-foreground text-sm">File</Text>
|
|
1300
|
+
</MenubarTrigger>
|
|
1301
|
+
<MenubarContent>
|
|
1302
|
+
<MenubarLabel>Project</MenubarLabel>
|
|
1303
|
+
<MenubarItem>
|
|
1304
|
+
<Text className="text-foreground">New Project</Text>
|
|
1305
|
+
</MenubarItem>
|
|
1306
|
+
<MenubarSeparator />
|
|
1307
|
+
<MenubarItem>
|
|
1308
|
+
<Text className="text-foreground">Export</Text>
|
|
1309
|
+
</MenubarItem>
|
|
1310
|
+
</MenubarContent>
|
|
1311
|
+
</MenubarMenu>
|
|
1312
|
+
</Menubar>;
|
|
1313
|
+
```
|
|
1314
|
+
|
|
1315
|
+
`MenubarLabel` is a non-interactive section heading. It takes a string or elements, and
|
|
1316
|
+
announces as a heading to assistive technology — as do `DropdownMenuLabel` and
|
|
1317
|
+
`ContextMenuLabel`.
|
|
1318
|
+
|
|
1319
|
+
`MenubarContent` supports the same frosted props as `PopoverContent` (default blur preset: medium).
|
|
1320
|
+
|
|
1321
|
+
---
|
|
1322
|
+
|
|
1323
|
+
# Blocks API
|
|
1324
|
+
|
|
1325
|
+
---
|
|
1326
|
+
|
|
1327
|
+
- **Toast** (Feedback) — ToastProvider, ToastContainer, useToast() hook
|
|
1328
|
+
|
|
1329
|
+
## Toast
|
|
1330
|
+
|
|
1331
|
+
```tsx
|
|
1332
|
+
import { ToastProvider, ToastContainer, useToast } from '@thewhileloop/whileui';
|
|
1333
|
+
|
|
1334
|
+
// Wrap app
|
|
1335
|
+
<ToastProvider>
|
|
1336
|
+
<App />
|
|
1337
|
+
<ToastContainer position="top" />
|
|
1338
|
+
</ToastProvider>;
|
|
1339
|
+
|
|
1340
|
+
// Use in component
|
|
1341
|
+
const { toast } = useToast();
|
|
1342
|
+
toast({ title: 'Success', description: 'Saved!', variant: 'success' });
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
| Toast Options | Type |
|
|
1346
|
+
| ------------- | ----------------------------------------- |
|
|
1347
|
+
| title | `string` |
|
|
1348
|
+
| description | `string` |
|
|
1349
|
+
| variant | `'default' \| 'success' \| 'destructive'` |
|
|
1350
|
+
| duration | `number` (ms) |
|
|
1351
|
+
|
|
1352
|
+
Each toast owns its duration from the moment it appears; adding or dismissing another
|
|
1353
|
+
toast does not restart timers already in progress.
|
|
1354
|
+
|
|
1355
|
+
Every variant sits on the theme's `popover` surface; `success` and `destructive` mark the
|
|
1356
|
+
toast with a status dot and tinted border rather than a solid fill. The stack is capped
|
|
1357
|
+
at `max-w-md` and centred, which never engages on a phone. A toast is announced politely
|
|
1358
|
+
to screen readers without taking focus, and its slide-in follows the motion policy.
|
|
1359
|
+
|
|
1360
|
+
---
|
|
1361
|
+
|
|
1362
|
+
- **SignInForm** (Auth, copy-paste template) — Email/password sign in with callbacks
|
|
1363
|
+
|
|
1364
|
+
## SignInForm
|
|
1365
|
+
|
|
1366
|
+
Copy `sign-in-form.tsx` and its sibling `auth-form-footer.tsx` from
|
|
1367
|
+
`apps/showcase/catalog/templates/auth/`, then:
|
|
1368
|
+
|
|
1369
|
+
```tsx
|
|
1370
|
+
import { SignInForm } from './templates/auth'; // or your path
|
|
1371
|
+
|
|
1372
|
+
<SignInForm
|
|
1373
|
+
onSubmit={({ email, password }) => signIn(email, password)}
|
|
1374
|
+
onForgotPassword={() => navigate('ForgotPassword')}
|
|
1375
|
+
onSignUp={() => navigate('SignUp')}
|
|
1376
|
+
onGooglePress={() => signInWithGoogle()}
|
|
1377
|
+
onApplePress={() => signInWithApple()}
|
|
1378
|
+
/>;
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
| Prop | Type | Description |
|
|
1382
|
+
| ---------------- | ------------------------------------- | ---------------------------- |
|
|
1383
|
+
| onSubmit | `(data: { email, password }) => void` | Called on sign-in submit |
|
|
1384
|
+
| onForgotPassword | `() => void` | Called when "Forgot?" tapped |
|
|
1385
|
+
| onSignUp | `() => void` | Called when "Sign Up" tapped |
|
|
1386
|
+
| onGooglePress | `() => void` | Called when Google tapped |
|
|
1387
|
+
| onApplePress | `() => void` | Called when Apple tapped |
|
|
1388
|
+
|
|
1389
|
+
---
|
|
1390
|
+
|
|
1391
|
+
- **SignUpForm** (Auth, copy-paste template) — Registration form with callbacks
|
|
1392
|
+
|
|
1393
|
+
## SignUpForm
|
|
1394
|
+
|
|
1395
|
+
Copy `sign-up-form.tsx` and its sibling `auth-form-footer.tsx` from
|
|
1396
|
+
`apps/showcase/catalog/templates/auth/`, then:
|
|
1397
|
+
|
|
1398
|
+
```tsx
|
|
1399
|
+
import { SignUpForm } from './templates/auth'; // or your path
|
|
1400
|
+
|
|
1401
|
+
<SignUpForm
|
|
1402
|
+
onSubmit={({ firstName, lastName, email, password }) =>
|
|
1403
|
+
signUp(firstName, lastName, email, password)
|
|
1404
|
+
}
|
|
1405
|
+
onSignIn={() => navigate('SignIn')}
|
|
1406
|
+
onGooglePress={() => signInWithGoogle()}
|
|
1407
|
+
onApplePress={() => signInWithApple()}
|
|
1408
|
+
/>;
|
|
1409
|
+
```
|
|
1410
|
+
|
|
1411
|
+
| Prop | Type | Description |
|
|
1412
|
+
| ------------- | ---------------------------------------------------------- | ---------------------------- |
|
|
1413
|
+
| onSubmit | `(data: { firstName, lastName, email, password }) => void` | Called on registration |
|
|
1414
|
+
| onSignIn | `() => void` | Called when "Sign In" tapped |
|
|
1415
|
+
| onGooglePress | `() => void` | Called when Google tapped |
|
|
1416
|
+
| onApplePress | `() => void` | Called when Apple tapped |
|
|
1417
|
+
|
|
1418
|
+
---
|
|
1419
|
+
|
|
1420
|
+
- **ForgotPasswordForm** (Auth, copy-paste template) — Password reset request
|
|
1421
|
+
|
|
1422
|
+
No API section yet; the summary above is the full description.
|
|
1423
|
+
|
|
1424
|
+
---
|
|
1425
|
+
|
|
1426
|
+
- **ResetPasswordForm** (Auth, copy-paste template) — Set new password
|
|
1427
|
+
|
|
1428
|
+
No API section yet; the summary above is the full description.
|
|
1429
|
+
|
|
1430
|
+
---
|
|
1431
|
+
|
|
1432
|
+
- **VerifyEmailForm** (Auth, copy-paste template) — Email verification code input
|
|
1433
|
+
|
|
1434
|
+
No API section yet; the summary above is the full description.
|
|
1435
|
+
|
|
1436
|
+
---
|
|
1437
|
+
|
|
1438
|
+
- **SocialConnections** (Auth, copy-paste template) — OAuth provider buttons
|
|
1439
|
+
|
|
1440
|
+
No API section yet; the summary above is the full description.
|
|
1441
|
+
|
|
1442
|
+
---
|
|
1443
|
+
|
|
1444
|
+
- **UserMenu** (Auth, copy-paste template) — Profile dropdown for auth flows
|
|
1445
|
+
|
|
1446
|
+
No API section yet; the summary above is the full description.
|
|
1447
|
+
|
|
1448
|
+
---
|
|
1449
|
+
|
|
1450
|
+
- **AppShell** (Navigation) — Layout shell with header/footer/bottomNav slots
|
|
1451
|
+
|
|
1452
|
+
## AppShell
|
|
1453
|
+
|
|
1454
|
+
Layout shell for full-screen pages. Keep shell chrome mounted and swap only content via `loading` + `skeleton`.
|
|
1455
|
+
|
|
1456
|
+
```tsx
|
|
1457
|
+
import { AppShell, PageSkeleton } from '@thewhileloop/whileui';
|
|
1458
|
+
|
|
1459
|
+
<AppShell
|
|
1460
|
+
header={<Header title="Settings" />}
|
|
1461
|
+
bottomNav={<BottomNav items={[...]} activeKey="settings" onSelect={setTab} />}
|
|
1462
|
+
loading={loading}
|
|
1463
|
+
skeleton={<PageSkeleton variant="settings" headerPlaceholder />}
|
|
1464
|
+
>
|
|
1465
|
+
<ScrollView className="flex-1 p-4">{/* Content */}</ScrollView>
|
|
1466
|
+
</AppShell>;
|
|
1467
|
+
```
|
|
1468
|
+
|
|
1469
|
+
|
|
1470
|
+
| Prop | Type | Default | Description |
|
|
1471
|
+
| --------- | ----------------- | ------- | --------------------------------------------------------- |
|
|
1472
|
+
| header | `React.ReactNode` | — | Header slot. |
|
|
1473
|
+
| footer | `React.ReactNode` | — | Footer slot. |
|
|
1474
|
+
| bottomNav | `React.ReactNode` | — | Bottom navigation slot. |
|
|
1475
|
+
| safeArea | `boolean` | `true` | Wrap in SafeAreaView. |
|
|
1476
|
+
| loading | `boolean` | `false` | Keep shell mounted while swapping content with skeleton. |
|
|
1477
|
+
| skeleton | `React.ReactNode` | — | Optional custom loading placeholder for the content area. |
|
|
1478
|
+
| className | `string` | — | |
|
|
1479
|
+
|
|
1480
|
+
Also accepts the props of `ViewProps`.
|
|
1481
|
+
|
|
1482
|
+
---
|
|
1483
|
+
|
|
1484
|
+
- **Header** (Navigation) — Top app bar with back/actions
|
|
1485
|
+
|
|
1486
|
+
## Header
|
|
1487
|
+
|
|
1488
|
+
```tsx
|
|
1489
|
+
import { Header, HeaderBackButton } from '@thewhileloop/whileui';
|
|
1490
|
+
|
|
1491
|
+
<Header
|
|
1492
|
+
title="Settings"
|
|
1493
|
+
subtitle="Manage preferences"
|
|
1494
|
+
leftAction={<HeaderBackButton onPress={() => {}} />}
|
|
1495
|
+
rightActions={[
|
|
1496
|
+
{ key: 'search', icon: <Icon />, onPress: () => {}, accessibilityLabel: 'Search' },
|
|
1497
|
+
]}
|
|
1498
|
+
/>;
|
|
1499
|
+
```
|
|
1500
|
+
|
|
1501
|
+
---
|
|
1502
|
+
|
|
1503
|
+
- **BottomNav** (Navigation) — Tab-style bottom navigation bar
|
|
1504
|
+
|
|
1505
|
+
## BottomNav
|
|
1506
|
+
|
|
1507
|
+
```tsx
|
|
1508
|
+
import { BottomNav } from '@thewhileloop/whileui';
|
|
1509
|
+
|
|
1510
|
+
<BottomNav
|
|
1511
|
+
items={[
|
|
1512
|
+
{ key: 'home', label: 'Home', icon: <Icon /> },
|
|
1513
|
+
{ key: 'profile', label: 'Profile', icon: <Icon />, badge: 3 },
|
|
1514
|
+
]}
|
|
1515
|
+
activeKey="home"
|
|
1516
|
+
onSelect={(key) => {}}
|
|
1517
|
+
/>;
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
---
|
|
1521
|
+
|
|
1522
|
+
- **FloatingBottomNav** (Navigation) — Elevated bottom nav with safe area support
|
|
1523
|
+
|
|
1524
|
+
### FloatingBottomNav
|
|
1525
|
+
|
|
1526
|
+
Elevated bottom nav with safe area support
|
|
1527
|
+
|
|
1528
|
+
```tsx
|
|
1529
|
+
import { FloatingBottomNav } from '@thewhileloop/whileui';
|
|
1530
|
+
```
|
|
1531
|
+
|
|
1532
|
+
|
|
1533
|
+
| Prop | Type | Default | Description |
|
|
1534
|
+
| -------------------- | ------------------------- | ------- | ----------- |
|
|
1535
|
+
| items (required) | `FloatingBottomNavItem[]` | — | |
|
|
1536
|
+
| activeKey (required) | `string` | — | |
|
|
1537
|
+
| onSelect (required) | `(key: string) => void` | — | |
|
|
1538
|
+
| className | `string` | — | |
|
|
1539
|
+
|
|
1540
|
+
Also accepts the props of `ViewProps`.
|
|
1541
|
+
|
|
1542
|
+
---
|
|
1543
|
+
|
|
1544
|
+
- **TabBar** (Navigation) — Top tab bar with indicator
|
|
1545
|
+
|
|
1546
|
+
### TabBar
|
|
1547
|
+
|
|
1548
|
+
Top tab bar with indicator
|
|
1549
|
+
|
|
1550
|
+
```tsx
|
|
1551
|
+
import { TabBar } from '@thewhileloop/whileui';
|
|
1552
|
+
```
|
|
1553
|
+
|
|
1554
|
+
|
|
1555
|
+
| Prop | Type | Default | Description |
|
|
1556
|
+
| -------------------- | ------------------------------------- | ----------- | ----------- |
|
|
1557
|
+
| items (required) | `TabBarItem[]` | — | |
|
|
1558
|
+
| activeKey (required) | `string` | — | |
|
|
1559
|
+
| onSelect (required) | `(key: string) => void` | — | |
|
|
1560
|
+
| variant | `'default' \| 'pills' \| 'underline'` | `'default'` | |
|
|
1561
|
+
| scrollable | `boolean` | `false` | |
|
|
1562
|
+
| className | `string` | — | |
|
|
1563
|
+
|
|
1564
|
+
Also accepts the props of `ViewProps`.
|
|
1565
|
+
|
|
1566
|
+
---
|
|
1567
|
+
|
|
1568
|
+
- **DrawerMenu** (Navigation) — Drawer with sections and items
|
|
1569
|
+
|
|
1570
|
+
## DrawerMenu
|
|
1571
|
+
|
|
1572
|
+
```tsx
|
|
1573
|
+
import { DrawerMenu } from '@thewhileloop/whileui';
|
|
1574
|
+
|
|
1575
|
+
<DrawerMenu
|
|
1576
|
+
visible={open}
|
|
1577
|
+
onClose={() => setOpen(false)}
|
|
1578
|
+
sections={[
|
|
1579
|
+
{
|
|
1580
|
+
title: 'Menu',
|
|
1581
|
+
items: [
|
|
1582
|
+
{ key: 'home', label: 'Home', icon: <Icon /> },
|
|
1583
|
+
{ key: 'settings', label: 'Settings', icon: <Icon /> },
|
|
1584
|
+
],
|
|
1585
|
+
},
|
|
1586
|
+
]}
|
|
1587
|
+
activeKey="home"
|
|
1588
|
+
onSelect={(key) => {}}
|
|
1589
|
+
header={<View>...</View>}
|
|
1590
|
+
footer={<Text>v1.0</Text>}
|
|
1591
|
+
/>;
|
|
1592
|
+
```
|
|
1593
|
+
|
|
1594
|
+
|
|
1595
|
+
| Prop | Type | Default | Description |
|
|
1596
|
+
| ------------------- | ----------------------- | ---------------------- | ----------------------------------------------------------- |
|
|
1597
|
+
| visible (required) | `boolean` | — | |
|
|
1598
|
+
| onClose (required) | `() => void` | — | |
|
|
1599
|
+
| sections (required) | `DrawerMenuSection[]` | — | |
|
|
1600
|
+
| activeKey | `string` | — | |
|
|
1601
|
+
| onSelect | `(key: string) => void` | — | |
|
|
1602
|
+
| header | `React.ReactNode` | — | |
|
|
1603
|
+
| footer | `React.ReactNode` | — | |
|
|
1604
|
+
| maxWidth | `number` | — | Max width in px. On web, defaults to 360 when not provided. |
|
|
1605
|
+
| className | `string` | — | |
|
|
1606
|
+
| style | `StyleProp<ViewStyle>` | — | |
|
|
1607
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1608
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1609
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
Closes from the backdrop, an item, Android back or Escape on web. It is hosted in a modal
|
|
1613
|
+
on every platform, so keyboard focus stays inside it while it is open.
|
|
1614
|
+
|
|
1615
|
+
---
|
|
1616
|
+
|
|
1617
|
+
- **ActionBar** (Layout) — Sticky bottom action row with safe-area padding
|
|
1618
|
+
|
|
1619
|
+
## ActionBar
|
|
1620
|
+
|
|
1621
|
+
```tsx
|
|
1622
|
+
import { ActionBar, Button, ButtonText } from '@thewhileloop/whileui';
|
|
1623
|
+
|
|
1624
|
+
<ActionBar>
|
|
1625
|
+
<Button variant="outline" className="flex-1">
|
|
1626
|
+
<ButtonText>Cancel</ButtonText>
|
|
1627
|
+
</Button>
|
|
1628
|
+
<Button className="flex-1">
|
|
1629
|
+
<ButtonText>Save</ButtonText>
|
|
1630
|
+
</Button>
|
|
1631
|
+
</ActionBar>;
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
---
|
|
1635
|
+
|
|
1636
|
+
- **ConfirmActionSheet** (Layout) — Reusable destructive confirmation sheet
|
|
1637
|
+
|
|
1638
|
+
## ConfirmActionSheet
|
|
1639
|
+
|
|
1640
|
+
```tsx
|
|
1641
|
+
import { ConfirmActionSheet } from '@thewhileloop/whileui';
|
|
1642
|
+
|
|
1643
|
+
<ConfirmActionSheet
|
|
1644
|
+
open={open}
|
|
1645
|
+
onOpenChange={setOpen}
|
|
1646
|
+
title="Delete project?"
|
|
1647
|
+
description="This action cannot be undone."
|
|
1648
|
+
confirmLabel="Delete"
|
|
1649
|
+
destructive
|
|
1650
|
+
onConfirm={() => deleteProject()}
|
|
1651
|
+
/>;
|
|
1652
|
+
```
|
|
1653
|
+
|
|
1654
|
+
|
|
1655
|
+
| Prop | Type | Default | Description |
|
|
1656
|
+
| ----------------------- | ---------------------------- | ---------------------- | ------------------------------------ |
|
|
1657
|
+
| open (required) | `boolean` | — | |
|
|
1658
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | |
|
|
1659
|
+
| title (required) | `string` | — | |
|
|
1660
|
+
| description | `string` | — | |
|
|
1661
|
+
| actions | `ConfirmActionSheetAction[]` | — | |
|
|
1662
|
+
| confirmLabel | `string` | `'Confirm'` | |
|
|
1663
|
+
| cancelLabel | `string` | `'Cancel'` | |
|
|
1664
|
+
| onConfirm | `() => void` | — | |
|
|
1665
|
+
| onCancel | `() => void` | — | |
|
|
1666
|
+
| destructive | `boolean` | `true` | |
|
|
1667
|
+
| className | `string` | — | |
|
|
1668
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1669
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1670
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1671
|
+
|
|
1672
|
+
Also accepts the props of `ViewProps`.
|
|
1673
|
+
|
|
1674
|
+
---
|
|
1675
|
+
|
|
1676
|
+
- **Sheet** (Layout) — Bottom sheet modal with header/content/footer slots
|
|
1677
|
+
|
|
1678
|
+
## Sheet
|
|
1679
|
+
|
|
1680
|
+
Bottom sheet modal with slide animation. Slots: `SheetHeader`, `SheetContent`, `SheetFooter`, `SheetClose`.
|
|
1681
|
+
|
|
1682
|
+
```tsx
|
|
1683
|
+
import {
|
|
1684
|
+
Sheet,
|
|
1685
|
+
SheetHeader,
|
|
1686
|
+
SheetContent,
|
|
1687
|
+
SheetFooter,
|
|
1688
|
+
SheetClose,
|
|
1689
|
+
Button,
|
|
1690
|
+
ButtonText,
|
|
1691
|
+
} from '@thewhileloop/whileui';
|
|
1692
|
+
|
|
1693
|
+
<Sheet open={open} onOpenChange={setOpen} maxHeight="half">
|
|
1694
|
+
<SheetHeader title="Settings" description="Adjust preferences" />
|
|
1695
|
+
<SheetContent>{/* Scrollable body */}</SheetContent>
|
|
1696
|
+
<SheetFooter>
|
|
1697
|
+
<SheetClose asChild>
|
|
1698
|
+
<Button>
|
|
1699
|
+
<ButtonText>Save</ButtonText>
|
|
1700
|
+
</Button>
|
|
1701
|
+
</SheetClose>
|
|
1702
|
+
</SheetFooter>
|
|
1703
|
+
</Sheet>;
|
|
1704
|
+
```
|
|
1705
|
+
|
|
1706
|
+
|
|
1707
|
+
| Prop | Type | Default | Description |
|
|
1708
|
+
| ----------------------- | ---------------------------- | ---------------------- | ----------------------------------------------------------- |
|
|
1709
|
+
| open (required) | `boolean` | — | |
|
|
1710
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | |
|
|
1711
|
+
| children (required) | `React.ReactNode` | — | |
|
|
1712
|
+
| maxHeight | `'half' \| 'full' \| number` | `'full'` | Max height: 'half' \| 'full' \| number (px). Default 'full' |
|
|
1713
|
+
| maxWidth | `number` | — | Web: max width. Default ~360 for mobile-first. |
|
|
1714
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
1715
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
1716
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
1717
|
+
|
|
1718
|
+
|
|
1719
|
+
`SheetContent` is a `ScrollView` and accepts its props. It enables nested scrolling and
|
|
1720
|
+
keeps taps on inputs while the keyboard is open (`keyboardShouldPersistTaps="handled"`).
|
|
1721
|
+
Your `contentContainerStyle` is merged after the default padding. Tapping the backdrop
|
|
1722
|
+
closes the sheet; a drag across the backdrop does not, so a missed scroll never dismisses
|
|
1723
|
+
it. There is no drag-to-dismiss: close with the backdrop, Android back, Escape on web or
|
|
1724
|
+
`SheetClose`. A Select, Popover or confirmation Dialog placed inside the sheet opens above
|
|
1725
|
+
it, and back or Escape closes that inner layer before the sheet.
|
|
1726
|
+
|
|
1727
|
+
---
|
|
1728
|
+
|
|
1729
|
+
- **FormModalScreen** (Layout) — Modal scaffold for forms with loading states
|
|
1730
|
+
|
|
1731
|
+
### FormModalScreen
|
|
1732
|
+
|
|
1733
|
+
Modal scaffold for forms with loading states
|
|
1734
|
+
|
|
1735
|
+
```tsx
|
|
1736
|
+
import { FormModalScreen } from '@thewhileloop/whileui';
|
|
1737
|
+
```
|
|
1738
|
+
|
|
1739
|
+
|
|
1740
|
+
| Prop | Type | Default | Description |
|
|
1741
|
+
| ------------------- | ----------------- | ------------- | ----------------------------------------------------------------------------------------- |
|
|
1742
|
+
| className | `string` | — | |
|
|
1743
|
+
| title (required) | `string` | — | |
|
|
1744
|
+
| subtitle | `string` | — | |
|
|
1745
|
+
| leftSlot | `React.ReactNode` | — | |
|
|
1746
|
+
| rightSlot | `React.ReactNode` | — | |
|
|
1747
|
+
| onClose | `() => void` | — | |
|
|
1748
|
+
| loading | `boolean` | `false` | |
|
|
1749
|
+
| saving | `boolean` | `false` | |
|
|
1750
|
+
| savingText | `string` | `'Saving...'` | |
|
|
1751
|
+
| loadingSpinnerColor | `string` | — | Hex color for spinner in loading state. ActivityIndicator requires hex. |
|
|
1752
|
+
| savingSpinnerColor | `string` | — | Hex color for spinner in saving banner. Defaults to platform (Android blue) when not set. |
|
|
1753
|
+
| scrollEnabled | `boolean` | `true` | |
|
|
1754
|
+
| children (required) | `React.ReactNode` | — | |
|
|
1755
|
+
|
|
1756
|
+
Also accepts the props of `ViewProps`.
|
|
1757
|
+
|
|
1758
|
+
---
|
|
1759
|
+
|
|
1760
|
+
- **ContentSkeleton** (Layout) — Page/content placeholder with variants (list, card, generic)
|
|
1761
|
+
|
|
1762
|
+
## ContentSkeleton
|
|
1763
|
+
|
|
1764
|
+
Page/content placeholder with layout presets. Use while loading data instead of a spinner when you want to preview the layout.
|
|
1765
|
+
|
|
1766
|
+
```tsx
|
|
1767
|
+
import { ContentSkeleton } from '@thewhileloop/whileui';
|
|
1768
|
+
|
|
1769
|
+
<ContentSkeleton variant="list" rows={4} />
|
|
1770
|
+
<ContentSkeleton variant="card" />
|
|
1771
|
+
<ContentSkeleton variant="generic" />
|
|
1772
|
+
```
|
|
1773
|
+
|
|
1774
|
+
|
|
1775
|
+
| Prop | Type | Default | Description |
|
|
1776
|
+
| --------- | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |
|
|
1777
|
+
| variant | `'list' \| 'card' \| 'generic'` | `'list'` | Layout preset. `list` = avatar + lines rows, `card` = card with image/body, `generic` = simple page lines. |
|
|
1778
|
+
| rows | `number` | `4` | Number of list rows (list variant only). Default 4. |
|
|
1779
|
+
| className | `string` | — | |
|
|
1780
|
+
|
|
1781
|
+
Also accepts the props of `ViewProps`.
|
|
1782
|
+
|
|
1783
|
+
---
|
|
1784
|
+
|
|
1785
|
+
- **PageSkeleton** (Layout) — Variant-based page layouts (dashboard, list, settings, card, generic)
|
|
1786
|
+
|
|
1787
|
+
## PageSkeleton
|
|
1788
|
+
|
|
1789
|
+
Variant-based page layouts for loading states. Replaces app-specific skeletons with presets for dashboard, list, settings, card, and generic pages.
|
|
1790
|
+
|
|
1791
|
+
```tsx
|
|
1792
|
+
import { PageSkeleton } from '@thewhileloop/whileui';
|
|
1793
|
+
|
|
1794
|
+
<PageSkeleton variant="dashboard" />
|
|
1795
|
+
<PageSkeleton variant="list" count={5} />
|
|
1796
|
+
<PageSkeleton variant="settings" count={6} />
|
|
1797
|
+
<PageSkeleton variant="card" />
|
|
1798
|
+
<PageSkeleton variant="generic" />
|
|
1799
|
+
<PageSkeleton variant="dashboard" headerPlaceholder />
|
|
1800
|
+
<PageSkeleton variant="list" header={<Header title="Loading..." />} />
|
|
1801
|
+
<PageSkeleton variant="list" padding="none" className="flex-1" />
|
|
1802
|
+
```
|
|
1803
|
+
|
|
1804
|
+
|
|
1805
|
+
| Prop | Type | Default | Description |
|
|
1806
|
+
| ------------------ | ------------------------------------------ | ----------- | ------------------------------------------------------------------- |
|
|
1807
|
+
| variant (required) | `PageSkeletonVariant` | — | Layout preset. |
|
|
1808
|
+
| count | `number` | `3` | Rows/items for list (default 3) or settings (default 4) variant. |
|
|
1809
|
+
| padding | `PageSkeletonPadding` | `'default'` | Container padding. |
|
|
1810
|
+
| header | `ReactNode` | — | Optional header slot rendered above skeleton content. |
|
|
1811
|
+
| headerPlaceholder | `boolean \| PageSkeletonHeaderPlaceholder` | `false` | Optional placeholder header (when no real header is available yet). |
|
|
1812
|
+
| className | `string` | — | |
|
|
1813
|
+
|
|
1814
|
+
Also accepts the props of `ViewProps`.
|
|
1815
|
+
|
|
1816
|
+
---
|
|
1817
|
+
|
|
1818
|
+
- **ScreenSkeleton** (Layout) — Header slot/placeholder + PageSkeleton content in one block
|
|
1819
|
+
|
|
1820
|
+
## ScreenSkeleton
|
|
1821
|
+
|
|
1822
|
+
Convenience block for loading screens that need both header and content continuity.
|
|
1823
|
+
|
|
1824
|
+
```tsx
|
|
1825
|
+
import { ScreenSkeleton } from '@thewhileloop/whileui';
|
|
1826
|
+
|
|
1827
|
+
<ScreenSkeleton variant="dashboard" headerPlaceholder />
|
|
1828
|
+
<ScreenSkeleton variant="list" count={5} header={<Header title="Projects" />} />
|
|
1829
|
+
```
|
|
1830
|
+
|
|
1831
|
+
|
|
1832
|
+
| Prop | Type | Default | Description |
|
|
1833
|
+
| ----------------- | ------------------------------------------ | ----------- | -------------------------------------- |
|
|
1834
|
+
| variant | `PageSkeletonVariant` | `'generic'` | Skeleton content preset. |
|
|
1835
|
+
| count | `number` | — | Rows/items for list/settings variants. |
|
|
1836
|
+
| padding | `PageSkeletonPadding` | `'default'` | Inner content padding. |
|
|
1837
|
+
| header | `ReactNode` | — | Real header slot. |
|
|
1838
|
+
| headerPlaceholder | `boolean \| PageSkeletonHeaderPlaceholder` | `'default'` | Placeholder header. |
|
|
1839
|
+
| className | `string` | — | |
|
|
1840
|
+
|
|
1841
|
+
Also accepts the props of `ViewProps`.
|
|
1842
|
+
|
|
1843
|
+
---
|
|
1844
|
+
|
|
1845
|
+
- **ErrorBoundary** (Layout) — React ErrorBoundary that renders ErrorState by default
|
|
1846
|
+
|
|
1847
|
+
## ErrorBoundary
|
|
1848
|
+
|
|
1849
|
+
React ErrorBoundary that catches render errors and renders ErrorState by default.
|
|
1850
|
+
|
|
1851
|
+
```tsx
|
|
1852
|
+
import { ErrorBoundary } from '@thewhileloop/whileui';
|
|
1853
|
+
|
|
1854
|
+
<ErrorBoundary onError={(err) => console.error(err)}>
|
|
1855
|
+
<App />
|
|
1856
|
+
</ErrorBoundary>;
|
|
1857
|
+
```
|
|
1858
|
+
|
|
1859
|
+
|
|
1860
|
+
| Prop | Type | Default | Description |
|
|
1861
|
+
| ------------------- | --------------------------------------------------------------- | ------- | -------------------------------------------------- |
|
|
1862
|
+
| children (required) | `ReactNode` | — | |
|
|
1863
|
+
| fallback | `ReactNode \| ((error: Error, reset: () => void) => ReactNode)` | — | Custom fallback. If not provided, uses ErrorState. |
|
|
1864
|
+
| onError | `(error: Error, errorInfo: ErrorInfo) => void` | — | Called when error is caught. |
|
|
1865
|
+
|
|
1866
|
+
---
|
|
1867
|
+
|
|
1868
|
+
- **EmptyState** (Layout) — Empty content placeholder
|
|
1869
|
+
|
|
1870
|
+
## EmptyState
|
|
1871
|
+
|
|
1872
|
+
```tsx
|
|
1873
|
+
import { EmptyState } from '@thewhileloop/whileui';
|
|
1874
|
+
|
|
1875
|
+
<EmptyState
|
|
1876
|
+
icon={<Icon />}
|
|
1877
|
+
title="No items"
|
|
1878
|
+
description="Add your first item."
|
|
1879
|
+
action={{ label: 'Add Item', onPress: () => {} }}
|
|
1880
|
+
/>;
|
|
1881
|
+
```
|
|
1882
|
+
|
|
1883
|
+
---
|
|
1884
|
+
|
|
1885
|
+
- **ErrorState** (Layout) — Error display with retry
|
|
1886
|
+
|
|
1887
|
+
### ErrorState
|
|
1888
|
+
|
|
1889
|
+
Error display with retry
|
|
1890
|
+
|
|
1891
|
+
```tsx
|
|
1892
|
+
import { ErrorState } from '@thewhileloop/whileui';
|
|
1893
|
+
```
|
|
1894
|
+
|
|
1895
|
+
|
|
1896
|
+
| Prop | Type | Default | Description |
|
|
1897
|
+
| ----------- | ----------------- | ------------------------ | ----------- |
|
|
1898
|
+
| icon | `React.ReactNode` | — | |
|
|
1899
|
+
| title | `string` | `'Something went wrong'` | |
|
|
1900
|
+
| description | `string` | — | |
|
|
1901
|
+
| error | `Error \| string` | — | |
|
|
1902
|
+
| onRetry | `() => void` | — | |
|
|
1903
|
+
| retryLabel | `string` | `'Try again'` | |
|
|
1904
|
+
| className | `string` | — | |
|
|
1905
|
+
|
|
1906
|
+
Also accepts the props of `ViewProps`.
|
|
1907
|
+
|
|
1908
|
+
---
|
|
1909
|
+
|
|
1910
|
+
- **LoadingState** (Layout) — In-flow loading placeholder for a section still fetching
|
|
1911
|
+
|
|
1912
|
+
## LoadingState
|
|
1913
|
+
|
|
1914
|
+
```tsx
|
|
1915
|
+
import { LoadingState } from '@thewhileloop/whileui';
|
|
1916
|
+
|
|
1917
|
+
<LoadingState label="Loading activity" />;
|
|
1918
|
+
```
|
|
1919
|
+
|
|
1920
|
+
The in-flow counterpart of `EmptyState` and `ErrorState` for a section that is still
|
|
1921
|
+
fetching. The spinner is named for assistive technology by `label` (default "Loading").
|
|
1922
|
+
|
|
1923
|
+
|
|
1924
|
+
| Prop | Type | Default | Description |
|
|
1925
|
+
| --------- | --------------------------- | ----------- | ------------------------------------------------------------------------------ |
|
|
1926
|
+
| label | `string` | — | Read by assistive technology as the spinner's name; shown under it when given. |
|
|
1927
|
+
| size | `'sm' \| 'default' \| 'lg'` | `'default'` | |
|
|
1928
|
+
| className | `string` | — | |
|
|
1929
|
+
|
|
1930
|
+
Also accepts the props of `ViewProps`.
|
|
1931
|
+
|
|
1932
|
+
---
|
|
1933
|
+
|
|
1934
|
+
- **LoadingScreen** (Layout) — Full-screen loading indicator
|
|
1935
|
+
|
|
1936
|
+
### LoadingScreen
|
|
1937
|
+
|
|
1938
|
+
Full-screen loading indicator
|
|
1939
|
+
|
|
1940
|
+
```tsx
|
|
1941
|
+
import { LoadingScreen } from '@thewhileloop/whileui';
|
|
1942
|
+
```
|
|
1943
|
+
|
|
1944
|
+
|
|
1945
|
+
| Prop | Type | Default | Description |
|
|
1946
|
+
| ------------ | ---------------------- | ------- | ------------------------------------------------------ |
|
|
1947
|
+
| message | `string` | — | |
|
|
1948
|
+
| size | `'sm' \| 'md' \| 'lg'` | `'md'` | |
|
|
1949
|
+
| spinnerColor | `string` | — | Hex color for spinner. ActivityIndicator requires hex. |
|
|
1950
|
+
| className | `string` | — | |
|
|
1951
|
+
|
|
1952
|
+
Also accepts the props of `ViewProps`.
|
|
1953
|
+
|
|
1954
|
+
---
|
|
1955
|
+
|
|
1956
|
+
- **PullToRefreshScrollView** (Layout) — Themed ScrollView with RefreshControl (useThemeTokens for colors)
|
|
1957
|
+
|
|
1958
|
+
## PullToRefreshScrollView
|
|
1959
|
+
|
|
1960
|
+
Themed ScrollView with RefreshControl. Uses `useThemeTokens` for spinner color.
|
|
1961
|
+
|
|
1962
|
+
```tsx
|
|
1963
|
+
import { PullToRefreshScrollView } from '@thewhileloop/whileui';
|
|
1964
|
+
|
|
1965
|
+
<PullToRefreshScrollView refreshing={refreshing} onRefresh={fetchData} refreshColor="#22c55e">
|
|
1966
|
+
{content}
|
|
1967
|
+
</PullToRefreshScrollView>;
|
|
1968
|
+
```
|
|
1969
|
+
|
|
1970
|
+
|
|
1971
|
+
| Prop | Type | Default | Description |
|
|
1972
|
+
| --------------------- | ----------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1973
|
+
| refreshing (required) | `boolean` | — | Whether refresh is in progress. |
|
|
1974
|
+
| 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. |
|
|
1975
|
+
| refreshColor | `string` | — | Override tint/colors. Defaults to theme primary. |
|
|
1976
|
+
| className | `string` | — | |
|
|
1977
|
+
|
|
1978
|
+
Also accepts the props of `ScrollViewProps`.
|
|
1979
|
+
|
|
1980
|
+
The scroll view owns the refresh gesture; don't pass `refreshControl`. It bounces even
|
|
1981
|
+
when content is shorter than the screen, so a short list can still be pulled. Screen
|
|
1982
|
+
reader users get the same refresh as a **Refresh** accessibility action, since they
|
|
1983
|
+
cannot pull. It is ignored while `refreshing` is true, and any `accessibilityActions` you
|
|
1984
|
+
pass are kept.
|
|
1985
|
+
|
|
1986
|
+
---
|
|
1987
|
+
|
|
1988
|
+
- **KeyboardAwareScreen** (Layout) — One screen-level keyboard policy for bottom-docked controls
|
|
1989
|
+
|
|
1990
|
+
## KeyboardAwareScreen and DockedInput
|
|
1991
|
+
|
|
1992
|
+
Use one keyboard-aware root for the screen, then place the composer in a bottom dock. Feed the dock height into the scroll content inset so the last item remains visible.
|
|
1993
|
+
|
|
1994
|
+
```tsx
|
|
1995
|
+
import { DockedInput, KeyboardAwareScreen, SmartInput } from '@thewhileloop/whileui';
|
|
1996
|
+
|
|
1997
|
+
<KeyboardAwareScreen>
|
|
1998
|
+
<ScrollView contentContainerStyle={{ paddingBottom: inputHeight }} />
|
|
1999
|
+
<DockedInput onHeightChange={setInputHeight}>
|
|
2000
|
+
<SmartInput variant="card" safeArea={false} />
|
|
2001
|
+
</DockedInput>
|
|
2002
|
+
</KeyboardAwareScreen>;
|
|
2003
|
+
```
|
|
2004
|
+
|
|
2005
|
+
---
|
|
2006
|
+
|
|
2007
|
+
- **DockedInput** (Layout) — Safe-area-aware bottom dock that reports its complete height
|
|
2008
|
+
|
|
2009
|
+
Documented under KeyboardAwareScreen.
|
|
2010
|
+
|
|
2011
|
+
---
|
|
2012
|
+
|
|
2013
|
+
- **SmartInput** (Layout) — Compose input with slots, variants, and optional rotating examples
|
|
2014
|
+
|
|
2015
|
+
## SmartInput
|
|
2016
|
+
|
|
2017
|
+
Compose input for use inline or inside `DockedInput`. `variant`: "bar" or "card". Slots: `leftSlot`, `centerSlot` (intent selector), `rightSlot`. `submitBehavior`: "newline" (default) or "submit"/"blurAndSubmit". `placeholderExamples` animates rotating prompts and respects the platform reduced-motion setting. Forwards ref to `TextInput`.
|
|
2018
|
+
|
|
2019
|
+
```tsx
|
|
2020
|
+
import { SmartInput, Button, ButtonText } from '@thewhileloop/whileui';
|
|
2021
|
+
|
|
2022
|
+
<SmartInput
|
|
2023
|
+
value={message}
|
|
2024
|
+
onChangeText={setMessage}
|
|
2025
|
+
placeholderExamples={['Log breakfast', 'Add a 30 minute walk', 'Set weight to 72 kg']}
|
|
2026
|
+
leftSlot={
|
|
2027
|
+
<Button variant="ghost" size="icon" accessibilityLabel="Add">
|
|
2028
|
+
<Icon name="add" />
|
|
2029
|
+
</Button>
|
|
2030
|
+
}
|
|
2031
|
+
rightSlot={
|
|
2032
|
+
<Button size="icon" onPress={handleSend}>
|
|
2033
|
+
<ButtonText>Send</ButtonText>
|
|
2034
|
+
</Button>
|
|
2035
|
+
}
|
|
2036
|
+
/>;
|
|
2037
|
+
```
|
|
2038
|
+
|
|
2039
|
+
---
|
|
2040
|
+
|
|
2041
|
+
- **SplashScreen** (Layout) — Branded splash (fade/scale/slide variants)
|
|
2042
|
+
|
|
2043
|
+
## SplashScreen
|
|
2044
|
+
|
|
2045
|
+
```tsx
|
|
2046
|
+
import { SplashScreen } from '@thewhileloop/whileui';
|
|
2047
|
+
|
|
2048
|
+
<SplashScreen
|
|
2049
|
+
logo={<MyLogo />}
|
|
2050
|
+
appName="MyApp"
|
|
2051
|
+
tagline="Your tagline"
|
|
2052
|
+
variant="scale"
|
|
2053
|
+
duration={1500}
|
|
2054
|
+
showLoading
|
|
2055
|
+
onAnimationComplete={() => {}}
|
|
2056
|
+
/>;
|
|
2057
|
+
```
|
|
2058
|
+
|
|
2059
|
+
|
|
2060
|
+
| Prop | Type | Default | Description |
|
|
2061
|
+
| ------------------- | ------------------------------ | --------- | ----------------------------------------------- |
|
|
2062
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2063
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2064
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2065
|
+
| variant | `'fade' \| 'scale' \| 'slide'` | `'scale'` | Animation variant |
|
|
2066
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2067
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2068
|
+
| showLoading | `boolean` | `false` | Whether to show loading indicator |
|
|
2069
|
+
| loadingText | `string` | — | Loading text |
|
|
2070
|
+
| className | `string` | — | |
|
|
2071
|
+
|
|
2072
|
+
Also accepts the props of `ViewProps`.
|
|
2073
|
+
|
|
2074
|
+
---
|
|
2075
|
+
|
|
2076
|
+
- **MinimalSplash** (Layout) — Minimal monochrome splash
|
|
2077
|
+
|
|
2078
|
+
### MinimalSplash
|
|
2079
|
+
|
|
2080
|
+
Minimal monochrome splash
|
|
2081
|
+
|
|
2082
|
+
```tsx
|
|
2083
|
+
import { MinimalSplash } from '@thewhileloop/whileui';
|
|
2084
|
+
```
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
| Prop | Type | Default | Description |
|
|
2088
|
+
| ------------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
2089
|
+
| className | `string` | — | |
|
|
2090
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2091
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2092
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2093
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2094
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2095
|
+
| showLoading | `boolean` | `false` | Whether to show loading indicator |
|
|
2096
|
+
| loadingText | `string` | — | Loading text |
|
|
2097
|
+
|
|
2098
|
+
Also accepts the props of `SplashScreenProps`.
|
|
2099
|
+
|
|
2100
|
+
---
|
|
2101
|
+
|
|
2102
|
+
- **BrandedSplash** (Layout) — Splash with brand imagery
|
|
2103
|
+
|
|
2104
|
+
### BrandedSplash
|
|
2105
|
+
|
|
2106
|
+
Splash with brand imagery
|
|
2107
|
+
|
|
2108
|
+
```tsx
|
|
2109
|
+
import { BrandedSplash } from '@thewhileloop/whileui';
|
|
2110
|
+
```
|
|
2111
|
+
|
|
2112
|
+
|
|
2113
|
+
| Prop | Type | Default | Description |
|
|
2114
|
+
| ------------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
2115
|
+
| className | `string` | — | |
|
|
2116
|
+
| appName | `string` | — | App name displayed below logo |
|
|
2117
|
+
| tagline | `string` | — | Tagline displayed below app name |
|
|
2118
|
+
| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |
|
|
2119
|
+
| duration | `number` | `800` | Animation duration in ms |
|
|
2120
|
+
| onAnimationComplete | `() => void` | — | Callback when animation completes |
|
|
2121
|
+
| loadingText | `string` | — | Loading text |
|
|
2122
|
+
|
|
2123
|
+
Also accepts the props of `SplashScreenProps`.
|
|
2124
|
+
|
|
2125
|
+
---
|
|
2126
|
+
|
|
2127
|
+
- **Chat** (Chat) — Opinionated messages, suggestions, and SmartInput composition
|
|
2128
|
+
|
|
2129
|
+
### Chat
|
|
2130
|
+
|
|
2131
|
+
| Block | Description |
|
|
2132
|
+
| --------------------- | --------------------------------------------------------------- |
|
|
2133
|
+
| **Chat** | Opinionated messages, suggestions, and SmartInput composition |
|
|
2134
|
+
| **ChatConversation** | Constrained conversation viewport that follows streaming output |
|
|
2135
|
+
| **ChatMessageBubble** | Message bubble (user/assistant, big/small text) |
|
|
2136
|
+
| **ChatSuggestions** | Suggestion chips when empty |
|
|
2137
|
+
|
|
2138
|
+
---
|
|
2139
|
+
|
|
2140
|
+
- **ChatConversation** (Chat) — Constrained conversation viewport that follows streaming output
|
|
2141
|
+
|
|
2142
|
+
## ChatConversation / Chat
|
|
2143
|
+
|
|
2144
|
+
`ChatConversation` is the low-level viewport for custom conversation screens. Its content stays constrained on wide windows, follows new or streaming output while the reader is near the bottom, and stops following after the reader scrolls upward. Compose it with `SmartInput`; the application owns message data, streaming, tools, approvals, and sources.
|
|
2145
|
+
|
|
2146
|
+
It is a `ScrollView` for a bounded active transcript. Paginate or window historical messages; use a virtualized list when the full history can grow without a bound.
|
|
2147
|
+
|
|
2148
|
+
```tsx
|
|
2149
|
+
import { ChatConversation, SmartInput, View } from '@thewhileloop/whileui';
|
|
2150
|
+
|
|
2151
|
+
<View className="flex-1">
|
|
2152
|
+
<ChatConversation contentContainerClassName="gap-4 px-4 py-6">
|
|
2153
|
+
{messages.map((message) => (
|
|
2154
|
+
<ProductMessage key={message.id} message={message} />
|
|
2155
|
+
))}
|
|
2156
|
+
</ChatConversation>
|
|
2157
|
+
<SmartInput
|
|
2158
|
+
value={value}
|
|
2159
|
+
onChangeText={setValue}
|
|
2160
|
+
onSubmitEditing={send}
|
|
2161
|
+
submitBehavior="submit"
|
|
2162
|
+
leftSlot={<AttachButton />}
|
|
2163
|
+
centerSlot={<ModelSelect />}
|
|
2164
|
+
rightSlot={<SendButton />}
|
|
2165
|
+
/>
|
|
2166
|
+
</View>;
|
|
2167
|
+
```
|
|
2168
|
+
|
|
2169
|
+
`Chat` is the opinionated convenience composition: message bubbles, suggestion chips, and `SmartInput` with attach/send slots. It uses semantic tokens; theme via `global.css`. Use `renderMessage` for markdown, code, tool calls, reasoning, or sources, and `loadingIndicator` for streaming feedback.
|
|
2170
|
+
|
|
2171
|
+
```tsx
|
|
2172
|
+
import { Chat, type ChatMessage } from '@thewhileloop/whileui';
|
|
2173
|
+
|
|
2174
|
+
const [messages, setMessages] = useState<ChatMessage[]>([...]);
|
|
2175
|
+
const [value, setValue] = useState('');
|
|
2176
|
+
|
|
2177
|
+
<Chat
|
|
2178
|
+
messages={messages}
|
|
2179
|
+
value={value}
|
|
2180
|
+
onChangeText={setValue}
|
|
2181
|
+
onSend={() => { /* append user msg, clear input */ }}
|
|
2182
|
+
placeholder="Message..."
|
|
2183
|
+
suggestions={['Summarize this', 'Explain simply', 'Translate']}
|
|
2184
|
+
onSuggestionPress={(text) => setValue(text)}
|
|
2185
|
+
emptyTitle="How can I help?"
|
|
2186
|
+
emptyDescription="Ask anything."
|
|
2187
|
+
leftSlot={<Button variant="ghost" size="icon" accessibilityLabel="Attach"><Icon name="paperclip" /></Button>}
|
|
2188
|
+
rightSlot={<Button size="icon" accessibilityLabel="Send" onPress={handleSend}><Icon name="send" /></Button>}
|
|
2189
|
+
/>;
|
|
2190
|
+
```
|
|
2191
|
+
|
|
2192
|
+
|
|
2193
|
+
| Prop | Type | Default | Description |
|
|
2194
|
+
| ----------------------- | ------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------- |
|
|
2195
|
+
| messages (required) | `ChatMessage[]` | — | Messages to show, each `{ id, role, content, secondary?, contentSize? }`. |
|
|
2196
|
+
| value (required) | `string` | — | Input value. |
|
|
2197
|
+
| onChangeText (required) | `(text: string) => void` | — | Input change. |
|
|
2198
|
+
| onSend (required) | `() => void` | — | Send handler. |
|
|
2199
|
+
| placeholder | `string` | `'Message...'` | |
|
|
2200
|
+
| suggestions | `string[]` | `EMPTY_SUGGESTIONS` | Chips when empty. |
|
|
2201
|
+
| onSuggestionPress | `(text: string) => void` | — | |
|
|
2202
|
+
| leftSlot | `React.ReactNode` | — | |
|
|
2203
|
+
| rightSlot | `React.ReactNode` | — | |
|
|
2204
|
+
| loading | `boolean` | `false` | |
|
|
2205
|
+
| header | `React.ReactNode` | — | |
|
|
2206
|
+
| emptyTitle | `string` | — | |
|
|
2207
|
+
| emptyDescription | `string` | — | |
|
|
2208
|
+
| exampleMessage | `ChatMessage` | — | Example message shown in empty state so users see the bubble UI. |
|
|
2209
|
+
| renderMessage | `(message: ChatMessage) => React.ReactNode` | — | Custom message renderer (markdown, code, images). Falls back to ChatMessageBubble. |
|
|
2210
|
+
| loadingIndicator | `React.ReactNode` | — | Shown at bottom when loading (typing indicator). |
|
|
2211
|
+
| inputSafeArea | `boolean` | `true` | Passed to SmartInput. Use false when Chat is in a contained demo. |
|
|
2212
|
+
| keyboardVerticalOffset | `number` | `0` | Offset for KeyboardAvoidingView when Chat has a header (e.g. StatusBar + header height). |
|
|
2213
|
+
| className | `string` | — | |
|
|
2214
|
+
| messagesClassName | `string` | — | |
|
|
2215
|
+
|
|
2216
|
+
|
|
2217
|
+
`ChatConversation` extends React Native `ScrollViewProps`, forwards its ref, and adds `contentContainerClassName` to style the constrained inner column. Caller `onScroll` and `onContentSizeChange` callbacks are preserved.
|
|
2218
|
+
|
|
2219
|
+
### Optional Markdown and code
|
|
2220
|
+
|
|
2221
|
+
These are explicit subpaths so the root package remains independent of a Markdown
|
|
2222
|
+
parser. Install `markdown-it` only in applications that render Markdown.
|
|
2223
|
+
|
|
2224
|
+
```tsx
|
|
2225
|
+
import { Markdown } from '@thewhileloop/whileui/markdown';
|
|
2226
|
+
import { CodeBlock } from '@thewhileloop/whileui/code';
|
|
2227
|
+
|
|
2228
|
+
<Markdown onLinkPress={openLink}>{assistantText}</Markdown>
|
|
2229
|
+
<CodeBlock code={source} language="TypeScript" actions={<CopyButton />} />
|
|
2230
|
+
```
|
|
2231
|
+
|
|
2232
|
+
`Markdown` supports headings, paragraphs, emphasis, links, lists, quotes, rules, inline
|
|
2233
|
+
code, and fenced code. It never opens links or writes to the clipboard itself:
|
|
2234
|
+
`onLinkPress` and `codeActions` keep those capabilities in the application. `CodeBlock`
|
|
2235
|
+
is dependency-free and accepts an application-owned `actions` slot.
|
|
2236
|
+
|
|
2237
|
+
---
|
|
2238
|
+
|
|
2239
|
+
- **ChatMessageBubble** (Chat) — Message bubble (user/assistant, big/small text)
|
|
2240
|
+
|
|
2241
|
+
### ChatMessageBubble
|
|
2242
|
+
|
|
2243
|
+
One message in a `Chat`, rendered on its own when a screen composes the conversation itself.
|
|
2244
|
+
|
|
2245
|
+
```tsx
|
|
2246
|
+
import { ChatMessageBubble } from '@thewhileloop/whileui';
|
|
2247
|
+
```
|
|
2248
|
+
|
|
2249
|
+
|
|
2250
|
+
| Prop | Type | Default | Description |
|
|
2251
|
+
| ------------------ | ------------- | ------- | --------------------------------------------------------------- |
|
|
2252
|
+
| message (required) | `ChatMessage` | — | The message to render; its `role` decides alignment and colour. |
|
|
2253
|
+
| contentClassName | `string` | — | Classes for the bubble's content wrapper. |
|
|
2254
|
+
|
|
2255
|
+
---
|
|
2256
|
+
|
|
2257
|
+
- **ChatSuggestions** (Chat) — Suggestion chips when empty
|
|
2258
|
+
|
|
2259
|
+
### ChatSuggestions
|
|
2260
|
+
|
|
2261
|
+
Suggestion chips when empty
|
|
2262
|
+
|
|
2263
|
+
```tsx
|
|
2264
|
+
import { ChatSuggestions } from '@thewhileloop/whileui';
|
|
2265
|
+
```
|
|
2266
|
+
|
|
2267
|
+
|
|
2268
|
+
| Prop | Type | Default | Description |
|
|
2269
|
+
| ---------------------- | ------------------------ | ------------------- | ----------- |
|
|
2270
|
+
| suggestions (required) | `string[]` | `EMPTY_SUGGESTIONS` | |
|
|
2271
|
+
| onSelect (required) | `(text: string) => void` | — | |
|
|
2272
|
+
| className | `string` | — | |
|
|
2273
|
+
|
|
2274
|
+
---
|
|
2275
|
+
|
|
2276
|
+
- **ProfileHeader** (Profile & Settings, copy-paste template) — Profile header with stats
|
|
2277
|
+
|
|
2278
|
+
## ProfileHeader
|
|
2279
|
+
|
|
2280
|
+
Copy from `apps/showcase/catalog/templates/profile/profile-header.tsx`, then:
|
|
2281
|
+
|
|
2282
|
+
```tsx
|
|
2283
|
+
import { ProfileHeader } from './templates/profile'; // or your path
|
|
2284
|
+
|
|
2285
|
+
<ProfileHeader
|
|
2286
|
+
name="John Doe"
|
|
2287
|
+
username="johndoe"
|
|
2288
|
+
bio="Designer & Developer"
|
|
2289
|
+
avatarFallback="JD"
|
|
2290
|
+
avatarUrl="https://..."
|
|
2291
|
+
verified
|
|
2292
|
+
stats={[
|
|
2293
|
+
{ label: 'Followers', value: '1.2K' },
|
|
2294
|
+
{ label: 'Following', value: 234 },
|
|
2295
|
+
]}
|
|
2296
|
+
action={{ label: 'Edit Profile', onPress: () => {} }}
|
|
2297
|
+
/>;
|
|
2298
|
+
```
|
|
2299
|
+
|
|
2300
|
+
---
|
|
2301
|
+
|
|
2302
|
+
- **AccountCard** (Profile & Settings, copy-paste template) — Account summary card
|
|
2303
|
+
|
|
2304
|
+
No API section yet; the summary above is the full description.
|
|
2305
|
+
|
|
2306
|
+
---
|
|
2307
|
+
|
|
2308
|
+
- **SettingsSection** (Profile & Settings, copy-paste template) — Section header with optional action
|
|
2309
|
+
|
|
2310
|
+
## SettingsSection / SettingsItem
|
|
2311
|
+
|
|
2312
|
+
Copy from `apps/showcase/catalog/templates/profile/`, then:
|
|
2313
|
+
|
|
2314
|
+
```tsx
|
|
2315
|
+
import { SettingsSection, SettingsItem } from './templates/profile'; // or your path
|
|
2316
|
+
|
|
2317
|
+
<SettingsSection title="Preferences">
|
|
2318
|
+
<SettingsItem
|
|
2319
|
+
icon={<Icon />}
|
|
2320
|
+
label="Notifications"
|
|
2321
|
+
type="toggle"
|
|
2322
|
+
toggleValue={enabled}
|
|
2323
|
+
onToggle={setEnabled}
|
|
2324
|
+
/>
|
|
2325
|
+
<SettingsItem icon={<Icon />} label="Privacy" value="Public" onPress={() => {}} />
|
|
2326
|
+
<SettingsItem icon={<Icon />} label="Sign Out" type="action" destructive />
|
|
2327
|
+
</SettingsSection>;
|
|
2328
|
+
```
|
|
2329
|
+
|
|
2330
|
+
---
|
|
2331
|
+
|
|
2332
|
+
- **SettingsItem** (Profile & Settings, copy-paste template) — Row for toggles/links/settings
|
|
2333
|
+
|
|
2334
|
+
Documented under SettingsSection.
|
|
2335
|
+
|
|
2336
|
+
---
|
|
2337
|
+
|
|
2338
|
+
- **ListItem** (Lists) — Title/subtitle row
|
|
2339
|
+
|
|
2340
|
+
### ListItem
|
|
2341
|
+
|
|
2342
|
+
Title/subtitle row
|
|
2343
|
+
|
|
2344
|
+
```tsx
|
|
2345
|
+
import { ListItem } from '@thewhileloop/whileui';
|
|
2346
|
+
```
|
|
2347
|
+
|
|
2348
|
+
|
|
2349
|
+
| Prop | Type | Default | Description |
|
|
2350
|
+
| ---------------- | ----------------- | ------- | ----------------------------------------------- |
|
|
2351
|
+
| icon | `React.ReactNode` | — | |
|
|
2352
|
+
| title (required) | `string` | — | |
|
|
2353
|
+
| subtitle | `string` | — | |
|
|
2354
|
+
| description | `string` | — | |
|
|
2355
|
+
| rightIcon | `React.ReactNode` | — | |
|
|
2356
|
+
| rightText | `string` | — | |
|
|
2357
|
+
| action | `React.ReactNode` | — | |
|
|
2358
|
+
| showBorder | `boolean` | `true` | |
|
|
2359
|
+
| compact | `boolean` | `false` | |
|
|
2360
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2361
|
+
| className | `string` | — | |
|
|
2362
|
+
|
|
2363
|
+
Also accepts the props of `PressableProps`.
|
|
2364
|
+
|
|
2365
|
+
---
|
|
2366
|
+
|
|
2367
|
+
- **NotificationItem** (Lists) — Notification row with metadata
|
|
2368
|
+
|
|
2369
|
+
### NotificationItem
|
|
2370
|
+
|
|
2371
|
+
Notification row with metadata
|
|
2372
|
+
|
|
2373
|
+
```tsx
|
|
2374
|
+
import { NotificationItem } from '@thewhileloop/whileui';
|
|
2375
|
+
```
|
|
2376
|
+
|
|
2377
|
+
|
|
2378
|
+
| Prop | Type | Default | Description |
|
|
2379
|
+
| ------------------ | ----------------- | ------- | ----------------------------------------------- |
|
|
2380
|
+
| icon | `React.ReactNode` | — | |
|
|
2381
|
+
| avatarUrl | `string` | — | |
|
|
2382
|
+
| avatarFallback | `string` | — | |
|
|
2383
|
+
| title (required) | `string` | — | |
|
|
2384
|
+
| message (required) | `string` | — | |
|
|
2385
|
+
| time (required) | `string` | — | |
|
|
2386
|
+
| read | `boolean` | `false` | |
|
|
2387
|
+
| showDot | `boolean` | `true` | |
|
|
2388
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2389
|
+
| className | `string` | — | |
|
|
2390
|
+
|
|
2391
|
+
Also accepts the props of `PressableProps`.
|
|
2392
|
+
|
|
2393
|
+
---
|
|
2394
|
+
|
|
2395
|
+
- **TimelineFeed** (Lists) — Vertical feed with connecting lines
|
|
2396
|
+
|
|
2397
|
+
### TimelineFeed
|
|
2398
|
+
|
|
2399
|
+
Vertical feed with connecting lines
|
|
2400
|
+
|
|
2401
|
+
```tsx
|
|
2402
|
+
import { TimelineFeed } from '@thewhileloop/whileui';
|
|
2403
|
+
```
|
|
2404
|
+
|
|
2405
|
+
|
|
2406
|
+
| Prop | Type | Default | Description |
|
|
2407
|
+
| ---------------- | ---------------- | ------- | ----------- |
|
|
2408
|
+
| className | `string` | — | |
|
|
2409
|
+
| items (required) | `TimelineItem[]` | — | |
|
|
2410
|
+
| showConnector | `boolean` | `true` | |
|
|
2411
|
+
|
|
2412
|
+
Also accepts the props of `ViewProps`.
|
|
2413
|
+
|
|
2414
|
+
---
|
|
2415
|
+
|
|
2416
|
+
- **ProductCard** (Commerce) — Product card with badge/media
|
|
2417
|
+
|
|
2418
|
+
## ProductCard
|
|
2419
|
+
|
|
2420
|
+
```tsx
|
|
2421
|
+
import { ProductCard } from '@thewhileloop/whileui';
|
|
2422
|
+
|
|
2423
|
+
<ProductCard
|
|
2424
|
+
title="Product Name"
|
|
2425
|
+
price="$99"
|
|
2426
|
+
originalPrice="$129"
|
|
2427
|
+
badge="-23%"
|
|
2428
|
+
rating={4.5}
|
|
2429
|
+
reviewCount={128}
|
|
2430
|
+
variant="vertical"
|
|
2431
|
+
onPress={() => {}}
|
|
2432
|
+
/>;
|
|
2433
|
+
```
|
|
2434
|
+
|
|
2435
|
+
|
|
2436
|
+
| Prop | Type | Default | Description |
|
|
2437
|
+
| ---------------- | ---------------------------- | ------------ | ----------------------------------------------- |
|
|
2438
|
+
| imageUrl | `string` | — | |
|
|
2439
|
+
| title (required) | `string` | — | |
|
|
2440
|
+
| description | `string` | — | |
|
|
2441
|
+
| price (required) | `string` | — | |
|
|
2442
|
+
| originalPrice | `string` | — | |
|
|
2443
|
+
| badge | `string` | — | |
|
|
2444
|
+
| rating | `number` | — | |
|
|
2445
|
+
| reviewCount | `number` | — | |
|
|
2446
|
+
| inStock | `boolean` | `true` | |
|
|
2447
|
+
| variant | `'vertical' \| 'horizontal'` | `'vertical'` | |
|
|
2448
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2449
|
+
| className | `string` | — | |
|
|
2450
|
+
|
|
2451
|
+
Also accepts the props of `PressableProps`.
|
|
2452
|
+
|
|
2453
|
+
---
|
|
2454
|
+
|
|
2455
|
+
- **PricingCard** (Commerce) — Pricing tiers with feature list
|
|
2456
|
+
|
|
2457
|
+
## PricingCard
|
|
2458
|
+
|
|
2459
|
+
```tsx
|
|
2460
|
+
import { PricingCard } from '@thewhileloop/whileui';
|
|
2461
|
+
|
|
2462
|
+
<PricingCard
|
|
2463
|
+
name="Pro"
|
|
2464
|
+
description="For teams"
|
|
2465
|
+
price="$29"
|
|
2466
|
+
period="/month"
|
|
2467
|
+
badge="Popular"
|
|
2468
|
+
highlighted
|
|
2469
|
+
features={[
|
|
2470
|
+
{ label: 'Unlimited users', included: true },
|
|
2471
|
+
{ label: 'Priority support', included: true },
|
|
2472
|
+
{ label: 'Custom domain', included: false },
|
|
2473
|
+
]}
|
|
2474
|
+
onPress={() => {}}
|
|
2475
|
+
/>;
|
|
2476
|
+
```
|
|
2477
|
+
|
|
2478
|
+
---
|
|
2479
|
+
|
|
2480
|
+
- **CheckoutSummary** (Commerce) — Cart summary with line items
|
|
2481
|
+
|
|
2482
|
+
### CheckoutSummary
|
|
2483
|
+
|
|
2484
|
+
Cart summary with line items
|
|
2485
|
+
|
|
2486
|
+
```tsx
|
|
2487
|
+
import { CheckoutSummary } from '@thewhileloop/whileui';
|
|
2488
|
+
```
|
|
2489
|
+
|
|
2490
|
+
|
|
2491
|
+
| Prop | Type | Default | Description |
|
|
2492
|
+
| ------------------- | -------------------- | ------------ | ----------- |
|
|
2493
|
+
| items (required) | `CheckoutLineItem[]` | — | |
|
|
2494
|
+
| subtotal (required) | `string` | — | |
|
|
2495
|
+
| shipping | `string` | — | |
|
|
2496
|
+
| tax | `string` | — | |
|
|
2497
|
+
| discount | `string` | — | |
|
|
2498
|
+
| total (required) | `string` | — | |
|
|
2499
|
+
| buttonLabel | `string` | `'Checkout'` | |
|
|
2500
|
+
| onCheckout | `() => void` | — | |
|
|
2501
|
+
| loading | `boolean` | `false` | |
|
|
2502
|
+
| className | `string` | — | |
|
|
2503
|
+
|
|
2504
|
+
Also accepts the props of `ViewProps`.
|
|
2505
|
+
|
|
2506
|
+
---
|
|
2507
|
+
|
|
2508
|
+
- **MetricCard** (Commerce) — Stats/progress card for dashboards
|
|
2509
|
+
|
|
2510
|
+
### MetricCard
|
|
2511
|
+
|
|
2512
|
+
Stats/progress card for dashboards
|
|
2513
|
+
|
|
2514
|
+
```tsx
|
|
2515
|
+
import { MetricCard } from '@thewhileloop/whileui';
|
|
2516
|
+
```
|
|
2517
|
+
|
|
2518
|
+
|
|
2519
|
+
| Prop | Type | Default | Description |
|
|
2520
|
+
| ---------------- | ------------------------------------ | ----------- | ----------------------------------------------- |
|
|
2521
|
+
| className | `string` | — | |
|
|
2522
|
+
| label (required) | `string` | — | |
|
|
2523
|
+
| value (required) | `string \| number` | — | |
|
|
2524
|
+
| subtitle | `string` | — | |
|
|
2525
|
+
| progress | `number` | — | |
|
|
2526
|
+
| segments | `{ value: number; color: string }[]` | — | |
|
|
2527
|
+
| icon | `React.ReactNode` | — | |
|
|
2528
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2529
|
+
| variant | `'default' \| 'outlined'` | `'default'` | |
|
|
2530
|
+
|
|
2531
|
+
Also accepts the props of `ViewProps`.
|
|
2532
|
+
|
|
2533
|
+
---
|
|
2534
|
+
|
|
2535
|
+
- **SubscriptionCard** (Commerce) — Current plan card with manage/upgrade actions
|
|
2536
|
+
|
|
2537
|
+
## SubscriptionCard
|
|
2538
|
+
|
|
2539
|
+
```tsx
|
|
2540
|
+
import { SubscriptionCard } from '@thewhileloop/whileui';
|
|
2541
|
+
|
|
2542
|
+
<SubscriptionCard
|
|
2543
|
+
planName="Pro"
|
|
2544
|
+
price="$29"
|
|
2545
|
+
period="/month"
|
|
2546
|
+
expiresAt="April 18, 2026"
|
|
2547
|
+
isActive
|
|
2548
|
+
onManage={() => {}}
|
|
2549
|
+
onUpgrade={() => {}}
|
|
2550
|
+
/>;
|
|
2551
|
+
```
|
|
2552
|
+
|
|
2553
|
+
|
|
2554
|
+
| Prop | Type | Default | Description |
|
|
2555
|
+
| ------------------- | ------------ | --------------- | ----------------------------------------------- |
|
|
2556
|
+
| planName (required) | `string` | — | Current plan label. |
|
|
2557
|
+
| price (required) | `string` | — | Plan price. |
|
|
2558
|
+
| period | `string` | `'/month'` | Billing period label. |
|
|
2559
|
+
| expiresAt | `string` | — | Renewal/expiry date text. |
|
|
2560
|
+
| isActive | `boolean` | `true` | Active/inactive badge state. |
|
|
2561
|
+
| onManage | `() => void` | — | Manage action. |
|
|
2562
|
+
| onUpgrade | `() => void` | — | Upgrade action. |
|
|
2563
|
+
| manageLabel | `string` | `'Manage plan'` | |
|
|
2564
|
+
| upgradeLabel | `string` | `'Upgrade'` | |
|
|
2565
|
+
| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |
|
|
2566
|
+
| className | `string` | — | |
|
|
2567
|
+
|
|
2568
|
+
Also accepts the props of `ViewProps`.
|
|
2569
|
+
|
|
2570
|
+
---
|
|
2571
|
+
|
|
2572
|
+
- **FeatureGate** (Commerce) — Locked feature state with upgrade CTA
|
|
2573
|
+
|
|
2574
|
+
## FeatureGate
|
|
2575
|
+
|
|
2576
|
+
```tsx
|
|
2577
|
+
import { FeatureGate } from '@thewhileloop/whileui';
|
|
2578
|
+
|
|
2579
|
+
<FeatureGate
|
|
2580
|
+
title="Advanced export is locked"
|
|
2581
|
+
description="Upgrade to unlock 4K export."
|
|
2582
|
+
buttonLabel="Upgrade"
|
|
2583
|
+
onUpgrade={() => {}}
|
|
2584
|
+
/>;
|
|
2585
|
+
```
|
|
2586
|
+
|
|
2587
|
+
Use `children` to show dimmed preview content with an overlay CTA.
|
|
2588
|
+
|
|
2589
|
+
---
|
|
2590
|
+
|
|
2591
|
+
- **UsageBar** (Commerce) — Quota meter with warning/exceeded states
|
|
2592
|
+
|
|
2593
|
+
## UsageBar
|
|
2594
|
+
|
|
2595
|
+
```tsx
|
|
2596
|
+
import { UsageBar } from '@thewhileloop/whileui';
|
|
2597
|
+
|
|
2598
|
+
<UsageBar label="AI generations" used={8} limit={20} />;
|
|
2599
|
+
```
|
|
2600
|
+
|
|
2601
|
+
|
|
2602
|
+
| Prop | Type | Default | Description |
|
|
2603
|
+
| ---------------- | ----------------- | ------- | ------------------------------- |
|
|
2604
|
+
| label (required) | `string` | — | Usage label. |
|
|
2605
|
+
| used (required) | `number` | — | Used amount. |
|
|
2606
|
+
| limit (required) | `number` | — | Quota limit. |
|
|
2607
|
+
| variant | `UsageBarVariant` | — | Optional explicit visual state. |
|
|
2608
|
+
| className | `string` | — | |
|
|
2609
|
+
|
|
2610
|
+
Also accepts the props of `ViewProps`.
|
|
2611
|
+
|
|
2612
|
+
---
|
|
2613
|
+
|
|
2614
|
+
- **PlanToggle** (Commerce) — Monthly vs annual plan switcher
|
|
2615
|
+
|
|
2616
|
+
## PlanToggle
|
|
2617
|
+
|
|
2618
|
+
```tsx
|
|
2619
|
+
import { PlanToggle } from '@thewhileloop/whileui';
|
|
2620
|
+
|
|
2621
|
+
<PlanToggle
|
|
2622
|
+
selected="monthly"
|
|
2623
|
+
monthlyLabel="Monthly"
|
|
2624
|
+
annualLabel="Annual"
|
|
2625
|
+
annualDiscount="Save 20%"
|
|
2626
|
+
onChange={(next) => {}}
|
|
2627
|
+
/>;
|
|
2628
|
+
```
|
|
2629
|
+
|
|
2630
|
+
---
|
|
2631
|
+
|
|
2632
|
+
- **UpgradeBanner** (Commerce) — Inline upgrade banner with action/dismiss
|
|
2633
|
+
|
|
2634
|
+
## UpgradeBanner
|
|
2635
|
+
|
|
2636
|
+
```tsx
|
|
2637
|
+
import { UpgradeBanner } from '@thewhileloop/whileui';
|
|
2638
|
+
|
|
2639
|
+
<UpgradeBanner
|
|
2640
|
+
message="Unlock unlimited exports with Pro."
|
|
2641
|
+
actionLabel="See plans"
|
|
2642
|
+
onAction={() => {}}
|
|
2643
|
+
onDismiss={() => {}}
|
|
2644
|
+
/>;
|
|
2645
|
+
```
|
|
2646
|
+
|
|
2647
|
+
---
|
|
2648
|
+
|
|
2649
|
+
- **SmartImage** (Media) — Image with explicit loading, loaded, fallback, and error lifecycle
|
|
2650
|
+
|
|
2651
|
+
## SmartImage
|
|
2652
|
+
|
|
2653
|
+
```tsx
|
|
2654
|
+
import { SmartImage } from '@thewhileloop/whileui';
|
|
2655
|
+
|
|
2656
|
+
<SmartImage
|
|
2657
|
+
source={{ uri: imageUrl }}
|
|
2658
|
+
fallbackSource={{ uri: fallbackUrl }}
|
|
2659
|
+
alt="Product preview"
|
|
2660
|
+
className="h-48 w-full"
|
|
2661
|
+
/>;
|
|
2662
|
+
```
|
|
2663
|
+
|
|
2664
|
+
|
|
2665
|
+
| Prop | Type | Default | Description |
|
|
2666
|
+
| ----------------- | -------------------------------------------------- | --------- | ----------- |
|
|
2667
|
+
| className | `string` | — | |
|
|
2668
|
+
| source (required) | `ImageProps['source']` | — | |
|
|
2669
|
+
| alt (required) | `string` | — | |
|
|
2670
|
+
| skeleton | `boolean` | `true` | |
|
|
2671
|
+
| skeletonClassName | `string` | — | |
|
|
2672
|
+
| transition | `number` | `200` | |
|
|
2673
|
+
| onLoad | `() => void` | — | |
|
|
2674
|
+
| onError | `() => void` | — | |
|
|
2675
|
+
| fallbackSource | `ImageProps['source']` | — | |
|
|
2676
|
+
| radius | `'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | |
|
|
2677
|
+
| objectFit | `'fill' \| 'cover' \| 'contain' \| 'scale-down'` | `'cover'` | |
|
|
2678
|
+
|
|
2679
|
+
Also accepts the props of `ViewProps`.
|
|
2680
|
+
|
|
2681
|
+
The skeleton returns whenever the primary image starts loading again. A failed primary
|
|
2682
|
+
source switches to `fallbackSource` when supplied without re-entering a primary/fallback
|
|
2683
|
+
loop.
|
|
2684
|
+
|
|
2685
|
+
---
|
|
2686
|
+
|
|
2687
|
+
- **DatePickerModal** (Date Picker) — Bottom sheet modal with calendar, compact trigger
|
|
2688
|
+
|
|
2689
|
+
## DatePickerModal / DatePickerInline / DateRangePickerModal / TimePickerModal
|
|
2690
|
+
|
|
2691
|
+
Date selection blocks using react-native-calendars. Theme-aware via `useCalendarTheme` (Uniwind light/dark). Optional `theme` prop for custom RN color strings (hex/rgb/hsl/named). `CalendarTheme` maps to react-native-calendars Theme and supports `arrowColor`, `disabledArrowColor`, and optional font keys.
|
|
2692
|
+
|
|
2693
|
+
**DatePickerModal** — Compact trigger opens bottom sheet with calendar. Use `DatePickerTrigger` as the trigger content.
|
|
2694
|
+
|
|
2695
|
+
**DatePickerInline** — Calendar embedded inline for forms or dashboards.
|
|
2696
|
+
|
|
2697
|
+
**DateRangePickerModal** — Range selection with period marking. Use `DateRangePickerTrigger` as the trigger content.
|
|
2698
|
+
|
|
2699
|
+
**TimePickerModal** — Hour and minute wheels (plus AM/PM under a 12-hour `hourCycle`) in the same bottom sheet, so a form's date and time fields open the same way. `value` is always `HH:MM` in 24-hour form. Each wheel is one `adjustable` element for assistive tech and the keyboard. Use `TimePickerTrigger` as the trigger content.
|
|
2700
|
+
|
|
2701
|
+
```tsx
|
|
2702
|
+
import {
|
|
2703
|
+
DatePickerModal,
|
|
2704
|
+
DatePickerTrigger,
|
|
2705
|
+
DatePickerInline,
|
|
2706
|
+
DateRangePickerModal,
|
|
2707
|
+
DateRangePickerTrigger,
|
|
2708
|
+
TimePickerModal,
|
|
2709
|
+
TimePickerTrigger,
|
|
2710
|
+
type DateRange,
|
|
2711
|
+
} from '@thewhileloop/whileui';
|
|
2712
|
+
|
|
2713
|
+
// Single date (modal)
|
|
2714
|
+
const [date, setDate] = useState<string | null>(null);
|
|
2715
|
+
const [open, setOpen] = useState(false);
|
|
2716
|
+
|
|
2717
|
+
<DatePickerModal
|
|
2718
|
+
value={date}
|
|
2719
|
+
onValueChange={setDate}
|
|
2720
|
+
open={open}
|
|
2721
|
+
onOpenChange={setOpen}
|
|
2722
|
+
trigger={<DatePickerTrigger value={date} placeholder="Pick a date" />}
|
|
2723
|
+
title="Select date"
|
|
2724
|
+
/>;
|
|
2725
|
+
|
|
2726
|
+
// Inline calendar
|
|
2727
|
+
<DatePickerInline value={date} onValueChange={setDate} />;
|
|
2728
|
+
|
|
2729
|
+
// Date range (modal)
|
|
2730
|
+
const [range, setRange] = useState<DateRange | null>(null);
|
|
2731
|
+
|
|
2732
|
+
<DateRangePickerModal
|
|
2733
|
+
value={range}
|
|
2734
|
+
onValueChange={setRange}
|
|
2735
|
+
open={rangeOpen}
|
|
2736
|
+
onOpenChange={setRangeOpen}
|
|
2737
|
+
trigger={<DateRangePickerTrigger value={range} placeholder="Pick range" />}
|
|
2738
|
+
/>;
|
|
2739
|
+
|
|
2740
|
+
// Time (modal) — "HH:MM", 24-hour, whatever the wheels show
|
|
2741
|
+
const [time, setTime] = useState<string | null>(null);
|
|
2742
|
+
|
|
2743
|
+
<TimePickerModal
|
|
2744
|
+
value={time}
|
|
2745
|
+
onValueChange={setTime}
|
|
2746
|
+
open={timeOpen}
|
|
2747
|
+
onOpenChange={setTimeOpen}
|
|
2748
|
+
minuteStep={5}
|
|
2749
|
+
trigger={<TimePickerTrigger value={time} placeholder="Pick a time" />}
|
|
2750
|
+
/>;
|
|
2751
|
+
```
|
|
2752
|
+
|
|
2753
|
+
|
|
2754
|
+
| Prop | Type | Default | Description |
|
|
2755
|
+
| ----------------------- | -------------------------------- | ---------------------- | --------------------------------------------------------- |
|
|
2756
|
+
| value | `string \| null` | — | Controlled: selected date in YYYY-MM-DD |
|
|
2757
|
+
| onValueChange | `(date: string \| null) => void` | — | Controlled: change handler |
|
|
2758
|
+
| open (required) | `boolean` | — | Modal open state |
|
|
2759
|
+
| onOpenChange (required) | `(open: boolean) => void` | — | Modal open change handler |
|
|
2760
|
+
| minDate | `string` | — | Minimum selectable date YYYY-MM-DD |
|
|
2761
|
+
| maxDate | `string` | — | Maximum selectable date YYYY-MM-DD |
|
|
2762
|
+
| theme | `CalendarTheme` | — | Override calendar theme (RN color strings) |
|
|
2763
|
+
| trigger | `React.ReactNode` | — | Trigger element. If not provided, no trigger is rendered. |
|
|
2764
|
+
| title | `string` | `'Select date'` | Title shown in modal header |
|
|
2765
|
+
| confirmLabel | `string` | `'Done'` | Button label for confirm |
|
|
2766
|
+
| className | `string` | — | |
|
|
2767
|
+
| frosted | `boolean` | `false` | Enable the optional frosted overlay. |
|
|
2768
|
+
| blurIntensity | `number` | medium preset | Override blur amount directly. |
|
|
2769
|
+
| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |
|
|
2770
|
+
|
|
2771
|
+
---
|
|
2772
|
+
|
|
2773
|
+
- **DatePickerInline** (Date Picker) — Inline calendar for forms or dashboards
|
|
2774
|
+
|
|
2775
|
+
Documented under DatePickerModal.
|
|
2776
|
+
|
|
2777
|
+
---
|
|
2778
|
+
|
|
2779
|
+
- **DateRangePickerModal** (Date Picker) — Range selection modal with period marking
|
|
2780
|
+
|
|
2781
|
+
Documented under DatePickerModal.
|
|
2782
|
+
|
|
2783
|
+
---
|
|
2784
|
+
|
|
2785
|
+
- **TimePickerModal** (Date Picker) — Hour and minute wheels in the same bottom sheet
|
|
2786
|
+
|
|
2787
|
+
Documented under DatePickerModal.
|