@etherealui/react 0.0.0-stage → 0.0.3

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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +62 -2
  3. package/dist/index.d.mts +583 -0
  4. package/dist/index.d.ts +583 -0
  5. package/dist/index.js +5189 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +5189 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +83 -3
  10. package/src/blocks/app-shell.tsx +441 -0
  11. package/src/blocks/billing-card.tsx +2 -0
  12. package/src/blocks/data-table.tsx +479 -0
  13. package/src/blocks/filter-bar.tsx +134 -0
  14. package/src/blocks/header-navbar.tsx +7 -0
  15. package/src/blocks/metric-grid.tsx +5 -0
  16. package/src/blocks/profile-form.tsx +2 -0
  17. package/src/blocks/security-settings.tsx +2 -0
  18. package/src/blocks/settings-blocks.tsx +406 -0
  19. package/src/blocks/sidebar.tsx +6 -0
  20. package/src/blocks/stat-card.tsx +7 -0
  21. package/src/blocks/stat-cards.tsx +212 -0
  22. package/src/blocks/team-members.tsx +5 -0
  23. package/src/index.ts +114 -0
  24. package/src/primitives/accordion.tsx +61 -0
  25. package/src/primitives/announcement.tsx +33 -0
  26. package/src/primitives/avatar.tsx +156 -0
  27. package/src/primitives/badge.tsx +108 -0
  28. package/src/primitives/breadcrumb.tsx +45 -0
  29. package/src/primitives/button-group.tsx +79 -0
  30. package/src/primitives/button.tsx +165 -0
  31. package/src/primitives/carousel.tsx +84 -0
  32. package/src/primitives/chart.tsx +831 -0
  33. package/src/primitives/checkbox.tsx +26 -0
  34. package/src/primitives/chip.tsx +37 -0
  35. package/src/primitives/close-button.tsx +29 -0
  36. package/src/primitives/color-picker.tsx +62 -0
  37. package/src/primitives/combobox.tsx +123 -0
  38. package/src/primitives/command-palette.tsx +216 -0
  39. package/src/primitives/context-menu.tsx +84 -0
  40. package/src/primitives/date-calendar.tsx +76 -0
  41. package/src/primitives/date-picker.tsx +400 -0
  42. package/src/primitives/dialog.tsx +279 -0
  43. package/src/primitives/divider.tsx +31 -0
  44. package/src/primitives/drawer.tsx +95 -0
  45. package/src/primitives/dropdown-menu.tsx +164 -0
  46. package/src/primitives/file-upload.tsx +123 -0
  47. package/src/primitives/icon-button.tsx +23 -0
  48. package/src/primitives/input.tsx +124 -0
  49. package/src/primitives/modal.tsx +26 -0
  50. package/src/primitives/pagination.tsx +58 -0
  51. package/src/primitives/popover.tsx +28 -0
  52. package/src/primitives/progress.tsx +145 -0
  53. package/src/primitives/radio-group.tsx +63 -0
  54. package/src/primitives/select.tsx +207 -0
  55. package/src/primitives/skeleton.tsx +61 -0
  56. package/src/primitives/slider.tsx +44 -0
  57. package/src/primitives/switch.tsx +146 -0
  58. package/src/primitives/table.tsx +47 -0
  59. package/src/primitives/tabs.tsx +86 -0
  60. package/src/primitives/textarea.tsx +40 -0
  61. package/src/primitives/toast.tsx +71 -0
  62. package/src/primitives/tooltip.tsx +58 -0
  63. package/src/styles/globals.css +49 -0
  64. package/src/templates/control-tower.tsx +41 -0
  65. package/src/templates/executive-analytics.tsx +272 -0
  66. package/src/templates/workspace-settings.tsx +179 -0
  67. package/src/utils/cn.ts +7 -0
package/package.json CHANGED
@@ -1,6 +1,86 @@
1
1
  {
2
2
  "name": "@etherealui/react",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.3",
4
+ "description": "Free, Open-Source, Modern, Dark-First Enterprise Dashboard Blocks & Component System for React",
5
+ "license": "MIT",
6
+ "author": "Saeful Rohman <saefulrohmandevelopers@gmail.com> (https://saefulrohman.dev)",
7
+ "homepage": "https://ethereal-ui.saefulrohman.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/XenchinRyu7/Ethereal-UI.git",
11
+ "directory": "packages/ui"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/XenchinRyu7/Ethereal-UI/issues"
15
+ },
16
+ "funding": {
17
+ "type": "github",
18
+ "url": "https://github.com/sponsors/XenchinRyu7"
19
+ },
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "keywords": [
24
+ "react",
25
+ "ui",
26
+ "components",
27
+ "dashboard-blocks",
28
+ "tailwind-css",
29
+ "tailwindcss-v4",
30
+ "radix-ui",
31
+ "design-system",
32
+ "enterprise-ui",
33
+ "ethereal-ui"
34
+ ],
35
+ "main": "./dist/index.js",
36
+ "module": "./dist/index.mjs",
37
+ "types": "./dist/index.d.ts",
38
+ "sideEffects": [
39
+ "**/*.css"
40
+ ],
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/index.d.ts",
44
+ "import": "./dist/index.mjs",
45
+ "require": "./dist/index.js"
46
+ },
47
+ "./styles.css": "./src/styles/globals.css"
48
+ },
49
+ "files": [
50
+ "dist",
51
+ "src"
52
+ ],
53
+ "dependencies": {
54
+ "@radix-ui/react-checkbox": "^1.1.4",
55
+ "@radix-ui/react-dialog": "^1.1.6",
56
+ "@radix-ui/react-dropdown-menu": "^2.1.6",
57
+ "@radix-ui/react-popover": "^1.1.6",
58
+ "@radix-ui/react-select": "^2.1.6",
59
+ "@radix-ui/react-slot": "^1.1.2",
60
+ "@radix-ui/react-switch": "^1.1.3",
61
+ "@radix-ui/react-tabs": "^1.1.3",
62
+ "@radix-ui/react-tooltip": "^1.1.8",
63
+ "class-variance-authority": "^0.7.1",
64
+ "clsx": "^2.1.1",
65
+ "lucide-react": "^0.475.0",
66
+ "tailwind-merge": "^3.0.1"
67
+ },
68
+ "peerDependencies": {
69
+ "react": "^18.0.0 || ^19.0.0",
70
+ "react-dom": "^18.0.0 || ^19.0.0"
71
+ },
72
+ "devDependencies": {
73
+ "@types/react": "^19.0.8",
74
+ "@types/react-dom": "^19.0.3",
75
+ "react": "^19.0.0",
76
+ "react-dom": "^19.0.0",
77
+ "tsup": "^8.3.6",
78
+ "typescript": "^5.7.3"
79
+ },
80
+ "scripts": {
81
+ "build": "tsup",
82
+ "dev": "tsup --watch",
83
+ "lint": "tsc --noEmit",
84
+ "clean": "rimraf dist"
85
+ }
6
86
  }
@@ -0,0 +1,441 @@
1
+ import * as React from "react"
2
+ import {
3
+ LayoutDashboard,
4
+ Users,
5
+ Settings,
6
+ Bell,
7
+ Search,
8
+ ChevronRight,
9
+ ChevronDown,
10
+ PanelLeftClose,
11
+ PanelLeftOpen,
12
+ Layers,
13
+ CreditCard,
14
+ FolderKanban,
15
+ BarChart3,
16
+ } from "lucide-react"
17
+ import { cn } from "../utils/cn"
18
+ import { Avatar } from "../primitives/avatar"
19
+ import { Badge } from "../primitives/badge"
20
+ import { Button } from "../primitives/button"
21
+ import { CommandPalette } from "../primitives/command-palette"
22
+
23
+ export interface SidebarNavItem {
24
+ id: string
25
+ label: string
26
+ icon?: React.ReactNode
27
+ badge?: string
28
+ active?: boolean
29
+ children?: Array<{ id: string; label: string; active?: boolean }>
30
+ }
31
+
32
+ const defaultNavItems: SidebarNavItem[] = [
33
+ {
34
+ id: "overview",
35
+ label: "Overview",
36
+ icon: <LayoutDashboard className="h-4 w-4" />,
37
+ active: true,
38
+ },
39
+ {
40
+ id: "analytics",
41
+ label: "Analytics",
42
+ icon: <BarChart3 className="h-4 w-4" />,
43
+ children: [
44
+ { id: "revenue", label: "Revenue & MRR", active: true },
45
+ { id: "retention", label: "Cohort Retention" },
46
+ { id: "funnels", label: "Conversion Funnels" },
47
+ ],
48
+ },
49
+ {
50
+ id: "projects",
51
+ label: "Projects",
52
+ icon: <FolderKanban className="h-4 w-4" />,
53
+ badge: "12",
54
+ },
55
+ {
56
+ id: "customers",
57
+ label: "Customers",
58
+ icon: <Users className="h-4 w-4" />,
59
+ },
60
+ {
61
+ id: "billing",
62
+ label: "Billing",
63
+ icon: <CreditCard className="h-4 w-4" />,
64
+ },
65
+ {
66
+ id: "settings",
67
+ label: "Settings",
68
+ icon: <Settings className="h-4 w-4" />,
69
+ },
70
+ ]
71
+
72
+ export interface SidebarShellProps {
73
+ items?: SidebarNavItem[]
74
+ activeId?: string
75
+ onSelect?: (id: string) => void
76
+ defaultCollapsed?: boolean
77
+ workspaceName?: string
78
+ workspaceTier?: string
79
+ className?: string
80
+ }
81
+
82
+ export const SidebarShell: React.FC<SidebarShellProps> = ({
83
+ items = defaultNavItems,
84
+ activeId,
85
+ onSelect,
86
+ defaultCollapsed = false,
87
+ workspaceName = "Acme Corp",
88
+ workspaceTier = "Pro",
89
+ className,
90
+ }) => {
91
+ const [collapsed, setCollapsed] = React.useState(defaultCollapsed)
92
+ const [selectedId, setSelectedId] = React.useState(activeId || "overview")
93
+ const [openGroups, setOpenGroups] = React.useState<Record<string, boolean>>({
94
+ analytics: true,
95
+ })
96
+
97
+ const currentId = activeId !== undefined ? activeId : selectedId
98
+
99
+ const handleSelect = (id: string) => {
100
+ setSelectedId(id)
101
+ onSelect?.(id)
102
+ }
103
+
104
+ return (
105
+ <aside
106
+ className={cn(
107
+ "flex flex-col border-r border-zinc-200 bg-zinc-50/70 text-zinc-700 transition-all duration-200 select-none shrink-0 dark:border-zinc-800 dark:bg-zinc-900/60 dark:text-zinc-200",
108
+ collapsed ? "w-16" : "w-60",
109
+ className
110
+ )}
111
+ >
112
+ {/* Workspace Header */}
113
+ <div className="flex h-14 items-center justify-between border-b border-zinc-200 px-3.5 dark:border-zinc-800">
114
+ <div className="flex items-center gap-2.5 min-w-0">
115
+ <div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-blue-600 text-white">
116
+ <Layers className="h-4 w-4" />
117
+ </div>
118
+ {!collapsed && (
119
+ <div className="min-w-0">
120
+ <div className="flex items-center gap-1.5">
121
+ <p className="truncate text-xs font-semibold text-zinc-900 dark:text-zinc-100">
122
+ {workspaceName}
123
+ </p>
124
+ <span className="rounded bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-700 dark:bg-blue-500/15 dark:text-blue-300">
125
+ {workspaceTier}
126
+ </span>
127
+ </div>
128
+ </div>
129
+ )}
130
+ </div>
131
+ <button
132
+ type="button"
133
+ onClick={() => setCollapsed(!collapsed)}
134
+ aria-label="Toggle sidebar"
135
+ className="rounded-lg p-1.5 text-zinc-400 hover:bg-zinc-200/60 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-200 cursor-pointer"
136
+ >
137
+ {collapsed ? (
138
+ <PanelLeftOpen className="h-4 w-4" />
139
+ ) : (
140
+ <PanelLeftClose className="h-4 w-4" />
141
+ )}
142
+ </button>
143
+ </div>
144
+
145
+ {/* Navigation List */}
146
+ <div className="flex-1 overflow-y-auto p-2.5 space-y-1">
147
+ {!collapsed && (
148
+ <div className="px-2.5 py-1 text-[10px] font-semibold uppercase tracking-wider text-zinc-400">
149
+ Workspace
150
+ </div>
151
+ )}
152
+ {items.map((item) => {
153
+ const isActive = currentId === item.id
154
+ const hasChildren = Boolean(item.children && item.children.length > 0)
155
+ const isOpen = openGroups[item.id]
156
+
157
+ return (
158
+ <div key={item.id} className="space-y-0.5">
159
+ <button
160
+ type="button"
161
+ onClick={() => {
162
+ handleSelect(item.id)
163
+ if (hasChildren) {
164
+ setOpenGroups((prev) => ({
165
+ ...prev,
166
+ [item.id]: !prev[item.id],
167
+ }))
168
+ }
169
+ }}
170
+ className={cn(
171
+ "flex w-full items-center justify-between rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors cursor-pointer",
172
+ isActive
173
+ ? "bg-zinc-200/75 text-zinc-900 dark:bg-zinc-800 dark:text-white"
174
+ : "text-zinc-600 hover:bg-zinc-200/40 hover:text-zinc-900 dark:text-zinc-400 dark:hover:bg-zinc-800/50 dark:hover:text-zinc-200"
175
+ )}
176
+ >
177
+ <span className="flex items-center gap-2.5 min-w-0">
178
+ <span
179
+ className={cn(
180
+ "shrink-0",
181
+ isActive
182
+ ? "text-blue-600 dark:text-blue-400"
183
+ : "text-zinc-400"
184
+ )}
185
+ >
186
+ {item.icon}
187
+ </span>
188
+ {!collapsed && <span className="truncate">{item.label}</span>}
189
+ </span>
190
+
191
+ {!collapsed && (
192
+ <span className="flex items-center gap-1.5">
193
+ {item.badge && (
194
+ <span className="rounded bg-zinc-200/80 px-1.5 py-0.5 text-[10px] text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400">
195
+ {item.badge}
196
+ </span>
197
+ )}
198
+ {hasChildren && (
199
+ <ChevronDown
200
+ className={cn(
201
+ "h-3.5 w-3.5 text-zinc-400 transition-transform",
202
+ isOpen ? "rotate-0" : "-rotate-90"
203
+ )}
204
+ />
205
+ )}
206
+ </span>
207
+ )}
208
+ </button>
209
+
210
+ {!collapsed && hasChildren && isOpen && (
211
+ <div className="ml-4 border-l border-zinc-200 pl-2.5 space-y-0.5 dark:border-zinc-800">
212
+ {item.children!.map((sub) => {
213
+ const subActive = currentId === sub.id
214
+ return (
215
+ <button
216
+ key={sub.id}
217
+ type="button"
218
+ onClick={() => handleSelect(sub.id)}
219
+ className={cn(
220
+ "flex w-full items-center rounded-md px-2.5 py-1.5 text-xs transition-colors cursor-pointer",
221
+ subActive
222
+ ? "text-blue-600 font-medium dark:text-blue-400"
223
+ : "text-zinc-500 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-200"
224
+ )}
225
+ >
226
+ {sub.label}
227
+ </button>
228
+ )
229
+ })}
230
+ </div>
231
+ )}
232
+ </div>
233
+ )
234
+ })}
235
+ </div>
236
+
237
+ {/* Footer User Pod */}
238
+ <div className="border-t border-zinc-200 p-3 dark:border-zinc-800">
239
+ <div className="flex items-center justify-between gap-2.5">
240
+ <div className="flex items-center gap-2.5 min-w-0">
241
+ <Avatar fallback="SR" tint="blue" size="sm" status="online" />
242
+ {!collapsed && (
243
+ <div className="min-w-0">
244
+ <p className="truncate text-xs font-medium text-zinc-900 dark:text-zinc-100">
245
+ Saeful Rohman
246
+ </p>
247
+ <p className="truncate text-[11px] text-zinc-500">
248
+ saeful@acme.com
249
+ </p>
250
+ </div>
251
+ )}
252
+ </div>
253
+ </div>
254
+ </div>
255
+ </aside>
256
+ )
257
+ }
258
+
259
+ export interface HeaderNavbarProps {
260
+ breadcrumbs?: string[]
261
+ onOpenCommandPalette?: () => void
262
+ className?: string
263
+ }
264
+
265
+ export const HeaderNavbar: React.FC<HeaderNavbarProps> = ({
266
+ breadcrumbs = ["Workspace", "Analytics", "Overview"],
267
+ onOpenCommandPalette,
268
+ className,
269
+ }) => {
270
+ const [internalCmdOpen, setInternalCmdOpen] = React.useState(false)
271
+
272
+ const handleOpenSearch = () => {
273
+ if (onOpenCommandPalette) {
274
+ onOpenCommandPalette()
275
+ } else {
276
+ setInternalCmdOpen(true)
277
+ }
278
+ }
279
+
280
+ return (
281
+ <>
282
+ <header
283
+ className={cn(
284
+ "flex h-14 w-full items-center justify-between border-b border-zinc-200 bg-white px-5 dark:border-zinc-800 dark:bg-zinc-900",
285
+ className
286
+ )}
287
+ >
288
+ {/* Breadcrumb Trail */}
289
+ <nav aria-label="Breadcrumb" className="flex items-center gap-1.5 text-xs">
290
+ {breadcrumbs.map((crumb, idx) => {
291
+ const isLast = idx === breadcrumbs.length - 1
292
+ return (
293
+ <React.Fragment key={crumb}>
294
+ {idx > 0 && (
295
+ <ChevronRight className="h-3.5 w-3.5 text-zinc-400 shrink-0" />
296
+ )}
297
+ <span
298
+ className={cn(
299
+ "truncate",
300
+ isLast
301
+ ? "font-medium text-zinc-900 dark:text-zinc-100"
302
+ : "text-zinc-500 dark:text-zinc-400"
303
+ )}
304
+ >
305
+ {crumb}
306
+ </span>
307
+ </React.Fragment>
308
+ )
309
+ })}
310
+ </nav>
311
+
312
+ {/* Right Controls */}
313
+ <div className="flex items-center gap-2.5">
314
+ <button
315
+ type="button"
316
+ onClick={handleOpenSearch}
317
+ className="hidden sm:flex h-8 w-52 items-center justify-between rounded-lg border border-zinc-200 bg-zinc-50 px-2.5 text-xs text-zinc-500 transition-colors hover:border-zinc-300 hover:text-zinc-800 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:border-zinc-600 dark:hover:text-zinc-100 cursor-pointer"
318
+ >
319
+ <span className="flex items-center gap-2">
320
+ <Search className="h-3.5 w-3.5 text-zinc-400 dark:text-zinc-400" />
321
+ <span>Quick search...</span>
322
+ </span>
323
+ <kbd className="rounded border border-zinc-200 bg-white px-1.5 py-0.5 font-mono text-[10px] text-zinc-500 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300">
324
+ ⌘K
325
+ </kbd>
326
+ </button>
327
+
328
+ <button
329
+ type="button"
330
+ aria-label="Notifications"
331
+ className="relative flex h-8 w-8 items-center justify-center rounded-lg border border-zinc-200 bg-white text-zinc-500 hover:bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-300 dark:hover:bg-zinc-700 cursor-pointer"
332
+ >
333
+ <Bell className="h-3.5 w-3.5" />
334
+ <span className="absolute right-1.5 top-1.5 h-1.5 w-1.5 rounded-full bg-blue-600" />
335
+ </button>
336
+ </div>
337
+ </header>
338
+
339
+ {!onOpenCommandPalette && (
340
+ <CommandPalette
341
+ open={internalCmdOpen}
342
+ onOpenChange={setInternalCmdOpen}
343
+ enableGlobalShortcut={false}
344
+ />
345
+ )}
346
+ </>
347
+ )
348
+ }
349
+
350
+ export interface PageHeaderBarProps {
351
+ title: string
352
+ subtitle?: string
353
+ badge?: string
354
+ actions?: React.ReactNode
355
+ className?: string
356
+ }
357
+
358
+ export const PageHeaderBar: React.FC<PageHeaderBarProps> = ({
359
+ title,
360
+ subtitle,
361
+ badge,
362
+ actions,
363
+ className,
364
+ }) => {
365
+ return (
366
+ <div
367
+ className={cn(
368
+ "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between border-b border-zinc-200 pb-5 dark:border-zinc-800",
369
+ className
370
+ )}
371
+ >
372
+ <div className="space-y-1">
373
+ <div className="flex items-center gap-2.5">
374
+ <h2 className="text-lg font-semibold tracking-tight text-zinc-900 dark:text-zinc-100">
375
+ {title}
376
+ </h2>
377
+ {badge && (
378
+ <Badge variant="blue" dot>
379
+ {badge}
380
+ </Badge>
381
+ )}
382
+ </div>
383
+ {subtitle && (
384
+ <p className="text-xs text-zinc-500 dark:text-zinc-400">{subtitle}</p>
385
+ )}
386
+ </div>
387
+ <div className="flex items-center gap-2">
388
+ {actions || (
389
+ <>
390
+ <Button variant="outline" size="sm">
391
+ Export report
392
+ </Button>
393
+ <Button variant="primary" size="sm">
394
+ New report
395
+ </Button>
396
+ </>
397
+ )}
398
+ </div>
399
+ </div>
400
+ )
401
+ }
402
+
403
+ export interface AppShellProps {
404
+ workspaceName?: string
405
+ planBadge?: string
406
+ breadcrumbs?: string[]
407
+ user?: {
408
+ name: string
409
+ email: string
410
+ initials: string
411
+ avatarSrc?: string
412
+ }
413
+ children?: React.ReactNode
414
+ className?: string
415
+ }
416
+
417
+ export const AppShell: React.FC<AppShellProps> = ({
418
+ workspaceName = "Acme Corp",
419
+ planBadge = "Pro",
420
+ breadcrumbs = ["Workspace", "Overview"],
421
+ children,
422
+ className,
423
+ }) => {
424
+ return (
425
+ <div
426
+ className={cn(
427
+ "flex min-h-[600px] w-full overflow-hidden rounded-2xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-950",
428
+ className
429
+ )}
430
+ >
431
+ <SidebarShell
432
+ workspaceName={workspaceName}
433
+ workspaceTier={planBadge}
434
+ />
435
+ <div className="flex flex-1 flex-col min-w-0">
436
+ <HeaderNavbar breadcrumbs={breadcrumbs} />
437
+ <main className="flex-1 p-6 space-y-6 overflow-y-auto">{children}</main>
438
+ </div>
439
+ </div>
440
+ )
441
+ }
@@ -0,0 +1,2 @@
1
+ export { BillingPlanCardBlock } from "./settings-blocks"
2
+