@handled-ai/design-system 0.8.0 → 0.9.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 +14 -4
- package/dist/charts/bar-chart-component.d.ts +24 -0
- package/dist/charts/bar-chart-component.js +123 -0
- package/dist/charts/bar-chart-component.js.map +1 -0
- package/dist/charts/chart-tooltip.d.ts +26 -0
- package/dist/charts/chart-tooltip.js +69 -0
- package/dist/charts/chart-tooltip.js.map +1 -0
- package/dist/charts/chart.d.ts +64 -0
- package/dist/charts/chart.js +285 -0
- package/dist/charts/chart.js.map +1 -0
- package/dist/charts/donut-chart.d.ts +21 -0
- package/dist/charts/donut-chart.js +96 -0
- package/dist/charts/donut-chart.js.map +1 -0
- package/dist/charts/index.d.ts +11 -0
- package/dist/charts/index.js +10 -0
- package/dist/charts/index.js.map +1 -0
- package/dist/charts/pipeline-overview.d.ts +76 -0
- package/dist/charts/pipeline-overview.js +372 -0
- package/dist/charts/pipeline-overview.js.map +1 -0
- package/dist/charts/sankey-chart.d.ts +52 -0
- package/dist/charts/sankey-chart.js +219 -0
- package/dist/charts/sankey-chart.js.map +1 -0
- package/dist/charts/top-line-metrics.d.ts +26 -0
- package/dist/charts/top-line-metrics.js +224 -0
- package/dist/charts/top-line-metrics.js.map +1 -0
- package/dist/charts/trend-area-chart.d.ts +21 -0
- package/dist/charts/trend-area-chart.js +150 -0
- package/dist/charts/trend-area-chart.js.map +1 -0
- package/dist/charts/volume-analysis-chart.d.ts +19 -0
- package/dist/charts/volume-analysis-chart.js +121 -0
- package/dist/charts/volume-analysis-chart.js.map +1 -0
- package/dist/components/activity-detail.d.ts +38 -0
- package/dist/components/activity-detail.js +163 -0
- package/dist/components/activity-detail.js.map +1 -0
- package/dist/components/activity-log.d.ts +21 -0
- package/dist/components/activity-log.js +61 -0
- package/dist/components/activity-log.js.map +1 -0
- package/dist/components/agent-popover.d.ts +71 -0
- package/dist/components/agent-popover.js +282 -0
- package/dist/components/agent-popover.js.map +1 -0
- package/dist/components/agent-widget.d.ts +24 -0
- package/dist/components/agent-widget.js +117 -0
- package/dist/components/agent-widget.js.map +1 -0
- package/dist/components/avatar.d.ts +13 -0
- package/dist/components/avatar.js +140 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +12 -0
- package/dist/components/badge.js +75 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/button.d.ts +13 -0
- package/dist/components/button.js +83 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/card.d.ts +11 -0
- package/dist/components/card.js +119 -0
- package/dist/components/card.js.map +1 -0
- package/dist/components/contact-list.d.ts +34 -0
- package/dist/components/contact-list.js +84 -0
- package/dist/components/contact-list.js.map +1 -0
- package/dist/components/dashboard-cards.d.ts +10 -0
- package/dist/components/dashboard-cards.js +164 -0
- package/dist/components/dashboard-cards.js.map +1 -0
- package/dist/components/data-table-display.d.ts +19 -0
- package/dist/components/data-table-display.js +109 -0
- package/dist/components/data-table-display.js.map +1 -0
- package/dist/components/data-table-filter.d.ts +18 -0
- package/dist/components/data-table-filter.js +107 -0
- package/dist/components/data-table-filter.js.map +1 -0
- package/dist/components/data-table-quick-views.d.ts +13 -0
- package/dist/components/data-table-quick-views.js +90 -0
- package/dist/components/data-table-quick-views.js.map +1 -0
- package/dist/components/data-table-toolbar.d.ts +18 -0
- package/dist/components/data-table-toolbar.js +45 -0
- package/dist/components/data-table-toolbar.js.map +1 -0
- package/dist/components/data-table.d.ts +39 -0
- package/dist/components/data-table.js +821 -0
- package/dist/components/data-table.js.map +1 -0
- package/dist/components/detail-view.d.ts +44 -0
- package/dist/components/detail-view.js +165 -0
- package/dist/components/detail-view.js.map +1 -0
- package/dist/components/dialog.d.ts +19 -0
- package/dist/components/dialog.js +188 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +27 -0
- package/dist/components/dropdown-menu.js +279 -0
- package/dist/components/dropdown-menu.js.map +1 -0
- package/dist/components/entity-panel.d.ts +69 -0
- package/dist/components/entity-panel.js +584 -0
- package/dist/components/entity-panel.js.map +1 -0
- package/dist/components/inbox-row.d.ts +27 -0
- package/dist/components/inbox-row.js +139 -0
- package/dist/components/inbox-row.js.map +1 -0
- package/dist/components/inbox-toolbar.d.ts +21 -0
- package/dist/components/inbox-toolbar.js +203 -0
- package/dist/components/inbox-toolbar.js.map +1 -0
- package/dist/components/input.d.ts +5 -0
- package/dist/components/input.js +50 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/insights-filter-bar.d.ts +21 -0
- package/dist/components/insights-filter-bar.js +99 -0
- package/dist/components/insights-filter-bar.js.map +1 -0
- package/dist/components/item-list-display.d.ts +22 -0
- package/dist/components/item-list-display.js +240 -0
- package/dist/components/item-list-display.js.map +1 -0
- package/dist/components/item-list-filter.d.ts +16 -0
- package/dist/components/item-list-filter.js +87 -0
- package/dist/components/item-list-filter.js.map +1 -0
- package/dist/components/item-list-toolbar.d.ts +25 -0
- package/dist/components/item-list-toolbar.js +79 -0
- package/dist/components/item-list-toolbar.js.map +1 -0
- package/dist/components/item-list.d.ts +20 -0
- package/dist/components/item-list.js +702 -0
- package/dist/components/item-list.js.map +1 -0
- package/dist/components/label.d.ts +6 -0
- package/dist/components/label.js +55 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/message.d.ts +23 -0
- package/dist/components/message.js +117 -0
- package/dist/components/message.js.map +1 -0
- package/dist/components/metric-card.d.ts +25 -0
- package/dist/components/metric-card.js +107 -0
- package/dist/components/metric-card.js.map +1 -0
- package/dist/components/performance-metrics-table.d.ts +38 -0
- package/dist/components/performance-metrics-table.js +342 -0
- package/dist/components/performance-metrics-table.js.map +1 -0
- package/dist/components/preview-list.d.ts +14 -0
- package/dist/components/preview-list.js +83 -0
- package/dist/components/preview-list.js.map +1 -0
- package/dist/components/progress.d.ts +6 -0
- package/dist/components/progress.js +69 -0
- package/dist/components/progress.js.map +1 -0
- package/dist/components/quick-action-chat-area.d.ts +24 -0
- package/dist/components/quick-action-chat-area.js +178 -0
- package/dist/components/quick-action-chat-area.js.map +1 -0
- package/dist/components/quick-action-modal.d.ts +30 -0
- package/dist/components/quick-action-modal.js +288 -0
- package/dist/components/quick-action-modal.js.map +1 -0
- package/dist/components/quick-action-sidebar-nav.d.ts +51 -0
- package/dist/components/quick-action-sidebar-nav.js +528 -0
- package/dist/components/quick-action-sidebar-nav.js.map +1 -0
- package/dist/components/recommended-actions-section.d.ts +23 -0
- package/dist/components/recommended-actions-section.js +215 -0
- package/dist/components/recommended-actions-section.js.map +1 -0
- package/dist/components/report-card.d.ts +26 -0
- package/dist/components/report-card.js +69 -0
- package/dist/components/report-card.js.map +1 -0
- package/dist/components/score-analysis-modal.d.ts +26 -0
- package/dist/components/score-analysis-modal.js +141 -0
- package/dist/components/score-analysis-modal.js.map +1 -0
- package/dist/components/score-breakdown.d.ts +17 -0
- package/dist/components/score-breakdown.js +162 -0
- package/dist/components/score-breakdown.js.map +1 -0
- package/dist/components/score-feedback.d.ts +40 -0
- package/dist/components/score-feedback.js +209 -0
- package/dist/components/score-feedback.js.map +1 -0
- package/dist/components/score-ring.d.ts +14 -0
- package/dist/components/score-ring.js +79 -0
- package/dist/components/score-ring.js.map +1 -0
- package/dist/components/scroll-area.d.ts +7 -0
- package/dist/components/scroll-area.js +101 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/select.d.ts +17 -0
- package/dist/components/select.js +228 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +6 -0
- package/dist/components/separator.js +61 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sheet.d.ts +16 -0
- package/dist/components/sheet.js +168 -0
- package/dist/components/sheet.js.map +1 -0
- package/dist/components/sidebar.d.ts +73 -0
- package/dist/components/sidebar.js +723 -0
- package/dist/components/sidebar.js.map +1 -0
- package/dist/components/signal-feedback-inline.d.ts +51 -0
- package/dist/components/signal-feedback-inline.js +548 -0
- package/dist/components/signal-feedback-inline.js.map +1 -0
- package/dist/components/simple-data-table.d.ts +15 -0
- package/dist/components/simple-data-table.js +91 -0
- package/dist/components/simple-data-table.js.map +1 -0
- package/dist/components/skeleton.d.ts +5 -0
- package/dist/components/skeleton.js +44 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/status-badge.d.ts +10 -0
- package/dist/components/status-badge.js +82 -0
- package/dist/components/status-badge.js.map +1 -0
- package/dist/components/styled-bar-list.d.ts +20 -0
- package/dist/components/styled-bar-list.js +59 -0
- package/dist/components/styled-bar-list.js.map +1 -0
- package/dist/components/suggested-actions.d.ts +110 -0
- package/dist/components/suggested-actions.js +1538 -0
- package/dist/components/suggested-actions.js.map +1 -0
- package/dist/components/table.d.ts +12 -0
- package/dist/components/table.js +147 -0
- package/dist/components/table.js.map +1 -0
- package/dist/components/tabs.d.ts +14 -0
- package/dist/components/tabs.js +129 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +5 -0
- package/dist/components/textarea.js +47 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/timeline-activity.d.ts +34 -0
- package/dist/components/timeline-activity.js +181 -0
- package/dist/components/timeline-activity.js.map +1 -0
- package/dist/components/tooltip.d.ts +9 -0
- package/dist/components/tooltip.js +93 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/components/view-mode-toggle.d.ts +16 -0
- package/dist/components/view-mode-toggle.js +24 -0
- package/dist/components/view-mode-toggle.js.map +1 -0
- package/dist/hooks/use-mobile.d.ts +3 -0
- package/dist/hooks/use-mobile.js +21 -0
- package/dist/hooks/use-mobile.js.map +1 -0
- package/dist/index.d.ts +68 -1878
- package/dist/index.js +69 -10918
- package/dist/index.js.map +1 -1
- package/dist/lib/icons.d.ts +18 -0
- package/dist/lib/icons.js +21 -0
- package/dist/lib/icons.js.map +1 -0
- package/dist/lib/utils.d.ts +5 -0
- package/dist/lib/utils.js +9 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/prototype/index.d.ts +20 -0
- package/dist/prototype/index.js +8 -0
- package/dist/prototype/index.js.map +1 -0
- package/dist/prototype/prototype-accounts-view.d.ts +22 -0
- package/dist/prototype/prototype-accounts-view.js +70 -0
- package/dist/prototype/prototype-accounts-view.js.map +1 -0
- package/dist/prototype/prototype-admin-view.d.ts +21 -0
- package/dist/prototype/prototype-admin-view.js +53 -0
- package/dist/prototype/prototype-admin-view.js.map +1 -0
- package/dist/prototype/prototype-config.d.ts +226 -0
- package/dist/prototype/prototype-config.js +1 -0
- package/dist/prototype/prototype-config.js.map +1 -0
- package/dist/prototype/prototype-inbox-view.d.ts +48 -0
- package/dist/prototype/prototype-inbox-view.js +701 -0
- package/dist/prototype/prototype-inbox-view.js.map +1 -0
- package/dist/prototype/prototype-insights-view.d.ts +23 -0
- package/dist/prototype/prototype-insights-view.js +335 -0
- package/dist/prototype/prototype-insights-view.js.map +1 -0
- package/dist/prototype/prototype-shell.d.ts +40 -0
- package/dist/prototype/prototype-shell.js +190 -0
- package/dist/prototype/prototype-shell.js.map +1 -0
- package/dist/prototype/prototype-work-queue-view.d.ts +8 -0
- package/dist/prototype/prototype-work-queue-view.js +17 -0
- package/dist/prototype/prototype-work-queue-view.js.map +1 -0
- package/dist/three/agent-orb.d.ts +39 -0
- package/dist/three/agent-orb.js +500 -0
- package/dist/three/agent-orb.js.map +1 -0
- package/dist/three/index.d.ts +2 -0
- package/dist/three/index.js +2 -0
- package/dist/three/index.js.map +1 -0
- package/package.json +98 -17
- package/src/charts/bar-chart-component.tsx +150 -0
- package/src/charts/chart-tooltip.tsx +86 -0
- package/src/charts/chart.tsx +371 -0
- package/src/charts/donut-chart.tsx +112 -0
- package/src/charts/index.ts +13 -0
- package/src/charts/pipeline-overview.tsx +476 -0
- package/src/charts/sankey-chart.tsx +290 -0
- package/src/charts/top-line-metrics.tsx +261 -0
- package/src/charts/trend-area-chart.tsx +150 -0
- package/src/charts/volume-analysis-chart.tsx +124 -0
- package/src/components/activity-detail.tsx +233 -0
- package/src/components/activity-log.tsx +89 -0
- package/src/components/agent-popover.tsx +373 -0
- package/src/components/agent-widget.tsx +163 -0
- package/src/components/avatar.tsx +109 -0
- package/src/components/badge.tsx +48 -0
- package/src/components/button.tsx +59 -0
- package/src/components/card.tsx +92 -0
- package/src/components/contact-list.tsx +121 -0
- package/src/components/dashboard-cards.tsx +170 -0
- package/src/components/data-table-display.tsx +139 -0
- package/src/components/data-table-filter.tsx +138 -0
- package/src/components/data-table-quick-views.tsx +103 -0
- package/src/components/data-table-toolbar.tsx +56 -0
- package/src/components/data-table.tsx +915 -0
- package/src/components/detail-view.tsx +237 -0
- package/src/components/dialog.tsx +158 -0
- package/src/components/dropdown-menu.tsx +257 -0
- package/src/components/entity-panel.tsx +767 -0
- package/src/components/inbox-row.tsx +132 -0
- package/src/components/inbox-toolbar.tsx +213 -0
- package/src/components/input.tsx +21 -0
- package/src/components/insights-filter-bar.tsx +132 -0
- package/src/components/item-list-display.tsx +278 -0
- package/src/components/item-list-filter.tsx +118 -0
- package/src/components/item-list-toolbar.tsx +97 -0
- package/src/components/item-list.tsx +843 -0
- package/src/components/label.tsx +24 -0
- package/src/components/message.tsx +83 -0
- package/src/components/metric-card.tsx +178 -0
- package/src/components/performance-metrics-table.tsx +442 -0
- package/src/components/preview-list.tsx +62 -0
- package/src/components/progress.tsx +31 -0
- package/src/components/quick-action-chat-area.tsx +156 -0
- package/src/components/quick-action-modal.tsx +331 -0
- package/src/components/quick-action-sidebar-nav.tsx +592 -0
- package/src/components/recommended-actions-section.tsx +258 -0
- package/src/components/report-card.tsx +106 -0
- package/src/components/score-analysis-modal.tsx +172 -0
- package/src/components/score-breakdown.tsx +179 -0
- package/src/components/score-feedback.tsx +288 -0
- package/src/components/score-ring.tsx +87 -0
- package/src/components/scroll-area.tsx +58 -0
- package/src/components/select.tsx +190 -0
- package/src/components/separator.tsx +28 -0
- package/src/components/sheet.tsx +143 -0
- package/src/components/sidebar.tsx +726 -0
- package/src/components/signal-feedback-inline.tsx +591 -0
- package/src/components/simple-data-table.tsx +124 -0
- package/src/components/skeleton.tsx +15 -0
- package/src/components/status-badge.tsx +63 -0
- package/src/components/styled-bar-list.tsx +70 -0
- package/src/components/suggested-actions.tsx +1985 -0
- package/src/components/table.tsx +116 -0
- package/src/components/tabs.tsx +91 -0
- package/src/components/textarea.tsx +18 -0
- package/src/components/timeline-activity.tsx +234 -0
- package/src/components/tooltip.tsx +57 -0
- package/src/components/view-mode-toggle.tsx +39 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/index.ts +77 -0
- package/src/lib/icons.ts +18 -0
- package/src/lib/utils.ts +6 -0
- package/src/prototype/index.ts +11 -0
- package/src/prototype/prototype-accounts-view.tsx +112 -0
- package/src/prototype/prototype-admin-view.tsx +67 -0
- package/src/prototype/prototype-config.ts +243 -0
- package/src/prototype/prototype-inbox-view.tsx +810 -0
- package/src/prototype/prototype-insights-view.tsx +379 -0
- package/src/prototype/prototype-shell.tsx +219 -0
- package/src/prototype/prototype-work-queue-view.tsx +30 -0
- package/src/styles/globals.css +299 -0
- package/src/three/agent-orb.tsx +557 -0
- package/src/three/index.ts +5 -0
- package/src/types/r3f.d.ts +8 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Label as LabelPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
|
|
8
|
+
function Label({
|
|
9
|
+
className,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<LabelPrimitive.Root
|
|
14
|
+
data-slot="label"
|
|
15
|
+
className={cn(
|
|
16
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { Label }
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import type { ComponentProps, HTMLAttributes } from "react"
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
6
|
+
|
|
7
|
+
import { cn } from "../lib/utils"
|
|
8
|
+
import { Avatar, AvatarFallback, AvatarImage } from "./avatar"
|
|
9
|
+
|
|
10
|
+
export type MessageProps = HTMLAttributes<HTMLDivElement> & {
|
|
11
|
+
from: "user" | "assistant"
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const Message = ({ className, from, ...props }: MessageProps) => (
|
|
15
|
+
<div
|
|
16
|
+
data-slot="message"
|
|
17
|
+
className={cn(
|
|
18
|
+
"group flex w-full items-end gap-3 py-2",
|
|
19
|
+
from === "user" ? "is-user justify-end" : "is-assistant justify-start",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const messageContentVariants = cva(
|
|
27
|
+
"flex flex-col gap-2 overflow-hidden text-sm leading-relaxed shadow-sm whitespace-pre-wrap",
|
|
28
|
+
{
|
|
29
|
+
variants: {
|
|
30
|
+
variant: {
|
|
31
|
+
contained: [
|
|
32
|
+
"max-w-[85%] px-6 py-4 rounded-[24px]",
|
|
33
|
+
"group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground group-[.is-user]:rounded-br-sm",
|
|
34
|
+
"group-[.is-assistant]:bg-muted group-[.is-assistant]:text-foreground group-[.is-assistant]:rounded-bl-sm",
|
|
35
|
+
],
|
|
36
|
+
flat: [
|
|
37
|
+
"group-[.is-user]:max-w-[85%] group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
|
|
38
|
+
"group-[.is-assistant]:text-foreground",
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
defaultVariants: {
|
|
43
|
+
variant: "contained",
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
export type MessageContentProps = HTMLAttributes<HTMLDivElement> &
|
|
49
|
+
VariantProps<typeof messageContentVariants>
|
|
50
|
+
|
|
51
|
+
export const MessageContent = ({
|
|
52
|
+
children,
|
|
53
|
+
className,
|
|
54
|
+
variant,
|
|
55
|
+
...props
|
|
56
|
+
}: MessageContentProps) => (
|
|
57
|
+
<div
|
|
58
|
+
data-slot="message-content"
|
|
59
|
+
className={cn(messageContentVariants({ variant, className }))}
|
|
60
|
+
{...props}
|
|
61
|
+
>
|
|
62
|
+
{children}
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
export type MessageAvatarProps = ComponentProps<typeof Avatar> & {
|
|
67
|
+
src?: string
|
|
68
|
+
name?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const MessageAvatar = ({
|
|
72
|
+
src,
|
|
73
|
+
name,
|
|
74
|
+
className,
|
|
75
|
+
children,
|
|
76
|
+
...props
|
|
77
|
+
}: MessageAvatarProps) => (
|
|
78
|
+
<Avatar className={cn("ring-border size-8 ring-1 shrink-0", className)} {...props}>
|
|
79
|
+
{children}
|
|
80
|
+
{src && <AvatarImage alt="" className="mt-0 mb-0" src={src} />}
|
|
81
|
+
{!children && !src && <AvatarFallback>{name?.slice(0, 2) || "ME"}</AvatarFallback>}
|
|
82
|
+
</Avatar>
|
|
83
|
+
)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ArrowUp, ArrowDown, Info, ExternalLink } from "lucide-react"
|
|
3
|
+
import { cn } from "../lib/utils"
|
|
4
|
+
|
|
5
|
+
export interface MetricDataPoint {
|
|
6
|
+
label: string
|
|
7
|
+
value: number | string
|
|
8
|
+
color?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface MetricCardProps {
|
|
12
|
+
title: string
|
|
13
|
+
value?: string | number
|
|
14
|
+
unit?: string
|
|
15
|
+
subtitle?: string
|
|
16
|
+
change?: {
|
|
17
|
+
value: string
|
|
18
|
+
direction: "up" | "down" | "neutral"
|
|
19
|
+
isGood?: boolean // if true, up is green. if false, up is red (e.g. churn).
|
|
20
|
+
}
|
|
21
|
+
footerText?: string
|
|
22
|
+
dataPoints?: MetricDataPoint[]
|
|
23
|
+
showExternalLink?: boolean
|
|
24
|
+
showInfo?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function MetricCard({
|
|
28
|
+
title,
|
|
29
|
+
value,
|
|
30
|
+
unit,
|
|
31
|
+
subtitle,
|
|
32
|
+
change,
|
|
33
|
+
footerText,
|
|
34
|
+
dataPoints,
|
|
35
|
+
showExternalLink,
|
|
36
|
+
showInfo = true,
|
|
37
|
+
}: MetricCardProps) {
|
|
38
|
+
// SVG Donut Chart logic for variants with dataPoints
|
|
39
|
+
const renderDonut = () => {
|
|
40
|
+
if (!dataPoints || dataPoints.length === 0 || value === undefined) return null
|
|
41
|
+
|
|
42
|
+
// Simple pseudo-donut chart logic assuming specific colors from the image
|
|
43
|
+
// In a real prod environment we'd use recharts/visx, but for this standalone component
|
|
44
|
+
// we can draw an SVG circle with stroke-dasharray based on the data
|
|
45
|
+
const size = 80
|
|
46
|
+
const strokeWidth = 12
|
|
47
|
+
const radius = (size - strokeWidth) / 2
|
|
48
|
+
const circumference = 2 * Math.PI * radius
|
|
49
|
+
|
|
50
|
+
// Calculate total to distribute the circle
|
|
51
|
+
const total = dataPoints.reduce((sum, dp) => sum + (typeof dp.value === 'number' ? dp.value : 0), 0)
|
|
52
|
+
let currentOffset = 0
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="relative" style={{ width: size, height: size }}>
|
|
56
|
+
<svg width={size} height={size} className="transform -rotate-90">
|
|
57
|
+
{dataPoints.map((dp, i) => {
|
|
58
|
+
const val = typeof dp.value === 'number' ? dp.value : 0
|
|
59
|
+
const percentage = val / total
|
|
60
|
+
const strokeLength = percentage * circumference
|
|
61
|
+
const offset = currentOffset
|
|
62
|
+
currentOffset += strokeLength
|
|
63
|
+
|
|
64
|
+
// Fallback colors matching the image's teal/green palette
|
|
65
|
+
const colors = ["#166534", "#22c55e", "#6ee7b7", "#ccfbf1", "#f1f5f9"]
|
|
66
|
+
const color = dp.color || colors[i % colors.length]
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<circle
|
|
70
|
+
key={dp.label}
|
|
71
|
+
cx={size / 2}
|
|
72
|
+
cy={size / 2}
|
|
73
|
+
r={radius}
|
|
74
|
+
fill="none"
|
|
75
|
+
stroke={color}
|
|
76
|
+
strokeWidth={strokeWidth}
|
|
77
|
+
strokeDasharray={`${Math.max(strokeLength - 2, 0)} ${circumference}`}
|
|
78
|
+
strokeDashoffset={-offset}
|
|
79
|
+
className="transition-all duration-300"
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
})}
|
|
83
|
+
</svg>
|
|
84
|
+
<div className="absolute inset-0 flex items-center justify-center flex-col">
|
|
85
|
+
<span className="text-xl font-bold text-foreground leading-none">{value}</span>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="flex flex-col rounded-xl border border-border bg-card p-5 shadow-sm h-full w-full">
|
|
93
|
+
<div className={cn("flex justify-between items-start", title ? "mb-4" : "mb-4")}>
|
|
94
|
+
{title ? (
|
|
95
|
+
<h3 className="font-semibold text-sm text-foreground/80">{title}</h3>
|
|
96
|
+
) : (
|
|
97
|
+
<div className="flex flex-col">
|
|
98
|
+
<div className="flex items-baseline gap-1">
|
|
99
|
+
<span className="text-3xl font-bold tracking-tight text-foreground">{value}</span>
|
|
100
|
+
{unit && <span className="text-2xl font-bold tracking-tight text-foreground">{unit}</span>}
|
|
101
|
+
</div>
|
|
102
|
+
{subtitle && (
|
|
103
|
+
<p className="text-sm font-medium text-muted-foreground mt-2">{subtitle}</p>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
<div className="flex items-center gap-1.5 text-muted-foreground shrink-0 mt-0.5">
|
|
108
|
+
{showExternalLink && <ExternalLink className="w-3.5 h-3.5 cursor-pointer hover:text-foreground transition-colors" />}
|
|
109
|
+
{showInfo && <Info className="w-3.5 h-3.5 cursor-pointer hover:text-foreground transition-colors" />}
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
114
|
+
{dataPoints && dataPoints.length > 0 ? (
|
|
115
|
+
// Donut Chart Variant
|
|
116
|
+
<div className="flex items-center gap-4 mt-2 mb-6">
|
|
117
|
+
<div className="shrink-0">
|
|
118
|
+
{renderDonut()}
|
|
119
|
+
</div>
|
|
120
|
+
<div className="flex flex-col gap-2 flex-1 min-w-0">
|
|
121
|
+
{dataPoints.slice(0, 5).map((dp, i) => {
|
|
122
|
+
const colors = ["bg-[#166534]", "bg-[#22c55e]", "bg-[#6ee7b7]", "bg-[#ccfbf1]", "bg-[#f1f5f9]"]
|
|
123
|
+
return (
|
|
124
|
+
<div key={dp.label} className="flex items-center justify-between gap-2 text-[11px] font-medium min-w-0">
|
|
125
|
+
<div className="flex items-center gap-1.5 text-muted-foreground min-w-0">
|
|
126
|
+
<div className={cn("w-1.5 h-1.5 rounded-full shrink-0", dp.color ? "" : colors[i % colors.length])} style={dp.color ? { backgroundColor: dp.color } : {}} />
|
|
127
|
+
<span className="whitespace-nowrap">{dp.label}</span>
|
|
128
|
+
</div>
|
|
129
|
+
<span className="text-foreground font-semibold shrink-0">{dp.value}</span>
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
})}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
) : title && (
|
|
136
|
+
// Standard Big Number Variant (only if title exists)
|
|
137
|
+
<div className="mb-6">
|
|
138
|
+
<div className="flex items-baseline gap-1">
|
|
139
|
+
<span className="text-4xl font-bold tracking-tight text-foreground">{value}</span>
|
|
140
|
+
{unit && <span className="text-2xl font-bold tracking-tight text-foreground">{unit}</span>}
|
|
141
|
+
</div>
|
|
142
|
+
{subtitle && (
|
|
143
|
+
<p className="text-sm font-medium text-muted-foreground mt-1">{subtitle}</p>
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{/* Footer section (Change indicator & extra text) */}
|
|
149
|
+
<div className="mt-auto flex flex-col gap-1.5">
|
|
150
|
+
{change && (
|
|
151
|
+
<div className="flex items-center gap-1">
|
|
152
|
+
{(() => {
|
|
153
|
+
// Determine color based on isGood property
|
|
154
|
+
// By default, up is green (good), down is red (bad)
|
|
155
|
+
const isGoodDirection = change.isGood !== undefined
|
|
156
|
+
? change.isGood
|
|
157
|
+
: change.direction === "up";
|
|
158
|
+
|
|
159
|
+
const colorClass = isGoodDirection ? "text-emerald-600" : "text-red-600";
|
|
160
|
+
const Icon = change.direction === "down" ? ArrowDown : ArrowUp;
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<span className={cn("text-xs font-semibold flex items-center gap-0.5", colorClass)}>
|
|
164
|
+
<Icon className="w-3 h-3 stroke-[3]" />
|
|
165
|
+
{change.value}
|
|
166
|
+
</span>
|
|
167
|
+
)
|
|
168
|
+
})()}
|
|
169
|
+
</div>
|
|
170
|
+
)}
|
|
171
|
+
{footerText && (
|
|
172
|
+
<span className="text-[11px] text-muted-foreground font-medium">{footerText}</span>
|
|
173
|
+
)}
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
)
|
|
178
|
+
}
|