wimui 0.3.0 → 0.5.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 +3 -3
- package/dist/WimProvider.cjs +1 -1
- package/dist/WimProvider.d.ts +9 -2
- package/dist/WimProvider.js +27 -21
- package/dist/components/data-display/Badge/badge.module.cjs +1 -1
- package/dist/components/data-display/Badge/badge.module.js +1 -1
- package/dist/components/data-display/Chip/chip.module.cjs +1 -1
- package/dist/components/data-display/Chip/chip.module.js +2 -2
- package/dist/components/data-display/DataGrid/DataGrid.cjs +1 -1
- package/dist/components/data-display/DataGrid/DataGrid.js +3 -0
- package/dist/components/data-display/Tag/tag.module.cjs +1 -1
- package/dist/components/data-display/Tag/tag.module.js +1 -1
- package/dist/components/layout/BentoGrid/BentoGrid.cjs +1 -1
- package/dist/components/layout/BentoGrid/BentoGrid.d.ts +3 -1
- package/dist/components/layout/BentoGrid/BentoGrid.js +6 -2
- package/dist/components/layout/BentoGrid/bento-grid.module.cjs +1 -1
- package/dist/components/layout/BentoGrid/bento-grid.module.js +1 -1
- package/dist/components/layout/Navbar/Navbar.cjs +1 -1
- package/dist/components/layout/Navbar/Navbar.d.ts +2 -0
- package/dist/components/layout/Navbar/Navbar.js +7 -7
- package/dist/components/layout/Navbar/navbar.module.cjs +1 -1
- package/dist/components/layout/Navbar/navbar.module.js +15 -14
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -2
- package/dist/llms-full.txt +531 -2
- package/dist/llms.txt +109 -2
- package/dist/preset.cjs +2 -0
- package/dist/preset.d.ts +11 -0
- package/dist/preset.js +16 -0
- package/dist/rhf/zod.d.ts +1 -1
- package/dist/styles.css +2 -2
- package/dist/tokens/generated/presets.cjs +2 -0
- package/dist/tokens/generated/presets.d.ts +19 -0
- package/dist/tokens/generated/presets.js +21 -0
- package/dist/wimui.umd.css +1 -1
- package/dist/wimui.umd.js +2 -2
- package/package.json +7 -4
package/dist/index.d.ts
CHANGED
|
@@ -15,4 +15,5 @@ export * from './icon';
|
|
|
15
15
|
export { setWimLocale, getWimLocale } from './i18n/instance';
|
|
16
16
|
export { setWimDensity, getWimDensity, type WimDensity } from './density';
|
|
17
17
|
export { setWimTheme, getWimTheme, type WimTheme } from './theme';
|
|
18
|
+
export { setWimPreset, getWimPreset, WIM_PRESETS, type WimPreset, type WimPresetName, } from './preset';
|
|
18
19
|
export { WimProvider, useWim, type WimProviderProps, type WimContextValue } from './WimProvider';
|
package/dist/index.js
CHANGED
|
@@ -263,5 +263,7 @@ import { ChatAvatar as ho, ChatContainer as go, ChatInput as _o, ChatInputArea a
|
|
|
263
263
|
import { useMediaQuery as xo } from "./hooks/useMediaQuery.js";
|
|
264
264
|
import { BREAKPOINTS as So, useBreakpoint as Co, useBreakpointDown as wo, useBreakpointUp as To } from "./hooks/useBreakpoint.js";
|
|
265
265
|
import { getWimDensity as Eo, setWimDensity as Do } from "./density.js";
|
|
266
|
-
import {
|
|
267
|
-
|
|
266
|
+
import { WIM_PRESETS as Oo } from "./tokens/generated/presets.js";
|
|
267
|
+
import { getWimPreset as ko, setWimPreset as Ao } from "./preset.js";
|
|
268
|
+
import { WimProvider as jo, useWim as Mo } from "./WimProvider.js";
|
|
269
|
+
export { no as AIResponseFeedback, Ct as ALL_ICONS, ii as Accordion, ai as AccordionContent, oi as AccordionItem, si as AccordionTrigger, kr as Affix, so as AgentStatus, sr as Alert, ae as AlertCircleIcon, oe as AlignJustifyIcon, Ar as Anchor, e as AppShell, t as AppShellBody, n as AppShellFooter, r as AppShellHeader, i as AppShellMain, a as AppShellNavbar, o as AppShellRoot, s as AppShellSidebar, c as AspectRatio, Ha as Audio, Or as Autosave, ci as Avatar, li as AvatarGroup, So as BREAKPOINTS, jr as BackTop, Lt as Badge, cr as Banner, se as BellIcon, l as BentoGrid, u as BentoGridItem, za as Blockquote, oa as BottomSheet, sa as BottomSheetBody, ca as BottomSheetClose, la as BottomSheetContent, ua as BottomSheetDescription, da as BottomSheetFooter, fa as BottomSheetHeader, pa as BottomSheetTitle, ma as BottomSheetTrigger, d as Box, Nr as Breadcrumb, kt as Button, Ft as ButtonGroup, Yt as Calendar, ta as CalendarHeatmap, ce as CalendarIcon, Tt as Card, Et as CardBody, Dt as CardFooter, Ot as CardHeader, di as Carousel, Ht as Cascader, f as Center, le as ChartIcon, ho as ChatAvatar, go as ChatContainer, _o as ChatInput, vo as ChatInputArea, yo as ChatMessage, bo as ChatMessageList, ue as CheckCircleIcon, de as CheckIcon, Ut as Checkbox, Wt as CheckboxGroup, fe as ChevronDownIcon, pe as ChevronLeftIcon, me as ChevronRightIcon, he as ChevronUpIcon, sn as Chip, ge as CircleIcon, _e as ClockIcon, ye as CloseIcon, ve as CloseSmallIcon, Ia as Code, ao as CodeBlock, Kt as ColorInput, qt as ColorPicker, be as ColumnsIcon, Jt as Combobox, Pr as CommandPalette, Fr as CommandPaletteContent, Ir as CommandPaletteEmpty, Lr as CommandPaletteFooter, Rr as CommandPaletteGroup, zr as CommandPaletteInput, Br as CommandPaletteItem, Vr as CommandPaletteList, Hr as CommandPaletteTrigger, p as Container, ha as ContextMenu, ga as ContextMenuDivider, _a as ContextMenuGroup, va as ContextMenuItem, uo as ConversationList, Kn as CopyButton, xe as CopyIcon, aa as Countdown, rr as CounterTextarea, Se as CreditCardIcon, nr as CreditCardInput, At as Dashboard, jt as DashboardWidgetCard, xi as DataGrid, Xt as DatePicker, Zt as DateRangePicker, Si as DescriptionList, Ci as DescriptionListDetails, wi as DescriptionListItem, Ti as DescriptionListTerm, hn as Dialog, gn as DialogClose, _n as DialogContent, vn as DialogDescription, yn as DialogFooter, bn as DialogHeader, xn as DialogTitle, Sn as DialogTrigger, m as Divider, Ce as DocumentIcon, we as DownloadIcon, j as Drawer, M as DrawerClose, N as DrawerContent, P as DrawerDescription, F as DrawerFooter, I as DrawerHeader, L as DrawerTitle, R as DrawerTrigger, ya as Dropdown, ba as DropdownItem, xa as DropdownMenu, Sa as DropdownTrigger, Qt as Dropzone, Te as EditIcon, Ee as EmailIcon, Ei as EmptyState, lr as ErrorBoundary, De as ExternalLinkIcon, ke as EyeIcon, Oe as EyeOffIcon, qi as FAQSection, Bt as FieldError, Vt as FieldTemplate, $t as Fieldset, en as FileUpload, Ae as FilterIcon, h as Flex, Yn as FloatButton, A as FocusTrap, g as Footer, _ as FooterBottom, v as FooterSection, Za as Gallery, y as Grid, je as GripVerticalIcon, b as Group, D as HamburgerMenu, x as Header, S as HeaderSection, Me as HelpCircleIcon, La as Highlight, Ne as HomeIcon, Pe as HourglassIcon, Ca as HoverCard, wa as HoverCardContent, Ta as HoverCardTrigger, wt as Icon, qn as IconButton, Ua as Image, Qa as ImageCompare, $n as ImageCropper, Fe as ImageIcon, ur as Indicator, Ji as InfiniteScroll, Ie as InfoCircleIcon, Zn as InlineEdit, Gt as Input, It as InputBase, tn as InputGroup, nn as InputGroupText, rn as InputMask, C as InteractiveArea, ea as JsonViewer, Di as Kanban, Oi as KanbanBoard, ki as KanbanCard, Ai as KanbanColumn, ti as Kbd, ni as KeyboardShortcuts, Rt as Label, ra as Leaderboard, Ra as Legend, Ga as Lightbox, Ka as LightboxContent, qa as LightboxGallery, Ja as LightboxRoot, Ya as LightboxTrigger, Mr as Link, Jn as LinkButton, ji as List, Mi as ListItem, dr as Loader, Le as LoadingIcon, pr as LoadingOverlay, Re as LogInIcon, ze as LogOutIcon, Ni as Marquee, w as Masonry, Be as MaximizeIcon, on as Mentions, Ea as Menu, Da as MenuDivider, Oa as MenuItem, ka as MenuItemGroup, ri as Menubar, Ve as MinimizeIcon, He as MinusIcon, lo as ModelSelector, Ue as MonitorIcon, We as MoonIcon, Ge as MoreHorizontalIcon, Ke as MoreVerticalIcon, cn as MultiSelect, qe as MuteIcon, B as Navbar, V as NavbarBrand, H as NavbarContent, U as NavbarItem, W as NavbarLink, G as NavbarMenu, K as NavbarMenuItem, q as NavbarToggle, mr as Notification, hr as NotificationProvider, ln as NumberInput, un as OtpInput, Ur as Pagination, Wr as PaginationPage, Je as PaperclipIcon, dn as PasswordInput, Qn as PasswordStrength, Ye as PauseIcon, Xe as PdfIcon, Ze as PhoneIcon, fn as PhoneInput, Qe as PipIcon, $e as PlayIcon, et as PlusIcon, Fa as Popconfirm, ja as Popover, Ma as PopoverClose, Na as PopoverContent, Pa as PopoverTrigger, O as Portal, _r as Progress, tt as ProjectIcon, $a as PromptInput, $i as PullToRefresh, Xn as QueryBuilder, pn as Radio, mn as RadioGroup, ui as RangeCalendar, Tn as RangeSlider, En as Rating, na as Reaction, nt as RefreshIcon, ia as RelativeTime, rt as RepeatIcon, vr as Result, wn as RichTextEditor, J as ScrollArea, yr as ScrollProgress, it as SearchIcon, Dn as SearchInput, An as SegmentedControl, jn as Select, at as SendIcon, ot as SettingsIcon, st as ShareIcon, ct as ShuffleIcon, Q as Sidebar, $ as SidebarContent, ee as SidebarFooter, te as SidebarHeader, ne as SidebarItem, Mn as SignaturePad, Y as SimpleGrid, br as Skeleton, lt as SkipBackIcon, ut as SkipForwardIcon, Nn as Slider, kn as SmartSearchInput, dt as SmartphoneIcon, xr as Snackbar, Sr as SnackbarProvider, Yi as SortableList, Xi as SortableListDragHandle, Zi as SortableListItem, ro as SourceCitation, io as SourceCitationList, Ba as Span, ei as SpeedDial, fr as Spinner, ft as SpinnerIcon, X as Splitter, Va as Spoiler, pt as SquareIcon, Z as Stack, mt as StarIcon, Pi as Stats, Fi as StatsDescription, Ii as StatsLabel, Li as StatsTrend, Ri as StatsValue, Gr as Stepper, Aa as SubMenu, ht as SunIcon, Qi as SwipeAction, Pn as Switch, Fn as SwitchGroup, Kr as TabBar, qr as TabBarItem, Jr as TabNavigation, Yr as TabNavigationItem, mi as Table, hi as TableBody, gi as TableCell, _i as TableFooter, vi as TableHead, yi as TableHeader, bi as TableRow, Xr as Tabs, Zr as TabsContent, Qr as TabsList, $r as TabsTrigger, er as Tag, tr as TagInput, oo as Terminal, zt as Text, an as Textarea, or as ThemeToggle, eo as ThoughtProcess, to as ThoughtStep, fo as ThreadList, gt as ThumbDownIcon, _t as ThumbUpIcon, In as TimePicker, zi as Timeline, Bi as TimelineConnector, Vi as TimelineContent, Hi as TimelineItem, Ui as TimelineOppositeContent, Wi as TimelinePoint, Gi as TimelineSeparator, Ki as Title, wr as Toast, Tr as ToastProvider, Ln as ToggleGroup, po as TokenCounter, Mt as Toolbar, Nt as ToolbarGroup, Pt as ToolbarSeparator, Un as Tooltip, Wn as TooltipContent, Gn as TooltipTrigger, Dr as Tour, zn as Transfer, k as Transition, vt as TrashIcon, Hn as TreeSelect, Bn as TreeView, Vn as TreeViewItem, yt as UploadIcon, mo as UsageMeter, bt as UserIcon, Wa as Video, xt as VideoIcon, Rn as VirtualList, re as VisuallyHidden, co as VoiceVisualizer, St as VolumeIcon, Oo as WIM_PRESETS, ie as Watermark, jo as WimProvider, fi as getNextSortDirection, Eo as getWimDensity, T as getWimLocale, ko as getWimPreset, ir as getWimTheme, Do as setWimDensity, E as setWimLocale, Ao as setWimPreset, ar as setWimTheme, On as useAutoResize, Co as useBreakpoint, wo as useBreakpointDown, To as useBreakpointUp, Cn as useDialog, z as useDrawer, Xa as useLightbox, xo as useMediaQuery, gr as useNotification, Cr as useSnackbar, pi as useTableSort, Er as useToast, Mo as useWim };
|
package/dist/llms-full.txt
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## What this is
|
|
6
6
|
|
|
7
|
-
**wimui** v0.
|
|
7
|
+
**wimui** v0.5.0 — a React 19 component library: 216 documented components across 10 categories, with design tokens, dark mode, i18n (en/ja/pt-BR) and WAI-ARIA compliant a11y. Peer deps: react ^19, react-dom ^19 (plus optional peers for specific components — see package.json).
|
|
8
8
|
|
|
9
9
|
## Install & required setup
|
|
10
10
|
|
|
@@ -55,12 +55,17 @@ Single components are judged by state/a11y/token compliance. **Composed screens*
|
|
|
55
55
|
- Emoji used as UI icons (☰ ★ ✓) → use `src/icon/` components. (Emoji as functional markers in docs/README is fine; as UI icons it is not.)
|
|
56
56
|
- Skeleton used as anything but loading (fake screenshots) → real-looking content or an actual mini UI.
|
|
57
57
|
- Every section center-aligned → default to left-aligned; at most one centered section per page.
|
|
58
|
-
- Round numbers ("1000+", "99%") + generic copy ("Get started"
|
|
58
|
+
- Round numbers ("1000+", "99%") + generic copy ("Get started") → jagged realistic numbers (4,281) and product-specific voice.
|
|
59
59
|
- Sequential/alphabetical dummy names (Alice/Bob/Charlie, User 1/2) → culturally diverse, unordered, real-feeling names.
|
|
60
60
|
- Rainbow color-per-category → color carries state only (success/warning/danger); everything else neutral.
|
|
61
61
|
- Perfectly even grids (2×2, 3×3 all same size) → break the grid (span the protagonist cell, size differences, hero+sidebar asymmetry).
|
|
62
62
|
- Filling every slot / uniform gaps everywhere → leave whitespace; group by proximity (tight within a group, loose between groups).
|
|
63
63
|
|
|
64
|
+
**Machine-enforced banned vocabulary** (a linter, `check:slop`, fails builds that use these in demo copy — say concrete actions/numbers instead):
|
|
65
|
+
- Hype adjectives/verbs: seamless, powerful, effortless, cutting-edge, next-gen, next-generation, revolutionary, game-changing, state-of-the-art, world-class, best-in-class, unparalleled, lightning-fast, blazing-fast, supercharge, innovative, disruptive, harness, leverage, elevate, empower, reimagine, unleash, delight, magical, 圧倒的, 革新的, 次世代, 究極, 最先端.
|
|
66
|
+
- Hype phrases: "in today's digital landscape", "built for the modern team", "supercharge your", "unleash your", "reimagine the way", "experience the power of", "innovative solutions".
|
|
67
|
+
- Stock placeholder names: John Doe, Jane Doe, John Smith, Jane Smith, Lorem Ipsum, Example User, Acme (a realistic example name in an input placeholder is fine).
|
|
68
|
+
|
|
64
69
|
**Must rules:**
|
|
65
70
|
1. One visual protagonist per screen (jump in size/weight/color creates the entry point).
|
|
66
71
|
2. Contrast density: hero/showcase sparse, data regions (tables/lists) dense. Uniform "medium density" reads as AI.
|
|
@@ -71,6 +76,528 @@ Single components are judged by state/a11y/token compliance. **Composed screens*
|
|
|
71
76
|
7. Give demo content real substance (product-context copy, internally consistent numbers/dates/names — active ≤ total, dates not evenly spaced).
|
|
72
77
|
8. Add intentional "wobble": mix in 1–2 incomplete rows (a truncated long name, a missing optional field, an extreme value, an error/unread state) and show non-happy-path states (hover/focus/disabled/error/empty/loading).
|
|
73
78
|
|
|
79
|
+
## Recipes — copy-paste starting points
|
|
80
|
+
|
|
81
|
+
### 1. Required setup (the contract)
|
|
82
|
+
|
|
83
|
+
Without `styles.css` nothing is styled — this is the one step Storybook hides from you. Import it once at the app entry, wrap the tree in `WimProvider`, then build screens inside.
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// main.tsx — app entry
|
|
87
|
+
import { createRoot } from "react-dom/client";
|
|
88
|
+
import "wimui/styles.css"; // REQUIRED: design tokens + component styles
|
|
89
|
+
import "wimui/reset.css"; // optional base reset
|
|
90
|
+
import { WimProvider } from "wimui";
|
|
91
|
+
import { App } from "./App";
|
|
92
|
+
|
|
93
|
+
// theme: "light" | "dark" | "system" (default). density: "comfortable" | "compact".
|
|
94
|
+
createRoot(document.getElementById("root")!).render(
|
|
95
|
+
<WimProvider theme="system" density="comfortable">
|
|
96
|
+
<App />
|
|
97
|
+
</WimProvider>,
|
|
98
|
+
);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// App.tsx — app frame. AppShell wires header/sidebar; page content is children.
|
|
103
|
+
import { AppShell, Header, Sidebar, Stack, Title, Text, Button } from "wimui";
|
|
104
|
+
|
|
105
|
+
export function App() {
|
|
106
|
+
return (
|
|
107
|
+
<AppShell
|
|
108
|
+
header={<Header sticky bordered><Title tag="h1" size="md">Larkfield</Title></Header>}
|
|
109
|
+
sidebar={
|
|
110
|
+
<Sidebar width={240}>
|
|
111
|
+
<Stack gap="2xs" p="md">
|
|
112
|
+
<Button variant="ghost" justify="start" fullWidth>Overview</Button>
|
|
113
|
+
<Button variant="ghost" justify="start" fullWidth>Customers</Button>
|
|
114
|
+
<Button variant="ghost" justify="start" fullWidth>Settings</Button>
|
|
115
|
+
</Stack>
|
|
116
|
+
</Sidebar>
|
|
117
|
+
}
|
|
118
|
+
>
|
|
119
|
+
<Stack gap="lg">
|
|
120
|
+
<Title tag="h2" size="lg">Overview</Title>
|
|
121
|
+
<Text color="secondary">Spacing/size/color come from --wim-* tokens via props — never hardcode px/hex.</Text>
|
|
122
|
+
</Stack>
|
|
123
|
+
</AppShell>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 2. A composed content screen
|
|
129
|
+
|
|
130
|
+
One protagonist (the KPI row), a dense table below, tokens via props, jagged real data, and one deliberately incomplete row. Note the compound components (`Stats.Value`, `Table.Head`) and that `Grid` uses `cols` (not `columns`).
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { Stack, Grid, Card, Stats, Table, Badge, Title, Text } from "wimui";
|
|
134
|
+
|
|
135
|
+
const rows = [
|
|
136
|
+
{ id: "in_9f2a", name: "Marisol Okonkwo", plan: "Scale", amount: "$4,610.50", status: "paid" },
|
|
137
|
+
{ id: "in_7b41", name: "Dmitri Sørensen", plan: "Enterprise", amount: "$12,199.00", status: "failed" },
|
|
138
|
+
{ id: "in_2a90", name: "Thomas O'Reilly", plan: null, amount: "$89.00", status: "pending" }, // incomplete row
|
|
139
|
+
];
|
|
140
|
+
const intent = { paid: "success", failed: "danger", pending: undefined } as const;
|
|
141
|
+
|
|
142
|
+
export function BillingOverview() {
|
|
143
|
+
return (
|
|
144
|
+
<Stack gap="lg">
|
|
145
|
+
<Title tag="h2" size="lg">Billing</Title>
|
|
146
|
+
|
|
147
|
+
{/* Protagonist: KPI row. Uneven content per tile — not three clones. */}
|
|
148
|
+
<Grid cols={{ base: 1, sm: 2, lg: 3 }} gap="md">
|
|
149
|
+
<Stats><Stats.Label>MRR</Stats.Label><Stats.Value>$48,210</Stats.Value><Stats.Trend>+6.4%</Stats.Trend></Stats>
|
|
150
|
+
<Stats><Stats.Label>Active workspaces</Stats.Label><Stats.Value>1,204</Stats.Value><Stats.Description>176 idle over 30 days</Stats.Description></Stats>
|
|
151
|
+
<Stats><Stats.Label>Failed webhooks</Stats.Label><Stats.Value>137</Stats.Value><Stats.Trend>+23 today</Stats.Trend></Stats>
|
|
152
|
+
</Grid>
|
|
153
|
+
|
|
154
|
+
{/* Dense data region */}
|
|
155
|
+
<Card padding="none">
|
|
156
|
+
<Table hoverable fullWidth>
|
|
157
|
+
<Table.Header>
|
|
158
|
+
<Table.Row>
|
|
159
|
+
<Table.Head>Customer</Table.Head><Table.Head>Plan</Table.Head>
|
|
160
|
+
<Table.Head>Amount</Table.Head><Table.Head>Status</Table.Head>
|
|
161
|
+
</Table.Row>
|
|
162
|
+
</Table.Header>
|
|
163
|
+
<Table.Body>
|
|
164
|
+
{rows.map((r) => (
|
|
165
|
+
<Table.Row key={r.id}>
|
|
166
|
+
<Table.Cell>{r.name}</Table.Cell>
|
|
167
|
+
<Table.Cell>{r.plan ?? <Text color="tertiary">—</Text>}</Table.Cell>
|
|
168
|
+
<Table.Cell>{r.amount}</Table.Cell>
|
|
169
|
+
<Table.Cell><Badge variant="subtle" intent={intent[r.status]}>{r.status}</Badge></Table.Cell>
|
|
170
|
+
</Table.Row>
|
|
171
|
+
))}
|
|
172
|
+
</Table.Body>
|
|
173
|
+
</Table>
|
|
174
|
+
</Card>
|
|
175
|
+
</Stack>
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 3. Auth — sign-in screen
|
|
181
|
+
|
|
182
|
+
A focused single-protagonist screen: one centered card, a left-aligned form. Not the generic badge→heading→two-buttons hero. Real product context in the copy.
|
|
183
|
+
|
|
184
|
+
```tsx
|
|
185
|
+
import { Center, Card, Stack, Group, Title, Text, Input, PasswordInput, Checkbox, Button, Link } from "wimui";
|
|
186
|
+
|
|
187
|
+
export function SignIn() {
|
|
188
|
+
return (
|
|
189
|
+
<Center h="100dvh" p="lg">
|
|
190
|
+
<Card padding="lg" style={{ width: "min(380px, 100%)" }}>
|
|
191
|
+
<Stack gap="lg">
|
|
192
|
+
<Stack gap="2xs">
|
|
193
|
+
<Title tag="h1" size="lg">Sign in to Larkfield</Title>
|
|
194
|
+
<Text color="secondary">Use your work email — SSO is enabled for Enterprise workspaces.</Text>
|
|
195
|
+
</Stack>
|
|
196
|
+
<form onSubmit={(e) => e.preventDefault()}>
|
|
197
|
+
<Stack gap="md">
|
|
198
|
+
<Input label="Work email" type="email" placeholder="you@company.com" fullWidth />
|
|
199
|
+
<PasswordInput label="Password" fullWidth />
|
|
200
|
+
<Group justify="between" align="center">
|
|
201
|
+
<Checkbox>Keep me signed in</Checkbox>
|
|
202
|
+
<Link href="#" priority="secondary">Forgot password?</Link>
|
|
203
|
+
</Group>
|
|
204
|
+
<Button type="submit" variant="solid" fullWidth>Sign in</Button>
|
|
205
|
+
</Stack>
|
|
206
|
+
</form>
|
|
207
|
+
<Text size="sm" color="tertiary">No account? <Link href="#">Start a 14-day trial</Link></Text>
|
|
208
|
+
</Stack>
|
|
209
|
+
</Card>
|
|
210
|
+
</Center>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### 4. Settings — sectioned form
|
|
216
|
+
|
|
217
|
+
Dense label-left / control-right rows grouped in one card, separated by `Divider`. Density comes from token gaps, not hardcoded spacing. Actions right-aligned at the bottom.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { Stack, Group, Title, Text, Card, Divider, Select, Switch, Button } from "wimui";
|
|
221
|
+
|
|
222
|
+
export function NotificationSettings() {
|
|
223
|
+
return (
|
|
224
|
+
<Stack gap="lg" style={{ maxWidth: 720 }}>
|
|
225
|
+
<Stack gap="2xs">
|
|
226
|
+
<Title tag="h1" size="lg">Notifications</Title>
|
|
227
|
+
<Text color="secondary">Control what Larkfield emails you about. Changes apply immediately.</Text>
|
|
228
|
+
</Stack>
|
|
229
|
+
|
|
230
|
+
<Card padding="lg">
|
|
231
|
+
<Stack gap="md">
|
|
232
|
+
<SettingRow label="Deliverability alerts" hint="Bounce-rate spikes and blocklist hits.">
|
|
233
|
+
<Switch defaultChecked />
|
|
234
|
+
</SettingRow>
|
|
235
|
+
<Divider />
|
|
236
|
+
<SettingRow label="Weekly summary" hint="Every Monday, 09:00 in your timezone.">
|
|
237
|
+
<Switch />
|
|
238
|
+
</SettingRow>
|
|
239
|
+
<Divider />
|
|
240
|
+
<SettingRow label="Digest timezone">
|
|
241
|
+
<Select
|
|
242
|
+
aria-label="Digest timezone"
|
|
243
|
+
value="jst"
|
|
244
|
+
options={[
|
|
245
|
+
{ label: "Asia/Tokyo (JST)", value: "jst" },
|
|
246
|
+
{ label: "Europe/Berlin (CET)", value: "cet" },
|
|
247
|
+
{ label: "UTC", value: "utc" },
|
|
248
|
+
]}
|
|
249
|
+
/>
|
|
250
|
+
</SettingRow>
|
|
251
|
+
</Stack>
|
|
252
|
+
</Card>
|
|
253
|
+
|
|
254
|
+
<Group justify="end" gap="sm">
|
|
255
|
+
<Button variant="ghost">Reset</Button>
|
|
256
|
+
<Button variant="solid">Save changes</Button>
|
|
257
|
+
</Group>
|
|
258
|
+
</Stack>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Local helper: label-left / control-right row. One protagonist per row = the control.
|
|
263
|
+
function SettingRow({ label, hint, children }: { label: string; hint?: string; children: React.ReactNode }) {
|
|
264
|
+
return (
|
|
265
|
+
<Group justify="between" align="center" gap="md">
|
|
266
|
+
<Stack gap="3xs">
|
|
267
|
+
<Text weight="medium">{label}</Text>
|
|
268
|
+
{hint ? <Text size="sm" color="secondary">{hint}</Text> : null}
|
|
269
|
+
</Stack>
|
|
270
|
+
{children}
|
|
271
|
+
</Group>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### 5. Empty state — a real zero state, not a shrug
|
|
277
|
+
|
|
278
|
+
One protagonist (the primary action), calm copy that says *why* it's empty and what happens next. Reuse the `EmptyState` component instead of hand-rolling centered divs.
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
import { Center, EmptyState, Button } from "wimui";
|
|
282
|
+
import { DocumentIcon } from "wimui/icons";
|
|
283
|
+
|
|
284
|
+
export function NoInvoices() {
|
|
285
|
+
return (
|
|
286
|
+
<Center h="60dvh" p="lg">
|
|
287
|
+
<EmptyState
|
|
288
|
+
icon={<DocumentIcon />}
|
|
289
|
+
title="No invoices yet"
|
|
290
|
+
description="Invoices show up here after your first billing cycle closes. Nothing is due today."
|
|
291
|
+
extra={<Button variant="solid">Create a manual invoice</Button>}
|
|
292
|
+
/>
|
|
293
|
+
</Center>
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### 6. Filtered data table — toolbar + table
|
|
299
|
+
|
|
300
|
+
A search/filter toolbar as the sparse region above a dense table. Filtering is client-side here; swap in your query. Note the deliberately jagged data: a failed row, a member with no team (incomplete), and a long name.
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
import { Stack, Group, SearchInput, Select, Table, Badge, Text } from "wimui";
|
|
304
|
+
import { useMemo, useState } from "react";
|
|
305
|
+
|
|
306
|
+
const members = [
|
|
307
|
+
{ id: "u_1", name: "Marisol Okonkwo", team: "Growth", role: "admin", status: "active" },
|
|
308
|
+
{ id: "u_2", name: "Dmitri Sørensen", team: "Platform", role: "member", status: "invited" },
|
|
309
|
+
{ id: "u_3", name: "Aleksandra Wiśniewska-Nowak", team: null, role: "member", status: "active" }, // no team
|
|
310
|
+
{ id: "u_4", name: "Thomas O'Reilly", team: "Growth", role: "member", status: "suspended" }, // non-happy path
|
|
311
|
+
];
|
|
312
|
+
const roleIntent = { admin: "primary", member: undefined } as const;
|
|
313
|
+
const statusIntent = { active: "success", invited: undefined, suspended: "danger" } as const;
|
|
314
|
+
|
|
315
|
+
export function MembersTable() {
|
|
316
|
+
const [q, setQ] = useState("");
|
|
317
|
+
const [role, setRole] = useState("all");
|
|
318
|
+
|
|
319
|
+
const rows = useMemo(
|
|
320
|
+
() =>
|
|
321
|
+
members.filter(
|
|
322
|
+
(m) =>
|
|
323
|
+
(role === "all" || m.role === role) &&
|
|
324
|
+
m.name.toLowerCase().includes(q.trim().toLowerCase()),
|
|
325
|
+
),
|
|
326
|
+
[q, role],
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
return (
|
|
330
|
+
<Stack gap="md">
|
|
331
|
+
{/* Sparse toolbar */}
|
|
332
|
+
<Group justify="between" align="center" gap="md">
|
|
333
|
+
<SearchInput
|
|
334
|
+
placeholder="Search members"
|
|
335
|
+
value={q}
|
|
336
|
+
onChange={(e) => setQ(e.target.value)}
|
|
337
|
+
allowClear
|
|
338
|
+
width={280}
|
|
339
|
+
/>
|
|
340
|
+
<Select
|
|
341
|
+
aria-label="Filter by role"
|
|
342
|
+
value={role}
|
|
343
|
+
onChange={setRole}
|
|
344
|
+
options={[
|
|
345
|
+
{ label: "All roles", value: "all" },
|
|
346
|
+
{ label: "Admins", value: "admin" },
|
|
347
|
+
{ label: "Members", value: "member" },
|
|
348
|
+
]}
|
|
349
|
+
/>
|
|
350
|
+
</Group>
|
|
351
|
+
|
|
352
|
+
{/* Dense data region */}
|
|
353
|
+
<Table hoverable fullWidth>
|
|
354
|
+
<Table.Header>
|
|
355
|
+
<Table.Row>
|
|
356
|
+
<Table.Head>Member</Table.Head><Table.Head>Team</Table.Head>
|
|
357
|
+
<Table.Head>Role</Table.Head><Table.Head>Status</Table.Head>
|
|
358
|
+
</Table.Row>
|
|
359
|
+
</Table.Header>
|
|
360
|
+
<Table.Body>
|
|
361
|
+
{rows.map((m) => (
|
|
362
|
+
<Table.Row key={m.id}>
|
|
363
|
+
<Table.Cell>{m.name}</Table.Cell>
|
|
364
|
+
<Table.Cell>{m.team ?? <Text color="tertiary">No team</Text>}</Table.Cell>
|
|
365
|
+
<Table.Cell><Badge variant="subtle" intent={roleIntent[m.role]}>{m.role}</Badge></Table.Cell>
|
|
366
|
+
<Table.Cell><Badge variant="subtle" intent={statusIntent[m.status]}>{m.status}</Badge></Table.Cell>
|
|
367
|
+
</Table.Row>
|
|
368
|
+
))}
|
|
369
|
+
</Table.Body>
|
|
370
|
+
</Table>
|
|
371
|
+
</Stack>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### 7. Onboarding — a stepper flow
|
|
377
|
+
|
|
378
|
+
The `Stepper` shows where you are; the card is the single focused task for the current step. Starts mid-flow (step 1) so it reads like a real session, not a fresh render.
|
|
379
|
+
|
|
380
|
+
```tsx
|
|
381
|
+
import { Stack, Card, Stepper, Title, Text, Group, Button, Input } from "wimui";
|
|
382
|
+
import { useState } from "react";
|
|
383
|
+
|
|
384
|
+
const steps = [
|
|
385
|
+
{ title: "Account", description: "Your details" },
|
|
386
|
+
{ title: "Workspace", description: "Name & URL" },
|
|
387
|
+
{ title: "Invite", description: "Optional" },
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
export function Onboarding() {
|
|
391
|
+
const [current, setCurrent] = useState(1); // mid-flow
|
|
392
|
+
const back = () => setCurrent((c) => Math.max(0, c - 1));
|
|
393
|
+
const next = () => setCurrent((c) => Math.min(steps.length - 1, c + 1));
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<Stack gap="lg" style={{ maxWidth: 640 }}>
|
|
397
|
+
<Stepper steps={steps} current={current} onChange={setCurrent} />
|
|
398
|
+
|
|
399
|
+
<Card padding="lg">
|
|
400
|
+
<Stack gap="md">
|
|
401
|
+
<Stack gap="2xs">
|
|
402
|
+
<Title tag="h2" size="md">Name your workspace</Title>
|
|
403
|
+
<Text color="secondary">You can change this later in Settings — existing links keep working.</Text>
|
|
404
|
+
</Stack>
|
|
405
|
+
<Input label="Workspace name" placeholder="Acme Inc." fullWidth />
|
|
406
|
+
</Stack>
|
|
407
|
+
</Card>
|
|
408
|
+
|
|
409
|
+
<Group justify="between" align="center">
|
|
410
|
+
<Button variant="ghost" onClick={back} disabled={current === 0}>Back</Button>
|
|
411
|
+
<Button variant="solid" onClick={next}>Continue</Button>
|
|
412
|
+
</Group>
|
|
413
|
+
</Stack>
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Idioms — per-category minimal combinations
|
|
419
|
+
|
|
420
|
+
Smaller than the full-screen recipes above: the canonical way to wire one
|
|
421
|
+
category together. Same rules apply (tokens via props, real copy, show a
|
|
422
|
+
non-happy-path state).
|
|
423
|
+
|
|
424
|
+
### Form — field + validation + submit
|
|
425
|
+
|
|
426
|
+
The field owns its own label and error: pass `error` a **string** to render the message and the danger state together. No wrapper component needed.
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
import { Stack, Input, Textarea, Button } from "wimui";
|
|
430
|
+
import { useState } from "react";
|
|
431
|
+
|
|
432
|
+
export function ContactForm() {
|
|
433
|
+
const [email, setEmail] = useState("");
|
|
434
|
+
const [error, setError] = useState<string>();
|
|
435
|
+
|
|
436
|
+
function submit(e: React.FormEvent) {
|
|
437
|
+
e.preventDefault();
|
|
438
|
+
setError(email.includes("@") ? undefined : "Enter a valid work email.");
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return (
|
|
442
|
+
<form onSubmit={submit} noValidate>
|
|
443
|
+
<Stack gap="md" style={{ maxWidth: 420 }}>
|
|
444
|
+
<Input
|
|
445
|
+
label="Work email"
|
|
446
|
+
type="email"
|
|
447
|
+
required
|
|
448
|
+
value={email}
|
|
449
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
450
|
+
error={error} // string → message + danger intent
|
|
451
|
+
fullWidth
|
|
452
|
+
/>
|
|
453
|
+
<Textarea label="What do you need help with?" fullWidth />
|
|
454
|
+
<Button type="submit" variant="solid">Send</Button>
|
|
455
|
+
</Stack>
|
|
456
|
+
</form>
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Navigation — sidebar (desktop) ⇄ tab bar (mobile)
|
|
462
|
+
|
|
463
|
+
Same destinations, two shells. `AppShell` takes a `sidebar` for wide viewports; `TabBar` (fixed, bottom) is the mobile equivalent — show/hide each with your breakpoint CSS. Active state is data, not duplicated markup.
|
|
464
|
+
|
|
465
|
+
```tsx
|
|
466
|
+
import { AppShell, Sidebar, Stack, Button, TabBar } from "wimui";
|
|
467
|
+
import { HomeIcon, UserIcon, SettingsIcon } from "wimui/icons";
|
|
468
|
+
|
|
469
|
+
const nav = [
|
|
470
|
+
{ id: "home", label: "Home", icon: <HomeIcon /> },
|
|
471
|
+
{ id: "people", label: "People", icon: <UserIcon /> },
|
|
472
|
+
{ id: "settings", label: "Settings", icon: <SettingsIcon /> },
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
export function Shell({
|
|
476
|
+
tab,
|
|
477
|
+
onTab,
|
|
478
|
+
children,
|
|
479
|
+
}: {
|
|
480
|
+
tab: string;
|
|
481
|
+
onTab: (id: string) => void;
|
|
482
|
+
children: React.ReactNode;
|
|
483
|
+
}) {
|
|
484
|
+
return (
|
|
485
|
+
<AppShell
|
|
486
|
+
sidebar={
|
|
487
|
+
<Sidebar width={240}>
|
|
488
|
+
<Stack gap="2xs" p="md">
|
|
489
|
+
{nav.map((n) => (
|
|
490
|
+
<Button
|
|
491
|
+
key={n.id}
|
|
492
|
+
variant={tab === n.id ? "solid" : "ghost"}
|
|
493
|
+
justify="start"
|
|
494
|
+
icon={n.icon}
|
|
495
|
+
fullWidth
|
|
496
|
+
onClick={() => onTab(n.id)}
|
|
497
|
+
>
|
|
498
|
+
{n.label}
|
|
499
|
+
</Button>
|
|
500
|
+
))}
|
|
501
|
+
</Stack>
|
|
502
|
+
</Sidebar>
|
|
503
|
+
}
|
|
504
|
+
>
|
|
505
|
+
{children}
|
|
506
|
+
{/* Mobile: same destinations as a fixed bottom bar */}
|
|
507
|
+
<TabBar>
|
|
508
|
+
{nav.map((n) => (
|
|
509
|
+
<TabBar.Item
|
|
510
|
+
key={n.id}
|
|
511
|
+
active={tab === n.id}
|
|
512
|
+
icon={n.icon}
|
|
513
|
+
label={n.label}
|
|
514
|
+
onClick={() => onTab(n.id)}
|
|
515
|
+
/>
|
|
516
|
+
))}
|
|
517
|
+
</TabBar>
|
|
518
|
+
</AppShell>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Feedback — persistent Alert vs. transient toast
|
|
524
|
+
|
|
525
|
+
Use an inline `Alert` for state that stays true (a limit, an outage); use a toast (`useToast().show`) for a one-off confirmation. Wrap the app once in `ToastProvider`.
|
|
526
|
+
|
|
527
|
+
```tsx
|
|
528
|
+
import { ToastProvider, useToast, Alert, Button, Stack } from "wimui";
|
|
529
|
+
|
|
530
|
+
// once, at the app root
|
|
531
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
532
|
+
return <ToastProvider position="top-right">{children}</ToastProvider>;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export function InvitePanel() {
|
|
536
|
+
const { show } = useToast();
|
|
537
|
+
return (
|
|
538
|
+
<Stack gap="md" style={{ maxWidth: 480 }}>
|
|
539
|
+
<Alert
|
|
540
|
+
intent="warning"
|
|
541
|
+
title="Seat limit reached"
|
|
542
|
+
description="Your plan includes 5 seats. Remove a member or upgrade to invite more."
|
|
543
|
+
/>
|
|
544
|
+
<Button
|
|
545
|
+
variant="solid"
|
|
546
|
+
onClick={() =>
|
|
547
|
+
show({
|
|
548
|
+
intent: "success",
|
|
549
|
+
title: "Invitation sent",
|
|
550
|
+
description: "We emailed marisol@okonkwo.dev.",
|
|
551
|
+
})
|
|
552
|
+
}
|
|
553
|
+
>
|
|
554
|
+
Resend invite
|
|
555
|
+
</Button>
|
|
556
|
+
</Stack>
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Overlay — Dialog wrapping a form
|
|
562
|
+
|
|
563
|
+
`Dialog` is compound: a `DialogTrigger asChild` around your own button, and `DialogClose asChild` around the cancel action so it dismisses without wiring state. Keep the form inside `DialogContent`.
|
|
564
|
+
|
|
565
|
+
```tsx
|
|
566
|
+
import {
|
|
567
|
+
Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle,
|
|
568
|
+
DialogDescription, DialogFooter, DialogClose, Button, Stack, Input,
|
|
569
|
+
} from "wimui";
|
|
570
|
+
|
|
571
|
+
export function RenameWorkspace() {
|
|
572
|
+
return (
|
|
573
|
+
<Dialog>
|
|
574
|
+
<DialogTrigger asChild>
|
|
575
|
+
<Button variant="outline">Rename workspace</Button>
|
|
576
|
+
</DialogTrigger>
|
|
577
|
+
<DialogContent>
|
|
578
|
+
<DialogHeader>
|
|
579
|
+
<DialogTitle>Rename workspace</DialogTitle>
|
|
580
|
+
<DialogDescription>
|
|
581
|
+
This changes the URL. Existing links keep working via a redirect.
|
|
582
|
+
</DialogDescription>
|
|
583
|
+
</DialogHeader>
|
|
584
|
+
<form onSubmit={(e) => e.preventDefault()}>
|
|
585
|
+
<Stack gap="md" p="md">
|
|
586
|
+
<Input label="Workspace name" defaultValue="Larkfield" fullWidth />
|
|
587
|
+
</Stack>
|
|
588
|
+
<DialogFooter>
|
|
589
|
+
<DialogClose asChild>
|
|
590
|
+
<Button variant="ghost">Cancel</Button>
|
|
591
|
+
</DialogClose>
|
|
592
|
+
<Button type="submit" variant="solid">Save</Button>
|
|
593
|
+
</DialogFooter>
|
|
594
|
+
</form>
|
|
595
|
+
</DialogContent>
|
|
596
|
+
</Dialog>
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
```
|
|
600
|
+
|
|
74
601
|
## Components
|
|
75
602
|
|
|
76
603
|
### layout — `import { … } from "wimui/layout"`
|
|
@@ -88,6 +615,7 @@ Single components are judged by state/a11y/token compliance. **Composed screens*
|
|
|
88
615
|
- `children: React.ReactNode` (required) — Child content
|
|
89
616
|
- **BentoGrid** — Modern card grid
|
|
90
617
|
- `children: React.ReactNode` — Grid items (typically BentoGrid.Item elements)
|
|
618
|
+
- `rowHeight: string` — Fixed height of each grid row (`grid-auto-rows`). Any CSS length; defaults to `18rem`. Lower it when tiles hold compact content so they don't read as mostly-empty panels.
|
|
91
619
|
- **Box** — Polymorphic box
|
|
92
620
|
- `as: C` — The component used for the root node. Either a string to use a HTML element or a component.
|
|
93
621
|
- `asChild: boolean` = false — If true, the box will be rendered as its child, merging its props onto that child.
|
|
@@ -237,6 +765,7 @@ The container-query wrapper div is preserved to maintain responsive column behav
|
|
|
237
765
|
- `transparent: boolean` — Whether the navbar background is transparent
|
|
238
766
|
- `glass: boolean` — Whether to apply the frosted-glass effect
|
|
239
767
|
- `bordered: boolean` — Whether to show a bottom border
|
|
768
|
+
- `fluid: boolean` — Expand content to full width (disable the centered max-width container)
|
|
240
769
|
- `defaultMenuOpen: boolean` = false — Initial open state of the mobile menu (uncontrolled)
|
|
241
770
|
- `isMenuOpen: boolean` — Open state of the mobile menu (controlled)
|
|
242
771
|
- `onMenuOpenChange: (isOpen: boolean) => void` — Callback when the mobile menu open state changes
|