@jallall/oidea-ui 0.1.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.
Files changed (86) hide show
  1. package/README.md +448 -0
  2. package/dist/components/app/action-item-list.d.ts +22 -0
  3. package/dist/components/app/activity-timeline.d.ts +20 -0
  4. package/dist/components/app/aging-bucket-bars.d.ts +16 -0
  5. package/dist/components/app/app-page-header.d.ts +10 -0
  6. package/dist/components/app/app-sidebar.d.ts +27 -0
  7. package/dist/components/app/app-surface.d.ts +13 -0
  8. package/dist/components/app/avatar-thumbnail.d.ts +9 -0
  9. package/dist/components/app/avatar-tile.d.ts +10 -0
  10. package/dist/components/app/charts/area-chart.d.ts +21 -0
  11. package/dist/components/app/charts/bar-chart.d.ts +21 -0
  12. package/dist/components/app/charts/donut-chart.d.ts +16 -0
  13. package/dist/components/app/charts/index.d.ts +4 -0
  14. package/dist/components/app/charts/sparkline.d.ts +11 -0
  15. package/dist/components/app/confirm-dialog.d.ts +12 -0
  16. package/dist/components/app/data-table-types.d.ts +19 -0
  17. package/dist/components/app/data-table.d.ts +31 -0
  18. package/dist/components/app/detail-drawer.d.ts +14 -0
  19. package/dist/components/app/drawer-field.d.ts +9 -0
  20. package/dist/components/app/drawer-section.d.ts +9 -0
  21. package/dist/components/app/empty-state.d.ts +14 -0
  22. package/dist/components/app/filter-bar.d.ts +13 -0
  23. package/dist/components/app/filter-chips.d.ts +18 -0
  24. package/dist/components/app/health-score-ring.d.ts +12 -0
  25. package/dist/components/app/hero-stat-card.d.ts +17 -0
  26. package/dist/components/app/period-selector.d.ts +9 -0
  27. package/dist/components/app/reference-combobox.d.ts +19 -0
  28. package/dist/components/app/router-shell.d.ts +11 -0
  29. package/dist/components/app/search-input.d.ts +11 -0
  30. package/dist/components/app/stat-card.d.ts +19 -0
  31. package/dist/components/app/status-badge.d.ts +13 -0
  32. package/dist/components/app/status-filter-popover.d.ts +12 -0
  33. package/dist/components/app/status-filter.d.ts +15 -0
  34. package/dist/components/app/step-indicator.d.ts +8 -0
  35. package/dist/components/app/submission-funnel.d.ts +14 -0
  36. package/dist/components/app/tabs-context.d.ts +7 -0
  37. package/dist/components/app/tabs-list.d.ts +9 -0
  38. package/dist/components/app/tabs-tab.d.ts +10 -0
  39. package/dist/components/app/tabs.d.ts +9 -0
  40. package/dist/components/app/trend-card.d.ts +21 -0
  41. package/dist/components/ui/alert.d.ts +9 -0
  42. package/dist/components/ui/avatar.d.ts +11 -0
  43. package/dist/components/ui/badge.d.ts +12 -0
  44. package/dist/components/ui/breadcrumb.d.ts +11 -0
  45. package/dist/components/ui/button.d.ts +10 -0
  46. package/dist/components/ui/calendar.d.ts +8 -0
  47. package/dist/components/ui/card.d.ts +9 -0
  48. package/dist/components/ui/chart.d.ts +44 -0
  49. package/dist/components/ui/checkbox.d.ts +4 -0
  50. package/dist/components/ui/collapsible.d.ts +5 -0
  51. package/dist/components/ui/command.d.ts +18 -0
  52. package/dist/components/ui/dialog.d.ts +17 -0
  53. package/dist/components/ui/dropdown-menu.d.ts +25 -0
  54. package/dist/components/ui/form.d.ts +23 -0
  55. package/dist/components/ui/input.d.ts +3 -0
  56. package/dist/components/ui/label.d.ts +4 -0
  57. package/dist/components/ui/pagination.d.ts +13 -0
  58. package/dist/components/ui/popover.d.ts +10 -0
  59. package/dist/components/ui/progress.d.ts +4 -0
  60. package/dist/components/ui/radio-group.d.ts +5 -0
  61. package/dist/components/ui/scroll-area.d.ts +5 -0
  62. package/dist/components/ui/select.d.ts +15 -0
  63. package/dist/components/ui/separator.d.ts +4 -0
  64. package/dist/components/ui/sheet.d.ts +14 -0
  65. package/dist/components/ui/sidebar.d.ts +69 -0
  66. package/dist/components/ui/skeleton.d.ts +2 -0
  67. package/dist/components/ui/sonner.d.ts +4 -0
  68. package/dist/components/ui/switch.d.ts +6 -0
  69. package/dist/components/ui/table.d.ts +10 -0
  70. package/dist/components/ui/tabs.d.ts +11 -0
  71. package/dist/components/ui/textarea.d.ts +3 -0
  72. package/dist/components/ui/toggle-group.d.ts +9 -0
  73. package/dist/components/ui/toggle.d.ts +9 -0
  74. package/dist/components/ui/tooltip.d.ts +7 -0
  75. package/dist/hooks/use-mobile.d.ts +1 -0
  76. package/dist/index.cjs +125 -0
  77. package/dist/index.cjs.map +1 -0
  78. package/dist/index.d.ts +72 -0
  79. package/dist/index.js +40933 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/lib/period.d.ts +14 -0
  82. package/dist/lib/utils.d.ts +2 -0
  83. package/dist/providers/query-provider.d.ts +11 -0
  84. package/dist/providers/router-provider.d.ts +6 -0
  85. package/dist/styles.css +3 -0
  86. package/package.json +103 -0
package/README.md ADDED
@@ -0,0 +1,448 @@
1
+ # @oidea/ui
2
+
3
+ Reusable React components and design tokens used by Oidea applications. The package includes accessible UI primitives, application-level building blocks, data visualization components, and the complete Oidea visual theme.
4
+
5
+ ## Requirements
6
+
7
+ - React 18 or 19
8
+ - React DOM 18 or 19
9
+ - Node.js 20 or newer for local development
10
+
11
+ The package is written in TypeScript and includes its own type declarations. It works in JavaScript projects too.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @oidea/ui react react-dom
17
+ ```
18
+
19
+ Applications using `OideaQueryProvider` or the TanStack integrations should also install:
20
+
21
+ ```bash
22
+ npm install @tanstack/react-query@^5 @tanstack/react-router@^1 @tanstack/react-table@^8
23
+ ```
24
+
25
+ ## Quick start
26
+
27
+ Import the stylesheet once at the root of your application:
28
+
29
+ ```tsx
30
+ // main.tsx, app.tsx, or your root layout
31
+ import "@oidea/ui/styles.css"
32
+ ```
33
+
34
+ Components can then be imported directly from the package:
35
+
36
+ ```tsx
37
+ import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@oidea/ui"
38
+
39
+ export function Example() {
40
+ return (
41
+ <Card className="max-w-md">
42
+ <CardHeader>
43
+ <CardTitle>Invoice ready</CardTitle>
44
+ </CardHeader>
45
+ <CardContent className="flex items-center gap-3">
46
+ <Badge color="#123526" size="sm">Approved</Badge>
47
+ <Button>View invoice</Button>
48
+ </CardContent>
49
+ </Card>
50
+ )
51
+ }
52
+ ```
53
+
54
+ ## Providers
55
+
56
+ Most components do not need a provider. If your application uses React Query, `OideaQueryProvider` supplies a sensible default `QueryClient`:
57
+
58
+ ```tsx
59
+ import { OideaQueryProvider } from "@oidea/ui"
60
+ import "@oidea/ui/styles.css"
61
+
62
+ export function App() {
63
+ return (
64
+ <OideaQueryProvider>
65
+ <Routes />
66
+ </OideaQueryProvider>
67
+ )
68
+ }
69
+ ```
70
+
71
+ Pass your own client when you need custom query behavior:
72
+
73
+ ```tsx
74
+ import { QueryClient } from "@tanstack/react-query"
75
+ import { OideaQueryProvider } from "@oidea/ui"
76
+
77
+ const queryClient = new QueryClient()
78
+
79
+ export function App() {
80
+ return <OideaQueryProvider client={queryClient}><Routes /></OideaQueryProvider>
81
+ }
82
+ ```
83
+
84
+ ## Badges
85
+
86
+ ### Badge
87
+
88
+ `Badge` supports visual variants, four sizes, arbitrary CSS colors, links through `asChild`, and normal HTML attributes.
89
+
90
+ ```tsx
91
+ import { Badge } from "@oidea/ui"
92
+
93
+ export function BadgeExamples() {
94
+ return (
95
+ <div className="flex items-center gap-2">
96
+ <Badge variant="default" size="xs">New</Badge>
97
+ <Badge variant="outline" size="md">Draft</Badge>
98
+ <Badge color="#7c3aed" size="lg">Premium</Badge>
99
+ <Badge color="rebeccapurple">Custom color</Badge>
100
+ </div>
101
+ )
102
+ }
103
+ ```
104
+
105
+ Available sizes are `xs`, `sm`, `md`, and `lg`. Available variants are `default`, `secondary`, `destructive`, `outline`, `ghost`, and `link`. A `color` value overrides the variant's foreground, background, and border colors.
106
+
107
+ ### StatusBadge
108
+
109
+ Use `StatusBadge` for semantic statuses. Its tones are `success`, `warning`, `gold`, `danger`, `neutral`, `muted`, and `info`.
110
+
111
+ ```tsx
112
+ import { StatusBadge } from "@oidea/ui"
113
+
114
+ <StatusBadge tone="success" size="sm" dot>Paid</StatusBadge>
115
+ <StatusBadge tone="danger">Rejected</StatusBadge>
116
+ <StatusBadge color="#2563eb" size="lg" dot>Processing</StatusBadge>
117
+ ```
118
+
119
+ `tone` defaults to `neutral`. An arbitrary `color` overrides the selected tone and also colors the optional dot.
120
+
121
+ ## Toast notifications
122
+
123
+ The included toaster is powered by Sonner. Render `Toaster` once near the root, then use Sonner's `toast` function anywhere in the application:
124
+
125
+ ```tsx
126
+ import { Toaster, toast } from "@oidea/ui"
127
+
128
+ export function App() {
129
+ return (
130
+ <>
131
+ <button onClick={() => toast.success("Invoice saved")}>Save</button>
132
+ <Toaster position="top-right" richColors />
133
+ </>
134
+ )
135
+ }
136
+ ```
137
+
138
+ ## Data tables
139
+
140
+ `DataTable` provides typed columns, local sorting, search, pagination, loading skeletons, selectable rows, and manual/infinite data loading.
141
+
142
+ ```tsx
143
+ import { DataTable, type DataTableColumn } from "@oidea/ui"
144
+
145
+ type Invoice = {
146
+ id: string
147
+ customer: string
148
+ total: number
149
+ }
150
+
151
+ const columns: DataTableColumn<Invoice>[] = [
152
+ {
153
+ id: "customer",
154
+ header: "Customer",
155
+ cell: (invoice) => invoice.customer,
156
+ sortAccessor: (invoice) => invoice.customer,
157
+ },
158
+ {
159
+ id: "total",
160
+ header: "Total",
161
+ cell: (invoice) => `₦${invoice.total.toLocaleString()}`,
162
+ sortAccessor: (invoice) => invoice.total,
163
+ align: "right",
164
+ },
165
+ ]
166
+
167
+ export function InvoiceTable({ invoices }: { invoices: Invoice[] }) {
168
+ return (
169
+ <DataTable
170
+ columns={columns}
171
+ data={invoices}
172
+ rowKey={(invoice) => invoice.id}
173
+ searchable
174
+ paginated
175
+ searchAccessor={(invoice) => `${invoice.customer} ${invoice.total}`}
176
+ onRowClick={(invoice) => console.log(invoice.id)}
177
+ />
178
+ )
179
+ }
180
+ ```
181
+
182
+ For server-managed data, set `manualData`, supply `totalCount`, and use `hasMore`, `loadingMore`, and `onLoadMore`.
183
+
184
+ ## Tabs
185
+
186
+ The package includes two tab systems:
187
+
188
+ - `Tabs`, `TabsList`, `TabsTrigger`, and `TabsContent` are Radix-based UI primitives.
189
+ - `AppTabs`, `AppTabsList`, and `AppTabsTab` provide Oidea's animated application-tab appearance.
190
+
191
+ The `App` prefix prevents naming conflicts when both systems are imported together.
192
+
193
+ ## Routing and application sidebar
194
+
195
+ The package integrates with TanStack Router while leaving your route definitions and application data under your control. Create the route tree in your application, then render it with `OideaRouterProvider`:
196
+
197
+ ```tsx
198
+ import {
199
+ OideaRouterProvider,
200
+ Outlet,
201
+ createRootRoute,
202
+ createRoute,
203
+ createRouter,
204
+ } from "@oidea/ui"
205
+
206
+ const rootRoute = createRootRoute({ component: Outlet })
207
+
208
+ const dashboardRoute = createRoute({
209
+ getParentRoute: () => rootRoute,
210
+ path: "/dashboard",
211
+ component: () => <h1>Dashboard</h1>,
212
+ })
213
+
214
+ const routeTree = rootRoute.addChildren([dashboardRoute])
215
+ const router = createRouter({ routeTree })
216
+
217
+ export function App() {
218
+ return <OideaRouterProvider router={router} />
219
+ }
220
+ ```
221
+
222
+ For full TanStack Router type registration, add this in your application:
223
+
224
+ ```tsx
225
+ declare module "@tanstack/react-router" {
226
+ interface Register {
227
+ router: typeof router
228
+ }
229
+ }
230
+ ```
231
+
232
+ `AppSidebar` accepts configurable navigation groups and automatically marks the current TanStack route as active:
233
+
234
+ ```tsx
235
+ import { BarChart3, FileText, LayoutDashboard } from "lucide-react"
236
+ import { AppSidebar, SidebarProvider } from "@oidea/ui"
237
+
238
+ const groups = [
239
+ {
240
+ label: "Platform",
241
+ items: [
242
+ { to: "/dashboard", label: "Dashboard", icon: LayoutDashboard },
243
+ { to: "/invoices", label: "Invoices", icon: FileText },
244
+ { to: "/reports", label: "Reports", icon: BarChart3 },
245
+ ],
246
+ },
247
+ ]
248
+
249
+ <SidebarProvider>
250
+ <AppSidebar
251
+ brand={<strong>My application</strong>}
252
+ brandTo="/dashboard"
253
+ groups={groups}
254
+ footer={<span>Signed in</span>}
255
+ />
256
+ </SidebarProvider>
257
+ ```
258
+
259
+ `RouterShell` combines the responsive sidebar, sidebar provider, mobile trigger, header, and route content:
260
+
261
+ ```tsx
262
+ import { Outlet, RouterShell } from "@oidea/ui"
263
+
264
+ export function AuthenticatedLayout() {
265
+ return (
266
+ <RouterShell
267
+ sidebar={{ groups, brand: <strong>My application</strong>, brandTo: "/dashboard" }}
268
+ header={<span>Workspace</span>}
269
+ >
270
+ <Outlet />
271
+ </RouterShell>
272
+ )
273
+ }
274
+ ```
275
+
276
+ Each navigation item supports `to`, `label`, an optional icon, `disabled`, and a custom `isActive(pathname)` matcher.
277
+
278
+ ## Component groups
279
+
280
+ ### UI primitives
281
+
282
+ Alert, Avatar, Badge, Breadcrumb, Button, Calendar, Card, Chart, Checkbox, Collapsible, Command, Dialog, Dropdown Menu, Form, Input, Label, Pagination, Popover, Progress, Radio Group, Scroll Area, Select, Separator, Sheet, Sidebar, Skeleton, Sonner Toaster, Switch, Table, Tabs, Textarea, Toggle, Toggle Group, and Tooltip.
283
+
284
+ ### Application components
285
+
286
+ Action Item List, Activity Timeline, Aging Bucket Bars, App Page Header, App Sidebar, App Surface, Avatar Thumbnail, Avatar Tile, Confirm Dialog, Data Table, Detail Drawer, Drawer Field, Drawer Section, Empty State, Filter Bar, Filter Chips, Health Score Ring, Hero Stat Card, Period Selector, Reference Combobox, Router Shell, Search Input, Stat Card, Status Badge, Status Filters, Step Indicator, Submission Funnel, animated App Tabs, and Trend Card.
287
+
288
+ ### Charts
289
+
290
+ Area Chart, Bar Chart, Donut Chart, and Sparkline. Chart components are built on Recharts and use the Oidea theme variables by default.
291
+
292
+ ## Complete component reference
293
+
294
+ Every example below assumes the component is imported from `@oidea/ui`. Import `@oidea/ui/styles.css` once in the application root.
295
+
296
+ ### All 34 UI primitive modules
297
+
298
+ | Module | Main exports | Minimal use |
299
+ | --- | --- | --- |
300
+ | Alert | `Alert`, `AlertTitle`, `AlertDescription` | `<Alert><AlertTitle>Notice</AlertTitle><AlertDescription>Details</AlertDescription></Alert>` |
301
+ | Avatar | `Avatar`, `AvatarImage`, `AvatarFallback`, `AvatarBadge`, `AvatarGroup`, `AvatarGroupCount` | `<Avatar><AvatarImage src="/user.jpg" /><AvatarFallback>AJ</AvatarFallback></Avatar>` |
302
+ | Badge | `Badge` | `<Badge color="#2563eb" size="sm">New</Badge>` |
303
+ | Breadcrumb | `Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis` | `<Breadcrumb><BreadcrumbList><BreadcrumbItem><BreadcrumbLink href="/">Home</BreadcrumbLink></BreadcrumbItem><BreadcrumbSeparator /><BreadcrumbPage>Invoices</BreadcrumbPage></BreadcrumbList></Breadcrumb>` |
304
+ | Button | `Button` | `<Button variant="default" size="sm">Save</Button>` |
305
+ | Calendar | `Calendar`, `CalendarDayButton` | `<Calendar mode="single" selected={date} onSelect={setDate} />` |
306
+ | Card | `Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardAction`, `CardContent`, `CardFooter` | `<Card><CardHeader><CardTitle>Title</CardTitle></CardHeader><CardContent>Content</CardContent></Card>` |
307
+ | Chart | `ChartContainer`, `ChartTooltip`, `ChartTooltipContent`, `ChartLegend`, `ChartLegendContent` | `<ChartContainer config={{ total: { label: "Total", color: "#123526" } }}>{rechartsContent}</ChartContainer>` |
308
+ | Checkbox | `Checkbox` | `<Checkbox checked={checked} onCheckedChange={setChecked} />` |
309
+ | Collapsible | `Collapsible`, `CollapsibleTrigger`, `CollapsibleContent` | `<Collapsible><CollapsibleTrigger>Details</CollapsibleTrigger><CollapsibleContent>Hidden content</CollapsibleContent></Collapsible>` |
310
+ | Command | `Command`, `CommandInput`, `CommandList`, `CommandEmpty`, `CommandGroup`, `CommandItem`, `CommandSeparator`, `CommandShortcut`, `CommandDialog` | `<Command><CommandInput placeholder="Search" /><CommandList><CommandItem>Invoice</CommandItem></CommandList></Command>` |
311
+ | Dialog | `Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter`, `DialogClose` | `<Dialog><DialogTrigger asChild><Button>Open</Button></DialogTrigger><DialogContent><DialogTitle>Dialog</DialogTitle></DialogContent></Dialog>` |
312
+ | Dropdown Menu | `DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem` and checkbox, radio, label, separator, shortcut, and submenu helpers | `<DropdownMenu><DropdownMenuTrigger asChild><Button>Actions</Button></DropdownMenuTrigger><DropdownMenuContent><DropdownMenuItem>Edit</DropdownMenuItem></DropdownMenuContent></DropdownMenu>` |
313
+ | Form | `Form`, `FormField`, `FormItem`, `FormLabel`, `FormControl`, `FormDescription`, `FormMessage`, `useFormField` | `<Form {...form}><FormField control={form.control} name="email" render={({ field }) => <FormItem><FormLabel>Email</FormLabel><FormControl><Input {...field} /></FormControl><FormMessage /></FormItem>} /></Form>` |
314
+ | Input | `Input` | `<Input type="email" placeholder="name@example.com" />` |
315
+ | Label | `Label` | `<Label htmlFor="email">Email</Label>` |
316
+ | Pagination | `Pagination`, `PaginationContent`, `PaginationItem`, `PaginationLink`, `PaginationPrevious`, `PaginationNext`, `PaginationEllipsis` | `<Pagination><PaginationContent><PaginationItem><PaginationPrevious href="?page=1" /></PaginationItem><PaginationItem><PaginationLink href="?page=2">2</PaginationLink></PaginationItem></PaginationContent></Pagination>` |
317
+ | Popover | `Popover`, `PopoverTrigger`, `PopoverContent`, `PopoverAnchor`, `PopoverHeader`, `PopoverTitle`, `PopoverDescription` | `<Popover><PopoverTrigger asChild><Button>Open</Button></PopoverTrigger><PopoverContent>Content</PopoverContent></Popover>` |
318
+ | Progress | `Progress` | `<Progress value={65} />` |
319
+ | Radio Group | `RadioGroup`, `RadioGroupItem` | `<RadioGroup value={value} onValueChange={setValue}><RadioGroupItem value="monthly" /></RadioGroup>` |
320
+ | Scroll Area | `ScrollArea`, `ScrollBar` | `<ScrollArea className="h-64">Long content<ScrollBar /></ScrollArea>` |
321
+ | Select | `Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`, `SelectGroup`, `SelectLabel`, `SelectSeparator` | `<Select value={value} onValueChange={setValue}><SelectTrigger><SelectValue placeholder="Choose" /></SelectTrigger><SelectContent><SelectItem value="one">One</SelectItem></SelectContent></Select>` |
322
+ | Separator | `Separator` | `<Separator orientation="horizontal" />` |
323
+ | Sheet | `Sheet`, `SheetTrigger`, `SheetContent`, `SheetHeader`, `SheetTitle`, `SheetDescription`, `SheetFooter`, `SheetClose` | `<Sheet><SheetTrigger asChild><Button>Open</Button></SheetTrigger><SheetContent><SheetTitle>Details</SheetTitle></SheetContent></Sheet>` |
324
+ | Sidebar | `SidebarProvider`, `Sidebar`, `SidebarTrigger`, `SidebarInset`, plus header, footer, group, menu, submenu, badge, action, rail, input, and skeleton helpers | `<SidebarProvider><Sidebar><SidebarContent>Navigation</SidebarContent></Sidebar><SidebarInset><SidebarTrigger />Page</SidebarInset></SidebarProvider>` |
325
+ | Skeleton | `Skeleton` | `<Skeleton className="h-6 w-40" />` |
326
+ | Sonner | `Toaster`, `toast` | `<><Button onClick={() => toast.success("Saved")}>Save</Button><Toaster /></>` |
327
+ | Switch | `Switch` | `<Switch checked={enabled} onCheckedChange={setEnabled} />` |
328
+ | Table | `Table`, `TableHeader`, `TableBody`, `TableFooter`, `TableRow`, `TableHead`, `TableCell`, `TableCaption` | `<Table><TableHeader><TableRow><TableHead>Name</TableHead></TableRow></TableHeader><TableBody><TableRow><TableCell>Acme</TableCell></TableRow></TableBody></Table>` |
329
+ | Tabs | `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` | `<Tabs defaultValue="first"><TabsList><TabsTrigger value="first">First</TabsTrigger></TabsList><TabsContent value="first">Content</TabsContent></Tabs>` |
330
+ | Textarea | `Textarea` | `<Textarea placeholder="Add a note" rows={4} />` |
331
+ | Toggle Group | `ToggleGroup`, `ToggleGroupItem` | `<ToggleGroup type="single" value={view} onValueChange={setView}><ToggleGroupItem value="grid">Grid</ToggleGroupItem></ToggleGroup>` |
332
+ | Toggle | `Toggle` | `<Toggle pressed={active} onPressedChange={setActive}>Bold</Toggle>` |
333
+ | Tooltip | `TooltipProvider`, `Tooltip`, `TooltipTrigger`, `TooltipContent` | `<TooltipProvider><Tooltip><TooltipTrigger asChild><Button>Info</Button></TooltipTrigger><TooltipContent>Helpful text</TooltipContent></Tooltip></TooltipProvider>` |
334
+
335
+ The primitive components forward their underlying HTML or Radix props. This means attributes such as `disabled`, `aria-*`, event handlers, controlled state, and `className` remain available.
336
+
337
+ ### All 32 application component modules
338
+
339
+ | Component | Purpose and essential props | Minimal use |
340
+ | --- | --- | --- |
341
+ | `ActionItemList` | Action queue. Pass `items` containing `id`, `title`, and optional `description`, `tone`, `icon`, `count`, `href`, or `onClick`. | `<ActionItemList items={[{ id: "1", title: "Review invoice", tone: "warning", count: 3 }]} />` |
342
+ | `ActivityTimeline` | Chronological events. Pass `entries`; `density` is `comfortable` or `compact`. | `<ActivityTimeline entries={[{ id: "1", title: "Created", timestamp: "10:30", tone: "success" }]} />` |
343
+ | `AgingBucketBars` | Proportional aging distribution with optional legend and formatter. | `<AgingBucketBars buckets={[{ id: "current", label: "Current", value: 80 }, { id: "late", label: "Late", value: 20 }]} />` |
344
+ | `AppPageHeader` | Responsive page heading with `eyebrow`, `title`, `description`, and `actions`. | `<AppPageHeader title="Invoices" description="Manage invoices" actions={<Button>New</Button>} />` |
345
+ | `AppSidebar` | TanStack Router-aware navigation. Pass `groups`; optionally pass `brand`, `brandTo`, `footer`, and `collapsible`. | `<AppSidebar groups={[{ label: "Main", items: [{ to: "/dashboard", label: "Dashboard" }] }]} />` |
346
+ | `AppSurface` | Card-like page section. Supports `title`, `description`, `actions`, `footer`, and `padding`. | `<AppSurface title="Summary" padding="default">Content</AppSurface>` |
347
+ | `AvatarThumbnail` | Initials or icon tile with configurable `tint` and numeric `size`. | `<AvatarThumbnail initials="AJ" tint="#c9a87c" size={40} />` |
348
+ | `AvatarTile` | Identity row composed from `thumbnail`, `title`, `subtitle`, `titleSuffix`, and `trailing`. | `<AvatarTile thumbnail={<AvatarThumbnail initials="AJ" />} title="Abdul Jalal" subtitle="Admin" />` |
349
+ | `ConfirmDialog` | Controlled confirmation dialog with customizable labels and destructive/default tone. | `<ConfirmDialog open={open} onOpenChange={setOpen} title="Delete?" onConfirm={removeItem} />` |
350
+ | `DataTable` | Typed table with sorting, search, pagination, loading, density, and manual/infinite loading modes. | `<DataTable columns={columns} data={rows} rowKey={(row) => row.id} searchable paginated />` |
351
+ | `DetailDrawer` | Controlled left/right details sheet. Pass `open`, `onOpenChange`, `title`, `children`; sizes are `sm`, `md`, and `lg`. | `<DetailDrawer open={open} onOpenChange={setOpen} title="Invoice" size="lg">Details</DetailDrawer>` |
352
+ | `DrawerField` | Label/value row for drawer content, with optional helper text. | `<DrawerField label="Status" value="Paid" helper="Updated today" />` |
353
+ | `DrawerSection` | Groups drawer content under an optional title and description. | `<DrawerSection title="Customer"><DrawerField label="Name" value="Acme" /></DrawerSection>` |
354
+ | `EmptyState` | Empty-result presentation with optional icon, title, description, and action. | `<EmptyState title="No invoices" description="Create the first invoice." action={<Button>New</Button>} />` |
355
+ | `FilterBar` | Search field with clear behavior and dedicated `chips`, `period`, and `actions` slots. | `<FilterBar search={search} onSearchChange={setSearch} searchPlaceholder="Search invoices" chips={<FilterChips options={options} selected={statuses} onChange={setStatuses} />} />` |
356
+ | `FilterChips` | Multi-select chip list. Options use `id`, `label`, and optional count/icon; pass `selected` and `onChange`. | `<FilterChips options={[{ id: "paid", label: "Paid" }]} selected={["paid"]} onChange={setStatuses} />` |
357
+ | `HealthScoreRing` | Circular numeric health score with configurable `value`, `max`, `size`, `thickness`, label, caption, and tone. | `<HealthScoreRing value={92} label="Healthy" />` |
358
+ | `HeroStatCard` | Prominent metric card requiring `tone`, `icon`, `label`, `value`, and `currency`; supports subtitle and numeric delta. | `<HeroStatCard label="Revenue" value="2.4m" currency="NGN" tone="dark" icon={Wallet} />` |
359
+ | `PeriodSelector` | Controlled date preset/custom-range picker. Pass a `PeriodRange` as `value` and handle `onChange`. | `<PeriodSelector value={period} onChange={setPeriod} />` |
360
+ | `ReferenceCombobox` | Searchable controlled reference selector. Options use `value`, `label`, and optional `helper`. | `<ReferenceCombobox options={countries} value={country} onValueChange={setCountry} placeholder="Country" />` |
361
+ | `RouterShell` | Responsive TanStack route layout combining `AppSidebar`, header, trigger, and page content. | `<RouterShell sidebar={{ groups }} header="Workspace"><Outlet /></RouterShell>` |
362
+ | `SearchInput` | Controlled search input with search and clear icons. | `<SearchInput value={search} onChange={setSearch} placeholder="Search" />` |
363
+ | `StatCard` | Metric card with `label`, `value`, optional delta, icon, helper, and semantic tone. | `<StatCard label="Invoices" value={128} delta={{ value: "12%", direction: "up" }} tone="success" />` |
364
+ | `StatusBadge` | Semantic/custom-color status label. Sizes are `xs`–`lg`; supports `dot`. | `<StatusBadge tone="success" size="sm" dot>Paid</StatusBadge>` |
365
+ | `StatusFilterPopover` | Multi-select status filter inside a popover. Pass typed options, `selected`, and `onChange`. | `<StatusFilterPopover options={options} selected={statuses} onChange={setStatuses} />` |
366
+ | `StatusFilter` | Inline multi-select status selector. Options contain `id`, `label`, and a selected-state `toneClass`. | `<StatusFilter options={options} selected={statuses} onChange={setStatuses} />` |
367
+ | `StepIndicator` | Numbered progress indicator. Pass step labels and a zero-based `currentStep`; sizes are `sm` and `md`. | `<StepIndicator steps={["Account", "Business", "Done"]} currentStep={1} />` |
368
+ | `SubmissionFunnel` | Stage conversion visualization. Pass stages with `id`, `label`, `value`, optional helper and tone. | `<SubmissionFunnel stages={[{ id: "received", label: "Received", value: 100 }, { id: "accepted", label: "Accepted", value: 82, tone: "success" }]} />` |
369
+ | `AppTabs` | Controlled animated app-tab context. Pass `value` and `onChange`. | `<AppTabs value={tab} onChange={setTab}><AppTabsList><AppTabsTab value="all" count={12}>All</AppTabsTab></AppTabsList></AppTabs>` |
370
+ | `AppTabsList` | Layout for `AppTabsTab` children with start/center/end alignment and optional actions. | `<AppTabsList align="start" actions={<Button>Export</Button>}>{tabs}</AppTabsList>` |
371
+ | `AppTabsTab` | Individual animated app tab. Requires `value`; supports `count` and `disabled`. | `<AppTabsTab value="pending" count={4}>Pending</AppTabsTab>` |
372
+ | `TrendCard` | Metric card with sparkline. Pass `label`, `value`, numeric `data`, optional delta, tone, and line/area variant. | `<TrendCard label="Volume" value="1,240" data={[10, 14, 12, 18]} tone="success" />` |
373
+
374
+ ### All 4 chart modules
375
+
376
+ Chart data points for area and bar charts require a `label` property plus numeric series keys.
377
+
378
+ | Component | Essential props | Minimal use |
379
+ | --- | --- | --- |
380
+ | `AreaChart` | `data`, `series`; optional `height`, `variant`, axes, grid, legend, curve, and formatter. | `<AreaChart data={[{ label: "Jan", revenue: 10 }, { label: "Feb", revenue: 15 }]} series={[{ key: "revenue", label: "Revenue" }]} />` |
381
+ | `BarChart` | `data`, `series`; optional `stacked`, horizontal/vertical layout, axes, grid, radius, and formatter. | `<BarChart data={[{ label: "Paid", total: 20 }]} series={[{ key: "total", label: "Invoices" }]} />` |
382
+ | `DonutChart` | `data` entries with `key`, `label`, `value`, optional color; supports size, thickness, center text, and formatter. | `<DonutChart data={[{ key: "paid", label: "Paid", value: 75 }, { key: "open", label: "Open", value: 25 }]} centerValue="100" />` |
383
+ | `Sparkline` | Numeric `data`; optional area/line variant, color, height, width, and stroke width. | `<Sparkline data={[4, 8, 6, 12]} color="#123526" variant="line" />` |
384
+
385
+ These 70 modules expose more than 150 composable exports because compound modules such as Dialog, Dropdown Menu, Sidebar, Table, and Form contain multiple related subcomponents. TypeScript declarations provide the complete prop contract and editor autocomplete for every export.
386
+
387
+ ## Styling and customization
388
+
389
+ Always import `@oidea/ui/styles.css`. It contains Tailwind-generated component styles, animations, and Oidea's CSS variables.
390
+
391
+ All components accept `className` where applicable, so layout and presentation can be extended without modifying package source:
392
+
393
+ ```tsx
394
+ <Button className="w-full rounded-xl">Continue</Button>
395
+ ```
396
+
397
+ Theme values can be overridden after the package stylesheet:
398
+
399
+ ```css
400
+ :root {
401
+ --primary: #123526;
402
+ --ring: #c9a87c;
403
+ --radius: 0.75rem;
404
+ }
405
+ ```
406
+
407
+ Common variables include `--background`, `--foreground`, `--card`, `--primary`, `--secondary`, `--accent`, `--destructive`, `--border`, `--input`, `--ring`, and `--radius`.
408
+
409
+ ## TypeScript
410
+
411
+ Types are bundled automatically. Props can be imported from the main entry point:
412
+
413
+ ```tsx
414
+ import type { DataTableColumn, OideaQueryProviderProps, StatusBadgeProps } from "@oidea/ui"
415
+ ```
416
+
417
+ ## Development
418
+
419
+ ```bash
420
+ git clone https://github.com/jalal-codr/oidea-ui.git
421
+ cd oidea-ui
422
+ npm install
423
+ npm run dev
424
+ ```
425
+
426
+ Useful commands:
427
+
428
+ ```bash
429
+ npm run typecheck
430
+ npm test
431
+ npm run lint
432
+ npm run build
433
+ ```
434
+
435
+ ## Publishing
436
+
437
+ The README is automatically displayed on the npm package page. Before publishing, authenticate with npm and run:
438
+
439
+ ```bash
440
+ npm login
441
+ npm publish
442
+ ```
443
+
444
+ The `prepublishOnly` script runs the test suite and production build automatically. The package is configured as a public scoped package.
445
+
446
+ ## License
447
+
448
+ UNLICENSED. Contact the repository owner before redistributing or using the package outside authorized projects.
@@ -0,0 +1,22 @@
1
+ import { ComponentType, ReactNode } from 'react';
2
+ export type ActionItemTone = 'critical' | 'warning' | 'info' | 'success' | 'neutral';
3
+ export interface ActionItem {
4
+ id: string;
5
+ icon?: ComponentType<{
6
+ className?: string;
7
+ }>;
8
+ tone?: ActionItemTone;
9
+ title: string;
10
+ description?: ReactNode;
11
+ meta?: string;
12
+ count?: number;
13
+ onClick?: () => void;
14
+ href?: string;
15
+ }
16
+ interface ActionItemListProps {
17
+ items: ActionItem[];
18
+ className?: string;
19
+ empty?: ReactNode;
20
+ }
21
+ export declare function ActionItemList({ items, className, empty }: ActionItemListProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import('react').ReactPortal | import('react').ReactElement<unknown, string | import('react').JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined>;
22
+ export {};
@@ -0,0 +1,20 @@
1
+ import { ComponentType, ReactNode } from 'react';
2
+ export type TimelineTone = 'neutral' | 'success' | 'warning' | 'critical' | 'info';
3
+ export interface TimelineEntry {
4
+ id: string;
5
+ title: string;
6
+ description?: ReactNode;
7
+ timestamp: ReactNode;
8
+ icon?: ComponentType<{
9
+ className?: string;
10
+ }>;
11
+ tone?: TimelineTone;
12
+ actor?: string;
13
+ }
14
+ interface ActivityTimelineProps {
15
+ entries: TimelineEntry[];
16
+ className?: string;
17
+ density?: 'comfortable' | 'compact';
18
+ }
19
+ export declare function ActivityTimeline({ entries, className, density, }: ActivityTimelineProps): import("react").JSX.Element;
20
+ export {};
@@ -0,0 +1,16 @@
1
+ export interface AgingBucket {
2
+ id: string;
3
+ label: string;
4
+ value: number;
5
+ color?: string;
6
+ }
7
+ interface AgingBucketBarsProps {
8
+ buckets: AgingBucket[];
9
+ total?: number;
10
+ formatValue?: (v: number) => string;
11
+ className?: string;
12
+ height?: number;
13
+ showLegend?: boolean;
14
+ }
15
+ export declare function AgingBucketBars({ buckets, total, formatValue, className, height, showLegend, }: AgingBucketBarsProps): import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ interface AppPageHeaderProps {
3
+ eyebrow?: string;
4
+ title: string;
5
+ description?: string;
6
+ actions?: ReactNode;
7
+ className?: string;
8
+ }
9
+ export declare function AppPageHeader({ eyebrow, title, description, actions, className, }: AppPageHeaderProps): import("react").JSX.Element;
10
+ export {};
@@ -0,0 +1,27 @@
1
+ import { ComponentType, ReactNode } from 'react';
2
+ export interface AppSidebarItem {
3
+ to: string;
4
+ label: string;
5
+ icon?: ComponentType<{
6
+ className?: string;
7
+ }>;
8
+ /** Override the default prefix-based active-route check. */
9
+ isActive?: (pathname: string) => boolean;
10
+ disabled?: boolean;
11
+ }
12
+ export interface AppSidebarGroup {
13
+ label?: ReactNode;
14
+ items: AppSidebarItem[];
15
+ }
16
+ export interface AppSidebarProps {
17
+ groups: AppSidebarGroup[];
18
+ /** Brand content shown at the top of the sidebar. */
19
+ brand?: ReactNode;
20
+ /** Destination used when the brand is clicked. Omit to render a static brand. */
21
+ brandTo?: string;
22
+ footer?: ReactNode;
23
+ collapsible?: "offcanvas" | "icon" | "none";
24
+ className?: string;
25
+ }
26
+ /** A configurable application sidebar integrated with TanStack Router. */
27
+ export declare function AppSidebar({ groups, brand, brandTo, footer, collapsible, className, }: AppSidebarProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { ReactNode } from 'react';
2
+ interface AppSurfaceProps {
3
+ title?: string;
4
+ description?: string;
5
+ actions?: ReactNode;
6
+ footer?: ReactNode;
7
+ children: ReactNode;
8
+ className?: string;
9
+ contentClassName?: string;
10
+ padding?: 'default' | 'tight' | 'flush';
11
+ }
12
+ export declare function AppSurface({ title, description, actions, footer, children, className, contentClassName, padding, }: AppSurfaceProps): import("react").JSX.Element;
13
+ export {};
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ export interface AvatarThumbnailProps {
3
+ initials?: string;
4
+ icon?: ReactNode;
5
+ tint?: string;
6
+ size?: number;
7
+ className?: string;
8
+ }
9
+ export declare function AvatarThumbnail({ initials, icon, tint, size, className, }: AvatarThumbnailProps): import("react").JSX.Element;
@@ -0,0 +1,10 @@
1
+ import { ReactNode } from 'react';
2
+ export interface AvatarTileProps {
3
+ thumbnail: ReactNode;
4
+ title: ReactNode;
5
+ titleSuffix?: ReactNode;
6
+ subtitle?: ReactNode;
7
+ trailing?: ReactNode;
8
+ className?: string;
9
+ }
10
+ export declare function AvatarTile({ thumbnail, title, titleSuffix, subtitle, trailing, className, }: AvatarTileProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ interface AreaChartProps<TKey extends string> {
2
+ data: Array<Record<string, string | number> & {
3
+ label: string;
4
+ }>;
5
+ series: Array<{
6
+ key: TKey;
7
+ label: string;
8
+ color?: string;
9
+ }>;
10
+ className?: string;
11
+ height?: number;
12
+ variant?: 'area' | 'line';
13
+ yAxis?: boolean;
14
+ xAxis?: boolean;
15
+ grid?: boolean;
16
+ legend?: boolean;
17
+ curve?: 'monotone' | 'linear' | 'step';
18
+ formatValue?: (v: number) => string;
19
+ }
20
+ export declare function AreaChart<TKey extends string>({ data, series, className, height, variant, yAxis, xAxis, grid, legend, curve, formatValue, }: AreaChartProps<TKey>): import("react").JSX.Element;
21
+ export {};
@@ -0,0 +1,21 @@
1
+ interface BarChartProps<TKey extends string> {
2
+ data: Array<Record<string, string | number> & {
3
+ label: string;
4
+ }>;
5
+ series: Array<{
6
+ key: TKey;
7
+ label: string;
8
+ color?: string;
9
+ }>;
10
+ className?: string;
11
+ height?: number;
12
+ stacked?: boolean;
13
+ layout?: 'vertical' | 'horizontal';
14
+ yAxis?: boolean;
15
+ xAxis?: boolean;
16
+ grid?: boolean;
17
+ radius?: number;
18
+ formatValue?: (v: number) => string;
19
+ }
20
+ export declare function BarChart<TKey extends string>({ data, series, className, height, stacked, layout, yAxis, xAxis, grid, radius, formatValue, }: BarChartProps<TKey>): import("react").JSX.Element;
21
+ export {};
@@ -0,0 +1,16 @@
1
+ interface DonutChartProps {
2
+ data: Array<{
3
+ key: string;
4
+ label: string;
5
+ value: number;
6
+ color?: string;
7
+ }>;
8
+ className?: string;
9
+ size?: number;
10
+ thickness?: number;
11
+ centerLabel?: string;
12
+ centerValue?: string;
13
+ formatValue?: (v: number) => string;
14
+ }
15
+ export declare function DonutChart({ data, className, size, thickness, centerLabel, centerValue, formatValue, }: DonutChartProps): import("react").JSX.Element;
16
+ export {};
@@ -0,0 +1,4 @@
1
+ export { AreaChart } from './area-chart';
2
+ export { BarChart } from './bar-chart';
3
+ export { DonutChart } from './donut-chart';
4
+ export { Sparkline } from './sparkline';