@thewhileloop/whileui 4.2.3 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1235 -663
- package/dist/agent/catalog.json +1737 -0
- package/dist/agent/llms-components.txt +2787 -0
- package/dist/agent/llms-full.txt +2962 -0
- package/dist/agent/llms.txt +152 -0
- package/dist/blocks/chat/chat.d.ts +10 -2
- package/dist/blocks/chat/chat.js +1 -1
- package/dist/blocks/chat/index.d.ts +1 -1
- package/dist/blocks/commerce/feature-gate.js +2 -2
- package/dist/blocks/commerce/metric-card.js +1 -1
- package/dist/blocks/commerce/subscription-card.d.ts +7 -0
- package/dist/blocks/commerce/usage-bar.d.ts +4 -0
- package/dist/blocks/layout/app-shell.d.ts +4 -0
- package/dist/blocks/layout/empty-state.js +1 -1
- package/dist/blocks/layout/error-state.js +1 -1
- package/dist/blocks/layout/page-skeleton.d.ts +1 -0
- package/dist/blocks/layout/pull-to-refresh-scroll-view.d.ts +1 -0
- package/dist/blocks/layout/screen-skeleton.d.ts +5 -0
- package/dist/blocks/lists/list-item.js +1 -1
- package/dist/blocks/lists/notification-item.js +1 -1
- package/dist/blocks/media/smart-image.js +4 -2
- package/dist/blocks/navigation/bottom-nav.js +1 -1
- package/dist/blocks/navigation/drawer-menu.js +1 -1
- package/dist/blocks/navigation/floating-bottom-nav.js +3 -1
- package/dist/blocks/navigation/tab-bar.js +1 -1
- package/dist/components/alert/alert.d.ts +7 -0
- package/dist/components/alert/alert.js +23 -4
- package/dist/components/button/button.d.ts +3 -0
- package/dist/components/button/button.js +2 -1
- package/dist/components/form-field/form-field.d.ts +18 -0
- package/dist/components/form-field/form-field.js +25 -4
- package/dist/components/input/input.js +2 -2
- package/dist/components/numeric-input/numeric-input.d.ts +3 -0
- package/dist/components/numeric-input/numeric-input.js +2 -2
- package/dist/components/otp-input/otp-input.d.ts +8 -0
- package/dist/components/pressable/pressable.d.ts +1 -0
- package/dist/components/segmented-control/segmented-control.d.ts +1 -0
- package/dist/components/surface/surface.d.ts +5 -0
- package/dist/components/textarea/textarea.js +2 -2
- package/dist/components/toast/toast.d.ts +10 -10
- package/dist/components/toast/toast.js +53 -35
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/frosted-surface.d.ts +3 -0
- package/dist/lib/theme-contract.js +12 -12
- package/dist/lib/theme-presets.js +9 -9
- package/dist/lib/theme-seeds.d.ts +40 -0
- package/dist/lib/theme-seeds.js +143 -0
- package/dist/metro.cjs +5 -4
- package/dist/tokens.d.ts +1 -0
- package/dist/tokens.js +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,1737 @@
|
|
|
1
|
+
{
|
|
2
|
+
"package": "@thewhileloop/whileui",
|
|
3
|
+
"version": "4.3.0",
|
|
4
|
+
"guides": {
|
|
5
|
+
"quickReference": "## Quick Reference (AI / Code Generation)\n\n- **Full-screen:** `AppShell` + `Header` in `header` + `BottomNav` in `bottomNav` + content in `children`\n- **No-jump loading:** keep `AppShell` mounted, set `loading` + `skeleton` (e.g., `PageSkeleton`/`ScreenSkeleton`)\n- **Layout:** `Stack` (vertical), `Row` (horizontal) — both support `gap`, `align`, `justify`\n- **Auth callbacks:** Auth templates use objects: `onSubmit({ email, password })`, `onSubmit({ firstName, lastName, email, password })`, etc. Copy templates from `apps/showcase/catalog/templates/auth/`.\n- **PortalHost:** Add `<PortalHost />` at app root for Select, Popover, Tooltip, HoverCard.\n Inside any WhileUI overlay they render into that overlay's layer without configuration.\n- **Motion:** Wrap the root in `<MotionProvider>`; use `useMotionPolicy()` for semantic\n durations, the responsive spring, and reduced-motion behavior.\n- **Metro:** `withWhileUIMetro(config, { cssEntryFile: './global.css' })` from\n `@thewhileloop/whileui/metro` — wraps Uniwind and keeps icon fonts loading on device.\n `global.css` at app root, imported in `App.tsx`. Installing from npm also needs\n `@source './node_modules/@thewhileloop/whileui/dist';` or every component renders unstyled.\n- **States:** `LoadingState`, `EmptyState` and `ErrorState` share one shape; `ErrorState`\n takes `onRetry`. `Text variant=\"eyebrow\"` is the muted section label above a title.\n- **Reference:** Block props in `packages/ui/src/blocks` (core) and `apps/showcase/catalog/templates/` (auth, profile); flow patterns in README \"Flow Patterns\" section.\n\n### Composition grammar\n\n- Start with one page canvas and flat sections. Add a `Surface` only when grouping or\n elevation communicates something; do not wrap every section in a card.\n- Use `level=\"base\"` for grouped content, `raised` for selectable or floating regions,\n and `overlay` for content above the page. Avoid nesting more than one surfaced level.\n- Express hierarchy through `Text` variants before custom font-size classes. Keep one\n primary action per region and constrain long-form content with `Container`.\n- Forms use `FormField`; its disabled and invalid state flows into `Input`, `Textarea`,\n `NumericInput`, and `Select`. Pass a control prop only to override that inherited state.\n- Chat mechanics belong to `ChatConversation` + `SmartInput`. Message payloads, tools,\n reasoning, approvals, sources, and action rows are application-owned compositions.\n- Import Markdown and code rendering from their optional subpaths. Do not make the root\n package pay for content rendering when the screen does not use it.",
|
|
6
|
+
"compositionGrammar": "### Composition grammar\n\n- Start with one page canvas and flat sections. Add a `Surface` only when grouping or\n elevation communicates something; do not wrap every section in a card.\n- Use `level=\"base\"` for grouped content, `raised` for selectable or floating regions,\n and `overlay` for content above the page. Avoid nesting more than one surfaced level.\n- Express hierarchy through `Text` variants before custom font-size classes. Keep one\n primary action per region and constrain long-form content with `Container`.\n- Forms use `FormField`; its disabled and invalid state flows into `Input`, `Textarea`,\n `NumericInput`, and `Select`. Pass a control prop only to override that inherited state.\n- Chat mechanics belong to `ChatConversation` + `SmartInput`. Message payloads, tools,\n reasoning, approvals, sources, and action rows are application-owned compositions.\n- Import Markdown and code rendering from their optional subpaths. Do not make the root\n package pay for content rendering when the screen does not use it.",
|
|
7
|
+
"installation": "## Installation\n\n```bash\nbun add @thewhileloop/whileui\nbun add react@^19.3.0 react-native@^0.88.0 uniwind@^1.12.2 tailwindcss@^4.0.0 react-native-reanimated react-native-safe-area-context react-native-screens\n# Only if you use Select, Popover, Tooltip, or HoverCard:\nbun add @rn-primitives/portal @rn-primitives/hooks @rn-primitives/slot @rn-primitives/select @rn-primitives/popover @rn-primitives/tooltip @rn-primitives/hover-card\n# Only if you import @thewhileloop/whileui/markdown:\nbun add markdown-it\n```\n\nUse `npm install` instead of `bun add` if you prefer npm.\n\n### Setup Uniwind (required)\n\n1. **metro.config.js** (wrap with `withWhileUIMetro`):\n\n```js\nconst { getDefaultConfig } = require('expo/metro-config');\nconst { withWhileUIMetro } = require('@thewhileloop/whileui/metro');\n\n/** @type {import('expo/metro-config').MetroConfig} */\nconst config = getDefaultConfig(__dirname);\n\nmodule.exports = withWhileUIMetro(config, {\n cssEntryFile: './global.css',\n});\n```\n\n> `withWhileUIMetro` calls Uniwind's `withUniwindConfig(config, options)` for you — same\n> options, config first, `cssEntryFile` a relative path — and keeps `expo-asset`,\n> `expo-font` and `@expo/vector-icons` on the real `react-native`. Without that, Uniwind's\n> import rewrite leaves every icon glyph silently missing on iOS and Android while web\n> renders fine. Pass `exemptFromUniwindRewrite: ['pkg']` for any other package that\n> imports a `react-native` export Uniwind's wrapper lacks. It must be the outermost wrapper.\n\n2. **global.css** at app root, imported from `App.tsx`:\n\n```css\n@import 'tailwindcss';\n@import 'uniwind';\n\n/* Required when installed from npm: Tailwind does not scan node_modules, so without\n this line every component renders unstyled. */\n@source './node_modules/@thewhileloop/whileui/dist';\n\n/* Then your theme: generate it (Theming, below) rather than writing tokens by hand. */\n```\n\n3. **App.tsx**:\n\n```tsx\nimport './global.css';\nimport { MotionProvider, PortalHost } from '@thewhileloop/whileui';\nimport { Button, ButtonText } from '@thewhileloop/whileui';\n\nexport default function App() {\n return (\n <MotionProvider>\n {/* Your app content */}\n <PortalHost />\n </MotionProvider>\n );\n}\n```\n\n`PortalHost` (last child of the root) is where Select, Popover, Tooltip and HoverCard\nrender. Inside a Dialog, Sheet, menu or picker they use that overlay's own layer\nautomatically, and Android back or Escape closes the innermost layer first. Pass\n`portalHost` only to target a host you placed yourself, such as in a modal screen your\nnavigator presents.\n\n### Vite + React Native Web + WhileUI setup\n\nUse the WhileUI Vite compatibility helper when your app includes portal-based primitives.\n\n1. Install Vite + RN web basics:\n\n```bash\nbun add react-dom react-native-web\nbun add -d vite @vitejs/plugin-react\n```\n\n2. `vite.config.ts`:\n\n```ts\nimport { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react';\nimport { withWhileUIViteCompat } from '@thewhileloop/whileui/vite';\n\nexport default defineConfig(withWhileUIViteCompat({ plugins: [react()] }));\n```\n\n3. Keep `PortalHost` at app root when using Select/Popover/Tooltip/HoverCard.\n\nTroubleshooting:\n\n| Symptom | Likely cause | Fix |\n| -------------------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |\n| `Unexpected token <` from `@rn-primitives/*/dist/*.mjs` | rn-primitives ships raw JSX in dist for some packages | Use `withWhileUIViteCompat(...)` in `vite.config.ts` |\n| `className` rejected on RN primitives in consumer TS app | RN `className` augmentation not loaded from package entry | Import components from `@thewhileloop/whileui` (or side-effect import the package entry before use) |\n| Theme token resolves differently on native/web | Missing `--app-color-*` fallback for non-RN-native values | Add `--app-color-*` fallbacks in `global.css` for any `oklch(...)` token used by native APIs |",
|
|
8
|
+
"theming": "## Theming\n\nA theme is a set of CSS variables in `global.css`, in OKLCH, with a `light` and a `dark`\nvariant. Every component reads the same contract, so a theme must define **every** token\nin both modes: a missing one silently falls back to an unstyled value at runtime.\n\n### Generate a theme\n\n`themeFromSeeds` builds a complete light and dark theme from three colours, and\n`themePresetToCss` writes every token, including the hex fallbacks React Native APIs need,\nready to paste into `global.css`:\n\n```ts\nimport { checkThemeContrast, themeFromSeeds, themePresetToCss } from '@thewhileloop/whileui/tokens';\n\nconst theme = themeFromSeeds({\n name: 'studio',\n description: 'Bone paper and burnt amber',\n light: { background: '#f0ede9', foreground: '#13161c', primary: '#b65400' },\n // optional: `dark` seeds (otherwise a dark page with the brand lifted to read on it),\n // `overrides` for any base colour, `visual` for radius and density tokens\n});\n\ncheckThemeContrast(theme.light).filter((c) => !c.pass);\n// [{ pair: 'primary on background', ratio: 4.23, min: 4.5, pass: false }, …]\nconst css = themePresetToCss(theme); // paste into global.css\n```\n\nSeeds are kept as given; the colours it derives (muted text, text on each fill, status\ncolours) are tuned to WCAG AA. `checkThemeContrast` checks any palette, hand-written ones\nincluded: every text pairing at 4.5:1 and the focus ring at 3:1. A failing seed is\nreported, never changed, because a brand colour is a brand decision. The built-in presets,\n`WHILEUI_PRESET_SOFT` and `WHILEUI_PRESET_ATELIER`, go through `themePresetToCss` the same\nway.\n\nSwitch at runtime with `Uniwind.setTheme('dark')` (or `'light'`, `'system'`). A named\nextra theme is registered with `extraThemes: ['name']` in `metro.config.js` and defined\nwith `@variant name`. The full token list, with each default, is in `theme-contract.ts`,\nand agents can read it from the MCP server's `get_theme_tokens`.\n\n### Frosted surfaces\n\nCards and overlays take `frosted`, `blurIntensity` and `blurTintToken`\n(`'surfaceElevated' | 'surfaceTranslucent' | 'card' | 'popover'`). Without a blur view\nthey tint only; register `expo-blur` once for real blur:\n\n```tsx\nimport { BlurView } from 'expo-blur';\nimport { registerFrostedBlurView } from '@thewhileloop/whileui';\n\nregisterFrostedBlurView(BlurView);\n```\n\nFor a frosted look across a whole theme, make `surface-elevated`, `surface-border` and\n`surface-highlight` translucent, in your light and dark variants or in a separate theme.\nThe tuning tokens:\n\n| Token | Default | Effect |\n| ------------------------------------------------------------------------- | ------------ | -------------------------------------------------------------- |\n| `--ui-blur-intensity-subtle` / `-medium` / `-strong` | 18 / 26 / 34 | The three blur presets |\n| `--ui-frosted-android-tint-alpha-scale` | 1.18 | Android needs denser tint; `1` matches iOS and web |\n| `--ui-frosted-android-experimental-blur` | 1 | Uses `expo-blur`'s experimental Android blur; `0` turns it off |\n| `--ui-frosted-highlight-height` | 2 | Top sheen in px; `0` removes it |\n| `--ui-blur-saturation-pct` | 145 | Web saturation multiplier |\n| `--ui-frosted-backdrop-blur-intensity`, `-scale` | 22, 0.72 | Backdrop blur, and its ratio when a component overrides blur |\n| `--ui-drawer-frosted-inset`, `-radius`, `--ui-drawer-content-top-padding` | 10, 34, 0 | Frosted drawer shell |\n\n### Colours for React Native APIs\n\nIcons, `ActivityIndicator` and other native APIs need hex or rgb, not OKLCH.\n`useThemeColors()` returns the whole palette as React Native colours, falling back to the\n`--app-color-*` tokens a generated theme includes; `useIconColors()` is the subset icons\nuse, with `muted` mapped to `mutedForeground`.\n\n```tsx\nconst icons = useIconColors();\n<Feather name=\"heart\" size={20} color={icons.muted} />;\n```\n\nInput, Textarea, NumericInput, SmartInput, Spinner and LoadingScreen pick theme colours\nwhen you omit `placeholderTextColor` or `spinnerColor`.\n\n### Interaction and motion\n\nPress opacity, disabled opacity, motion durations, the responsive spring, and the drawer\nopen and close durations are package constants on `INTERACTION_TOKENS`. They are not CSS\nvariables, so a theme does not change them.\n\nFor a custom control, read that table:\n\n```tsx\nimport { useInteractionTokens, withInteractivePressableStyle } from '@thewhileloop/whileui';\n```\n\nWrap the application in `MotionProvider` to coordinate system reduced motion, an explicit\npreview mode, and deterministic animation-free tests. The provider is optional. Without\nit `useMotionPolicy` reports the durations and spring from `INTERACTION_TOKENS`, and\nReanimated follows the system setting. `reduced` preserves non-spatial feedback while asking Reanimated to skip motion;\n`disabled` also resolves semantic durations to zero.\n\n```tsx\nimport { MotionProvider, useMotionPolicy } from '@thewhileloop/whileui';\n\n<MotionProvider mode=\"system\">{/* application */}</MotionProvider>;\n\nconst motion = useMotionPolicy();\nmotion.durations.normal;\nmotion.springs.responsive;\n```"
|
|
9
|
+
},
|
|
10
|
+
"components": [
|
|
11
|
+
{
|
|
12
|
+
"name": "Text",
|
|
13
|
+
"category": "Primitives",
|
|
14
|
+
"kind": "package",
|
|
15
|
+
"summary": "Themed text with variant support",
|
|
16
|
+
"docs": "## Text\n\n`Text` maps semantic roles to the active visual typography tokens; custom font-size\nclasses are unnecessary for normal hierarchy. `heading` also sets\n`accessibilityRole=\"header\"` unless the caller supplies another role.\n\n\n| Prop | Type | Default | Description |\n| --------- | ---------------------------------------------------------------------------------------------------------- | ----------- | ----------- |\n| className | `string` | — | |\n| size | `'base' \\| 'xs' \\| 'sm' \\| 'lg' \\| 'xl' \\| '2xl' \\| '3xl'` | — | |\n| variant | `'default' \\| 'heading' \\| 'title' \\| 'subtitle' \\| 'body' \\| 'caption' \\| 'label' \\| 'eyebrow' \\| 'code'` | `'default'` | |\n\nAlso accepts the props of React Native's `TextProps`."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "Surface",
|
|
20
|
+
"category": "Primitives",
|
|
21
|
+
"kind": "package",
|
|
22
|
+
"summary": "Token-backed base, raised, and overlay container",
|
|
23
|
+
"docs": "## Surface\n\n```tsx\nimport { Surface } from '@thewhileloop/whileui';\n\n<Surface level=\"raised\" padding=\"default\">\n {/* grouped content */}\n</Surface>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------------ | ------------------- | ------------------------------------ |\n| level | `SurfaceLevel` | `'base'` | Semantic background and depth tier. |\n| padding | `SurfacePaddingSize` | `'default'` | Token-backed interior spacing. |\n| radius | `'sm' \\| 'md' \\| 'lg' \\| 'xl'` | `'xl'` | Token-backed corner radius. |\n| bordered | `boolean` | `true` | Draw the theme hairline border. |\n| shadow | `ShadowTier` | — | Override the level's depth. |\n| className | `string` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | subtle preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `LEVEL_TINT[level]` | Tint token used for frosted mode. |\n\nAlso accepts the props of `ViewProps`."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"name": "View",
|
|
27
|
+
"category": "Primitives",
|
|
28
|
+
"kind": "package",
|
|
29
|
+
"summary": "Themed view wrapper",
|
|
30
|
+
"docs": "### View\n\nThemed view wrapper\n\n```tsx\nimport { View } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | -------- | ------- | ----------- |\n| className | `string` | — | |\n\nAlso accepts the props of React Native's `ViewProps`."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"name": "Pressable",
|
|
34
|
+
"category": "Primitives",
|
|
35
|
+
"kind": "package",
|
|
36
|
+
"summary": "Themed pressable wrapper",
|
|
37
|
+
"docs": "## Pressable\n\n```tsx\nimport { Pressable, Text } from '@thewhileloop/whileui';\n\n<Pressable\n accessibilityRole=\"button\"\n feedback=\"strong\"\n disabledFeedback=\"default\"\n loading={false}\n onPress={() => {}}\n>\n <Text>Open</Text>\n</Pressable>;\n```\n\n`Pressable` preserves React Native's event, ref, accessibility, and function-style\n`style` contracts. Feedback changes opacity without changing layout. Native press state\ncomes directly from React Native; web adds a motion-policy-aware opacity transition.\n\n\n| Prop | Type | Default | Description |\n| ---------------- | ----------------- | ----------- | --------------------------------------------------------------------------- |\n| className | `string` | — | Additional Tailwind classes. |\n| feedback | `PressedVariant` | `'none'` | Semantic press feedback. `none` preserves a plain React Native Pressable. |\n| disabledFeedback | `DisabledVariant` | `'default'` | Opacity used while the control is unavailable. |\n| loading | `boolean` | `false` | Loading controls are busy and non-interactive, just like disabled controls. |\n\nAlso accepts the props of React Native's `PressableProps`."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "Stack",
|
|
41
|
+
"category": "Primitives",
|
|
42
|
+
"kind": "package",
|
|
43
|
+
"summary": "Vertical flex layout with gap",
|
|
44
|
+
"docs": "### Stack\n\nVertical flex layout with gap\n\n```tsx\nimport { Stack } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ------------------------------------------------------------------- | ----------- | ----------- |\n| className | `string` | — | |\n| gap | `'none' \\| 'xs' \\| 'sm' \\| 'md' \\| 'lg' \\| 'xl' \\| '2xl'` | `'md'` | |\n| align | `'start' \\| 'center' \\| 'end' \\| 'stretch' \\| 'baseline'` | `'stretch'` | |\n| justify | `'start' \\| 'center' \\| 'end' \\| 'between' \\| 'around' \\| 'evenly'` | `'start'` | |\n\nAlso accepts the props of `ViewProps`."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "Row",
|
|
48
|
+
"category": "Primitives",
|
|
49
|
+
"kind": "package",
|
|
50
|
+
"summary": "Horizontal flex layout with gap",
|
|
51
|
+
"docs": "## Layout Primitives (Stack, Row)\n\nUse `Stack` for vertical layouts, `Row` for horizontal layouts. Both support `gap`, `align`, and `justify` variants.\n\n```tsx\nimport { Stack, Row } from '@thewhileloop/whileui';\n\n<Stack gap=\"lg\">\n <Text>Title</Text>\n <Row gap=\"md\" justify=\"between\">\n <Button>\n <ButtonText>Cancel</ButtonText>\n </Button>\n <Button>\n <ButtonText>Save</ButtonText>\n </Button>\n </Row>\n</Stack>;\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ------------------------------------------------------------------- | ---------- | ----------- |\n| className | `string` | — | |\n| gap | `'none' \\| 'xs' \\| 'sm' \\| 'md' \\| 'lg' \\| 'xl' \\| '2xl'` | `'md'` | |\n| align | `'start' \\| 'center' \\| 'end' \\| 'stretch' \\| 'baseline'` | `'center'` | |\n| justify | `'start' \\| 'center' \\| 'end' \\| 'between' \\| 'around' \\| 'evenly'` | `'start'` | |\n\nAlso accepts the props of `ViewProps`.\n\n### Container\n\nCaps content width and centres it. Phone-first layouts stretch to the viewport, which\nis right at 390px and wrong at 1600px — line lengths become unreadable and related\ncontrols drift apart.\n\n```tsx\n<Container size=\"lg\">{children}</Container>\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ---------------------------------------- | ------- | ----------- |\n| className | `string` | — | |\n| size | `'sm' \\| 'md' \\| 'lg' \\| 'xl' \\| 'full'` | `'md'` | |\n| fill | `boolean` | `false` | |\n\nAlso accepts the props of `ViewProps`.\n\nNo platform branch, and none is needed: every size is wider than any phone, so on iOS\nand Android the cap never engages and it renders as a plain full-width `View`. The same\ncomponent is a no-op on native and the whole fix on desktop.\n\nFor a scroll view, put the column on the content container so the scrollbar stays at\nthe window edge:\n\n```tsx\n<ScrollView contentContainerClassName=\"w-full max-w-5xl self-center\" />\n```"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "MotionProvider",
|
|
55
|
+
"category": "Root Providers",
|
|
56
|
+
"kind": "package",
|
|
57
|
+
"summary": "System reduced motion, explicit preview modes, and deterministic testing",
|
|
58
|
+
"docs": "### MotionProvider\n\nSystem reduced motion, explicit preview modes, and deterministic testing\n\n```tsx\nimport { MotionProvider } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | ----------------- | ---------- | ----------- |\n| children (required) | `React.ReactNode` | — | |\n| mode | `MotionMode` | `'system'` | |"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "Button",
|
|
62
|
+
"category": "Form Controls",
|
|
63
|
+
"kind": "package",
|
|
64
|
+
"summary": "4 sizes, ButtonText & ButtonIcon sub-components",
|
|
65
|
+
"docs": "## Button\n\n```tsx\nimport { Button, ButtonText, ButtonIcon } from '@thewhileloop/whileui';\n\n<Button variant=\"default\" size=\"default\" disabled={false} loading={false} onPress={() => {}}>\n <ButtonIcon>\n <Icon />\n </ButtonIcon>\n <ButtonText>Click me</ButtonText>\n {/* Placement is child order — a trailing ButtonIcon renders after the label. */}\n <ButtonIcon>\n <Icon />\n </ButtonIcon>\n</Button>;\n```\n\n\n| Prop | Type | Default | Description |\n| ---------------- | ----------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------- |\n| children | `React.ReactNode` | — | |\n| className | `string` | — | Additional Tailwind classes. |\n| disabled | `boolean` | `false` | Disable the button. |\n| loading | `boolean` | `false` | Shows a centered spinner, exposes busy state, and prevents interaction. |\n| disabledFeedback | `DisabledVariant` | — | Opacity used while the control is unavailable. |\n| size | `'sm' \\| 'lg' \\| 'default' \\| 'icon'` | `'default'` | |\n| variant | `'default' \\| 'destructive' \\| 'outline' \\| 'secondary' \\| 'ghost' \\| 'link'` | `'default'` | |\n\nAlso accepts the props of `PressableProps`."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "Input",
|
|
69
|
+
"category": "Form Controls",
|
|
70
|
+
"kind": "package",
|
|
71
|
+
"summary": "TextInput wrapper with themed styling",
|
|
72
|
+
"docs": "## Input\n\n```tsx\nimport { Input } from '@thewhileloop/whileui';\n\n<Input placeholder=\"Email\" variant=\"default\" value={value} onChangeText={setValue} />;\n```\n\nOn web, filled Input, Textarea, NumericInput, and SmartInput surfaces preserve their\nthemed background, foreground, and caret when the browser autofills them.\n\n\n| Prop | Type | Default | Description |\n| -------------- | --------------------------------- | ----------- | ----------- |\n| aria-invalid | `boolean` | — | |\n| className | `string` | — | |\n| prefix | `React.ReactNode` | — | |\n| suffix | `React.ReactNode` | — | |\n| inputClassName | `string` | — | |\n| variant | `'default' \\| 'error' \\| 'plain'` | `'default'` | |\n\nAlso accepts the props of `TextInputProps`."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "NumericInput",
|
|
76
|
+
"category": "Form Controls",
|
|
77
|
+
"kind": "package",
|
|
78
|
+
"summary": "Numeric input with prefix/suffix slots, optional steppers, and compact size",
|
|
79
|
+
"docs": "## NumericInput\n\n```tsx\nimport { NumericInput } from '@thewhileloop/whileui';\n\n<NumericInput\n value={amount}\n onValueChange={setAmount}\n prefix={<Text className=\"text-muted-foreground\">$</Text>}\n suffix={<Text className=\"text-muted-foreground\">USD</Text>}\n min={0}\n step={0.5}\n showSteppers\n size=\"compact\"\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| -------------- | --------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------- |\n| aria-invalid | `boolean` | — | |\n| className | `string` | — | |\n| inputClassName | `string` | — | |\n| prefix | `React.ReactNode` | — | |\n| suffix | `React.ReactNode` | — | |\n| value | `number \\| null` | — | Controlled numeric value. |\n| defaultValue | `number` | — | |\n| onValueChange | `(value: number \\| null) => void` | — | Numeric value change callback. |\n| min | `number` | — | |\n| max | `number` | — | |\n| step | `number` | `1` | |\n| showSteppers | `boolean` | `false` | Show decrement/increment controls. |\n| unstyled | `boolean` | `false` | Drop the field shell (border, background, radius, padding) for embedding inside another field shell like LabeledField. |\n| size | `'default' \\| 'compact'` | `'default'` | |\n| variant | `'default' \\| 'error'` | `'default'` | |\n\nAlso accepts the props of `TextInputProps`."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "OTPInput",
|
|
83
|
+
"category": "Form Controls",
|
|
84
|
+
"kind": "package",
|
|
85
|
+
"summary": "Verification code input with auto-focus, paste, secure mask, loading skeleton, error shake",
|
|
86
|
+
"docs": "## OTPInput\n\n```tsx\nimport { OTPInput } from '@thewhileloop/whileui';\n\n<OTPInput\n value={otp}\n onValueChange={setOtp}\n onComplete={(code) => verify(code)}\n length={6}\n variant=\"default\"\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------- | ----------- | ---------------------------------- |\n| length | `number` | `6` | Number of digit cells. |\n| value | `string` | — | Controlled value. |\n| onValueChange | `(value: string) => void` | — | Called on each digit change. |\n| onComplete | `(code: string) => void` | — | Called when all digits are filled. |\n| variant | `'default' \\| 'error'` | `'default'` | Error variant triggers shake. |\n| size | `'default' \\| 'compact'` | `'default'` | Cell size. |\n| secure | `boolean` | `false` | Mask digits with dots. |\n| disabled | `boolean` | `false` | |\n| autoFocus | `boolean` | `false` | Focus first cell on mount. |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"name": "FormField",
|
|
90
|
+
"category": "Form Controls",
|
|
91
|
+
"kind": "package",
|
|
92
|
+
"summary": "Compound API: FormField, FormLabel, FormControl, FormHint, FormMessage",
|
|
93
|
+
"docs": "## FormField\n\n```tsx\nimport {\n FormField,\n FormLabel,\n FormControl,\n FormHint,\n FormMessage,\n Input,\n} from '@thewhileloop/whileui';\n\n<FormField required invalid={Boolean(error)}>\n <FormLabel>Email</FormLabel>\n <FormControl>\n <Input placeholder=\"you@example.com\" />\n </FormControl>\n {error ? <FormMessage>{error}</FormMessage> : <FormHint>We'll never share your email.</FormHint>}\n</FormField>;\n```\n\n`invalid` and `disabled` are inherited by `Input`, `Textarea`, `NumericInput`, and\n`Select`. An explicit control prop wins. `useFormFieldState()` exposes the same state for\ncustom controls; labels, hints, messages, density, and required indicators remain part\nof the compound field rather than each input.\n\n`FormLabel` names the `Input`, `Textarea` or `NumericInput` beside it for assistive\ntechnology (`aria-labelledby` on web and Android; `useFormFieldState().labelId` gives a\ncustom control the same id). A label passed to the control itself wins."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"name": "LabeledField",
|
|
97
|
+
"category": "Form Controls",
|
|
98
|
+
"kind": "package",
|
|
99
|
+
"summary": "Field wrapper with label/helper/error plus left/right slots",
|
|
100
|
+
"docs": "## LabeledField\n\n```tsx\nimport { LabeledField, LabeledFieldControl, Input } from '@thewhileloop/whileui';\n\n<LabeledField\n label=\"Username\"\n hint=\"3-20 characters\"\n leftSlot={<Icon />}\n rightSlot={\n <Button size=\"sm\">\n <ButtonText>Check</ButtonText>\n </Button>\n }\n>\n <LabeledFieldControl>\n <Input className=\"border-0 bg-transparent px-0\" />\n </LabeledFieldControl>\n</LabeledField>;\n```"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"name": "Textarea",
|
|
104
|
+
"category": "Form Controls",
|
|
105
|
+
"kind": "package",
|
|
106
|
+
"summary": "Multi-line text input",
|
|
107
|
+
"docs": "### Textarea\n\nMulti-line text input\n\n```tsx\nimport { Textarea } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------ | --------------------------- | ----------- | ----------- |\n| aria-invalid | `boolean` | — | |\n| className | `string` | — | |\n| size | `'sm' \\| 'lg' \\| 'default'` | `'default'` | |\n\nAlso accepts the props of `TextInputProps`."
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "Checkbox",
|
|
111
|
+
"category": "Form Controls",
|
|
112
|
+
"kind": "package",
|
|
113
|
+
"summary": "Controlled/uncontrolled, accessibility roles",
|
|
114
|
+
"docs": "## Checkbox\n\n```tsx\nimport { Checkbox } from '@thewhileloop/whileui';\n\n<Checkbox checked={checked} onCheckedChange={setChecked} />;\n```\n\n\n| Prop | Type | Default | Description |\n| --------------- | ---------------------------- | ------- | ----------- |\n| checked | `boolean` | — | |\n| defaultChecked | `boolean` | `false` | |\n| onCheckedChange | `(checked: boolean) => void` | — | |\n| disabled | `boolean` | `false` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "Switch",
|
|
118
|
+
"category": "Form Controls",
|
|
119
|
+
"kind": "package",
|
|
120
|
+
"summary": "Controlled/uncontrolled, accessibility roles",
|
|
121
|
+
"docs": "## Switch\n\n```tsx\nimport { Switch } from '@thewhileloop/whileui';\n\n<Switch checked={checked} onCheckedChange={setChecked} />;\n```\n\n\n| Prop | Type | Default | Description |\n| --------------- | ---------------------------- | ------- | ----------- |\n| checked | `boolean` | — | |\n| defaultChecked | `boolean` | `false` | |\n| onCheckedChange | `(checked: boolean) => void` | — | |\n| disabled | `boolean` | `false` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"name": "RadioGroup",
|
|
125
|
+
"category": "Form Controls",
|
|
126
|
+
"kind": "package",
|
|
127
|
+
"summary": "RadioGroup + RadioGroupItem",
|
|
128
|
+
"docs": "## RadioGroup\n\n```tsx\nimport { RadioGroup, RadioGroupItem } from '@thewhileloop/whileui';\n\n<RadioGroup value={value} onValueChange={setValue}>\n <RadioGroupItem value=\"option1\" />\n <RadioGroupItem value=\"option2\" />\n</RadioGroup>;\n```"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "Select",
|
|
132
|
+
"category": "Form Controls",
|
|
133
|
+
"kind": "package",
|
|
134
|
+
"summary": "Uses `SelectOption` type `{value, label}`. Includes SelectGroup, SelectLabel, SelectSeparator",
|
|
135
|
+
"docs": "## Select\n\n```tsx\nimport {\n Select,\n SelectTrigger,\n SelectValue,\n SelectContent,\n SelectItem,\n} from '@thewhileloop/whileui';\n\n<Select value={value} onValueChange={setValue}>\n <SelectTrigger>\n <SelectValue placeholder=\"Select...\" />\n </SelectTrigger>\n <SelectContent frosted blurIntensity={24} blurTintToken=\"surfaceTranslucent\">\n <SelectItem label=\"Option 1\" value=\"1\" />\n <SelectItem label=\"Option 2\" value=\"2\" />\n </SelectContent>\n</Select>;\n```\n\nFocused and pressed options use the subtle interaction-state tokens; the primary token\nmarks the selected indicator without replacing the option's foreground colour. Long\nselected values stay on one line and truncate without displacing the trigger chevron.\nFor popover presentation on web, the options panel matches the trigger width, caps\nitself to the collision-available viewport, and wraps long option labels inside it.\n\n`SelectContent` supports optional frosted props:\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------------------------------------------------ | ------------- | ------------------------------------ |\n| className | `string` | — | |\n| portalHost | `string` | — | |\n| insets | `{ top?: number; bottom?: number; left?: number; right?: number }` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | subtle preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'popover'` | Tint token used for frosted mode. |\n\nAlso accepts the props of the underlying primitive.\n\n`SelectContent` keeps its listbox inside the safe area by default; pass `insets` only to\nchange that margin. A list taller than the screen scrolls inside the listbox on every\nplatform. Inside a Dialog, Sheet or other WhileUI overlay it renders in that\noverlay's layer automatically. Option values must be non-empty strings: the web\nprimitive rejects `\"\"`, so model \"none\" as an explicit value such as `'none'`."
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"name": "Label",
|
|
139
|
+
"category": "Form Controls",
|
|
140
|
+
"kind": "package",
|
|
141
|
+
"summary": "Form field label",
|
|
142
|
+
"docs": "### Label\n\nForm field label\n\n```tsx\nimport { Label } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | -------- | ------- | ----------- |\n| className | `string` | — | |\n\nAlso accepts the props of `TextProps`."
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "SegmentedControl",
|
|
146
|
+
"category": "Form Controls",
|
|
147
|
+
"kind": "package",
|
|
148
|
+
"summary": "SegmentedControl, SegmentedControlItem, SegmentedControlItemText with wrapping layout support",
|
|
149
|
+
"docs": "## SegmentedControl\n\n```tsx\nimport {\n SegmentedControl,\n SegmentedControlItem,\n SegmentedControlItemText,\n} from '@thewhileloop/whileui';\n\n<SegmentedControl value={unit} onValueChange={setUnit} variant=\"pill\" wrap>\n <SegmentedControlItem value=\"metric\">\n <SegmentedControlItemText>Metric</SegmentedControlItemText>\n </SegmentedControlItem>\n <SegmentedControlItem value=\"imperial\">\n <SegmentedControlItemText>Imperial</SegmentedControlItemText>\n </SegmentedControlItem>\n</SegmentedControl>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------- | ----------- | -------------------------- |\n| value | `string` | — | |\n| defaultValue | `string` | `''` | |\n| onValueChange | `(value: string) => void` | — | |\n| variant | `'default' \\| 'pill'` | `'default'` | Pill = rounded-full items. |\n| wrap | `boolean \\| 'auto'` | `true` | |\n| size | `'default' \\| 'compact'` | `'default'` | |\n| disabled | `boolean` | `false` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"name": "Toggle",
|
|
153
|
+
"category": "Form Controls",
|
|
154
|
+
"kind": "package",
|
|
155
|
+
"summary": "ToggleText sub-component",
|
|
156
|
+
"docs": "### Toggle\n\nToggleText sub-component\n\n```tsx\nimport { Toggle } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| --------------- | ------------------------------------------------ | ----------- | ----------- |\n| pressed | `boolean` | `false` | |\n| defaultPressed | `boolean` | `false` | |\n| onPressedChange | `(pressed: boolean) => void` | — | |\n| variant | `VariantProps<typeof toggleVariants>['variant']` | `'default'` | |\n| size | `VariantProps<typeof toggleVariants>['size']` | `'default'` | |\n| className | `string` | — | |\n| children | `React.ReactNode` | — | |\n\nAlso accepts the props of `PressableProps`."
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"name": "ToggleGroup",
|
|
160
|
+
"category": "Form Controls",
|
|
161
|
+
"kind": "package",
|
|
162
|
+
"summary": "Group of toggle items",
|
|
163
|
+
"docs": "### ToggleGroup\n\nGroup of toggle items\n\n```tsx\nimport { ToggleGroup } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------------------- | ----------- | ----------- |\n| type | `ToggleGroupType` | `'single'` | |\n| defaultValue | `string \\| string[]` | — | |\n| value | `string \\| string[]` | — | |\n| onValueChange | `(value: string \\| string[]) => void` | — | |\n| variant | `'default' \\| 'outline'` | `'default'` | |\n| size | `'default' \\| 'sm' \\| 'lg'` | `'default'` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"name": "Card",
|
|
167
|
+
"category": "Display",
|
|
168
|
+
"kind": "package",
|
|
169
|
+
"summary": "Compound: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter",
|
|
170
|
+
"docs": "## Card\n\n```tsx\nimport {\n Card,\n CardHeader,\n CardTitle,\n CardDescription,\n CardContent,\n CardFooter,\n} from '@thewhileloop/whileui';\n\n<Card padding=\"default\">\n <CardHeader>\n <CardTitle>Title</CardTitle>\n <CardDescription>Description</CardDescription>\n </CardHeader>\n <CardContent>{/* Content */}</CardContent>\n <CardFooter>{/* Footer */}</CardFooter>\n</Card>;\n\n<Card unstyled padding=\"none\" className=\"rounded-xl border border-border bg-card\">\n {/* advanced custom layouts */}\n</Card>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------------------- | ----------- | ------------------------------------------------------- |\n| shadow | `ShadowTier` | `'sm'` | Elevation tier; 'sm' by default, 'none' for flat cards. |\n| className | `string` | — | |\n| padding | `'none' \\| 'sm' \\| 'lg' \\| 'default'` | `'default'` | |\n| unstyled | `boolean` | `false` | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | — | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | — | Tint token used for frosted mode. |\n\nAlso accepts the props of `ViewProps`."
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"name": "Surface",
|
|
174
|
+
"category": "Display",
|
|
175
|
+
"kind": "package",
|
|
176
|
+
"summary": "Shared radius, padding, border, shadow, and optional frosted treatment",
|
|
177
|
+
"docs": "## Surface\n\n```tsx\nimport { Surface } from '@thewhileloop/whileui';\n\n<Surface level=\"raised\" padding=\"default\">\n {/* grouped content */}\n</Surface>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------------ | ------------------- | ------------------------------------ |\n| level | `SurfaceLevel` | `'base'` | Semantic background and depth tier. |\n| padding | `SurfacePaddingSize` | `'default'` | Token-backed interior spacing. |\n| radius | `'sm' \\| 'md' \\| 'lg' \\| 'xl'` | `'xl'` | Token-backed corner radius. |\n| bordered | `boolean` | `true` | Draw the theme hairline border. |\n| shadow | `ShadowTier` | — | Override the level's depth. |\n| className | `string` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | subtle preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `LEVEL_TINT[level]` | Tint token used for frosted mode. |\n\nAlso accepts the props of `ViewProps`."
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "Badge",
|
|
181
|
+
"category": "Display",
|
|
182
|
+
"kind": "package",
|
|
183
|
+
"summary": "BadgeText sub-component",
|
|
184
|
+
"docs": "## Badge\n\n```tsx\nimport { Badge, BadgeText } from '@thewhileloop/whileui';\n\n<Badge variant=\"default\">\n <BadgeText>New</BadgeText>\n</Badge>;\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ----------- |\n| className | `string` | — | |\n| variant | `'default' \\| 'soft' \\| 'destructive' \\| 'outline' \\| 'secondary' \\| 'success' \\| 'warning' \\| 'info' \\| 'soft-secondary' \\| 'soft-destructive' \\| 'soft-success' \\| 'soft-warning' \\| 'soft-info'` | `'default'` | |\n\nAlso accepts the props of `ViewProps`."
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"name": "FAB",
|
|
188
|
+
"category": "Display",
|
|
189
|
+
"kind": "package",
|
|
190
|
+
"summary": "Floating action button. `label` required; pass your own icon as children",
|
|
191
|
+
"docs": "### FAB\n\nOne primary action, raised above the content. It owns no icon dependency — pass the\nglyph as children and colour it from `useIconColors()`, because icon libraries need a\nconcrete colour rather than a class.\n\n```tsx\nimport { FAB, useIconColors } from '@thewhileloop/whileui';\nimport { Feather } from '@expo/vector-icons';\n\nconst icons = useIconColors();\n\n<FAB label=\"New group\" onPress={create}>\n <Feather name=\"plus\" size={24} color={icons.primaryForeground} />\n</FAB>;\n```\n\n| Prop | Type | Default | Notes |\n| ----------- | ------------------------------------------------ | ---------- | --------------------------------------- |\n| `label` | `string` | — | Required. The accessible name |\n| `variant` | `default \\| secondary \\| destructive \\| surface` | `default` | |\n| `size` | `sm \\| default \\| lg` | `default` | 48 / 56 / 64 — all clear touch minimums |\n| `placement` | `floating \\| floating-left \\| inline` | `floating` | `inline` lets the caller position it |\n\n`FABGroup` stacks secondary actions above one. Sizes start at 48dp, so a FAB never\nneeds `hitSlop`."
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"name": "Alert",
|
|
195
|
+
"category": "Display",
|
|
196
|
+
"kind": "package",
|
|
197
|
+
"summary": "AlertTitle & AlertDescription",
|
|
198
|
+
"docs": "## Alert\n\n```tsx\nimport { Alert, AlertTitle, AlertDescription } from '@thewhileloop/whileui';\n\n<Alert variant=\"default\">\n <AlertTitle>Heads up!</AlertTitle>\n <AlertDescription>Something happened.</AlertDescription>\n</Alert>;\n\n// Shown after a failed submit: also spoken on iOS when it appears.\n<Alert variant=\"destructive\" announce>\n <AlertTitle>Could not save</AlertTitle>\n <AlertDescription>Check your connection and try again.</AlertDescription>\n</Alert>;\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ---------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| 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. |\n| className | `string` | — | |\n| variant | `'default' \\| 'destructive' \\| 'success' \\| 'warning' \\| 'info'` | `'default'` | |\n\nAlso accepts the props of `ViewProps`."
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"name": "Avatar",
|
|
202
|
+
"category": "Display",
|
|
203
|
+
"kind": "package",
|
|
204
|
+
"summary": "AvatarImage + AvatarFallback",
|
|
205
|
+
"docs": "## Avatar\n\n```tsx\nimport { Avatar, AvatarImage, AvatarFallback } from '@thewhileloop/whileui';\n\n<Avatar size=\"default\">\n <AvatarImage source={{ uri: 'https://...' }} />\n <AvatarFallback>JD</AvatarFallback>\n</Avatar>;\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ----------------------------------- | ----------- | ----------- |\n| className | `string` | — | |\n| size | `'sm' \\| 'lg' \\| 'xl' \\| 'default'` | `'default'` | |\n\nAlso accepts the props of `ViewProps`.\n\n`AvatarImage` keeps `AvatarFallback` visible while loading or after an error. Its\n`onLoadingStatusChange` callback reports `'loading'`, `'loaded'`, and `'error'`, including\nwhen a changed source begins loading, while the standard image event callbacks still run."
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
"name": "DataRow",
|
|
209
|
+
"category": "Display",
|
|
210
|
+
"kind": "package",
|
|
211
|
+
"summary": "DataRow, DataRowLeft/Center/Right, DataRowLabel/Description/Value",
|
|
212
|
+
"docs": "## DataRow\n\n```tsx\nimport {\n DataRow,\n DataRowLeft,\n DataRowCenter,\n DataRowRight,\n DataRowLabel,\n DataRowDescription,\n DataRowValue,\n Avatar,\n AvatarFallback,\n} from '@thewhileloop/whileui';\n\n<DataRow>\n <DataRowLeft>\n <Avatar size=\"sm\">\n <AvatarFallback>JD</AvatarFallback>\n </Avatar>\n </DataRowLeft>\n <DataRowCenter>\n <DataRowLabel>Jane Doe</DataRowLabel>\n <DataRowDescription>Product Designer</DataRowDescription>\n </DataRowCenter>\n <DataRowRight>\n <DataRowValue>Owner</DataRowValue>\n </DataRowRight>\n</DataRow>;\n```"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"name": "Separator",
|
|
216
|
+
"category": "Display",
|
|
217
|
+
"kind": "package",
|
|
218
|
+
"summary": "Themed divider",
|
|
219
|
+
"docs": "### Separator\n\nThemed divider\n\n```tsx\nimport { Separator } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ----------- | ---------------------------- | -------------- | ----------- |\n| orientation | `'horizontal' \\| 'vertical'` | `'horizontal'` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"name": "Progress",
|
|
223
|
+
"category": "Display",
|
|
224
|
+
"kind": "package",
|
|
225
|
+
"summary": "Value-based progress bar with accessibility",
|
|
226
|
+
"docs": "## Progress\n\n```tsx\nimport { Progress } from '@thewhileloop/whileui';\n\n<Progress value={50} size=\"default\" />;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------ | --------------------------- | ----------- | ----------- |\n| value | `number` | `0` | |\n| indicatorClassName | `string` | — | |\n| className | `string` | — | |\n| size | `'sm' \\| 'lg' \\| 'default'` | `'default'` | |\n\nAlso accepts the props of `ViewProps`."
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"name": "Spinner",
|
|
230
|
+
"category": "Display",
|
|
231
|
+
"kind": "package",
|
|
232
|
+
"summary": "ActivityIndicator wrapper",
|
|
233
|
+
"docs": "### Spinner\n\nActivityIndicator wrapper\n\n```tsx\nimport { Spinner } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | --------------------------- | ----------- | ----------- |\n| className | `string` | — | |\n| size | `'sm' \\| 'lg' \\| 'default'` | `'default'` | |\n\nAlso accepts the props of `ActivityIndicatorProps`."
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"name": "Skeleton",
|
|
237
|
+
"category": "Display",
|
|
238
|
+
"kind": "package",
|
|
239
|
+
"summary": "Loading placeholder (pulse = opacity fade, shimmer = sweep)",
|
|
240
|
+
"docs": "### Skeleton\n\nLoading placeholder (pulse = opacity fade, shimmer = sweep)\n\n```tsx\nimport { Skeleton } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ---------------------- | --------- | ----------- |\n| className | `string` | — | |\n| variant | `'pulse' \\| 'shimmer'` | `'pulse'` | |\n\nAlso accepts the props of `ViewProps`."
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"name": "AspectRatio",
|
|
244
|
+
"category": "Display",
|
|
245
|
+
"kind": "package",
|
|
246
|
+
"summary": "Maintain aspect ratio container",
|
|
247
|
+
"docs": "### AspectRatio\n\nMaintain aspect ratio container\n\n```tsx\nimport { AspectRatio } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | -------- | ------- | ----------- |\n| ratio | `number` | `1` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"name": "Tabs",
|
|
251
|
+
"category": "Layout",
|
|
252
|
+
"kind": "package",
|
|
253
|
+
"summary": "TabsList, TabsTrigger, TabsContent",
|
|
254
|
+
"docs": "## Tabs\n\n```tsx\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@thewhileloop/whileui';\n\n<Tabs defaultValue=\"tab1\">\n <TabsList>\n <TabsTrigger value=\"tab1\">\n <Text>Tab 1</Text>\n </TabsTrigger>\n <TabsTrigger value=\"tab2\">\n <Text>Tab 2</Text>\n </TabsTrigger>\n </TabsList>\n <TabsContent value=\"tab1\">{/* Content 1 */}</TabsContent>\n <TabsContent value=\"tab2\">{/* Content 2 */}</TabsContent>\n</Tabs>;\n```"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
"name": "Accordion",
|
|
258
|
+
"category": "Layout",
|
|
259
|
+
"kind": "package",
|
|
260
|
+
"summary": "AccordionItem, AccordionTrigger, Content",
|
|
261
|
+
"docs": "## Accordion\n\n```tsx\nimport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from '@thewhileloop/whileui';\n\n<Accordion type=\"single\" collapsible>\n <AccordionItem value=\"item1\">\n <AccordionTrigger>\n <Text>Section 1</Text>\n </AccordionTrigger>\n <AccordionContent>\n <Text>Content 1</Text>\n </AccordionContent>\n </AccordionItem>\n</Accordion>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------ | -------------------- | ---------- | ----------- |\n| type | `AccordionType` | `'single'` | |\n| defaultValue | `string \\| string[]` | — | |\n| collapsible | `boolean` | `true` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
"name": "Collapsible",
|
|
265
|
+
"category": "Layout",
|
|
266
|
+
"kind": "package",
|
|
267
|
+
"summary": "CollapsibleTrigger, CollapsibleContent",
|
|
268
|
+
"docs": "### Collapsible\n\nCollapsibleTrigger, CollapsibleContent\n\n```tsx\nimport { Collapsible } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------ | ------------------------- | ------- | ----------- |\n| defaultOpen | `boolean` | `false` | |\n| open | `boolean` | — | |\n| onOpenChange | `(open: boolean) => void` | — | |\n| disabled | `boolean` | `false` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"name": "Container",
|
|
272
|
+
"category": "Layout",
|
|
273
|
+
"kind": "package",
|
|
274
|
+
"summary": "Centred content column with a max width",
|
|
275
|
+
"docs": "### Container\n\nCaps content width and centres it. Phone-first layouts stretch to the viewport, which\nis right at 390px and wrong at 1600px — line lengths become unreadable and related\ncontrols drift apart.\n\n```tsx\n<Container size=\"lg\">{children}</Container>\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ---------------------------------------- | ------- | ----------- |\n| className | `string` | — | |\n| size | `'sm' \\| 'md' \\| 'lg' \\| 'xl' \\| 'full'` | `'md'` | |\n| fill | `boolean` | `false` | |\n\nAlso accepts the props of `ViewProps`.\n\nNo platform branch, and none is needed: every size is wider than any phone, so on iOS\nand Android the cap never engages and it renders as a plain full-width `View`. The same\ncomponent is a no-op on native and the whole fix on desktop.\n\nFor a scroll view, put the column on the content container so the scrollbar stays at\nthe window edge:\n\n```tsx\n<ScrollView contentContainerClassName=\"w-full max-w-5xl self-center\" />\n```"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"name": "Dialog",
|
|
279
|
+
"category": "Overlays & Menus",
|
|
280
|
+
"kind": "package",
|
|
281
|
+
"summary": "Modal dialog with Header, Footer, Title, Description",
|
|
282
|
+
"docs": "## Dialog\n\n```tsx\nimport {\n Dialog,\n DialogTrigger,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogDescription,\n DialogFooter,\n DialogClose,\n} from '@thewhileloop/whileui';\n\n<Dialog>\n <DialogTrigger asChild>\n <Button>\n <ButtonText>Open</ButtonText>\n </Button>\n </DialogTrigger>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Title</DialogTitle>\n <DialogDescription>Description</DialogDescription>\n </DialogHeader>\n {/* Content */}\n <DialogFooter>\n <DialogClose asChild>\n <Button>\n <ButtonText>Close</ButtonText>\n </Button>\n </DialogClose>\n </DialogFooter>\n </DialogContent>\n</Dialog>;\n```\n\n`DialogContent` supports frosted props:\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------ | ---------------------- | ------------------------------------ |\n| className | `string` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |\n\nAlso accepts the props of `ViewProps`."
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"name": "AlertDialog",
|
|
286
|
+
"category": "Overlays & Menus",
|
|
287
|
+
"kind": "package",
|
|
288
|
+
"summary": "Confirmation dialog with Action/Cancel buttons",
|
|
289
|
+
"docs": "## AlertDialog\n\n```tsx\nimport {\n AlertDialog,\n AlertDialogTrigger,\n AlertDialogContent,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogAction,\n AlertDialogCancel,\n} from '@thewhileloop/whileui';\n\n<AlertDialog>\n <AlertDialogTrigger asChild>\n <Button variant=\"destructive\">\n <ButtonText>Delete</ButtonText>\n </Button>\n </AlertDialogTrigger>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Are you sure?</AlertDialogTitle>\n <AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Cancel</AlertDialogCancel>\n <AlertDialogAction>Delete</AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n</AlertDialog>;\n```\n\n`AlertDialogContent` supports the same frosted props as `DialogContent`.\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| className | `string` | — | |\n| 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`). |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |\n\nAlso accepts the props of `ViewProps`.\n\n### React Native: stacked modals\n\nRender a confirmation **inside** the overlay it belongs to. An `AlertDialog` or `Dialog`\nnested in a `Sheet` opens above it, and Android back or Escape closes the confirmation\nbefore the sheet. If `AlertDialog` must be a **sibling** of another fullscreen `Modal`, iOS\noften fails to show a second `Modal` on top; use **`presentation=\"overlay\"`** inside that\n`Modal`, or **`presentation=\"native\"`** for a system `Alert.alert` when themed UI is not\nrequired."
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"name": "Popover",
|
|
293
|
+
"category": "Overlays & Menus",
|
|
294
|
+
"kind": "package",
|
|
295
|
+
"summary": "Position-aware popover (requires PortalHost)",
|
|
296
|
+
"docs": "## Popover\n\n```tsx\nimport { Popover, PopoverTrigger, PopoverContent } from '@thewhileloop/whileui';\n\n<Popover>\n <PopoverTrigger asChild>\n <Button>\n <ButtonText>Open</ButtonText>\n </Button>\n </PopoverTrigger>\n <PopoverContent>\n <Text>Popover content</Text>\n </PopoverContent>\n</Popover>;\n```\n\n`PopoverContent` supports frosted props:\n\n\n| Prop | Type | Default | Description |\n| ------------- | ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------- |\n| className | `string` | — | |\n| portalHost | `string` | — | |\n| insets | `Partial<OverlayInsets>` | — | Collision padding. Defaults to safe area plus a margin so the panel never sits flush against an edge. |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | subtle preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'popover'` | Tint token used for frosted mode. |\n\nAlso accepts the props of the underlying primitive."
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"name": "Tooltip",
|
|
300
|
+
"category": "Overlays & Menus",
|
|
301
|
+
"kind": "package",
|
|
302
|
+
"summary": "Position-aware tooltip (requires PortalHost)",
|
|
303
|
+
"docs": "### Tooltip\n\nPosition-aware tooltip (requires PortalHost)\n\n```tsx\nimport { Tooltip } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------------- | ------------------------- | ------- | ----------- |\n| onOpenChange | `(open: boolean) => void` | — | |\n| delayDuration | `number` | — | |\n| skipDelayDuration | `number` | — | |\n| disableHoverableContent | `boolean` | — | |\n| children (required) | `React.ReactNode` | — | |"
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"name": "DropdownMenu",
|
|
307
|
+
"category": "Overlays & Menus",
|
|
308
|
+
"kind": "package",
|
|
309
|
+
"summary": "DropdownMenuTrigger, Content, Item, Label, Separator",
|
|
310
|
+
"docs": "## DropdownMenu\n\n```tsx\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n} from '@thewhileloop/whileui';\n\n<DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button>\n <ButtonText>Menu</ButtonText>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent>\n <DropdownMenuLabel>Actions</DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <Text>Edit</Text>\n </DropdownMenuItem>\n <DropdownMenuItem>\n <Text>Delete</Text>\n </DropdownMenuItem>\n </DropdownMenuContent>\n</DropdownMenu>;\n```\n\n`DropdownMenuContent`, `ContextMenuContent`, and `MenubarContent` support the same frosted props as `PopoverContent` (default blur preset: medium)."
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"name": "ContextMenu",
|
|
314
|
+
"category": "Overlays & Menus",
|
|
315
|
+
"kind": "package",
|
|
316
|
+
"summary": "Long-press context menu",
|
|
317
|
+
"docs": "## ContextMenu\n\n`ContextMenuContent` supports the same frosted props as `PopoverContent` (default blur preset: medium)."
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"name": "HoverCard",
|
|
321
|
+
"category": "Overlays & Menus",
|
|
322
|
+
"kind": "package",
|
|
323
|
+
"summary": "Position-aware hover card (requires PortalHost)",
|
|
324
|
+
"docs": "## HoverCard\n\n`HoverCardContent` supports the same frosted props as `PopoverContent` (default blur preset: subtle)."
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"name": "Menubar",
|
|
328
|
+
"category": "Overlays & Menus",
|
|
329
|
+
"kind": "package",
|
|
330
|
+
"summary": "Horizontal menu bar",
|
|
331
|
+
"docs": "## Menubar\n\n```tsx\nimport {\n Menubar,\n MenubarMenu,\n MenubarTrigger,\n MenubarContent,\n MenubarLabel,\n MenubarItem,\n MenubarSeparator,\n} from '@thewhileloop/whileui';\n\n<Menubar>\n <MenubarMenu>\n <MenubarTrigger>\n <Text className=\"text-foreground text-sm\">File</Text>\n </MenubarTrigger>\n <MenubarContent>\n <MenubarLabel>Project</MenubarLabel>\n <MenubarItem>\n <Text className=\"text-foreground\">New Project</Text>\n </MenubarItem>\n <MenubarSeparator />\n <MenubarItem>\n <Text className=\"text-foreground\">Export</Text>\n </MenubarItem>\n </MenubarContent>\n </MenubarMenu>\n</Menubar>;\n```\n\n`MenubarLabel` is a non-interactive section heading. It takes a string or elements, and\nannounces as a heading to assistive technology — as do `DropdownMenuLabel` and\n`ContextMenuLabel`.\n\n`MenubarContent` supports the same frosted props as `PopoverContent` (default blur preset: medium).\n\n---\n\n# Blocks API"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"name": "Toast",
|
|
335
|
+
"category": "Feedback",
|
|
336
|
+
"kind": "package",
|
|
337
|
+
"summary": "ToastProvider, ToastContainer, useToast() hook",
|
|
338
|
+
"docs": "## Toast\n\n```tsx\nimport { ToastProvider, ToastContainer, useToast } from '@thewhileloop/whileui';\n\n// Wrap app\n<ToastProvider>\n <App />\n <ToastContainer position=\"top\" />\n</ToastProvider>;\n\n// Use in component\nconst { toast } = useToast();\ntoast({ title: 'Success', description: 'Saved!', variant: 'success' });\n```\n\n| Toast Options | Type |\n| ------------- | ----------------------------------------- |\n| title | `string` |\n| description | `string` |\n| variant | `'default' \\| 'success' \\| 'destructive'` |\n| duration | `number` (ms) |\n\nEach toast owns its duration from the moment it appears; adding or dismissing another\ntoast does not restart timers already in progress.\n\nEvery variant sits on the theme's `popover` surface; `success` and `destructive` mark the\ntoast with a status dot and tinted border rather than a solid fill. The stack is capped\nat `max-w-md` and centred, which never engages on a phone. A toast is announced politely\nto screen readers without taking focus, and its slide-in follows the motion policy."
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
"name": "SignInForm",
|
|
342
|
+
"category": "Auth",
|
|
343
|
+
"kind": "template",
|
|
344
|
+
"summary": "Email/password sign in with callbacks",
|
|
345
|
+
"docs": "## SignInForm\n\nCopy `sign-in-form.tsx` and its sibling `auth-form-footer.tsx` from\n`apps/showcase/catalog/templates/auth/`, then:\n\n```tsx\nimport { SignInForm } from './templates/auth'; // or your path\n\n<SignInForm\n onSubmit={({ email, password }) => signIn(email, password)}\n onForgotPassword={() => navigate('ForgotPassword')}\n onSignUp={() => navigate('SignUp')}\n onGooglePress={() => signInWithGoogle()}\n onApplePress={() => signInWithApple()}\n/>;\n```\n\n| Prop | Type | Description |\n| ---------------- | ------------------------------------- | ---------------------------- |\n| onSubmit | `(data: { email, password }) => void` | Called on sign-in submit |\n| onForgotPassword | `() => void` | Called when \"Forgot?\" tapped |\n| onSignUp | `() => void` | Called when \"Sign Up\" tapped |\n| onGooglePress | `() => void` | Called when Google tapped |\n| onApplePress | `() => void` | Called when Apple tapped |",
|
|
346
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
"name": "SignUpForm",
|
|
350
|
+
"category": "Auth",
|
|
351
|
+
"kind": "template",
|
|
352
|
+
"summary": "Registration form with callbacks",
|
|
353
|
+
"docs": "## SignUpForm\n\nCopy `sign-up-form.tsx` and its sibling `auth-form-footer.tsx` from\n`apps/showcase/catalog/templates/auth/`, then:\n\n```tsx\nimport { SignUpForm } from './templates/auth'; // or your path\n\n<SignUpForm\n onSubmit={({ firstName, lastName, email, password }) =>\n signUp(firstName, lastName, email, password)\n }\n onSignIn={() => navigate('SignIn')}\n onGooglePress={() => signInWithGoogle()}\n onApplePress={() => signInWithApple()}\n/>;\n```\n\n| Prop | Type | Description |\n| ------------- | ---------------------------------------------------------- | ---------------------------- |\n| onSubmit | `(data: { firstName, lastName, email, password }) => void` | Called on registration |\n| onSignIn | `() => void` | Called when \"Sign In\" tapped |\n| onGooglePress | `() => void` | Called when Google tapped |\n| onApplePress | `() => void` | Called when Apple tapped |",
|
|
354
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
"name": "ForgotPasswordForm",
|
|
358
|
+
"category": "Auth",
|
|
359
|
+
"kind": "template",
|
|
360
|
+
"summary": "Password reset request",
|
|
361
|
+
"docs": null,
|
|
362
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"name": "ResetPasswordForm",
|
|
366
|
+
"category": "Auth",
|
|
367
|
+
"kind": "template",
|
|
368
|
+
"summary": "Set new password",
|
|
369
|
+
"docs": null,
|
|
370
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
"name": "VerifyEmailForm",
|
|
374
|
+
"category": "Auth",
|
|
375
|
+
"kind": "template",
|
|
376
|
+
"summary": "Email verification code input",
|
|
377
|
+
"docs": null,
|
|
378
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
"name": "SocialConnections",
|
|
382
|
+
"category": "Auth",
|
|
383
|
+
"kind": "template",
|
|
384
|
+
"summary": "OAuth provider buttons",
|
|
385
|
+
"docs": null,
|
|
386
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
"name": "UserMenu",
|
|
390
|
+
"category": "Auth",
|
|
391
|
+
"kind": "template",
|
|
392
|
+
"summary": "Profile dropdown for auth flows",
|
|
393
|
+
"docs": null,
|
|
394
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
"name": "AppShell",
|
|
398
|
+
"category": "Navigation",
|
|
399
|
+
"kind": "package",
|
|
400
|
+
"summary": "Layout shell with header/footer/bottomNav slots",
|
|
401
|
+
"docs": "## AppShell\n\nLayout shell for full-screen pages. Keep shell chrome mounted and swap only content via `loading` + `skeleton`.\n\n```tsx\nimport { AppShell, PageSkeleton } from '@thewhileloop/whileui';\n\n<AppShell\n header={<Header title=\"Settings\" />}\n bottomNav={<BottomNav items={[...]} activeKey=\"settings\" onSelect={setTab} />}\n loading={loading}\n skeleton={<PageSkeleton variant=\"settings\" headerPlaceholder />}\n>\n <ScrollView className=\"flex-1 p-4\">{/* Content */}</ScrollView>\n</AppShell>;\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ----------------- | ------- | --------------------------------------------------------- |\n| header | `React.ReactNode` | — | Header slot. |\n| footer | `React.ReactNode` | — | Footer slot. |\n| bottomNav | `React.ReactNode` | — | Bottom navigation slot. |\n| safeArea | `boolean` | `true` | Wrap in SafeAreaView. |\n| loading | `boolean` | `false` | Keep shell mounted while swapping content with skeleton. |\n| skeleton | `React.ReactNode` | — | Optional custom loading placeholder for the content area. |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
"name": "Header",
|
|
405
|
+
"category": "Navigation",
|
|
406
|
+
"kind": "package",
|
|
407
|
+
"summary": "Top app bar with back/actions",
|
|
408
|
+
"docs": "## Header\n\n```tsx\nimport { Header, HeaderBackButton } from '@thewhileloop/whileui';\n\n<Header\n title=\"Settings\"\n subtitle=\"Manage preferences\"\n leftAction={<HeaderBackButton onPress={() => {}} />}\n rightActions={[\n { key: 'search', icon: <Icon />, onPress: () => {}, accessibilityLabel: 'Search' },\n ]}\n/>;\n```"
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
"name": "BottomNav",
|
|
412
|
+
"category": "Navigation",
|
|
413
|
+
"kind": "package",
|
|
414
|
+
"summary": "Tab-style bottom navigation bar",
|
|
415
|
+
"docs": "## BottomNav\n\n```tsx\nimport { BottomNav } from '@thewhileloop/whileui';\n\n<BottomNav\n items={[\n { key: 'home', label: 'Home', icon: <Icon /> },\n { key: 'profile', label: 'Profile', icon: <Icon />, badge: 3 },\n ]}\n activeKey=\"home\"\n onSelect={(key) => {}}\n/>;\n```"
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"name": "FloatingBottomNav",
|
|
419
|
+
"category": "Navigation",
|
|
420
|
+
"kind": "package",
|
|
421
|
+
"summary": "Elevated bottom nav with safe area support",
|
|
422
|
+
"docs": "### FloatingBottomNav\n\nElevated bottom nav with safe area support\n\n```tsx\nimport { FloatingBottomNav } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| -------------------- | ------------------------- | ------- | ----------- |\n| items (required) | `FloatingBottomNavItem[]` | — | |\n| activeKey (required) | `string` | — | |\n| onSelect (required) | `(key: string) => void` | — | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"name": "TabBar",
|
|
426
|
+
"category": "Navigation",
|
|
427
|
+
"kind": "package",
|
|
428
|
+
"summary": "Top tab bar with indicator",
|
|
429
|
+
"docs": "### TabBar\n\nTop tab bar with indicator\n\n```tsx\nimport { TabBar } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| -------------------- | ------------------------------------- | ----------- | ----------- |\n| items (required) | `TabBarItem[]` | — | |\n| activeKey (required) | `string` | — | |\n| onSelect (required) | `(key: string) => void` | — | |\n| variant | `'default' \\| 'pills' \\| 'underline'` | `'default'` | |\n| scrollable | `boolean` | `false` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
"name": "DrawerMenu",
|
|
433
|
+
"category": "Navigation",
|
|
434
|
+
"kind": "package",
|
|
435
|
+
"summary": "Drawer with sections and items",
|
|
436
|
+
"docs": "## DrawerMenu\n\n```tsx\nimport { DrawerMenu } from '@thewhileloop/whileui';\n\n<DrawerMenu\n visible={open}\n onClose={() => setOpen(false)}\n sections={[\n {\n title: 'Menu',\n items: [\n { key: 'home', label: 'Home', icon: <Icon /> },\n { key: 'settings', label: 'Settings', icon: <Icon /> },\n ],\n },\n ]}\n activeKey=\"home\"\n onSelect={(key) => {}}\n header={<View>...</View>}\n footer={<Text>v1.0</Text>}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | ----------------------- | ---------------------- | ----------------------------------------------------------- |\n| visible (required) | `boolean` | — | |\n| onClose (required) | `() => void` | — | |\n| sections (required) | `DrawerMenuSection[]` | — | |\n| activeKey | `string` | — | |\n| onSelect | `(key: string) => void` | — | |\n| header | `React.ReactNode` | — | |\n| footer | `React.ReactNode` | — | |\n| maxWidth | `number` | — | Max width in px. On web, defaults to 360 when not provided. |\n| className | `string` | — | |\n| style | `StyleProp<ViewStyle>` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |\n\n\nCloses from the backdrop, an item, Android back or Escape on web. It is hosted in a modal\non every platform, so keyboard focus stays inside it while it is open."
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
"name": "ActionBar",
|
|
440
|
+
"category": "Layout",
|
|
441
|
+
"kind": "package",
|
|
442
|
+
"summary": "Sticky bottom action row with safe-area padding",
|
|
443
|
+
"docs": "## ActionBar\n\n```tsx\nimport { ActionBar, Button, ButtonText } from '@thewhileloop/whileui';\n\n<ActionBar>\n <Button variant=\"outline\" className=\"flex-1\">\n <ButtonText>Cancel</ButtonText>\n </Button>\n <Button className=\"flex-1\">\n <ButtonText>Save</ButtonText>\n </Button>\n</ActionBar>;\n```"
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
"name": "ConfirmActionSheet",
|
|
447
|
+
"category": "Layout",
|
|
448
|
+
"kind": "package",
|
|
449
|
+
"summary": "Reusable destructive confirmation sheet",
|
|
450
|
+
"docs": "## ConfirmActionSheet\n\n```tsx\nimport { ConfirmActionSheet } from '@thewhileloop/whileui';\n\n<ConfirmActionSheet\n open={open}\n onOpenChange={setOpen}\n title=\"Delete project?\"\n description=\"This action cannot be undone.\"\n confirmLabel=\"Delete\"\n destructive\n onConfirm={() => deleteProject()}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------------- | ---------------------------- | ---------------------- | ------------------------------------ |\n| open (required) | `boolean` | — | |\n| onOpenChange (required) | `(open: boolean) => void` | — | |\n| title (required) | `string` | — | |\n| description | `string` | — | |\n| actions | `ConfirmActionSheetAction[]` | — | |\n| confirmLabel | `string` | `'Confirm'` | |\n| cancelLabel | `string` | `'Cancel'` | |\n| onConfirm | `() => void` | — | |\n| onCancel | `() => void` | — | |\n| destructive | `boolean` | `true` | |\n| className | `string` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |\n\nAlso accepts the props of `ViewProps`."
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
"name": "Sheet",
|
|
454
|
+
"category": "Layout",
|
|
455
|
+
"kind": "package",
|
|
456
|
+
"summary": "Bottom sheet modal with header/content/footer slots",
|
|
457
|
+
"docs": "## Sheet\n\nBottom sheet modal with slide animation. Slots: `SheetHeader`, `SheetContent`, `SheetFooter`, `SheetClose`.\n\n```tsx\nimport {\n Sheet,\n SheetHeader,\n SheetContent,\n SheetFooter,\n SheetClose,\n Button,\n ButtonText,\n} from '@thewhileloop/whileui';\n\n<Sheet open={open} onOpenChange={setOpen} maxHeight=\"half\">\n <SheetHeader title=\"Settings\" description=\"Adjust preferences\" />\n <SheetContent>{/* Scrollable body */}</SheetContent>\n <SheetFooter>\n <SheetClose asChild>\n <Button>\n <ButtonText>Save</ButtonText>\n </Button>\n </SheetClose>\n </SheetFooter>\n</Sheet>;\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------------- | ---------------------------- | ---------------------- | ----------------------------------------------------------- |\n| open (required) | `boolean` | — | |\n| onOpenChange (required) | `(open: boolean) => void` | — | |\n| children (required) | `React.ReactNode` | — | |\n| maxHeight | `'half' \\| 'full' \\| number` | `'full'` | Max height: 'half' \\| 'full' \\| number (px). Default 'full' |\n| maxWidth | `number` | — | Web: max width. Default ~360 for mobile-first. |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |\n\n\n`SheetContent` is a `ScrollView` and accepts its props. It enables nested scrolling and\nkeeps taps on inputs while the keyboard is open (`keyboardShouldPersistTaps=\"handled\"`).\nYour `contentContainerStyle` is merged after the default padding. Tapping the backdrop\ncloses the sheet; a drag across the backdrop does not, so a missed scroll never dismisses\nit. There is no drag-to-dismiss: close with the backdrop, Android back, Escape on web or\n`SheetClose`. A Select, Popover or confirmation Dialog placed inside the sheet opens above\nit, and back or Escape closes that inner layer before the sheet."
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"name": "FormModalScreen",
|
|
461
|
+
"category": "Layout",
|
|
462
|
+
"kind": "package",
|
|
463
|
+
"summary": "Modal scaffold for forms with loading states",
|
|
464
|
+
"docs": "### FormModalScreen\n\nModal scaffold for forms with loading states\n\n```tsx\nimport { FormModalScreen } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | ----------------- | ------------- | ----------------------------------------------------------------------------------------- |\n| className | `string` | — | |\n| title (required) | `string` | — | |\n| subtitle | `string` | — | |\n| leftSlot | `React.ReactNode` | — | |\n| rightSlot | `React.ReactNode` | — | |\n| onClose | `() => void` | — | |\n| loading | `boolean` | `false` | |\n| saving | `boolean` | `false` | |\n| savingText | `string` | `'Saving...'` | |\n| loadingSpinnerColor | `string` | — | Hex color for spinner in loading state. ActivityIndicator requires hex. |\n| savingSpinnerColor | `string` | — | Hex color for spinner in saving banner. Defaults to platform (Android blue) when not set. |\n| scrollEnabled | `boolean` | `true` | |\n| children (required) | `React.ReactNode` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"name": "ContentSkeleton",
|
|
468
|
+
"category": "Layout",
|
|
469
|
+
"kind": "package",
|
|
470
|
+
"summary": "Page/content placeholder with variants (list, card, generic)",
|
|
471
|
+
"docs": "## ContentSkeleton\n\nPage/content placeholder with layout presets. Use while loading data instead of a spinner when you want to preview the layout.\n\n```tsx\nimport { ContentSkeleton } from '@thewhileloop/whileui';\n\n<ContentSkeleton variant=\"list\" rows={4} />\n<ContentSkeleton variant=\"card\" />\n<ContentSkeleton variant=\"generic\" />\n```\n\n\n| Prop | Type | Default | Description |\n| --------- | ------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------- |\n| variant | `'list' \\| 'card' \\| 'generic'` | `'list'` | Layout preset. `list` = avatar + lines rows, `card` = card with image/body, `generic` = simple page lines. |\n| rows | `number` | `4` | Number of list rows (list variant only). Default 4. |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"name": "PageSkeleton",
|
|
475
|
+
"category": "Layout",
|
|
476
|
+
"kind": "package",
|
|
477
|
+
"summary": "Variant-based page layouts (dashboard, list, settings, card, generic)",
|
|
478
|
+
"docs": "## PageSkeleton\n\nVariant-based page layouts for loading states. Replaces app-specific skeletons with presets for dashboard, list, settings, card, and generic pages.\n\n```tsx\nimport { PageSkeleton } from '@thewhileloop/whileui';\n\n<PageSkeleton variant=\"dashboard\" />\n<PageSkeleton variant=\"list\" count={5} />\n<PageSkeleton variant=\"settings\" count={6} />\n<PageSkeleton variant=\"card\" />\n<PageSkeleton variant=\"generic\" />\n<PageSkeleton variant=\"dashboard\" headerPlaceholder />\n<PageSkeleton variant=\"list\" header={<Header title=\"Loading...\" />} />\n<PageSkeleton variant=\"list\" padding=\"none\" className=\"flex-1\" />\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------ | ------------------------------------------ | ----------- | ------------------------------------------------------------------- |\n| variant (required) | `PageSkeletonVariant` | — | Layout preset. |\n| count | `number` | `3` | Rows/items for list (default 3) or settings (default 4) variant. |\n| padding | `PageSkeletonPadding` | `'default'` | Container padding. |\n| header | `ReactNode` | — | Optional header slot rendered above skeleton content. |\n| headerPlaceholder | `boolean \\| PageSkeletonHeaderPlaceholder` | `false` | Optional placeholder header (when no real header is available yet). |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"name": "ScreenSkeleton",
|
|
482
|
+
"category": "Layout",
|
|
483
|
+
"kind": "package",
|
|
484
|
+
"summary": "Header slot/placeholder + PageSkeleton content in one block",
|
|
485
|
+
"docs": "## ScreenSkeleton\n\nConvenience block for loading screens that need both header and content continuity.\n\n```tsx\nimport { ScreenSkeleton } from '@thewhileloop/whileui';\n\n<ScreenSkeleton variant=\"dashboard\" headerPlaceholder />\n<ScreenSkeleton variant=\"list\" count={5} header={<Header title=\"Projects\" />} />\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------- | ------------------------------------------ | ----------- | -------------------------------------- |\n| variant | `PageSkeletonVariant` | `'generic'` | Skeleton content preset. |\n| count | `number` | — | Rows/items for list/settings variants. |\n| padding | `PageSkeletonPadding` | `'default'` | Inner content padding. |\n| header | `ReactNode` | — | Real header slot. |\n| headerPlaceholder | `boolean \\| PageSkeletonHeaderPlaceholder` | `'default'` | Placeholder header. |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
"name": "ErrorBoundary",
|
|
489
|
+
"category": "Layout",
|
|
490
|
+
"kind": "package",
|
|
491
|
+
"summary": "React ErrorBoundary that renders ErrorState by default",
|
|
492
|
+
"docs": "## ErrorBoundary\n\nReact ErrorBoundary that catches render errors and renders ErrorState by default.\n\n```tsx\nimport { ErrorBoundary } from '@thewhileloop/whileui';\n\n<ErrorBoundary onError={(err) => console.error(err)}>\n <App />\n</ErrorBoundary>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | --------------------------------------------------------------- | ------- | -------------------------------------------------- |\n| children (required) | `ReactNode` | — | |\n| fallback | `ReactNode \\| ((error: Error, reset: () => void) => ReactNode)` | — | Custom fallback. If not provided, uses ErrorState. |\n| onError | `(error: Error, errorInfo: ErrorInfo) => void` | — | Called when error is caught. |"
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"name": "EmptyState",
|
|
496
|
+
"category": "Layout",
|
|
497
|
+
"kind": "package",
|
|
498
|
+
"summary": "Empty content placeholder",
|
|
499
|
+
"docs": "## EmptyState\n\n```tsx\nimport { EmptyState } from '@thewhileloop/whileui';\n\n<EmptyState\n icon={<Icon />}\n title=\"No items\"\n description=\"Add your first item.\"\n action={{ label: 'Add Item', onPress: () => {} }}\n/>;\n```"
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
"name": "ErrorState",
|
|
503
|
+
"category": "Layout",
|
|
504
|
+
"kind": "package",
|
|
505
|
+
"summary": "Error display with retry",
|
|
506
|
+
"docs": "### ErrorState\n\nError display with retry\n\n```tsx\nimport { ErrorState } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ----------- | ----------------- | ------------------------ | ----------- |\n| icon | `React.ReactNode` | — | |\n| title | `string` | `'Something went wrong'` | |\n| description | `string` | — | |\n| error | `Error \\| string` | — | |\n| onRetry | `() => void` | — | |\n| retryLabel | `string` | `'Try again'` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
"name": "LoadingState",
|
|
510
|
+
"category": "Layout",
|
|
511
|
+
"kind": "package",
|
|
512
|
+
"summary": "In-flow loading placeholder for a section still fetching",
|
|
513
|
+
"docs": "## LoadingState\n\n```tsx\nimport { LoadingState } from '@thewhileloop/whileui';\n\n<LoadingState label=\"Loading activity\" />;\n```\n\nThe in-flow counterpart of `EmptyState` and `ErrorState` for a section that is still\nfetching. The spinner is named for assistive technology by `label` (default \"Loading\").\n\n\n| Prop | Type | Default | Description |\n| --------- | --------------------------- | ----------- | ------------------------------------------------------------------------------ |\n| label | `string` | — | Read by assistive technology as the spinner's name; shown under it when given. |\n| size | `'sm' \\| 'default' \\| 'lg'` | `'default'` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
514
|
+
},
|
|
515
|
+
{
|
|
516
|
+
"name": "LoadingScreen",
|
|
517
|
+
"category": "Layout",
|
|
518
|
+
"kind": "package",
|
|
519
|
+
"summary": "Full-screen loading indicator",
|
|
520
|
+
"docs": "### LoadingScreen\n\nFull-screen loading indicator\n\n```tsx\nimport { LoadingScreen } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------ | ---------------------- | ------- | ------------------------------------------------------ |\n| message | `string` | — | |\n| size | `'sm' \\| 'md' \\| 'lg'` | `'md'` | |\n| spinnerColor | `string` | — | Hex color for spinner. ActivityIndicator requires hex. |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"name": "PullToRefreshScrollView",
|
|
524
|
+
"category": "Layout",
|
|
525
|
+
"kind": "package",
|
|
526
|
+
"summary": "Themed ScrollView with RefreshControl (useThemeTokens for colors)",
|
|
527
|
+
"docs": "## PullToRefreshScrollView\n\nThemed ScrollView with RefreshControl. Uses `useThemeTokens` for spinner color.\n\n```tsx\nimport { PullToRefreshScrollView } from '@thewhileloop/whileui';\n\n<PullToRefreshScrollView refreshing={refreshing} onRefresh={fetchData} refreshColor=\"#22c55e\">\n {content}\n</PullToRefreshScrollView>;\n```\n\n\n| Prop | Type | Default | Description |\n| --------------------- | ----------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| refreshing (required) | `boolean` | — | Whether refresh is in progress. |\n| 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. |\n| refreshColor | `string` | — | Override tint/colors. Defaults to theme primary. |\n| className | `string` | — | |\n\nAlso accepts the props of `ScrollViewProps`.\n\nThe scroll view owns the refresh gesture; don't pass `refreshControl`. It bounces even\nwhen content is shorter than the screen, so a short list can still be pulled. Screen\nreader users get the same refresh as a **Refresh** accessibility action, since they\ncannot pull. It is ignored while `refreshing` is true, and any `accessibilityActions` you\npass are kept."
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
"name": "KeyboardAwareScreen",
|
|
531
|
+
"category": "Layout",
|
|
532
|
+
"kind": "package",
|
|
533
|
+
"summary": "One screen-level keyboard policy for bottom-docked controls",
|
|
534
|
+
"docs": "## KeyboardAwareScreen and DockedInput\n\nUse 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.\n\n```tsx\nimport { DockedInput, KeyboardAwareScreen, SmartInput } from '@thewhileloop/whileui';\n\n<KeyboardAwareScreen>\n <ScrollView contentContainerStyle={{ paddingBottom: inputHeight }} />\n <DockedInput onHeightChange={setInputHeight}>\n <SmartInput variant=\"card\" safeArea={false} />\n </DockedInput>\n</KeyboardAwareScreen>;\n```"
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
"name": "DockedInput",
|
|
538
|
+
"category": "Layout",
|
|
539
|
+
"kind": "package",
|
|
540
|
+
"summary": "Safe-area-aware bottom dock that reports its complete height",
|
|
541
|
+
"docs": "## KeyboardAwareScreen and DockedInput\n\nUse 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.\n\n```tsx\nimport { DockedInput, KeyboardAwareScreen, SmartInput } from '@thewhileloop/whileui';\n\n<KeyboardAwareScreen>\n <ScrollView contentContainerStyle={{ paddingBottom: inputHeight }} />\n <DockedInput onHeightChange={setInputHeight}>\n <SmartInput variant=\"card\" safeArea={false} />\n </DockedInput>\n</KeyboardAwareScreen>;\n```"
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
"name": "SmartInput",
|
|
545
|
+
"category": "Layout",
|
|
546
|
+
"kind": "package",
|
|
547
|
+
"summary": "Compose input with slots, variants, and optional rotating examples",
|
|
548
|
+
"docs": "## SmartInput\n\nCompose 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`.\n\n```tsx\nimport { SmartInput, Button, ButtonText } from '@thewhileloop/whileui';\n\n<SmartInput\n value={message}\n onChangeText={setMessage}\n placeholderExamples={['Log breakfast', 'Add a 30 minute walk', 'Set weight to 72 kg']}\n leftSlot={\n <Button variant=\"ghost\" size=\"icon\" accessibilityLabel=\"Add\">\n <Icon name=\"add\" />\n </Button>\n }\n rightSlot={\n <Button size=\"icon\" onPress={handleSend}>\n <ButtonText>Send</ButtonText>\n </Button>\n }\n/>;\n```"
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
"name": "SplashScreen",
|
|
552
|
+
"category": "Layout",
|
|
553
|
+
"kind": "package",
|
|
554
|
+
"summary": "Branded splash (fade/scale/slide variants)",
|
|
555
|
+
"docs": "## SplashScreen\n\n```tsx\nimport { SplashScreen } from '@thewhileloop/whileui';\n\n<SplashScreen\n logo={<MyLogo />}\n appName=\"MyApp\"\n tagline=\"Your tagline\"\n variant=\"scale\"\n duration={1500}\n showLoading\n onAnimationComplete={() => {}}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | ------------------------------ | --------- | ----------------------------------------------- |\n| appName | `string` | — | App name displayed below logo |\n| tagline | `string` | — | Tagline displayed below app name |\n| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |\n| variant | `'fade' \\| 'scale' \\| 'slide'` | `'scale'` | Animation variant |\n| duration | `number` | `800` | Animation duration in ms |\n| onAnimationComplete | `() => void` | — | Callback when animation completes |\n| showLoading | `boolean` | `false` | Whether to show loading indicator |\n| loadingText | `string` | — | Loading text |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
"name": "MinimalSplash",
|
|
559
|
+
"category": "Layout",
|
|
560
|
+
"kind": "package",
|
|
561
|
+
"summary": "Minimal monochrome splash",
|
|
562
|
+
"docs": "### MinimalSplash\n\nMinimal monochrome splash\n\n```tsx\nimport { MinimalSplash } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | ----------------- | ------- | ----------------------------------------------- |\n| className | `string` | — | |\n| appName | `string` | — | App name displayed below logo |\n| tagline | `string` | — | Tagline displayed below app name |\n| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |\n| duration | `number` | `800` | Animation duration in ms |\n| onAnimationComplete | `() => void` | — | Callback when animation completes |\n| showLoading | `boolean` | `false` | Whether to show loading indicator |\n| loadingText | `string` | — | Loading text |\n\nAlso accepts the props of `SplashScreenProps`."
|
|
563
|
+
},
|
|
564
|
+
{
|
|
565
|
+
"name": "BrandedSplash",
|
|
566
|
+
"category": "Layout",
|
|
567
|
+
"kind": "package",
|
|
568
|
+
"summary": "Splash with brand imagery",
|
|
569
|
+
"docs": "### BrandedSplash\n\nSplash with brand imagery\n\n```tsx\nimport { BrandedSplash } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | ----------------- | ------- | ----------------------------------------------- |\n| className | `string` | — | |\n| appName | `string` | — | App name displayed below logo |\n| tagline | `string` | — | Tagline displayed below app name |\n| logo | `React.ReactNode` | — | Logo element (icon, image, or custom component) |\n| duration | `number` | `800` | Animation duration in ms |\n| onAnimationComplete | `() => void` | — | Callback when animation completes |\n| loadingText | `string` | — | Loading text |\n\nAlso accepts the props of `SplashScreenProps`."
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
"name": "Chat",
|
|
573
|
+
"category": "Chat",
|
|
574
|
+
"kind": "package",
|
|
575
|
+
"summary": "Opinionated messages, suggestions, and SmartInput composition",
|
|
576
|
+
"docs": "### Chat\n\n| Block | Description |\n| --------------------- | --------------------------------------------------------------- |\n| **Chat** | Opinionated messages, suggestions, and SmartInput composition |\n| **ChatConversation** | Constrained conversation viewport that follows streaming output |\n| **ChatMessageBubble** | Message bubble (user/assistant, big/small text) |\n| **ChatSuggestions** | Suggestion chips when empty |"
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
"name": "ChatConversation",
|
|
580
|
+
"category": "Chat",
|
|
581
|
+
"kind": "package",
|
|
582
|
+
"summary": "Constrained conversation viewport that follows streaming output",
|
|
583
|
+
"docs": "## ChatConversation / Chat\n\n`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.\n\nIt 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.\n\n```tsx\nimport { ChatConversation, SmartInput, View } from '@thewhileloop/whileui';\n\n<View className=\"flex-1\">\n <ChatConversation contentContainerClassName=\"gap-4 px-4 py-6\">\n {messages.map((message) => (\n <ProductMessage key={message.id} message={message} />\n ))}\n </ChatConversation>\n <SmartInput\n value={value}\n onChangeText={setValue}\n onSubmitEditing={send}\n submitBehavior=\"submit\"\n leftSlot={<AttachButton />}\n centerSlot={<ModelSelect />}\n rightSlot={<SendButton />}\n />\n</View>;\n```\n\n`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.\n\n```tsx\nimport { Chat, type ChatMessage } from '@thewhileloop/whileui';\n\nconst [messages, setMessages] = useState<ChatMessage[]>([...]);\nconst [value, setValue] = useState('');\n\n<Chat\n messages={messages}\n value={value}\n onChangeText={setValue}\n onSend={() => { /* append user msg, clear input */ }}\n placeholder=\"Message...\"\n suggestions={['Summarize this', 'Explain simply', 'Translate']}\n onSuggestionPress={(text) => setValue(text)}\n emptyTitle=\"How can I help?\"\n emptyDescription=\"Ask anything.\"\n leftSlot={<Button variant=\"ghost\" size=\"icon\" accessibilityLabel=\"Attach\"><Icon name=\"paperclip\" /></Button>}\n rightSlot={<Button size=\"icon\" accessibilityLabel=\"Send\" onPress={handleSend}><Icon name=\"send\" /></Button>}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------------- | ------------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------- |\n| messages (required) | `ChatMessage[]` | — | Messages to show, each `{ id, role, content, secondary?, contentSize? }`. |\n| value (required) | `string` | — | Input value. |\n| onChangeText (required) | `(text: string) => void` | — | Input change. |\n| onSend (required) | `() => void` | — | Send handler. |\n| placeholder | `string` | `'Message...'` | |\n| suggestions | `string[]` | `EMPTY_SUGGESTIONS` | Chips when empty. |\n| onSuggestionPress | `(text: string) => void` | — | |\n| leftSlot | `React.ReactNode` | — | |\n| rightSlot | `React.ReactNode` | — | |\n| loading | `boolean` | `false` | |\n| header | `React.ReactNode` | — | |\n| emptyTitle | `string` | — | |\n| emptyDescription | `string` | — | |\n| exampleMessage | `ChatMessage` | — | Example message shown in empty state so users see the bubble UI. |\n| renderMessage | `(message: ChatMessage) => React.ReactNode` | — | Custom message renderer (markdown, code, images). Falls back to ChatMessageBubble. |\n| loadingIndicator | `React.ReactNode` | — | Shown at bottom when loading (typing indicator). |\n| inputSafeArea | `boolean` | `true` | Passed to SmartInput. Use false when Chat is in a contained demo. |\n| keyboardVerticalOffset | `number` | `0` | Offset for KeyboardAvoidingView when Chat has a header (e.g. StatusBar + header height). |\n| className | `string` | — | |\n| messagesClassName | `string` | — | |\n\n\n`ChatConversation` extends React Native `ScrollViewProps`, forwards its ref, and adds `contentContainerClassName` to style the constrained inner column. Caller `onScroll` and `onContentSizeChange` callbacks are preserved.\n\n### Optional Markdown and code\n\nThese are explicit subpaths so the root package remains independent of a Markdown\nparser. Install `markdown-it` only in applications that render Markdown.\n\n```tsx\nimport { Markdown } from '@thewhileloop/whileui/markdown';\nimport { CodeBlock } from '@thewhileloop/whileui/code';\n\n<Markdown onLinkPress={openLink}>{assistantText}</Markdown>\n<CodeBlock code={source} language=\"TypeScript\" actions={<CopyButton />} />\n```\n\n`Markdown` supports headings, paragraphs, emphasis, links, lists, quotes, rules, inline\ncode, and fenced code. It never opens links or writes to the clipboard itself:\n`onLinkPress` and `codeActions` keep those capabilities in the application. `CodeBlock`\nis dependency-free and accepts an application-owned `actions` slot."
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
"name": "ChatMessageBubble",
|
|
587
|
+
"category": "Chat",
|
|
588
|
+
"kind": "package",
|
|
589
|
+
"summary": "Message bubble (user/assistant, big/small text)",
|
|
590
|
+
"docs": "### ChatMessageBubble\n\nOne message in a `Chat`, rendered on its own when a screen composes the conversation itself.\n\n```tsx\nimport { ChatMessageBubble } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------ | ------------- | ------- | --------------------------------------------------------------- |\n| message (required) | `ChatMessage` | — | The message to render; its `role` decides alignment and colour. |\n| contentClassName | `string` | — | Classes for the bubble's content wrapper. |"
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
"name": "ChatSuggestions",
|
|
594
|
+
"category": "Chat",
|
|
595
|
+
"kind": "package",
|
|
596
|
+
"summary": "Suggestion chips when empty",
|
|
597
|
+
"docs": "### ChatSuggestions\n\nSuggestion chips when empty\n\n```tsx\nimport { ChatSuggestions } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ---------------------- | ------------------------ | ------------------- | ----------- |\n| suggestions (required) | `string[]` | `EMPTY_SUGGESTIONS` | |\n| onSelect (required) | `(text: string) => void` | — | |\n| className | `string` | — | |"
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
"name": "ProfileHeader",
|
|
601
|
+
"category": "Profile & Settings",
|
|
602
|
+
"kind": "template",
|
|
603
|
+
"summary": "Profile header with stats",
|
|
604
|
+
"docs": "## ProfileHeader\n\nCopy from `apps/showcase/catalog/templates/profile/profile-header.tsx`, then:\n\n```tsx\nimport { ProfileHeader } from './templates/profile'; // or your path\n\n<ProfileHeader\n name=\"John Doe\"\n username=\"johndoe\"\n bio=\"Designer & Developer\"\n avatarFallback=\"JD\"\n avatarUrl=\"https://...\"\n verified\n stats={[\n { label: 'Followers', value: '1.2K' },\n { label: 'Following', value: 234 },\n ]}\n action={{ label: 'Edit Profile', onPress: () => {} }}\n/>;\n```",
|
|
605
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"name": "AccountCard",
|
|
609
|
+
"category": "Profile & Settings",
|
|
610
|
+
"kind": "template",
|
|
611
|
+
"summary": "Account summary card",
|
|
612
|
+
"docs": null,
|
|
613
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
"name": "SettingsSection",
|
|
617
|
+
"category": "Profile & Settings",
|
|
618
|
+
"kind": "template",
|
|
619
|
+
"summary": "Section header with optional action",
|
|
620
|
+
"docs": "## SettingsSection / SettingsItem\n\nCopy from `apps/showcase/catalog/templates/profile/`, then:\n\n```tsx\nimport { SettingsSection, SettingsItem } from './templates/profile'; // or your path\n\n<SettingsSection title=\"Preferences\">\n <SettingsItem\n icon={<Icon />}\n label=\"Notifications\"\n type=\"toggle\"\n toggleValue={enabled}\n onToggle={setEnabled}\n />\n <SettingsItem icon={<Icon />} label=\"Privacy\" value=\"Public\" onPress={() => {}} />\n <SettingsItem icon={<Icon />} label=\"Sign Out\" type=\"action\" destructive />\n</SettingsSection>;\n```",
|
|
621
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
622
|
+
},
|
|
623
|
+
{
|
|
624
|
+
"name": "SettingsItem",
|
|
625
|
+
"category": "Profile & Settings",
|
|
626
|
+
"kind": "template",
|
|
627
|
+
"summary": "Row for toggles/links/settings",
|
|
628
|
+
"docs": "## SettingsSection / SettingsItem\n\nCopy from `apps/showcase/catalog/templates/profile/`, then:\n\n```tsx\nimport { SettingsSection, SettingsItem } from './templates/profile'; // or your path\n\n<SettingsSection title=\"Preferences\">\n <SettingsItem\n icon={<Icon />}\n label=\"Notifications\"\n type=\"toggle\"\n toggleValue={enabled}\n onToggle={setEnabled}\n />\n <SettingsItem icon={<Icon />} label=\"Privacy\" value=\"Public\" onPress={() => {}} />\n <SettingsItem icon={<Icon />} label=\"Sign Out\" type=\"action\" destructive />\n</SettingsSection>;\n```",
|
|
629
|
+
"source": "https://github.com/whileloophq/whileui/tree/main/apps/showcase/catalog/templates"
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
"name": "ListItem",
|
|
633
|
+
"category": "Lists",
|
|
634
|
+
"kind": "package",
|
|
635
|
+
"summary": "Title/subtitle row",
|
|
636
|
+
"docs": "### ListItem\n\nTitle/subtitle row\n\n```tsx\nimport { ListItem } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ---------------- | ----------------- | ------- | ----------------------------------------------- |\n| icon | `React.ReactNode` | — | |\n| title (required) | `string` | — | |\n| subtitle | `string` | — | |\n| description | `string` | — | |\n| rightIcon | `React.ReactNode` | — | |\n| rightText | `string` | — | |\n| action | `React.ReactNode` | — | |\n| showBorder | `boolean` | `true` | |\n| compact | `boolean` | `false` | |\n| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |\n| className | `string` | — | |\n\nAlso accepts the props of `PressableProps`."
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
"name": "NotificationItem",
|
|
640
|
+
"category": "Lists",
|
|
641
|
+
"kind": "package",
|
|
642
|
+
"summary": "Notification row with metadata",
|
|
643
|
+
"docs": "### NotificationItem\n\nNotification row with metadata\n\n```tsx\nimport { NotificationItem } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------ | ----------------- | ------- | ----------------------------------------------- |\n| icon | `React.ReactNode` | — | |\n| avatarUrl | `string` | — | |\n| avatarFallback | `string` | — | |\n| title (required) | `string` | — | |\n| message (required) | `string` | — | |\n| time (required) | `string` | — | |\n| read | `boolean` | `false` | |\n| showDot | `boolean` | `true` | |\n| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |\n| className | `string` | — | |\n\nAlso accepts the props of `PressableProps`."
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
"name": "TimelineFeed",
|
|
647
|
+
"category": "Lists",
|
|
648
|
+
"kind": "package",
|
|
649
|
+
"summary": "Vertical feed with connecting lines",
|
|
650
|
+
"docs": "### TimelineFeed\n\nVertical feed with connecting lines\n\n```tsx\nimport { TimelineFeed } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ---------------- | ---------------- | ------- | ----------- |\n| className | `string` | — | |\n| items (required) | `TimelineItem[]` | — | |\n| showConnector | `boolean` | `true` | |\n\nAlso accepts the props of `ViewProps`."
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
"name": "ProductCard",
|
|
654
|
+
"category": "Commerce",
|
|
655
|
+
"kind": "package",
|
|
656
|
+
"summary": "Product card with badge/media",
|
|
657
|
+
"docs": "## ProductCard\n\n```tsx\nimport { ProductCard } from '@thewhileloop/whileui';\n\n<ProductCard\n title=\"Product Name\"\n price=\"$99\"\n originalPrice=\"$129\"\n badge=\"-23%\"\n rating={4.5}\n reviewCount={128}\n variant=\"vertical\"\n onPress={() => {}}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ---------------- | ---------------------------- | ------------ | ----------------------------------------------- |\n| imageUrl | `string` | — | |\n| title (required) | `string` | — | |\n| description | `string` | — | |\n| price (required) | `string` | — | |\n| originalPrice | `string` | — | |\n| badge | `string` | — | |\n| rating | `number` | — | |\n| reviewCount | `number` | — | |\n| inStock | `boolean` | `true` | |\n| variant | `'vertical' \\| 'horizontal'` | `'vertical'` | |\n| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |\n| className | `string` | — | |\n\nAlso accepts the props of `PressableProps`."
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
"name": "PricingCard",
|
|
661
|
+
"category": "Commerce",
|
|
662
|
+
"kind": "package",
|
|
663
|
+
"summary": "Pricing tiers with feature list",
|
|
664
|
+
"docs": "## PricingCard\n\n```tsx\nimport { PricingCard } from '@thewhileloop/whileui';\n\n<PricingCard\n name=\"Pro\"\n description=\"For teams\"\n price=\"$29\"\n period=\"/month\"\n badge=\"Popular\"\n highlighted\n features={[\n { label: 'Unlimited users', included: true },\n { label: 'Priority support', included: true },\n { label: 'Custom domain', included: false },\n ]}\n onPress={() => {}}\n/>;\n```"
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
"name": "CheckoutSummary",
|
|
668
|
+
"category": "Commerce",
|
|
669
|
+
"kind": "package",
|
|
670
|
+
"summary": "Cart summary with line items",
|
|
671
|
+
"docs": "### CheckoutSummary\n\nCart summary with line items\n\n```tsx\nimport { CheckoutSummary } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | -------------------- | ------------ | ----------- |\n| items (required) | `CheckoutLineItem[]` | — | |\n| subtotal (required) | `string` | — | |\n| shipping | `string` | — | |\n| tax | `string` | — | |\n| discount | `string` | — | |\n| total (required) | `string` | — | |\n| buttonLabel | `string` | `'Checkout'` | |\n| onCheckout | `() => void` | — | |\n| loading | `boolean` | `false` | |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
"name": "MetricCard",
|
|
675
|
+
"category": "Commerce",
|
|
676
|
+
"kind": "package",
|
|
677
|
+
"summary": "Stats/progress card for dashboards",
|
|
678
|
+
"docs": "### MetricCard\n\nStats/progress card for dashboards\n\n```tsx\nimport { MetricCard } from '@thewhileloop/whileui';\n```\n\n\n| Prop | Type | Default | Description |\n| ---------------- | ------------------------------------ | ----------- | ----------------------------------------------- |\n| className | `string` | — | |\n| label (required) | `string` | — | |\n| value (required) | `string \\| number` | — | |\n| subtitle | `string` | — | |\n| progress | `number` | — | |\n| segments | `{ value: number; color: string }[]` | — | |\n| icon | `React.ReactNode` | — | |\n| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |\n| variant | `'default' \\| 'outlined'` | `'default'` | |\n\nAlso accepts the props of `ViewProps`."
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
"name": "SubscriptionCard",
|
|
682
|
+
"category": "Commerce",
|
|
683
|
+
"kind": "package",
|
|
684
|
+
"summary": "Current plan card with manage/upgrade actions",
|
|
685
|
+
"docs": "## SubscriptionCard\n\n```tsx\nimport { SubscriptionCard } from '@thewhileloop/whileui';\n\n<SubscriptionCard\n planName=\"Pro\"\n price=\"$29\"\n period=\"/month\"\n expiresAt=\"April 18, 2026\"\n isActive\n onManage={() => {}}\n onUpgrade={() => {}}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ------------------- | ------------ | --------------- | ----------------------------------------------- |\n| planName (required) | `string` | — | Current plan label. |\n| price (required) | `string` | — | Plan price. |\n| period | `string` | `'/month'` | Billing period label. |\n| expiresAt | `string` | — | Renewal/expiry date text. |\n| isActive | `boolean` | `true` | Active/inactive badge state. |\n| onManage | `() => void` | — | Manage action. |\n| onUpgrade | `() => void` | — | Upgrade action. |\n| manageLabel | `string` | `'Manage plan'` | |\n| upgradeLabel | `string` | `'Upgrade'` | |\n| loading | `boolean` | `false` | Show a skeleton placeholder instead of content. |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
"name": "FeatureGate",
|
|
689
|
+
"category": "Commerce",
|
|
690
|
+
"kind": "package",
|
|
691
|
+
"summary": "Locked feature state with upgrade CTA",
|
|
692
|
+
"docs": "## FeatureGate\n\n```tsx\nimport { FeatureGate } from '@thewhileloop/whileui';\n\n<FeatureGate\n title=\"Advanced export is locked\"\n description=\"Upgrade to unlock 4K export.\"\n buttonLabel=\"Upgrade\"\n onUpgrade={() => {}}\n/>;\n```\n\nUse `children` to show dimmed preview content with an overlay CTA."
|
|
693
|
+
},
|
|
694
|
+
{
|
|
695
|
+
"name": "UsageBar",
|
|
696
|
+
"category": "Commerce",
|
|
697
|
+
"kind": "package",
|
|
698
|
+
"summary": "Quota meter with warning/exceeded states",
|
|
699
|
+
"docs": "## UsageBar\n\n```tsx\nimport { UsageBar } from '@thewhileloop/whileui';\n\n<UsageBar label=\"AI generations\" used={8} limit={20} />;\n```\n\n\n| Prop | Type | Default | Description |\n| ---------------- | ----------------- | ------- | ------------------------------- |\n| label (required) | `string` | — | Usage label. |\n| used (required) | `number` | — | Used amount. |\n| limit (required) | `number` | — | Quota limit. |\n| variant | `UsageBarVariant` | — | Optional explicit visual state. |\n| className | `string` | — | |\n\nAlso accepts the props of `ViewProps`."
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
"name": "PlanToggle",
|
|
703
|
+
"category": "Commerce",
|
|
704
|
+
"kind": "package",
|
|
705
|
+
"summary": "Monthly vs annual plan switcher",
|
|
706
|
+
"docs": "## PlanToggle\n\n```tsx\nimport { PlanToggle } from '@thewhileloop/whileui';\n\n<PlanToggle\n selected=\"monthly\"\n monthlyLabel=\"Monthly\"\n annualLabel=\"Annual\"\n annualDiscount=\"Save 20%\"\n onChange={(next) => {}}\n/>;\n```"
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
"name": "UpgradeBanner",
|
|
710
|
+
"category": "Commerce",
|
|
711
|
+
"kind": "package",
|
|
712
|
+
"summary": "Inline upgrade banner with action/dismiss",
|
|
713
|
+
"docs": "## UpgradeBanner\n\n```tsx\nimport { UpgradeBanner } from '@thewhileloop/whileui';\n\n<UpgradeBanner\n message=\"Unlock unlimited exports with Pro.\"\n actionLabel=\"See plans\"\n onAction={() => {}}\n onDismiss={() => {}}\n/>;\n```"
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
"name": "SmartImage",
|
|
717
|
+
"category": "Media",
|
|
718
|
+
"kind": "package",
|
|
719
|
+
"summary": "Image with explicit loading, loaded, fallback, and error lifecycle",
|
|
720
|
+
"docs": "## SmartImage\n\n```tsx\nimport { SmartImage } from '@thewhileloop/whileui';\n\n<SmartImage\n source={{ uri: imageUrl }}\n fallbackSource={{ uri: fallbackUrl }}\n alt=\"Product preview\"\n className=\"h-48 w-full\"\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------- | -------------------------------------------------- | --------- | ----------- |\n| className | `string` | — | |\n| source (required) | `ImageProps['source']` | — | |\n| alt (required) | `string` | — | |\n| skeleton | `boolean` | `true` | |\n| skeletonClassName | `string` | — | |\n| transition | `number` | `200` | |\n| onLoad | `() => void` | — | |\n| onError | `() => void` | — | |\n| fallbackSource | `ImageProps['source']` | — | |\n| radius | `'none' \\| 'sm' \\| 'md' \\| 'lg' \\| 'xl' \\| 'full'` | `'md'` | |\n| objectFit | `'fill' \\| 'cover' \\| 'contain' \\| 'scale-down'` | `'cover'` | |\n\nAlso accepts the props of `ViewProps`.\n\nThe skeleton returns whenever the primary image starts loading again. A failed primary\nsource switches to `fallbackSource` when supplied without re-entering a primary/fallback\nloop."
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
"name": "DatePickerModal",
|
|
724
|
+
"category": "Date Picker",
|
|
725
|
+
"kind": "package",
|
|
726
|
+
"summary": "Bottom sheet modal with calendar, compact trigger",
|
|
727
|
+
"docs": "## DatePickerModal / DatePickerInline / DateRangePickerModal / TimePickerModal\n\nDate 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.\n\n**DatePickerModal** — Compact trigger opens bottom sheet with calendar. Use `DatePickerTrigger` as the trigger content.\n\n**DatePickerInline** — Calendar embedded inline for forms or dashboards.\n\n**DateRangePickerModal** — Range selection with period marking. Use `DateRangePickerTrigger` as the trigger content.\n\n**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.\n\n```tsx\nimport {\n DatePickerModal,\n DatePickerTrigger,\n DatePickerInline,\n DateRangePickerModal,\n DateRangePickerTrigger,\n TimePickerModal,\n TimePickerTrigger,\n type DateRange,\n} from '@thewhileloop/whileui';\n\n// Single date (modal)\nconst [date, setDate] = useState<string | null>(null);\nconst [open, setOpen] = useState(false);\n\n<DatePickerModal\n value={date}\n onValueChange={setDate}\n open={open}\n onOpenChange={setOpen}\n trigger={<DatePickerTrigger value={date} placeholder=\"Pick a date\" />}\n title=\"Select date\"\n/>;\n\n// Inline calendar\n<DatePickerInline value={date} onValueChange={setDate} />;\n\n// Date range (modal)\nconst [range, setRange] = useState<DateRange | null>(null);\n\n<DateRangePickerModal\n value={range}\n onValueChange={setRange}\n open={rangeOpen}\n onOpenChange={setRangeOpen}\n trigger={<DateRangePickerTrigger value={range} placeholder=\"Pick range\" />}\n/>;\n\n// Time (modal) — \"HH:MM\", 24-hour, whatever the wheels show\nconst [time, setTime] = useState<string | null>(null);\n\n<TimePickerModal\n value={time}\n onValueChange={setTime}\n open={timeOpen}\n onOpenChange={setTimeOpen}\n minuteStep={5}\n trigger={<TimePickerTrigger value={time} placeholder=\"Pick a time\" />}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------------- | -------------------------------- | ---------------------- | --------------------------------------------------------- |\n| value | `string \\| null` | — | Controlled: selected date in YYYY-MM-DD |\n| onValueChange | `(date: string \\| null) => void` | — | Controlled: change handler |\n| open (required) | `boolean` | — | Modal open state |\n| onOpenChange (required) | `(open: boolean) => void` | — | Modal open change handler |\n| minDate | `string` | — | Minimum selectable date YYYY-MM-DD |\n| maxDate | `string` | — | Maximum selectable date YYYY-MM-DD |\n| theme | `CalendarTheme` | — | Override calendar theme (RN color strings) |\n| trigger | `React.ReactNode` | — | Trigger element. If not provided, no trigger is rendered. |\n| title | `string` | `'Select date'` | Title shown in modal header |\n| confirmLabel | `string` | `'Done'` | Button label for confirm |\n| className | `string` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |"
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
"name": "DatePickerInline",
|
|
731
|
+
"category": "Date Picker",
|
|
732
|
+
"kind": "package",
|
|
733
|
+
"summary": "Inline calendar for forms or dashboards",
|
|
734
|
+
"docs": "## DatePickerModal / DatePickerInline / DateRangePickerModal / TimePickerModal\n\nDate 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.\n\n**DatePickerModal** — Compact trigger opens bottom sheet with calendar. Use `DatePickerTrigger` as the trigger content.\n\n**DatePickerInline** — Calendar embedded inline for forms or dashboards.\n\n**DateRangePickerModal** — Range selection with period marking. Use `DateRangePickerTrigger` as the trigger content.\n\n**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.\n\n```tsx\nimport {\n DatePickerModal,\n DatePickerTrigger,\n DatePickerInline,\n DateRangePickerModal,\n DateRangePickerTrigger,\n TimePickerModal,\n TimePickerTrigger,\n type DateRange,\n} from '@thewhileloop/whileui';\n\n// Single date (modal)\nconst [date, setDate] = useState<string | null>(null);\nconst [open, setOpen] = useState(false);\n\n<DatePickerModal\n value={date}\n onValueChange={setDate}\n open={open}\n onOpenChange={setOpen}\n trigger={<DatePickerTrigger value={date} placeholder=\"Pick a date\" />}\n title=\"Select date\"\n/>;\n\n// Inline calendar\n<DatePickerInline value={date} onValueChange={setDate} />;\n\n// Date range (modal)\nconst [range, setRange] = useState<DateRange | null>(null);\n\n<DateRangePickerModal\n value={range}\n onValueChange={setRange}\n open={rangeOpen}\n onOpenChange={setRangeOpen}\n trigger={<DateRangePickerTrigger value={range} placeholder=\"Pick range\" />}\n/>;\n\n// Time (modal) — \"HH:MM\", 24-hour, whatever the wheels show\nconst [time, setTime] = useState<string | null>(null);\n\n<TimePickerModal\n value={time}\n onValueChange={setTime}\n open={timeOpen}\n onOpenChange={setTimeOpen}\n minuteStep={5}\n trigger={<TimePickerTrigger value={time} placeholder=\"Pick a time\" />}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------------- | -------------------------------- | ---------------------- | --------------------------------------------------------- |\n| value | `string \\| null` | — | Controlled: selected date in YYYY-MM-DD |\n| onValueChange | `(date: string \\| null) => void` | — | Controlled: change handler |\n| open (required) | `boolean` | — | Modal open state |\n| onOpenChange (required) | `(open: boolean) => void` | — | Modal open change handler |\n| minDate | `string` | — | Minimum selectable date YYYY-MM-DD |\n| maxDate | `string` | — | Maximum selectable date YYYY-MM-DD |\n| theme | `CalendarTheme` | — | Override calendar theme (RN color strings) |\n| trigger | `React.ReactNode` | — | Trigger element. If not provided, no trigger is rendered. |\n| title | `string` | `'Select date'` | Title shown in modal header |\n| confirmLabel | `string` | `'Done'` | Button label for confirm |\n| className | `string` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |"
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
"name": "DateRangePickerModal",
|
|
738
|
+
"category": "Date Picker",
|
|
739
|
+
"kind": "package",
|
|
740
|
+
"summary": "Range selection modal with period marking",
|
|
741
|
+
"docs": "## DatePickerModal / DatePickerInline / DateRangePickerModal / TimePickerModal\n\nDate 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.\n\n**DatePickerModal** — Compact trigger opens bottom sheet with calendar. Use `DatePickerTrigger` as the trigger content.\n\n**DatePickerInline** — Calendar embedded inline for forms or dashboards.\n\n**DateRangePickerModal** — Range selection with period marking. Use `DateRangePickerTrigger` as the trigger content.\n\n**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.\n\n```tsx\nimport {\n DatePickerModal,\n DatePickerTrigger,\n DatePickerInline,\n DateRangePickerModal,\n DateRangePickerTrigger,\n TimePickerModal,\n TimePickerTrigger,\n type DateRange,\n} from '@thewhileloop/whileui';\n\n// Single date (modal)\nconst [date, setDate] = useState<string | null>(null);\nconst [open, setOpen] = useState(false);\n\n<DatePickerModal\n value={date}\n onValueChange={setDate}\n open={open}\n onOpenChange={setOpen}\n trigger={<DatePickerTrigger value={date} placeholder=\"Pick a date\" />}\n title=\"Select date\"\n/>;\n\n// Inline calendar\n<DatePickerInline value={date} onValueChange={setDate} />;\n\n// Date range (modal)\nconst [range, setRange] = useState<DateRange | null>(null);\n\n<DateRangePickerModal\n value={range}\n onValueChange={setRange}\n open={rangeOpen}\n onOpenChange={setRangeOpen}\n trigger={<DateRangePickerTrigger value={range} placeholder=\"Pick range\" />}\n/>;\n\n// Time (modal) — \"HH:MM\", 24-hour, whatever the wheels show\nconst [time, setTime] = useState<string | null>(null);\n\n<TimePickerModal\n value={time}\n onValueChange={setTime}\n open={timeOpen}\n onOpenChange={setTimeOpen}\n minuteStep={5}\n trigger={<TimePickerTrigger value={time} placeholder=\"Pick a time\" />}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------------- | -------------------------------- | ---------------------- | --------------------------------------------------------- |\n| value | `string \\| null` | — | Controlled: selected date in YYYY-MM-DD |\n| onValueChange | `(date: string \\| null) => void` | — | Controlled: change handler |\n| open (required) | `boolean` | — | Modal open state |\n| onOpenChange (required) | `(open: boolean) => void` | — | Modal open change handler |\n| minDate | `string` | — | Minimum selectable date YYYY-MM-DD |\n| maxDate | `string` | — | Maximum selectable date YYYY-MM-DD |\n| theme | `CalendarTheme` | — | Override calendar theme (RN color strings) |\n| trigger | `React.ReactNode` | — | Trigger element. If not provided, no trigger is rendered. |\n| title | `string` | `'Select date'` | Title shown in modal header |\n| confirmLabel | `string` | `'Done'` | Button label for confirm |\n| className | `string` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |"
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
"name": "TimePickerModal",
|
|
745
|
+
"category": "Date Picker",
|
|
746
|
+
"kind": "package",
|
|
747
|
+
"summary": "Hour and minute wheels in the same bottom sheet",
|
|
748
|
+
"docs": "## DatePickerModal / DatePickerInline / DateRangePickerModal / TimePickerModal\n\nDate 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.\n\n**DatePickerModal** — Compact trigger opens bottom sheet with calendar. Use `DatePickerTrigger` as the trigger content.\n\n**DatePickerInline** — Calendar embedded inline for forms or dashboards.\n\n**DateRangePickerModal** — Range selection with period marking. Use `DateRangePickerTrigger` as the trigger content.\n\n**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.\n\n```tsx\nimport {\n DatePickerModal,\n DatePickerTrigger,\n DatePickerInline,\n DateRangePickerModal,\n DateRangePickerTrigger,\n TimePickerModal,\n TimePickerTrigger,\n type DateRange,\n} from '@thewhileloop/whileui';\n\n// Single date (modal)\nconst [date, setDate] = useState<string | null>(null);\nconst [open, setOpen] = useState(false);\n\n<DatePickerModal\n value={date}\n onValueChange={setDate}\n open={open}\n onOpenChange={setOpen}\n trigger={<DatePickerTrigger value={date} placeholder=\"Pick a date\" />}\n title=\"Select date\"\n/>;\n\n// Inline calendar\n<DatePickerInline value={date} onValueChange={setDate} />;\n\n// Date range (modal)\nconst [range, setRange] = useState<DateRange | null>(null);\n\n<DateRangePickerModal\n value={range}\n onValueChange={setRange}\n open={rangeOpen}\n onOpenChange={setRangeOpen}\n trigger={<DateRangePickerTrigger value={range} placeholder=\"Pick range\" />}\n/>;\n\n// Time (modal) — \"HH:MM\", 24-hour, whatever the wheels show\nconst [time, setTime] = useState<string | null>(null);\n\n<TimePickerModal\n value={time}\n onValueChange={setTime}\n open={timeOpen}\n onOpenChange={setTimeOpen}\n minuteStep={5}\n trigger={<TimePickerTrigger value={time} placeholder=\"Pick a time\" />}\n/>;\n```\n\n\n| Prop | Type | Default | Description |\n| ----------------------- | -------------------------------- | ---------------------- | --------------------------------------------------------- |\n| value | `string \\| null` | — | Controlled: selected date in YYYY-MM-DD |\n| onValueChange | `(date: string \\| null) => void` | — | Controlled: change handler |\n| open (required) | `boolean` | — | Modal open state |\n| onOpenChange (required) | `(open: boolean) => void` | — | Modal open change handler |\n| minDate | `string` | — | Minimum selectable date YYYY-MM-DD |\n| maxDate | `string` | — | Maximum selectable date YYYY-MM-DD |\n| theme | `CalendarTheme` | — | Override calendar theme (RN color strings) |\n| trigger | `React.ReactNode` | — | Trigger element. If not provided, no trigger is rendered. |\n| title | `string` | `'Select date'` | Title shown in modal header |\n| confirmLabel | `string` | `'Done'` | Button label for confirm |\n| className | `string` | — | |\n| frosted | `boolean` | `false` | Enable the optional frosted overlay. |\n| blurIntensity | `number` | medium preset | Override blur amount directly. |\n| blurTintToken | `FrostedTintToken` | `'surfaceTranslucent'` | Tint token used for frosted mode. |"
|
|
749
|
+
}
|
|
750
|
+
],
|
|
751
|
+
"themeTokens": [
|
|
752
|
+
{
|
|
753
|
+
"key": "primary",
|
|
754
|
+
"cssVar": "--color-primary",
|
|
755
|
+
"appVar": "--app-color-primary",
|
|
756
|
+
"group": "core",
|
|
757
|
+
"light": "#4457ff",
|
|
758
|
+
"dark": "#ff5ca8"
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
"key": "primaryForeground",
|
|
762
|
+
"cssVar": "--color-primary-foreground",
|
|
763
|
+
"appVar": "--app-color-primary-foreground",
|
|
764
|
+
"group": "core",
|
|
765
|
+
"light": "#fffdf8",
|
|
766
|
+
"dark": "#2c1032"
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
"key": "foreground",
|
|
770
|
+
"cssVar": "--color-foreground",
|
|
771
|
+
"appVar": "--app-color-foreground",
|
|
772
|
+
"group": "core",
|
|
773
|
+
"light": "#271f48",
|
|
774
|
+
"dark": "#fff7eb"
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
"key": "background",
|
|
778
|
+
"cssVar": "--color-background",
|
|
779
|
+
"appVar": "--app-color-background",
|
|
780
|
+
"group": "core",
|
|
781
|
+
"light": "#fff8ef",
|
|
782
|
+
"dark": "#170f2d"
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
"key": "card",
|
|
786
|
+
"cssVar": "--color-card",
|
|
787
|
+
"appVar": "--app-color-card",
|
|
788
|
+
"group": "core",
|
|
789
|
+
"light": "rgba(255, 255, 255, 0.82)",
|
|
790
|
+
"dark": "rgba(38, 24, 69, 0.78)"
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
"key": "cardForeground",
|
|
794
|
+
"cssVar": "--color-card-foreground",
|
|
795
|
+
"appVar": "--app-color-card-foreground",
|
|
796
|
+
"group": "core",
|
|
797
|
+
"light": "#271f48",
|
|
798
|
+
"dark": "#fff7eb"
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
"key": "popover",
|
|
802
|
+
"cssVar": "--color-popover",
|
|
803
|
+
"appVar": "--app-color-popover",
|
|
804
|
+
"group": "core",
|
|
805
|
+
"light": "rgba(255, 252, 248, 0.92)",
|
|
806
|
+
"dark": "rgba(28, 18, 52, 0.90)"
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
"key": "popoverForeground",
|
|
810
|
+
"cssVar": "--color-popover-foreground",
|
|
811
|
+
"appVar": "--app-color-popover-foreground",
|
|
812
|
+
"group": "core",
|
|
813
|
+
"light": "#271f48",
|
|
814
|
+
"dark": "#fff7eb"
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
"key": "secondary",
|
|
818
|
+
"cssVar": "--color-secondary",
|
|
819
|
+
"appVar": "--app-color-secondary",
|
|
820
|
+
"group": "core",
|
|
821
|
+
"light": "rgba(255, 123, 92, 0.10)",
|
|
822
|
+
"dark": "rgba(255, 92, 168, 0.12)"
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
"key": "secondaryForeground",
|
|
826
|
+
"cssVar": "--color-secondary-foreground",
|
|
827
|
+
"appVar": "--app-color-secondary-foreground",
|
|
828
|
+
"group": "core",
|
|
829
|
+
"light": "#41206e",
|
|
830
|
+
"dark": "#fff3fa"
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
"key": "muted",
|
|
834
|
+
"cssVar": "--color-muted",
|
|
835
|
+
"appVar": "--app-color-muted",
|
|
836
|
+
"group": "core",
|
|
837
|
+
"light": "rgba(74, 55, 122, 0.07)",
|
|
838
|
+
"dark": "rgba(255, 247, 235, 0.08)"
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
"key": "mutedForeground",
|
|
842
|
+
"cssVar": "--color-muted-foreground",
|
|
843
|
+
"appVar": "--app-color-muted-foreground",
|
|
844
|
+
"group": "core",
|
|
845
|
+
"light": "#645783",
|
|
846
|
+
"dark": "#c5bad9"
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
"key": "border",
|
|
850
|
+
"cssVar": "--color-border",
|
|
851
|
+
"appVar": "--app-color-border",
|
|
852
|
+
"group": "core",
|
|
853
|
+
"light": "rgba(68, 87, 255, 0.16)",
|
|
854
|
+
"dark": "rgba(255, 255, 255, 0.12)"
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
"key": "input",
|
|
858
|
+
"cssVar": "--color-input",
|
|
859
|
+
"appVar": "--app-color-input",
|
|
860
|
+
"group": "core",
|
|
861
|
+
"light": "rgba(255, 255, 255, 0.84)",
|
|
862
|
+
"dark": "rgba(27, 18, 49, 0.86)"
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
"key": "ring",
|
|
866
|
+
"cssVar": "--color-ring",
|
|
867
|
+
"appVar": "--app-color-ring",
|
|
868
|
+
"group": "feedback",
|
|
869
|
+
"light": "#3d8bff",
|
|
870
|
+
"dark": "#7ff0c5"
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
"key": "accent",
|
|
874
|
+
"cssVar": "--color-accent",
|
|
875
|
+
"appVar": "--app-color-accent",
|
|
876
|
+
"group": "core",
|
|
877
|
+
"light": "#a7f432",
|
|
878
|
+
"dark": "#7ff0c5"
|
|
879
|
+
},
|
|
880
|
+
{
|
|
881
|
+
"key": "accentForeground",
|
|
882
|
+
"cssVar": "--color-accent-foreground",
|
|
883
|
+
"appVar": "--app-color-accent-foreground",
|
|
884
|
+
"group": "core",
|
|
885
|
+
"light": "#163218",
|
|
886
|
+
"dark": "#0f2d24"
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
"key": "destructive",
|
|
890
|
+
"cssVar": "--color-destructive",
|
|
891
|
+
"appVar": "--app-color-destructive",
|
|
892
|
+
"group": "status",
|
|
893
|
+
"light": "#d03d31",
|
|
894
|
+
"dark": "#ff6b57"
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
"key": "destructiveForeground",
|
|
898
|
+
"cssVar": "--color-destructive-foreground",
|
|
899
|
+
"appVar": "--app-color-destructive-foreground",
|
|
900
|
+
"group": "status",
|
|
901
|
+
"light": "#fff8f6",
|
|
902
|
+
"dark": "#2c0c0a"
|
|
903
|
+
},
|
|
904
|
+
{
|
|
905
|
+
"key": "success",
|
|
906
|
+
"cssVar": "--color-success",
|
|
907
|
+
"appVar": "--app-color-success",
|
|
908
|
+
"group": "status",
|
|
909
|
+
"light": "#00864d",
|
|
910
|
+
"dark": "#39db9b"
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
"key": "successForeground",
|
|
914
|
+
"cssVar": "--color-success-foreground",
|
|
915
|
+
"appVar": "--app-color-success-foreground",
|
|
916
|
+
"group": "status",
|
|
917
|
+
"light": "#f5fff9",
|
|
918
|
+
"dark": "#06241a"
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
"key": "warning",
|
|
922
|
+
"cssVar": "--color-warning",
|
|
923
|
+
"appVar": "--app-color-warning",
|
|
924
|
+
"group": "status",
|
|
925
|
+
"light": "#ffb224",
|
|
926
|
+
"dark": "#ffbe42"
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
"key": "warningForeground",
|
|
930
|
+
"cssVar": "--color-warning-foreground",
|
|
931
|
+
"appVar": "--app-color-warning-foreground",
|
|
932
|
+
"group": "status",
|
|
933
|
+
"light": "#3d2200",
|
|
934
|
+
"dark": "#2d1700"
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
"key": "info",
|
|
938
|
+
"cssVar": "--color-info",
|
|
939
|
+
"appVar": "--app-color-info",
|
|
940
|
+
"group": "status",
|
|
941
|
+
"light": "#2070e1",
|
|
942
|
+
"dark": "#78a7ff"
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
"key": "infoForeground",
|
|
946
|
+
"cssVar": "--color-info-foreground",
|
|
947
|
+
"appVar": "--app-color-info-foreground",
|
|
948
|
+
"group": "status",
|
|
949
|
+
"light": "#f8fbff",
|
|
950
|
+
"dark": "#09162d"
|
|
951
|
+
},
|
|
952
|
+
{
|
|
953
|
+
"key": "overlay",
|
|
954
|
+
"cssVar": "--color-overlay",
|
|
955
|
+
"appVar": "--app-color-overlay",
|
|
956
|
+
"group": "surface",
|
|
957
|
+
"light": "rgba(39, 31, 72, 0.14)",
|
|
958
|
+
"dark": "rgba(9, 4, 20, 0.50)"
|
|
959
|
+
},
|
|
960
|
+
{
|
|
961
|
+
"key": "overlayStrong",
|
|
962
|
+
"cssVar": "--color-overlay-strong",
|
|
963
|
+
"appVar": "--app-color-overlay-strong",
|
|
964
|
+
"group": "surface",
|
|
965
|
+
"light": "rgba(39, 31, 72, 0.22)",
|
|
966
|
+
"dark": "rgba(9, 4, 20, 0.68)"
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
"key": "surfaceElevated",
|
|
970
|
+
"cssVar": "--color-surface-elevated",
|
|
971
|
+
"appVar": "--app-color-surface-elevated",
|
|
972
|
+
"group": "surface",
|
|
973
|
+
"light": "rgba(255, 255, 255, 0.88)",
|
|
974
|
+
"dark": "rgba(45, 27, 79, 0.80)"
|
|
975
|
+
},
|
|
976
|
+
{
|
|
977
|
+
"key": "surfaceTranslucent",
|
|
978
|
+
"cssVar": "--color-surface-translucent",
|
|
979
|
+
"appVar": "--app-color-surface-translucent",
|
|
980
|
+
"group": "surface",
|
|
981
|
+
"light": "rgba(255, 255, 255, 0.62)",
|
|
982
|
+
"dark": "rgba(34, 20, 61, 0.58)"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
"key": "surfaceTranslucentBorder",
|
|
986
|
+
"cssVar": "--color-surface-translucent-border",
|
|
987
|
+
"appVar": "--app-color-surface-translucent-border",
|
|
988
|
+
"group": "surface",
|
|
989
|
+
"light": "rgba(255, 255, 255, 0.42)",
|
|
990
|
+
"dark": "rgba(255, 255, 255, 0.12)"
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
"key": "surfaceBorder",
|
|
994
|
+
"cssVar": "--color-surface-border",
|
|
995
|
+
"appVar": "--app-color-surface-border",
|
|
996
|
+
"group": "surface",
|
|
997
|
+
"light": "rgba(68, 87, 255, 0.16)",
|
|
998
|
+
"dark": "rgba(255, 255, 255, 0.12)"
|
|
999
|
+
},
|
|
1000
|
+
{
|
|
1001
|
+
"key": "surfaceHighlight",
|
|
1002
|
+
"cssVar": "--color-surface-highlight",
|
|
1003
|
+
"appVar": "--app-color-surface-highlight",
|
|
1004
|
+
"group": "surface",
|
|
1005
|
+
"light": "rgba(255, 255, 255, 0.74)",
|
|
1006
|
+
"dark": "rgba(255, 255, 255, 0.07)"
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
"key": "stateHover",
|
|
1010
|
+
"cssVar": "--color-state-hover",
|
|
1011
|
+
"appVar": "--app-color-state-hover",
|
|
1012
|
+
"group": "feedback",
|
|
1013
|
+
"light": "rgba(68, 87, 255, 0.06)",
|
|
1014
|
+
"dark": "rgba(255, 92, 168, 0.08)"
|
|
1015
|
+
},
|
|
1016
|
+
{
|
|
1017
|
+
"key": "statePressed",
|
|
1018
|
+
"cssVar": "--color-state-pressed",
|
|
1019
|
+
"appVar": "--app-color-state-pressed",
|
|
1020
|
+
"group": "feedback",
|
|
1021
|
+
"light": "rgba(68, 87, 255, 0.12)",
|
|
1022
|
+
"dark": "rgba(255, 92, 168, 0.16)"
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
"key": "stateDisabled",
|
|
1026
|
+
"cssVar": "--color-state-disabled",
|
|
1027
|
+
"appVar": "--app-color-state-disabled",
|
|
1028
|
+
"group": "feedback",
|
|
1029
|
+
"light": "rgba(100, 87, 131, 0.28)",
|
|
1030
|
+
"dark": "rgba(197, 186, 217, 0.22)"
|
|
1031
|
+
},
|
|
1032
|
+
{
|
|
1033
|
+
"key": "shadow",
|
|
1034
|
+
"cssVar": "--color-shadow",
|
|
1035
|
+
"appVar": "--app-color-shadow",
|
|
1036
|
+
"group": "surface",
|
|
1037
|
+
"light": "#271f48",
|
|
1038
|
+
"dark": "#000000"
|
|
1039
|
+
},
|
|
1040
|
+
{
|
|
1041
|
+
"key": "primaryActive",
|
|
1042
|
+
"cssVar": "--color-primary-active",
|
|
1043
|
+
"appVar": "--app-color-primary-active",
|
|
1044
|
+
"group": "feedback",
|
|
1045
|
+
"light": "#3346e6",
|
|
1046
|
+
"dark": "#ff74b6"
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
"key": "secondaryActive",
|
|
1050
|
+
"cssVar": "--color-secondary-active",
|
|
1051
|
+
"appVar": "--app-color-secondary-active",
|
|
1052
|
+
"group": "feedback",
|
|
1053
|
+
"light": "rgba(255, 123, 92, 0.18)",
|
|
1054
|
+
"dark": "rgba(255, 92, 168, 0.20)"
|
|
1055
|
+
},
|
|
1056
|
+
{
|
|
1057
|
+
"key": "destructiveActive",
|
|
1058
|
+
"cssVar": "--color-destructive-active",
|
|
1059
|
+
"appVar": "--app-color-destructive-active",
|
|
1060
|
+
"group": "feedback",
|
|
1061
|
+
"light": "#bb261d",
|
|
1062
|
+
"dark": "#ff8271"
|
|
1063
|
+
},
|
|
1064
|
+
{
|
|
1065
|
+
"key": "primarySoft",
|
|
1066
|
+
"cssVar": "--color-primary-soft",
|
|
1067
|
+
"appVar": "--app-color-primary-soft",
|
|
1068
|
+
"group": "feedback",
|
|
1069
|
+
"light": "rgba(68, 87, 255, 0.13)",
|
|
1070
|
+
"dark": "rgba(255, 92, 168, 0.16)"
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
"key": "primarySoftSubtle",
|
|
1074
|
+
"cssVar": "--color-primary-soft-subtle",
|
|
1075
|
+
"appVar": "--app-color-primary-soft-subtle",
|
|
1076
|
+
"group": "feedback",
|
|
1077
|
+
"light": "rgba(68, 87, 255, 0.07)",
|
|
1078
|
+
"dark": "rgba(255, 92, 168, 0.10)"
|
|
1079
|
+
},
|
|
1080
|
+
{
|
|
1081
|
+
"key": "secondarySoft",
|
|
1082
|
+
"cssVar": "--color-secondary-soft",
|
|
1083
|
+
"appVar": "--app-color-secondary-soft",
|
|
1084
|
+
"group": "feedback",
|
|
1085
|
+
"light": "rgba(255, 123, 92, 0.16)",
|
|
1086
|
+
"dark": "rgba(127, 240, 197, 0.08)"
|
|
1087
|
+
},
|
|
1088
|
+
{
|
|
1089
|
+
"key": "mutedSoft",
|
|
1090
|
+
"cssVar": "--color-muted-soft",
|
|
1091
|
+
"appVar": "--app-color-muted-soft",
|
|
1092
|
+
"group": "feedback",
|
|
1093
|
+
"light": "rgba(74, 55, 122, 0.05)",
|
|
1094
|
+
"dark": "rgba(255, 247, 235, 0.05)"
|
|
1095
|
+
},
|
|
1096
|
+
{
|
|
1097
|
+
"key": "destructiveSoft",
|
|
1098
|
+
"cssVar": "--color-destructive-soft",
|
|
1099
|
+
"appVar": "--app-color-destructive-soft",
|
|
1100
|
+
"group": "status",
|
|
1101
|
+
"light": "rgba(208, 61, 49, 0.12)",
|
|
1102
|
+
"dark": "rgba(255, 107, 87, 0.20)"
|
|
1103
|
+
},
|
|
1104
|
+
{
|
|
1105
|
+
"key": "destructiveSoftBorder",
|
|
1106
|
+
"cssVar": "--color-destructive-soft-border",
|
|
1107
|
+
"appVar": "--app-color-destructive-soft-border",
|
|
1108
|
+
"group": "status",
|
|
1109
|
+
"light": "rgba(208, 61, 49, 0.30)",
|
|
1110
|
+
"dark": "rgba(255, 107, 87, 0.40)"
|
|
1111
|
+
},
|
|
1112
|
+
{
|
|
1113
|
+
"key": "successSoft",
|
|
1114
|
+
"cssVar": "--color-success-soft",
|
|
1115
|
+
"appVar": "--app-color-success-soft",
|
|
1116
|
+
"group": "status",
|
|
1117
|
+
"light": "rgba(0, 134, 77, 0.12)",
|
|
1118
|
+
"dark": "rgba(57, 219, 155, 0.20)"
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
"key": "successSoftBorder",
|
|
1122
|
+
"cssVar": "--color-success-soft-border",
|
|
1123
|
+
"appVar": "--app-color-success-soft-border",
|
|
1124
|
+
"group": "status",
|
|
1125
|
+
"light": "rgba(0, 134, 77, 0.30)",
|
|
1126
|
+
"dark": "rgba(57, 219, 155, 0.40)"
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
"key": "warningSoft",
|
|
1130
|
+
"cssVar": "--color-warning-soft",
|
|
1131
|
+
"appVar": "--app-color-warning-soft",
|
|
1132
|
+
"group": "status",
|
|
1133
|
+
"light": "rgba(255, 178, 36, 0.14)",
|
|
1134
|
+
"dark": "rgba(255, 190, 66, 0.20)"
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
"key": "warningSoftBorder",
|
|
1138
|
+
"cssVar": "--color-warning-soft-border",
|
|
1139
|
+
"appVar": "--app-color-warning-soft-border",
|
|
1140
|
+
"group": "status",
|
|
1141
|
+
"light": "rgba(255, 178, 36, 0.34)",
|
|
1142
|
+
"dark": "rgba(255, 190, 66, 0.45)"
|
|
1143
|
+
},
|
|
1144
|
+
{
|
|
1145
|
+
"key": "infoSoft",
|
|
1146
|
+
"cssVar": "--color-info-soft",
|
|
1147
|
+
"appVar": "--app-color-info-soft",
|
|
1148
|
+
"group": "status",
|
|
1149
|
+
"light": "rgba(32, 112, 225, 0.12)",
|
|
1150
|
+
"dark": "rgba(120, 167, 255, 0.20)"
|
|
1151
|
+
},
|
|
1152
|
+
{
|
|
1153
|
+
"key": "infoSoftBorder",
|
|
1154
|
+
"cssVar": "--color-info-soft-border",
|
|
1155
|
+
"appVar": "--app-color-info-soft-border",
|
|
1156
|
+
"group": "status",
|
|
1157
|
+
"light": "rgba(32, 112, 225, 0.30)",
|
|
1158
|
+
"dark": "rgba(120, 167, 255, 0.40)"
|
|
1159
|
+
}
|
|
1160
|
+
],
|
|
1161
|
+
"visualTokens": [
|
|
1162
|
+
{
|
|
1163
|
+
"key": "radiusSm",
|
|
1164
|
+
"cssVar": "--ui-radius-sm",
|
|
1165
|
+
"kind": "pixel",
|
|
1166
|
+
"group": "foundation",
|
|
1167
|
+
"fallback": 10
|
|
1168
|
+
},
|
|
1169
|
+
{
|
|
1170
|
+
"key": "radiusMd",
|
|
1171
|
+
"cssVar": "--ui-radius-md",
|
|
1172
|
+
"kind": "pixel",
|
|
1173
|
+
"group": "foundation",
|
|
1174
|
+
"fallback": 14
|
|
1175
|
+
},
|
|
1176
|
+
{
|
|
1177
|
+
"key": "radiusLg",
|
|
1178
|
+
"cssVar": "--ui-radius-lg",
|
|
1179
|
+
"kind": "pixel",
|
|
1180
|
+
"group": "foundation",
|
|
1181
|
+
"fallback": 18
|
|
1182
|
+
},
|
|
1183
|
+
{
|
|
1184
|
+
"key": "radiusXl",
|
|
1185
|
+
"cssVar": "--ui-radius-xl",
|
|
1186
|
+
"kind": "pixel",
|
|
1187
|
+
"group": "foundation",
|
|
1188
|
+
"fallback": 24
|
|
1189
|
+
},
|
|
1190
|
+
{
|
|
1191
|
+
"key": "controlHeightSm",
|
|
1192
|
+
"cssVar": "--ui-control-height-sm",
|
|
1193
|
+
"kind": "pixel",
|
|
1194
|
+
"group": "foundation",
|
|
1195
|
+
"fallback": 36
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
"key": "controlHeightDefault",
|
|
1199
|
+
"cssVar": "--ui-control-height-default",
|
|
1200
|
+
"kind": "pixel",
|
|
1201
|
+
"group": "foundation",
|
|
1202
|
+
"fallback": 44
|
|
1203
|
+
},
|
|
1204
|
+
{
|
|
1205
|
+
"key": "controlHeightLg",
|
|
1206
|
+
"cssVar": "--ui-control-height-lg",
|
|
1207
|
+
"kind": "pixel",
|
|
1208
|
+
"group": "foundation",
|
|
1209
|
+
"fallback": 52
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
"key": "controlPaddingXSm",
|
|
1213
|
+
"cssVar": "--ui-control-padding-x-sm",
|
|
1214
|
+
"kind": "pixel",
|
|
1215
|
+
"group": "foundation",
|
|
1216
|
+
"fallback": 12
|
|
1217
|
+
},
|
|
1218
|
+
{
|
|
1219
|
+
"key": "controlPaddingXDefault",
|
|
1220
|
+
"cssVar": "--ui-control-padding-x-default",
|
|
1221
|
+
"kind": "pixel",
|
|
1222
|
+
"group": "foundation",
|
|
1223
|
+
"fallback": 16
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
"key": "controlPaddingXLg",
|
|
1227
|
+
"cssVar": "--ui-control-padding-x-lg",
|
|
1228
|
+
"kind": "pixel",
|
|
1229
|
+
"group": "foundation",
|
|
1230
|
+
"fallback": 24
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
"key": "controlPaddingYSm",
|
|
1234
|
+
"cssVar": "--ui-control-padding-y-sm",
|
|
1235
|
+
"kind": "pixel",
|
|
1236
|
+
"group": "foundation",
|
|
1237
|
+
"fallback": 8
|
|
1238
|
+
},
|
|
1239
|
+
{
|
|
1240
|
+
"key": "controlPaddingYDefault",
|
|
1241
|
+
"cssVar": "--ui-control-padding-y-default",
|
|
1242
|
+
"kind": "pixel",
|
|
1243
|
+
"group": "foundation",
|
|
1244
|
+
"fallback": 10
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
"key": "controlPaddingYLg",
|
|
1248
|
+
"cssVar": "--ui-control-padding-y-lg",
|
|
1249
|
+
"kind": "pixel",
|
|
1250
|
+
"group": "foundation",
|
|
1251
|
+
"fallback": 14
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
"key": "surfacePaddingSm",
|
|
1255
|
+
"cssVar": "--ui-surface-padding-sm",
|
|
1256
|
+
"kind": "pixel",
|
|
1257
|
+
"group": "foundation",
|
|
1258
|
+
"fallback": 16
|
|
1259
|
+
},
|
|
1260
|
+
{
|
|
1261
|
+
"key": "surfacePaddingDefault",
|
|
1262
|
+
"cssVar": "--ui-surface-padding-default",
|
|
1263
|
+
"kind": "pixel",
|
|
1264
|
+
"group": "foundation",
|
|
1265
|
+
"fallback": 24
|
|
1266
|
+
},
|
|
1267
|
+
{
|
|
1268
|
+
"key": "surfacePaddingLg",
|
|
1269
|
+
"cssVar": "--ui-surface-padding-lg",
|
|
1270
|
+
"kind": "pixel",
|
|
1271
|
+
"group": "foundation",
|
|
1272
|
+
"fallback": 32
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
"key": "badgeRadius",
|
|
1276
|
+
"cssVar": "--ui-badge-radius",
|
|
1277
|
+
"kind": "pixel",
|
|
1278
|
+
"group": "foundation",
|
|
1279
|
+
"fallback": 999
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
"key": "badgePaddingX",
|
|
1283
|
+
"cssVar": "--ui-badge-padding-x",
|
|
1284
|
+
"kind": "pixel",
|
|
1285
|
+
"group": "foundation",
|
|
1286
|
+
"fallback": 10
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
"key": "badgePaddingY",
|
|
1290
|
+
"cssVar": "--ui-badge-padding-y",
|
|
1291
|
+
"kind": "pixel",
|
|
1292
|
+
"group": "foundation",
|
|
1293
|
+
"fallback": 4
|
|
1294
|
+
},
|
|
1295
|
+
{
|
|
1296
|
+
"key": "fontSizeXs",
|
|
1297
|
+
"cssVar": "--ui-font-size-xs",
|
|
1298
|
+
"kind": "pixel",
|
|
1299
|
+
"group": "typography",
|
|
1300
|
+
"fallback": 11
|
|
1301
|
+
},
|
|
1302
|
+
{
|
|
1303
|
+
"key": "fontSizeSm",
|
|
1304
|
+
"cssVar": "--ui-font-size-sm",
|
|
1305
|
+
"kind": "pixel",
|
|
1306
|
+
"group": "typography",
|
|
1307
|
+
"fallback": 13
|
|
1308
|
+
},
|
|
1309
|
+
{
|
|
1310
|
+
"key": "fontSizeBase",
|
|
1311
|
+
"cssVar": "--ui-font-size-base",
|
|
1312
|
+
"kind": "pixel",
|
|
1313
|
+
"group": "typography",
|
|
1314
|
+
"fallback": 15
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
"key": "fontSizeLg",
|
|
1318
|
+
"cssVar": "--ui-font-size-lg",
|
|
1319
|
+
"kind": "pixel",
|
|
1320
|
+
"group": "typography",
|
|
1321
|
+
"fallback": 17
|
|
1322
|
+
},
|
|
1323
|
+
{
|
|
1324
|
+
"key": "fontSizeXl",
|
|
1325
|
+
"cssVar": "--ui-font-size-xl",
|
|
1326
|
+
"kind": "pixel",
|
|
1327
|
+
"group": "typography",
|
|
1328
|
+
"fallback": 20
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
"key": "fontSize2xl",
|
|
1332
|
+
"cssVar": "--ui-font-size-2xl",
|
|
1333
|
+
"kind": "pixel",
|
|
1334
|
+
"group": "typography",
|
|
1335
|
+
"fallback": 24
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
"key": "letterSpacingCaption",
|
|
1339
|
+
"cssVar": "--ui-letter-spacing-caption",
|
|
1340
|
+
"kind": "number",
|
|
1341
|
+
"group": "typography",
|
|
1342
|
+
"fallback": 0.2
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
"key": "letterSpacingLabel",
|
|
1346
|
+
"cssVar": "--ui-letter-spacing-label",
|
|
1347
|
+
"kind": "number",
|
|
1348
|
+
"group": "typography",
|
|
1349
|
+
"fallback": 0.1
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
"key": "letterSpacingBody",
|
|
1353
|
+
"cssVar": "--ui-letter-spacing-body",
|
|
1354
|
+
"kind": "number",
|
|
1355
|
+
"group": "typography",
|
|
1356
|
+
"fallback": 0
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
"key": "letterSpacingTitle",
|
|
1360
|
+
"cssVar": "--ui-letter-spacing-title",
|
|
1361
|
+
"kind": "number",
|
|
1362
|
+
"group": "typography",
|
|
1363
|
+
"fallback": -0.2
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
"key": "letterSpacingHeadline",
|
|
1367
|
+
"cssVar": "--ui-letter-spacing-headline",
|
|
1368
|
+
"kind": "number",
|
|
1369
|
+
"group": "typography",
|
|
1370
|
+
"fallback": -0.4
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
"key": "borderWidthHairline",
|
|
1374
|
+
"cssVar": "--ui-border-width-hairline",
|
|
1375
|
+
"kind": "scalar",
|
|
1376
|
+
"group": "depth",
|
|
1377
|
+
"fallback": 1
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
"key": "borderWidthControl",
|
|
1381
|
+
"cssVar": "--ui-border-width-control",
|
|
1382
|
+
"kind": "scalar",
|
|
1383
|
+
"group": "depth",
|
|
1384
|
+
"fallback": 1
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
"key": "borderWidthEmphasis",
|
|
1388
|
+
"cssVar": "--ui-border-width-emphasis",
|
|
1389
|
+
"kind": "scalar",
|
|
1390
|
+
"group": "depth",
|
|
1391
|
+
"fallback": 2
|
|
1392
|
+
},
|
|
1393
|
+
{
|
|
1394
|
+
"key": "focusRingWidth",
|
|
1395
|
+
"cssVar": "--ui-focus-ring-width",
|
|
1396
|
+
"kind": "scalar",
|
|
1397
|
+
"group": "depth",
|
|
1398
|
+
"fallback": 2
|
|
1399
|
+
},
|
|
1400
|
+
{
|
|
1401
|
+
"key": "shadowOpacitySm",
|
|
1402
|
+
"cssVar": "--ui-shadow-opacity-sm",
|
|
1403
|
+
"kind": "ratio",
|
|
1404
|
+
"group": "depth",
|
|
1405
|
+
"fallback": 0.06
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
"key": "shadowOpacityMd",
|
|
1409
|
+
"cssVar": "--ui-shadow-opacity-md",
|
|
1410
|
+
"kind": "ratio",
|
|
1411
|
+
"group": "depth",
|
|
1412
|
+
"fallback": 0.1
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
"key": "shadowOpacityLg",
|
|
1416
|
+
"cssVar": "--ui-shadow-opacity-lg",
|
|
1417
|
+
"kind": "ratio",
|
|
1418
|
+
"group": "depth",
|
|
1419
|
+
"fallback": 0.16
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
"key": "shadowRadiusSm",
|
|
1423
|
+
"cssVar": "--ui-shadow-radius-sm",
|
|
1424
|
+
"kind": "pixel",
|
|
1425
|
+
"group": "depth",
|
|
1426
|
+
"fallback": 3
|
|
1427
|
+
},
|
|
1428
|
+
{
|
|
1429
|
+
"key": "shadowRadiusMd",
|
|
1430
|
+
"cssVar": "--ui-shadow-radius-md",
|
|
1431
|
+
"kind": "pixel",
|
|
1432
|
+
"group": "depth",
|
|
1433
|
+
"fallback": 12
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
"key": "shadowRadiusLg",
|
|
1437
|
+
"cssVar": "--ui-shadow-radius-lg",
|
|
1438
|
+
"kind": "pixel",
|
|
1439
|
+
"group": "depth",
|
|
1440
|
+
"fallback": 28
|
|
1441
|
+
},
|
|
1442
|
+
{
|
|
1443
|
+
"key": "shadowOffsetYSm",
|
|
1444
|
+
"cssVar": "--ui-shadow-offset-y-sm",
|
|
1445
|
+
"kind": "pixel",
|
|
1446
|
+
"group": "depth",
|
|
1447
|
+
"fallback": 1
|
|
1448
|
+
},
|
|
1449
|
+
{
|
|
1450
|
+
"key": "shadowOffsetYMd",
|
|
1451
|
+
"cssVar": "--ui-shadow-offset-y-md",
|
|
1452
|
+
"kind": "pixel",
|
|
1453
|
+
"group": "depth",
|
|
1454
|
+
"fallback": 4
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
"key": "shadowOffsetYLg",
|
|
1458
|
+
"cssVar": "--ui-shadow-offset-y-lg",
|
|
1459
|
+
"kind": "pixel",
|
|
1460
|
+
"group": "depth",
|
|
1461
|
+
"fallback": 12
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
"key": "shadowElevationSm",
|
|
1465
|
+
"cssVar": "--ui-shadow-elevation-sm",
|
|
1466
|
+
"kind": "pixel",
|
|
1467
|
+
"group": "depth",
|
|
1468
|
+
"fallback": 1
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
"key": "shadowElevationMd",
|
|
1472
|
+
"cssVar": "--ui-shadow-elevation-md",
|
|
1473
|
+
"kind": "pixel",
|
|
1474
|
+
"group": "depth",
|
|
1475
|
+
"fallback": 4
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
"key": "shadowElevationLg",
|
|
1479
|
+
"cssVar": "--ui-shadow-elevation-lg",
|
|
1480
|
+
"kind": "pixel",
|
|
1481
|
+
"group": "depth",
|
|
1482
|
+
"fallback": 10
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
"key": "touchTargetMinSize",
|
|
1486
|
+
"cssVar": "--ui-touch-target-min-size",
|
|
1487
|
+
"kind": "pixel",
|
|
1488
|
+
"group": "foundation",
|
|
1489
|
+
"fallback": 44
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
"key": "drawerWidthRatio",
|
|
1493
|
+
"cssVar": "--ui-drawer-width-ratio",
|
|
1494
|
+
"kind": "ratio",
|
|
1495
|
+
"group": "component",
|
|
1496
|
+
"fallback": 0.84
|
|
1497
|
+
},
|
|
1498
|
+
{
|
|
1499
|
+
"key": "drawerMaxWidthWeb",
|
|
1500
|
+
"cssVar": "--ui-drawer-max-width-web",
|
|
1501
|
+
"kind": "pixel",
|
|
1502
|
+
"group": "component",
|
|
1503
|
+
"fallback": 380
|
|
1504
|
+
},
|
|
1505
|
+
{
|
|
1506
|
+
"key": "drawerFrostedInset",
|
|
1507
|
+
"cssVar": "--ui-drawer-frosted-inset",
|
|
1508
|
+
"kind": "pixel",
|
|
1509
|
+
"group": "surface",
|
|
1510
|
+
"fallback": 10
|
|
1511
|
+
},
|
|
1512
|
+
{
|
|
1513
|
+
"key": "drawerFrostedRadius",
|
|
1514
|
+
"cssVar": "--ui-drawer-frosted-radius",
|
|
1515
|
+
"kind": "pixel",
|
|
1516
|
+
"group": "surface",
|
|
1517
|
+
"fallback": 34
|
|
1518
|
+
},
|
|
1519
|
+
{
|
|
1520
|
+
"key": "drawerContentTopPadding",
|
|
1521
|
+
"cssVar": "--ui-drawer-content-top-padding",
|
|
1522
|
+
"kind": "pixel",
|
|
1523
|
+
"group": "component",
|
|
1524
|
+
"fallback": 0
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
"key": "drawerItemFontSize",
|
|
1528
|
+
"cssVar": "--ui-drawer-item-font-size",
|
|
1529
|
+
"kind": "pixel",
|
|
1530
|
+
"group": "component",
|
|
1531
|
+
"fallback": 15
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
"key": "drawerBadgeMinWidth",
|
|
1535
|
+
"cssVar": "--ui-drawer-badge-min-width",
|
|
1536
|
+
"kind": "pixel",
|
|
1537
|
+
"group": "component",
|
|
1538
|
+
"fallback": 20
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
"key": "navSectionTitleFontSize",
|
|
1542
|
+
"cssVar": "--ui-nav-section-title-font-size",
|
|
1543
|
+
"kind": "pixel",
|
|
1544
|
+
"group": "component",
|
|
1545
|
+
"fallback": 11
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
"key": "navItemBadgeFontSize",
|
|
1549
|
+
"cssVar": "--ui-nav-item-badge-font-size",
|
|
1550
|
+
"kind": "pixel",
|
|
1551
|
+
"group": "component",
|
|
1552
|
+
"fallback": 11
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
"key": "bottomNavBadgeFontSize",
|
|
1556
|
+
"cssVar": "--ui-bottom-nav-badge-font-size",
|
|
1557
|
+
"kind": "pixel",
|
|
1558
|
+
"group": "component",
|
|
1559
|
+
"fallback": 10
|
|
1560
|
+
},
|
|
1561
|
+
{
|
|
1562
|
+
"key": "dataRowCompactDescriptionSize",
|
|
1563
|
+
"cssVar": "--ui-data-row-compact-description-size",
|
|
1564
|
+
"kind": "pixel",
|
|
1565
|
+
"group": "component",
|
|
1566
|
+
"fallback": 11
|
|
1567
|
+
},
|
|
1568
|
+
{
|
|
1569
|
+
"key": "dataRowRightMaxWidthRatio",
|
|
1570
|
+
"cssVar": "--ui-data-row-right-max-width-ratio",
|
|
1571
|
+
"kind": "ratio",
|
|
1572
|
+
"group": "component",
|
|
1573
|
+
"fallback": 0.45
|
|
1574
|
+
},
|
|
1575
|
+
{
|
|
1576
|
+
"key": "segmentedWrapBasisRatio",
|
|
1577
|
+
"cssVar": "--ui-segmented-wrap-basis-ratio",
|
|
1578
|
+
"kind": "ratio",
|
|
1579
|
+
"group": "component",
|
|
1580
|
+
"fallback": 0.48
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
"key": "textareaMinHeightDefault",
|
|
1584
|
+
"cssVar": "--ui-textarea-min-height-default",
|
|
1585
|
+
"kind": "pixel",
|
|
1586
|
+
"group": "component",
|
|
1587
|
+
"fallback": 80
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
"key": "textareaMinHeightSm",
|
|
1591
|
+
"cssVar": "--ui-textarea-min-height-sm",
|
|
1592
|
+
"kind": "pixel",
|
|
1593
|
+
"group": "component",
|
|
1594
|
+
"fallback": 60
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
"key": "textareaMinHeightLg",
|
|
1598
|
+
"cssVar": "--ui-textarea-min-height-lg",
|
|
1599
|
+
"kind": "pixel",
|
|
1600
|
+
"group": "component",
|
|
1601
|
+
"fallback": 120
|
|
1602
|
+
},
|
|
1603
|
+
{
|
|
1604
|
+
"key": "chatBubbleMaxWidthRatio",
|
|
1605
|
+
"cssVar": "--ui-chat-bubble-max-width-ratio",
|
|
1606
|
+
"kind": "ratio",
|
|
1607
|
+
"group": "component",
|
|
1608
|
+
"fallback": 0.85
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
"key": "chatSystemBubbleMaxWidthRatio",
|
|
1612
|
+
"cssVar": "--ui-chat-system-bubble-max-width-ratio",
|
|
1613
|
+
"kind": "ratio",
|
|
1614
|
+
"group": "component",
|
|
1615
|
+
"fallback": 0.9
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
"key": "smartInputMaxHeight",
|
|
1619
|
+
"cssVar": "--ui-smart-input-max-height",
|
|
1620
|
+
"kind": "pixel",
|
|
1621
|
+
"group": "component",
|
|
1622
|
+
"fallback": 120
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
"key": "formModalContentMinHeightRatio",
|
|
1626
|
+
"cssVar": "--ui-form-modal-content-min-height-ratio",
|
|
1627
|
+
"kind": "ratio",
|
|
1628
|
+
"group": "component",
|
|
1629
|
+
"fallback": 0.4
|
|
1630
|
+
},
|
|
1631
|
+
{
|
|
1632
|
+
"key": "sheetTabletBreakpoint",
|
|
1633
|
+
"cssVar": "--ui-sheet-tablet-breakpoint",
|
|
1634
|
+
"kind": "pixel",
|
|
1635
|
+
"group": "component",
|
|
1636
|
+
"fallback": 600
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
"key": "sheetMaxWidth",
|
|
1640
|
+
"cssVar": "--ui-sheet-max-width",
|
|
1641
|
+
"kind": "pixel",
|
|
1642
|
+
"group": "component",
|
|
1643
|
+
"fallback": 400
|
|
1644
|
+
},
|
|
1645
|
+
{
|
|
1646
|
+
"key": "sheetHalfMaxHeightRatio",
|
|
1647
|
+
"cssVar": "--ui-sheet-half-max-height-ratio",
|
|
1648
|
+
"kind": "ratio",
|
|
1649
|
+
"group": "component",
|
|
1650
|
+
"fallback": 0.56
|
|
1651
|
+
},
|
|
1652
|
+
{
|
|
1653
|
+
"key": "sheetHalfMinHeight",
|
|
1654
|
+
"cssVar": "--ui-sheet-half-min-height",
|
|
1655
|
+
"kind": "pixel",
|
|
1656
|
+
"group": "component",
|
|
1657
|
+
"fallback": 340
|
|
1658
|
+
},
|
|
1659
|
+
{
|
|
1660
|
+
"key": "swipeActionWidth",
|
|
1661
|
+
"cssVar": "--ui-swipe-action-width",
|
|
1662
|
+
"kind": "pixel",
|
|
1663
|
+
"group": "component",
|
|
1664
|
+
"fallback": 80
|
|
1665
|
+
},
|
|
1666
|
+
{
|
|
1667
|
+
"key": "skeletonShimmerBandWidth",
|
|
1668
|
+
"cssVar": "--ui-skeleton-shimmer-band-width",
|
|
1669
|
+
"kind": "pixel",
|
|
1670
|
+
"group": "component",
|
|
1671
|
+
"fallback": 56
|
|
1672
|
+
},
|
|
1673
|
+
{
|
|
1674
|
+
"key": "blurIntensitySubtle",
|
|
1675
|
+
"cssVar": "--ui-blur-intensity-subtle",
|
|
1676
|
+
"kind": "pixel",
|
|
1677
|
+
"group": "surface",
|
|
1678
|
+
"fallback": 18
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
"key": "blurIntensityMedium",
|
|
1682
|
+
"cssVar": "--ui-blur-intensity-medium",
|
|
1683
|
+
"kind": "pixel",
|
|
1684
|
+
"group": "surface",
|
|
1685
|
+
"fallback": 26
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
"key": "blurIntensityStrong",
|
|
1689
|
+
"cssVar": "--ui-blur-intensity-strong",
|
|
1690
|
+
"kind": "pixel",
|
|
1691
|
+
"group": "surface",
|
|
1692
|
+
"fallback": 34
|
|
1693
|
+
},
|
|
1694
|
+
{
|
|
1695
|
+
"key": "blurSaturationPct",
|
|
1696
|
+
"cssVar": "--ui-blur-saturation-pct",
|
|
1697
|
+
"kind": "pixel",
|
|
1698
|
+
"group": "surface",
|
|
1699
|
+
"fallback": 145
|
|
1700
|
+
},
|
|
1701
|
+
{
|
|
1702
|
+
"key": "frostedHighlightHeight",
|
|
1703
|
+
"cssVar": "--ui-frosted-highlight-height",
|
|
1704
|
+
"kind": "pixel",
|
|
1705
|
+
"group": "surface",
|
|
1706
|
+
"fallback": 2
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
"key": "frostedBackdropBlurIntensity",
|
|
1710
|
+
"cssVar": "--ui-frosted-backdrop-blur-intensity",
|
|
1711
|
+
"kind": "pixel",
|
|
1712
|
+
"group": "surface",
|
|
1713
|
+
"fallback": 22
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
"key": "frostedBackdropBlurScale",
|
|
1717
|
+
"cssVar": "--ui-frosted-backdrop-blur-scale",
|
|
1718
|
+
"kind": "ratio",
|
|
1719
|
+
"group": "surface",
|
|
1720
|
+
"fallback": 0.72
|
|
1721
|
+
},
|
|
1722
|
+
{
|
|
1723
|
+
"key": "androidFrostedTintAlphaScale",
|
|
1724
|
+
"cssVar": "--ui-frosted-android-tint-alpha-scale",
|
|
1725
|
+
"kind": "ratio",
|
|
1726
|
+
"group": "surface",
|
|
1727
|
+
"fallback": 1.18
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
"key": "androidExperimentalBlurEnabled",
|
|
1731
|
+
"cssVar": "--ui-frosted-android-experimental-blur",
|
|
1732
|
+
"kind": "boolean",
|
|
1733
|
+
"group": "surface",
|
|
1734
|
+
"fallback": 1
|
|
1735
|
+
}
|
|
1736
|
+
]
|
|
1737
|
+
}
|