@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
@@ -0,0 +1,47 @@
1
+ import * as React from "react"
2
+ import { cn } from "../utils/cn"
3
+
4
+ export const Table = React.forwardRef<
5
+ HTMLTableElement,
6
+ React.HTMLAttributes<HTMLTableElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div className="w-full overflow-x-auto rounded-xl border border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900">
9
+ <table
10
+ ref={ref}
11
+ className={cn("w-full border-collapse text-left text-xs", className)}
12
+ {...props}
13
+ />
14
+ </div>
15
+ ))
16
+ Table.displayName = "Table"
17
+
18
+ export const TableHeader = React.forwardRef<
19
+ HTMLTableSectionElement,
20
+ React.HTMLAttributes<HTMLTableSectionElement>
21
+ >(({ className, ...props }, ref) => (
22
+ <thead
23
+ ref={ref}
24
+ className={cn(
25
+ "border-b border-zinc-200 bg-zinc-50/80 text-[11px] font-medium text-zinc-500 dark:border-zinc-800 dark:bg-zinc-950/50 dark:text-zinc-400",
26
+ className
27
+ )}
28
+ {...props}
29
+ />
30
+ ))
31
+ TableHeader.displayName = "TableHeader"
32
+
33
+ export const TableBody = React.forwardRef<
34
+ HTMLTableSectionElement,
35
+ React.HTMLAttributes<HTMLTableSectionElement>
36
+ >(({ className, ...props }, ref) => (
37
+ <tbody
38
+ ref={ref}
39
+ className={cn(
40
+ "divide-y divide-zinc-200 dark:divide-zinc-800",
41
+ className
42
+ )}
43
+ {...props}
44
+ />
45
+ ))
46
+ TableBody.displayName = "TableBody"
47
+
@@ -0,0 +1,86 @@
1
+ import * as React from "react"
2
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+ import { cn } from "../utils/cn"
5
+
6
+ const Tabs = TabsPrimitive.Root
7
+
8
+ const tabsListVariants = cva("inline-flex items-center text-zinc-500 dark:text-zinc-400", {
9
+ variants: {
10
+ variant: {
11
+ segmented:
12
+ "h-9 rounded-lg border border-zinc-200 bg-zinc-100/80 p-1 gap-1 dark:border-zinc-800 dark:bg-zinc-900",
13
+ underline:
14
+ "w-full justify-start border-b border-zinc-200 bg-transparent gap-6 px-1 dark:border-zinc-800",
15
+ pills: "gap-1.5 bg-transparent",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ variant: "segmented",
20
+ },
21
+ })
22
+
23
+ export interface TabsListProps
24
+ extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>,
25
+ VariantProps<typeof tabsListVariants> {}
26
+
27
+ const TabsList = React.forwardRef<
28
+ React.ElementRef<typeof TabsPrimitive.List>,
29
+ TabsListProps
30
+ >(({ className, variant, ...props }, ref) => (
31
+ <TabsPrimitive.List
32
+ ref={ref}
33
+ className={cn(tabsListVariants({ variant, className }))}
34
+ {...props}
35
+ />
36
+ ))
37
+ TabsList.displayName = TabsPrimitive.List.displayName
38
+
39
+ const tabsTriggerVariants = cva(
40
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap text-xs font-medium transition-all focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 cursor-pointer select-none",
41
+ {
42
+ variants: {
43
+ variant: {
44
+ segmented:
45
+ "rounded-md px-3 py-1 data-[state=active]:bg-white data-[state=active]:text-zinc-900 data-[state=active]:shadow-2xs hover:text-zinc-900 dark:data-[state=active]:bg-zinc-800 dark:data-[state=active]:text-zinc-100 dark:hover:text-zinc-200",
46
+ underline:
47
+ "border-b-2 border-transparent pb-2.5 pt-1 text-zinc-500 data-[state=active]:border-blue-600 data-[state=active]:text-zinc-900 hover:text-zinc-800 dark:text-zinc-400 dark:data-[state=active]:border-blue-500 dark:data-[state=active]:text-zinc-100 dark:hover:text-zinc-200",
48
+ pills:
49
+ "rounded-lg px-3 py-1.5 border border-transparent data-[state=active]:bg-blue-50 data-[state=active]:text-blue-700 data-[state=active]:border-blue-200 hover:bg-zinc-100 dark:data-[state=active]:bg-blue-500/15 dark:data-[state=active]:text-blue-300 dark:data-[state=active]:border-blue-500/30 dark:hover:bg-zinc-800",
50
+ },
51
+ },
52
+ defaultVariants: {
53
+ variant: "segmented",
54
+ },
55
+ }
56
+ )
57
+
58
+ export interface TabsTriggerProps
59
+ extends React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>,
60
+ VariantProps<typeof tabsTriggerVariants> {}
61
+
62
+ const TabsTrigger = React.forwardRef<
63
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
64
+ TabsTriggerProps
65
+ >(({ className, variant, ...props }, ref) => (
66
+ <TabsPrimitive.Trigger
67
+ ref={ref}
68
+ className={cn(tabsTriggerVariants({ variant, className }))}
69
+ {...props}
70
+ />
71
+ ))
72
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
73
+
74
+ const TabsContent = React.forwardRef<
75
+ React.ElementRef<typeof TabsPrimitive.Content>,
76
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
77
+ >(({ className, ...props }, ref) => (
78
+ <TabsPrimitive.Content
79
+ ref={ref}
80
+ className={cn("mt-3 focus-visible:outline-none", className)}
81
+ {...props}
82
+ />
83
+ ))
84
+ TabsContent.displayName = TabsPrimitive.Content.displayName
85
+
86
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1,40 @@
1
+ import * as React from "react"
2
+ import { cn } from "../utils/cn"
3
+
4
+ export interface TextareaProps
5
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
6
+ autoResize?: boolean
7
+ }
8
+
9
+ export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
10
+ ({ className, autoResize = false, onChange, ...props }, ref) => {
11
+ const innerRef = React.useRef<HTMLTextAreaElement | null>(null)
12
+
13
+ const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
14
+ if (autoResize && innerRef.current) {
15
+ innerRef.current.style.height = "auto"
16
+ innerRef.current.style.height = `${innerRef.current.scrollHeight}px`
17
+ }
18
+ onChange?.(e)
19
+ }
20
+
21
+ return (
22
+ <textarea
23
+ ref={(node) => {
24
+ innerRef.current = node
25
+ if (typeof ref === "function") ref(node)
26
+ else if (ref) ref.current = node
27
+ }}
28
+ onChange={handleChange}
29
+ className={cn(
30
+ "flex min-h-[84px] w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder:text-zinc-400 transition-colors focus-visible:outline-none focus-visible:border-blue-500 focus-visible:ring-2 focus-visible:ring-blue-500/20 disabled:cursor-not-allowed disabled:opacity-50 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder:text-zinc-500",
31
+ autoResize && "resize-none overflow-hidden",
32
+ className
33
+ )}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+ )
39
+ Textarea.displayName = "Textarea"
40
+
@@ -0,0 +1,71 @@
1
+ import * as React from "react"
2
+ import { CheckCircle2, AlertTriangle, XCircle, Info, X } from "lucide-react"
3
+ import { cn } from "../utils/cn"
4
+
5
+ export type ToastVariant = "success" | "error" | "warning" | "info"
6
+
7
+ export interface ToastItem {
8
+ id: string
9
+ title: string
10
+ description?: string
11
+ variant?: ToastVariant
12
+ }
13
+
14
+ const iconMap: Record<ToastVariant, React.ReactNode> = {
15
+ success: <CheckCircle2 className="h-4 w-4 text-emerald-600 dark:text-emerald-400 shrink-0" />,
16
+ error: <XCircle className="h-4 w-4 text-rose-600 dark:text-rose-400 shrink-0" />,
17
+ warning: <AlertTriangle className="h-4 w-4 text-amber-600 dark:text-amber-400 shrink-0" />,
18
+ info: <Info className="h-4 w-4 text-blue-600 dark:text-blue-400 shrink-0" />,
19
+ }
20
+
21
+ const borderMap: Record<ToastVariant, string> = {
22
+ success: "border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900",
23
+ error: "border-rose-200 bg-rose-50/40 dark:border-rose-500/30 dark:bg-rose-950/20",
24
+ warning: "border-amber-200 bg-amber-50/40 dark:border-amber-500/30 dark:bg-amber-950/20",
25
+ info: "border-zinc-200 bg-white dark:border-zinc-800 dark:bg-zinc-900",
26
+ }
27
+
28
+ export interface ToastCardProps extends Omit<ToastItem, "id"> {
29
+ onDismiss?: () => void
30
+ className?: string
31
+ }
32
+
33
+ export const ToastCard: React.FC<ToastCardProps> = ({
34
+ title,
35
+ description,
36
+ variant = "info",
37
+ onDismiss,
38
+ className,
39
+ }) => {
40
+ return (
41
+ <div
42
+ role="status"
43
+ className={cn(
44
+ "flex w-full max-w-sm items-start gap-3 rounded-xl border p-3.5 text-zinc-900 shadow-md transition-all dark:text-zinc-100",
45
+ borderMap[variant],
46
+ className
47
+ )}
48
+ >
49
+ <div className="mt-0.5">{iconMap[variant]}</div>
50
+ <div className="flex-1 space-y-1">
51
+ <p className="text-xs font-semibold leading-none text-zinc-900 dark:text-zinc-100">
52
+ {title}
53
+ </p>
54
+ {description && (
55
+ <p className="text-xs text-zinc-500 dark:text-zinc-400 leading-relaxed">
56
+ {description}
57
+ </p>
58
+ )}
59
+ </div>
60
+ {onDismiss && (
61
+ <button
62
+ type="button"
63
+ onClick={onDismiss}
64
+ className="rounded-md p-1 text-zinc-400 hover:bg-zinc-100 hover:text-zinc-700 dark:hover:bg-zinc-800 dark:hover:text-zinc-200 cursor-pointer"
65
+ >
66
+ <X className="h-3.5 w-3.5" />
67
+ </button>
68
+ )}
69
+ </div>
70
+ )
71
+ }
@@ -0,0 +1,58 @@
1
+ import * as React from "react"
2
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip"
3
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
4
+ import { cn } from "../utils/cn"
5
+
6
+ const TooltipProvider = TooltipPrimitive.Provider
7
+ const Tooltip = TooltipPrimitive.Root
8
+ const TooltipTrigger = TooltipPrimitive.Trigger
9
+
10
+ const TooltipContent = React.forwardRef<
11
+ React.ElementRef<typeof TooltipPrimitive.Content>,
12
+ React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
13
+ >(({ className, sideOffset = 6, ...props }, ref) => (
14
+ <TooltipPrimitive.Portal>
15
+ <TooltipPrimitive.Content
16
+ ref={ref}
17
+ sideOffset={sideOffset}
18
+ className={cn(
19
+ "z-50 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-900 px-2.5 py-1.5 text-xs text-white shadow-md dark:border-zinc-800 dark:bg-zinc-100 dark:text-zinc-900",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ </TooltipPrimitive.Portal>
25
+ ))
26
+ TooltipContent.displayName = TooltipPrimitive.Content.displayName
27
+
28
+ const Popover = PopoverPrimitive.Root
29
+ const PopoverTrigger = PopoverPrimitive.Trigger
30
+
31
+ const PopoverContent = React.forwardRef<
32
+ React.ElementRef<typeof PopoverPrimitive.Content>,
33
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
34
+ >(({ className, align = "center", sideOffset = 6, ...props }, ref) => (
35
+ <PopoverPrimitive.Portal>
36
+ <PopoverPrimitive.Content
37
+ ref={ref}
38
+ align={align}
39
+ sideOffset={sideOffset}
40
+ className={cn(
41
+ "z-50 w-72 rounded-xl border border-zinc-200 bg-white p-4 text-zinc-900 shadow-lg outline-none dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-100",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ </PopoverPrimitive.Portal>
47
+ ))
48
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
49
+
50
+ export {
51
+ Tooltip,
52
+ TooltipTrigger,
53
+ TooltipContent,
54
+ TooltipProvider,
55
+ Popover,
56
+ PopoverTrigger,
57
+ PopoverContent,
58
+ }
@@ -0,0 +1,49 @@
1
+ @import "tailwindcss";
2
+ @source "../**/*.{ts,tsx}";
3
+
4
+ @custom-variant dark (&:where(.dark, .dark *));
5
+
6
+ @theme {
7
+ --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
8
+ --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
9
+ }
10
+
11
+ :root {
12
+ --bg-canvas: #ffffff;
13
+ --bg-subtle: #fafafa;
14
+ --bg-muted: #f4f4f5;
15
+ --bg-elevated: #ffffff;
16
+ --fg-primary: #18181b;
17
+ --fg-secondary: #52525b;
18
+ --fg-muted: #71717a;
19
+ --border-default: #e4e4e7;
20
+ --border-subtle: #f4f4f5;
21
+ --accent-primary: #2563eb;
22
+ --accent-soft: #dbeafe;
23
+ color-scheme: light;
24
+ }
25
+
26
+ .dark {
27
+ --bg-canvas: #09090b;
28
+ --bg-subtle: #121215;
29
+ --bg-muted: #18181b;
30
+ --bg-elevated: #18181b;
31
+ --fg-primary: #fafafa;
32
+ --fg-secondary: #a1a1aa;
33
+ --fg-muted: #71717a;
34
+ --border-default: #27272a;
35
+ --border-subtle: #18181b;
36
+ --accent-primary: #3b82f6;
37
+ --accent-soft: rgba(59, 130, 246, 0.15);
38
+ color-scheme: dark;
39
+ }
40
+
41
+ @layer base {
42
+ body {
43
+ background-color: var(--bg-canvas);
44
+ color: var(--fg-primary);
45
+ font-family: var(--font-sans);
46
+ -webkit-font-smoothing: antialiased;
47
+ -moz-osx-font-smoothing: grayscale;
48
+ }
49
+ }
@@ -0,0 +1,41 @@
1
+ import * as React from "react"
2
+ import { cn } from "../utils/cn"
3
+ import {
4
+ SidebarShell,
5
+ HeaderNavbar,
6
+ PageHeaderBar,
7
+ } from "../blocks/app-shell"
8
+ import { AdvancedFilterBar } from "../blocks/filter-bar"
9
+ import { EnterpriseDataTable } from "../blocks/data-table"
10
+
11
+ export const ControlTowerTemplate: React.FC<{ className?: string }> = ({
12
+ className,
13
+ }) => {
14
+ return (
15
+ <div
16
+ className={cn(
17
+ "flex min-h-[720px] w-full overflow-hidden rounded-2xl border border-zinc-200 bg-zinc-50/40 text-zinc-900 shadow-sm dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-100",
18
+ className
19
+ )}
20
+ >
21
+ <SidebarShell activeId="customers" />
22
+
23
+ <div className="flex flex-1 flex-col min-w-0">
24
+ <HeaderNavbar
25
+ breadcrumbs={["Workspace", "Customers", "Directory"]}
26
+ />
27
+
28
+ <main className="flex-1 space-y-5 overflow-y-auto p-6">
29
+ <PageHeaderBar
30
+ title="Customer Directory"
31
+ subtitle="Search, filter, and inspect customer subscriptions. Click any row to open the details drawer."
32
+ />
33
+
34
+ <AdvancedFilterBar />
35
+
36
+ <EnterpriseDataTable />
37
+ </main>
38
+ </div>
39
+ </div>
40
+ )
41
+ }
@@ -0,0 +1,272 @@
1
+ import * as React from "react"
2
+ import { ArrowUpRight, RefreshCw } from "lucide-react"
3
+ import { cn } from "../utils/cn"
4
+ import {
5
+ SidebarShell,
6
+ HeaderNavbar,
7
+ PageHeaderBar,
8
+ } from "../blocks/app-shell"
9
+ import { MultiMetricGrid, ProgressMetricCard } from "../blocks/stat-cards"
10
+ import { Badge } from "../primitives/badge"
11
+ import { Button, ToggleGroup } from "../primitives/button"
12
+ import { Avatar } from "../primitives/avatar"
13
+ import { CommandPalette } from "../primitives/command-palette"
14
+
15
+ const chartSeriesData: Record<
16
+ string,
17
+ Array<{ label: string; revenue: number; signups: number }>
18
+ > = {
19
+ "30d": [
20
+ { label: "Oct 08", revenue: 62, signups: 48 },
21
+ { label: "Oct 12", revenue: 68, signups: 54 },
22
+ { label: "Oct 16", revenue: 64, signups: 59 },
23
+ { label: "Oct 20", revenue: 75, signups: 66 },
24
+ { label: "Oct 24", revenue: 81, signups: 72 },
25
+ { label: "Oct 28", revenue: 79, signups: 76 },
26
+ { label: "Nov 01", revenue: 89, signups: 84 },
27
+ { label: "Nov 06", revenue: 96, signups: 92 },
28
+ ],
29
+ "7d": [
30
+ { label: "Mon", revenue: 74, signups: 68 },
31
+ { label: "Tue", revenue: 79, signups: 72 },
32
+ { label: "Wed", revenue: 83, signups: 77 },
33
+ { label: "Thu", revenue: 80, signups: 75 },
34
+ { label: "Fri", revenue: 88, signups: 85 },
35
+ { label: "Sat", revenue: 92, signups: 89 },
36
+ { label: "Sun", revenue: 97, signups: 94 },
37
+ ],
38
+ }
39
+
40
+ const recentActivity = [
41
+ {
42
+ id: "act-1",
43
+ title: "Linear Orbit Inc. upgraded to Enterprise Annual",
44
+ meta: "+$4,200 MRR • Invoice #INV-2094 paid",
45
+ time: "12m ago",
46
+ fallback: "LO",
47
+ },
48
+ {
49
+ id: "act-2",
50
+ title: "Raycast Labs added 12 team seats",
51
+ meta: "Pro Plan expansion •Prorated billing applied",
52
+ time: "42m ago",
53
+ fallback: "RL",
54
+ },
55
+ {
56
+ id: "act-3",
57
+ title: "Weekly financial summary exported",
58
+ meta: "Generated by Elena Rostova",
59
+ time: "2h ago",
60
+ fallback: "ER",
61
+ },
62
+ ]
63
+
64
+ const topCustomers = [
65
+ {
66
+ name: "Linear Orbit Inc.",
67
+ domain: "linearorbit.io",
68
+ fallback: "LO",
69
+ mrr: "$4,200",
70
+ growth: "+18.4%",
71
+ status: "Enterprise",
72
+ },
73
+ {
74
+ name: "Raycast Labs GmbH",
75
+ domain: "raycastlabs.de",
76
+ fallback: "RL",
77
+ mrr: "$3,150",
78
+ growth: "+12.1%",
79
+ status: "Enterprise",
80
+ },
81
+ {
82
+ name: "Northstar Analytics",
83
+ domain: "northstar.co",
84
+ fallback: "NA",
85
+ mrr: "$1,950",
86
+ growth: "+9.4%",
87
+ status: "Scale",
88
+ },
89
+ ]
90
+
91
+ export const ExecutiveAnalyticsTemplate: React.FC<{ className?: string }> = ({
92
+ className,
93
+ }) => {
94
+ const [range, setRange] = React.useState("30d")
95
+ const [cmdOpen, setCmdOpen] = React.useState(false)
96
+ const points = chartSeriesData[range] || chartSeriesData["30d"]
97
+
98
+ return (
99
+ <div
100
+ className={cn(
101
+ "flex min-h-[720px] w-full overflow-hidden rounded-2xl border border-zinc-200 bg-zinc-50/40 text-zinc-900 shadow-sm dark:border-zinc-800 dark:bg-zinc-950 dark:text-zinc-100",
102
+ className
103
+ )}
104
+ >
105
+ <CommandPalette
106
+ open={cmdOpen}
107
+ onOpenChange={setCmdOpen}
108
+ enableGlobalShortcut={false}
109
+ />
110
+
111
+ <SidebarShell activeId="overview" />
112
+
113
+ <div className="flex flex-1 flex-col min-w-0">
114
+ <HeaderNavbar
115
+ breadcrumbs={["Workspace", "Analytics", "Overview"]}
116
+ onOpenCommandPalette={() => setCmdOpen(true)}
117
+ />
118
+
119
+ <main className="flex-1 space-y-6 overflow-y-auto p-6">
120
+ <PageHeaderBar
121
+ title="Analytics Overview"
122
+ subtitle="Track recurring revenue, subscriber retention, and account expansion."
123
+ />
124
+
125
+ <MultiMetricGrid />
126
+
127
+ <div className="grid grid-cols-1 gap-6 xl:grid-cols-3">
128
+ <div className="xl:col-span-2 rounded-xl border border-zinc-200 bg-white p-5 flex flex-col justify-between dark:border-zinc-800 dark:bg-zinc-900">
129
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between border-b border-zinc-200 pb-4 dark:border-zinc-800">
130
+ <div>
131
+ <div className="flex items-center gap-2">
132
+ <h3 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
133
+ Revenue &amp; Subscriber Growth
134
+ </h3>
135
+ <Badge variant="emerald">+14.2%</Badge>
136
+ </div>
137
+ <p className="mt-0.5 text-xs text-zinc-500 dark:text-zinc-400">
138
+ Net settled volume across active subscriptions
139
+ </p>
140
+ </div>
141
+
142
+ <ToggleGroup
143
+ size="sm"
144
+ value={range}
145
+ onValueChange={setRange}
146
+ items={[
147
+ { value: "7d", label: "7d" },
148
+ { value: "30d", label: "30d" },
149
+ ]}
150
+ />
151
+ </div>
152
+
153
+ <div className="mt-6 grid grid-cols-8 items-end gap-3 h-48 pt-4 px-2">
154
+ {points.map((pt) => (
155
+ <div
156
+ key={pt.label}
157
+ className="group flex flex-col items-center gap-2 h-full justify-end"
158
+ >
159
+ <div className="w-full flex items-end justify-center gap-1.5 h-full">
160
+ <div
161
+ style={{ height: `${pt.revenue}%` }}
162
+ className="w-full max-w-[18px] rounded-t-md bg-blue-600 transition-all duration-300 group-hover:bg-blue-500"
163
+ />
164
+ <div
165
+ style={{ height: `${pt.signups}%` }}
166
+ className="w-full max-w-[14px] rounded-t-md bg-zinc-200 transition-all duration-300 dark:bg-zinc-700"
167
+ />
168
+ </div>
169
+ <span className="text-[10px] text-zinc-400">
170
+ {pt.label}
171
+ </span>
172
+ </div>
173
+ ))}
174
+ </div>
175
+ </div>
176
+
177
+ <div className="space-y-4">
178
+ <ProgressMetricCard />
179
+
180
+ <div className="rounded-xl border border-zinc-200 bg-white p-5 space-y-4 dark:border-zinc-800 dark:bg-zinc-900">
181
+ <div className="flex items-center justify-between">
182
+ <h3 className="text-xs font-semibold text-zinc-700 dark:text-zinc-300">
183
+ Recent Activity
184
+ </h3>
185
+ <RefreshCw className="h-3.5 w-3.5 text-zinc-400" />
186
+ </div>
187
+
188
+ <div className="space-y-3.5">
189
+ {recentActivity.map((item, idx) => (
190
+ <div
191
+ key={item.id}
192
+ className="flex items-start justify-between gap-3 text-xs"
193
+ >
194
+ <div className="flex items-start gap-2.5">
195
+ <Avatar
196
+ fallback={item.fallback}
197
+ tint={idx % 2 === 0 ? "blue" : "neutral"}
198
+ size="xs"
199
+ />
200
+ <div>
201
+ <p className="font-medium text-zinc-800 dark:text-zinc-200">
202
+ {item.title}
203
+ </p>
204
+ <p className="text-[11px] text-zinc-400">
205
+ {item.meta}
206
+ </p>
207
+ </div>
208
+ </div>
209
+ <span className="text-[10px] text-zinc-400 shrink-0">
210
+ {item.time}
211
+ </span>
212
+ </div>
213
+ ))}
214
+ </div>
215
+ </div>
216
+ </div>
217
+ </div>
218
+
219
+ <div className="rounded-xl border border-zinc-200 bg-white overflow-hidden dark:border-zinc-800 dark:bg-zinc-900">
220
+ <div className="flex items-center justify-between border-b border-zinc-200 px-5 py-3.5 dark:border-zinc-800">
221
+ <div>
222
+ <h3 className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
223
+ Top Accounts
224
+ </h3>
225
+ <p className="text-xs text-zinc-500">
226
+ Sorted by monthly recurring revenue
227
+ </p>
228
+ </div>
229
+ <Button variant="outline" size="sm">
230
+ View all
231
+ <ArrowUpRight className="h-3.5 w-3.5" />
232
+ </Button>
233
+ </div>
234
+ <div className="divide-y divide-zinc-200 dark:divide-zinc-800">
235
+ {topCustomers.map((cust, i) => (
236
+ <div
237
+ key={cust.name}
238
+ className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between px-5 py-3 text-xs"
239
+ >
240
+ <div className="flex items-center gap-3">
241
+ <Avatar
242
+ fallback={cust.fallback}
243
+ tint={i % 2 === 0 ? "blue" : "neutral"}
244
+ size="sm"
245
+ />
246
+ <div>
247
+ <p className="font-medium text-zinc-900 dark:text-zinc-100">
248
+ {cust.name}
249
+ </p>
250
+ <p className="text-[11px] text-zinc-400">{cust.domain}</p>
251
+ </div>
252
+ </div>
253
+ <div className="flex items-center gap-6">
254
+ <Badge variant="default">{cust.status}</Badge>
255
+ <div className="text-right">
256
+ <span className="font-semibold text-zinc-900 dark:text-zinc-100">
257
+ {cust.mrr}
258
+ </span>
259
+ <span className="ml-2 text-emerald-600 dark:text-emerald-400">
260
+ {cust.growth}
261
+ </span>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ ))}
266
+ </div>
267
+ </div>
268
+ </main>
269
+ </div>
270
+ </div>
271
+ )
272
+ }