@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,373 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { X } from "lucide-react"
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
import { AgentWidget, type AgentWidgetMessage, type AgentWidgetStatus, type AgentWidgetMode } from "./agent-widget"
|
|
7
|
+
|
|
8
|
+
/* ========================================
|
|
9
|
+
AgentPopover (root)
|
|
10
|
+
======================================== */
|
|
11
|
+
|
|
12
|
+
export type AgentPopoverStep = string
|
|
13
|
+
|
|
14
|
+
export interface AgentPopoverContextValue {
|
|
15
|
+
step: AgentPopoverStep
|
|
16
|
+
setStep: (step: AgentPopoverStep) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AgentPopoverContext = React.createContext<AgentPopoverContextValue | null>(null)
|
|
20
|
+
|
|
21
|
+
export function useAgentPopover() {
|
|
22
|
+
const ctx = React.useContext(AgentPopoverContext)
|
|
23
|
+
if (!ctx) throw new Error("useAgentPopover must be used within <AgentPopover>")
|
|
24
|
+
return ctx
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AgentPopoverProps {
|
|
28
|
+
open: boolean
|
|
29
|
+
onOpenChange: (open: boolean) => void
|
|
30
|
+
defaultStep?: string
|
|
31
|
+
step?: string
|
|
32
|
+
onStepChange?: (step: string) => void
|
|
33
|
+
children: React.ReactNode
|
|
34
|
+
className?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function AgentPopover({
|
|
38
|
+
open,
|
|
39
|
+
onOpenChange,
|
|
40
|
+
defaultStep = "details",
|
|
41
|
+
step: controlledStep,
|
|
42
|
+
onStepChange,
|
|
43
|
+
children,
|
|
44
|
+
className,
|
|
45
|
+
}: AgentPopoverProps) {
|
|
46
|
+
const [uncontrolledStep, setUncontrolledStep] = React.useState(defaultStep)
|
|
47
|
+
const step = controlledStep ?? uncontrolledStep
|
|
48
|
+
const setStep = React.useCallback(
|
|
49
|
+
(s: string) => {
|
|
50
|
+
if (controlledStep === undefined) setUncontrolledStep(s)
|
|
51
|
+
onStepChange?.(s)
|
|
52
|
+
},
|
|
53
|
+
[controlledStep, onStepChange],
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
React.useEffect(() => {
|
|
57
|
+
if (!open) return
|
|
58
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
59
|
+
if (e.key === "Escape") onOpenChange(false)
|
|
60
|
+
}
|
|
61
|
+
document.addEventListener("keydown", onKeyDown)
|
|
62
|
+
return () => document.removeEventListener("keydown", onKeyDown)
|
|
63
|
+
}, [open, onOpenChange])
|
|
64
|
+
|
|
65
|
+
if (!open) return null
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<AgentPopoverContext.Provider value={{ step, setStep }}>
|
|
69
|
+
{/* Backdrop */}
|
|
70
|
+
<div
|
|
71
|
+
className="fixed inset-0 z-50 bg-black/30 backdrop-blur-sm animate-in fade-in duration-200"
|
|
72
|
+
onClick={() => onOpenChange(false)}
|
|
73
|
+
/>
|
|
74
|
+
{/* Panel */}
|
|
75
|
+
<div
|
|
76
|
+
role="dialog"
|
|
77
|
+
aria-modal="true"
|
|
78
|
+
className={cn(
|
|
79
|
+
"fixed inset-4 z-50 flex overflow-hidden rounded-3xl bg-background shadow-2xl ring-1 ring-border/50 animate-in zoom-in-95 fade-in duration-300",
|
|
80
|
+
"md:inset-auto md:left-1/2 md:top-1/2 md:-translate-x-1/2 md:-translate-y-1/2 md:max-w-5xl md:w-[92vw] md:h-[85vh]",
|
|
81
|
+
className,
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
{/* Close button */}
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
onClick={() => onOpenChange(false)}
|
|
88
|
+
className="absolute right-4 top-4 z-20 p-2 rounded-full bg-background/80 hover:bg-muted transition-colors text-foreground"
|
|
89
|
+
>
|
|
90
|
+
<X className="w-4 h-4" />
|
|
91
|
+
</button>
|
|
92
|
+
|
|
93
|
+
{children}
|
|
94
|
+
</div>
|
|
95
|
+
</AgentPopoverContext.Provider>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ========================================
|
|
100
|
+
AgentPopoverBranding (left panel)
|
|
101
|
+
======================================== */
|
|
102
|
+
|
|
103
|
+
export type ConnectionStatus = "ready" | "connected" | "listening" | "speaking"
|
|
104
|
+
|
|
105
|
+
export interface AgentPopoverBrandingProps {
|
|
106
|
+
title?: string
|
|
107
|
+
subtitle?: string
|
|
108
|
+
badge?: string
|
|
109
|
+
visualSlot?: React.ReactNode
|
|
110
|
+
statusIndicator?: ConnectionStatus
|
|
111
|
+
className?: string
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function AgentPopoverBranding({
|
|
115
|
+
title,
|
|
116
|
+
subtitle,
|
|
117
|
+
badge,
|
|
118
|
+
visualSlot,
|
|
119
|
+
statusIndicator = "ready",
|
|
120
|
+
className,
|
|
121
|
+
}: AgentPopoverBrandingProps) {
|
|
122
|
+
return (
|
|
123
|
+
<div
|
|
124
|
+
className={cn(
|
|
125
|
+
"relative hidden w-[40%] shrink-0 flex-col items-center justify-center p-8 md:flex",
|
|
126
|
+
"bg-gradient-to-br from-primary/5 via-background to-primary/10",
|
|
127
|
+
className,
|
|
128
|
+
)}
|
|
129
|
+
>
|
|
130
|
+
{/* Dot pattern overlay */}
|
|
131
|
+
<div
|
|
132
|
+
className="absolute inset-0 pointer-events-none opacity-[0.06]"
|
|
133
|
+
style={{
|
|
134
|
+
backgroundImage: "radial-gradient(circle, var(--primary) 1px, transparent 1px)",
|
|
135
|
+
backgroundSize: "24px 24px",
|
|
136
|
+
}}
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
<div className="relative z-10 flex flex-col items-center gap-6 text-center">
|
|
140
|
+
{visualSlot && (
|
|
141
|
+
<div className="w-[200px] h-[200px] flex items-center justify-center">
|
|
142
|
+
{visualSlot}
|
|
143
|
+
</div>
|
|
144
|
+
)}
|
|
145
|
+
|
|
146
|
+
{badge && (
|
|
147
|
+
<span className="inline-flex px-3 py-1 rounded-full bg-primary/10 text-primary text-xs font-medium border border-primary/10">
|
|
148
|
+
{badge}
|
|
149
|
+
</span>
|
|
150
|
+
)}
|
|
151
|
+
|
|
152
|
+
{title && <h2 className="text-xl font-semibold text-foreground">{title}</h2>}
|
|
153
|
+
{subtitle && <p className="text-sm text-muted-foreground max-w-[240px]">{subtitle}</p>}
|
|
154
|
+
|
|
155
|
+
<StatusPill status={statusIndicator} />
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function StatusPill({ status }: { status: ConnectionStatus }) {
|
|
162
|
+
const label: Record<ConnectionStatus, string> = {
|
|
163
|
+
ready: "Ready to connect",
|
|
164
|
+
connected: "Connected",
|
|
165
|
+
listening: "Listening...",
|
|
166
|
+
speaking: "Speaking...",
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const dotColor: Record<ConnectionStatus, string> = {
|
|
170
|
+
ready: "bg-muted-foreground",
|
|
171
|
+
connected: "bg-green-500",
|
|
172
|
+
listening: "bg-green-500 animate-pulse",
|
|
173
|
+
speaking: "bg-primary animate-pulse",
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<div className="flex items-center gap-2 rounded-full border border-border px-3 py-1.5 bg-background/50">
|
|
178
|
+
<div className={cn("h-2 w-2 rounded-full", dotColor[status])} />
|
|
179
|
+
<span className="text-xs text-muted-foreground">{label[status]}</span>
|
|
180
|
+
</div>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/* ========================================
|
|
185
|
+
AgentPopoverStepContent (right panel step)
|
|
186
|
+
======================================== */
|
|
187
|
+
|
|
188
|
+
export interface AgentPopoverStepContentProps {
|
|
189
|
+
step: string
|
|
190
|
+
children: React.ReactNode
|
|
191
|
+
className?: string
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function AgentPopoverStepContent({ step, children, className }: AgentPopoverStepContentProps) {
|
|
195
|
+
const { step: currentStep } = useAgentPopover()
|
|
196
|
+
if (currentStep !== step) return null
|
|
197
|
+
return <div className={cn("flex-1 flex flex-col overflow-hidden", className)}>{children}</div>
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/* ========================================
|
|
201
|
+
AgentPopoverForm (details step)
|
|
202
|
+
======================================== */
|
|
203
|
+
|
|
204
|
+
export interface AgentPopoverFormField {
|
|
205
|
+
name: string
|
|
206
|
+
label: string
|
|
207
|
+
type?: "text" | "email" | "tel" | "textarea"
|
|
208
|
+
placeholder?: string
|
|
209
|
+
required?: boolean
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface AgentPopoverFormProps {
|
|
213
|
+
fields?: AgentPopoverFormField[]
|
|
214
|
+
submitLabel?: string
|
|
215
|
+
onSubmit?: (values: Record<string, string>) => void
|
|
216
|
+
className?: string
|
|
217
|
+
children?: React.ReactNode
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function AgentPopoverForm({
|
|
221
|
+
fields = [],
|
|
222
|
+
submitLabel = "Continue",
|
|
223
|
+
onSubmit,
|
|
224
|
+
className,
|
|
225
|
+
children,
|
|
226
|
+
}: AgentPopoverFormProps) {
|
|
227
|
+
const [values, setValues] = React.useState<Record<string, string>>({})
|
|
228
|
+
|
|
229
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
230
|
+
e.preventDefault()
|
|
231
|
+
onSubmit?.(values)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<form
|
|
236
|
+
onSubmit={handleSubmit}
|
|
237
|
+
className={cn("flex flex-col h-full p-8 overflow-y-auto", className)}
|
|
238
|
+
>
|
|
239
|
+
<div className="flex-1 space-y-4">
|
|
240
|
+
{fields.map((field) =>
|
|
241
|
+
field.type === "textarea" ? (
|
|
242
|
+
<label key={field.name} className="block space-y-1.5">
|
|
243
|
+
<span className="text-sm font-medium text-foreground">
|
|
244
|
+
{field.label}
|
|
245
|
+
{field.required && <span className="text-destructive ml-0.5">*</span>}
|
|
246
|
+
</span>
|
|
247
|
+
<textarea
|
|
248
|
+
className="w-full rounded-xl border border-border bg-background px-4 py-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary min-h-[80px] resize-none"
|
|
249
|
+
placeholder={field.placeholder}
|
|
250
|
+
required={field.required}
|
|
251
|
+
value={values[field.name] ?? ""}
|
|
252
|
+
onChange={(e) => setValues((prev) => ({ ...prev, [field.name]: e.target.value }))}
|
|
253
|
+
/>
|
|
254
|
+
</label>
|
|
255
|
+
) : (
|
|
256
|
+
<label key={field.name} className="block space-y-1.5">
|
|
257
|
+
<span className="text-sm font-medium text-foreground">
|
|
258
|
+
{field.label}
|
|
259
|
+
{field.required && <span className="text-destructive ml-0.5">*</span>}
|
|
260
|
+
</span>
|
|
261
|
+
<input
|
|
262
|
+
type={field.type ?? "text"}
|
|
263
|
+
className="w-full rounded-xl border border-border bg-background px-4 py-3 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
|
264
|
+
placeholder={field.placeholder}
|
|
265
|
+
required={field.required}
|
|
266
|
+
value={values[field.name] ?? ""}
|
|
267
|
+
onChange={(e) => setValues((prev) => ({ ...prev, [field.name]: e.target.value }))}
|
|
268
|
+
/>
|
|
269
|
+
</label>
|
|
270
|
+
),
|
|
271
|
+
)}
|
|
272
|
+
{children}
|
|
273
|
+
</div>
|
|
274
|
+
<button
|
|
275
|
+
type="submit"
|
|
276
|
+
className="mt-6 w-full px-6 py-3 rounded-full bg-primary text-primary-foreground hover:bg-primary/90 font-medium transition-colors"
|
|
277
|
+
>
|
|
278
|
+
{submitLabel}
|
|
279
|
+
</button>
|
|
280
|
+
</form>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/* ========================================
|
|
285
|
+
AgentPopoverOverview
|
|
286
|
+
======================================== */
|
|
287
|
+
|
|
288
|
+
export interface AgentPopoverOverviewProps {
|
|
289
|
+
userSummary?: Record<string, string>
|
|
290
|
+
discussionPoints?: string[]
|
|
291
|
+
actions?: React.ReactNode
|
|
292
|
+
className?: string
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function AgentPopoverOverview({
|
|
296
|
+
userSummary,
|
|
297
|
+
discussionPoints,
|
|
298
|
+
actions,
|
|
299
|
+
className,
|
|
300
|
+
}: AgentPopoverOverviewProps) {
|
|
301
|
+
return (
|
|
302
|
+
<div className={cn("flex flex-col h-full p-8 overflow-y-auto", className)}>
|
|
303
|
+
{userSummary && Object.keys(userSummary).length > 0 && (
|
|
304
|
+
<div className="mb-6 rounded-xl bg-muted/50 p-4 space-y-2">
|
|
305
|
+
{Object.entries(userSummary).map(([key, val]) => (
|
|
306
|
+
<div key={key} className="flex items-center justify-between text-sm">
|
|
307
|
+
<span className="text-muted-foreground capitalize">{key}</span>
|
|
308
|
+
<span className="text-foreground font-medium">{val}</span>
|
|
309
|
+
</div>
|
|
310
|
+
))}
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
|
|
314
|
+
{discussionPoints && discussionPoints.length > 0 && (
|
|
315
|
+
<div className="mb-6">
|
|
316
|
+
<h3 className="text-sm font-semibold text-foreground mb-3">Discussion Points</h3>
|
|
317
|
+
<ul className="space-y-2">
|
|
318
|
+
{discussionPoints.map((point, i) => (
|
|
319
|
+
<li key={i} className="flex items-start gap-2 text-sm text-foreground">
|
|
320
|
+
<div className="mt-1.5 w-1.5 h-1.5 rounded-full bg-primary shrink-0" />
|
|
321
|
+
{point}
|
|
322
|
+
</li>
|
|
323
|
+
))}
|
|
324
|
+
</ul>
|
|
325
|
+
</div>
|
|
326
|
+
)}
|
|
327
|
+
|
|
328
|
+
{actions && <div className="mt-auto pt-6">{actions}</div>}
|
|
329
|
+
</div>
|
|
330
|
+
)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/* ========================================
|
|
334
|
+
AgentPopoverChat (interview step)
|
|
335
|
+
======================================== */
|
|
336
|
+
|
|
337
|
+
export interface AgentPopoverChatProps {
|
|
338
|
+
messages?: AgentWidgetMessage[]
|
|
339
|
+
onSendMessage?: (text: string) => void
|
|
340
|
+
onEndSession?: () => void
|
|
341
|
+
inputMode?: "voice" | "text" | "voice+text"
|
|
342
|
+
visualSlot?: React.ReactNode
|
|
343
|
+
assistantAvatarSlot?: React.ReactNode
|
|
344
|
+
status?: AgentWidgetStatus
|
|
345
|
+
mode?: AgentWidgetMode
|
|
346
|
+
className?: string
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export function AgentPopoverChat({
|
|
350
|
+
messages = [],
|
|
351
|
+
onSendMessage,
|
|
352
|
+
onEndSession,
|
|
353
|
+
inputMode = "voice+text",
|
|
354
|
+
visualSlot,
|
|
355
|
+
assistantAvatarSlot,
|
|
356
|
+
status = "connected",
|
|
357
|
+
mode = null,
|
|
358
|
+
className,
|
|
359
|
+
}: AgentPopoverChatProps) {
|
|
360
|
+
return (
|
|
361
|
+
<AgentWidget
|
|
362
|
+
messages={messages}
|
|
363
|
+
onSendMessage={onSendMessage}
|
|
364
|
+
onEndSession={onEndSession}
|
|
365
|
+
inputMode={inputMode}
|
|
366
|
+
visualSlot={visualSlot}
|
|
367
|
+
assistantAvatarSlot={assistantAvatarSlot}
|
|
368
|
+
status={status}
|
|
369
|
+
mode={mode}
|
|
370
|
+
className={cn("h-full", className)}
|
|
371
|
+
/>
|
|
372
|
+
)
|
|
373
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Mic, PhoneOff, Send, Sparkles } from "lucide-react"
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
import { Message, MessageContent, MessageAvatar } from "./message"
|
|
7
|
+
|
|
8
|
+
export type AgentWidgetStatus = "idle" | "connecting" | "connected" | "error"
|
|
9
|
+
export type AgentWidgetMode = "listening" | "speaking" | null
|
|
10
|
+
|
|
11
|
+
export interface AgentWidgetMessage {
|
|
12
|
+
from: "user" | "assistant"
|
|
13
|
+
text: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AgentWidgetProps {
|
|
17
|
+
status?: AgentWidgetStatus
|
|
18
|
+
mode?: AgentWidgetMode
|
|
19
|
+
messages?: AgentWidgetMessage[]
|
|
20
|
+
onSendMessage?: (text: string) => void
|
|
21
|
+
onEndSession?: () => void
|
|
22
|
+
inputMode?: "voice" | "text" | "voice+text"
|
|
23
|
+
visualSlot?: React.ReactNode
|
|
24
|
+
assistantAvatarSlot?: React.ReactNode
|
|
25
|
+
header?: React.ReactNode
|
|
26
|
+
footer?: React.ReactNode
|
|
27
|
+
className?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function AgentWidget({
|
|
31
|
+
status = "idle",
|
|
32
|
+
mode = null,
|
|
33
|
+
messages = [],
|
|
34
|
+
onSendMessage,
|
|
35
|
+
onEndSession,
|
|
36
|
+
inputMode = "voice+text",
|
|
37
|
+
visualSlot,
|
|
38
|
+
assistantAvatarSlot,
|
|
39
|
+
header,
|
|
40
|
+
footer,
|
|
41
|
+
className,
|
|
42
|
+
}: AgentWidgetProps) {
|
|
43
|
+
const [textInput, setTextInput] = React.useState("")
|
|
44
|
+
const messagesEndRef = React.useRef<HTMLDivElement>(null)
|
|
45
|
+
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" })
|
|
48
|
+
}, [messages])
|
|
49
|
+
|
|
50
|
+
const handleSend = () => {
|
|
51
|
+
if (!textInput.trim()) return
|
|
52
|
+
onSendMessage?.(textInput)
|
|
53
|
+
setTextInput("")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const showTextInput = inputMode === "text" || inputMode === "voice+text"
|
|
57
|
+
const isConnected = status === "connected"
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className={cn("flex flex-col h-full overflow-hidden", className)}>
|
|
61
|
+
{header}
|
|
62
|
+
|
|
63
|
+
{/* Chat area */}
|
|
64
|
+
<div className="flex-1 overflow-y-auto p-4 space-y-4 bg-muted/20">
|
|
65
|
+
{messages.length === 0 ? (
|
|
66
|
+
<div className="flex flex-col items-center justify-center h-full text-muted-foreground space-y-2 opacity-50">
|
|
67
|
+
{visualSlot ?? (
|
|
68
|
+
<div className="w-12 h-12 rounded-full bg-muted flex items-center justify-center">
|
|
69
|
+
<Sparkles className="w-5 h-5" />
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
<p className="text-sm">
|
|
73
|
+
{status === "connecting" ? "Connecting..." : "Conversation started..."}
|
|
74
|
+
</p>
|
|
75
|
+
</div>
|
|
76
|
+
) : (
|
|
77
|
+
<>
|
|
78
|
+
{messages.map((msg, idx) => (
|
|
79
|
+
<Message key={idx} from={msg.from}>
|
|
80
|
+
{msg.from === "assistant" && assistantAvatarSlot ? (
|
|
81
|
+
<MessageAvatar>{assistantAvatarSlot}</MessageAvatar>
|
|
82
|
+
) : null}
|
|
83
|
+
<MessageContent>{msg.text.replace(/^["']|["']$/g, "")}</MessageContent>
|
|
84
|
+
</Message>
|
|
85
|
+
))}
|
|
86
|
+
<div ref={messagesEndRef} />
|
|
87
|
+
</>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Voice status indicator */}
|
|
92
|
+
{inputMode !== "text" && isConnected && (
|
|
93
|
+
<div className="flex justify-center py-2 bg-background">
|
|
94
|
+
{mode === "listening" ? (
|
|
95
|
+
<div className="inline-flex items-center gap-2 px-4 py-2 bg-primary/5 rounded-full border border-primary/10">
|
|
96
|
+
<div className="w-2 h-2 bg-green-500 rounded-full animate-pulse" />
|
|
97
|
+
<span className="text-sm font-medium text-foreground">Listening</span>
|
|
98
|
+
</div>
|
|
99
|
+
) : mode === "speaking" ? (
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
className="inline-flex items-center gap-2 px-4 py-2 bg-background rounded-full border border-border hover:border-primary transition-colors"
|
|
103
|
+
>
|
|
104
|
+
<Mic className="w-4 h-4 text-primary" />
|
|
105
|
+
<span className="text-sm font-medium text-foreground">Talk to interrupt</span>
|
|
106
|
+
</button>
|
|
107
|
+
) : null}
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
{/* Input area */}
|
|
112
|
+
<div className="flex-none p-4 bg-background border-t border-border">
|
|
113
|
+
{showTextInput ? (
|
|
114
|
+
<div className="relative border-2 border-foreground rounded-2xl p-4 bg-background shadow-sm transition-all focus-within:shadow-md">
|
|
115
|
+
<textarea
|
|
116
|
+
className="w-full resize-none border-none focus:ring-0 p-0 text-sm placeholder:text-muted-foreground min-h-[60px] bg-transparent outline-none focus:outline-none pr-24"
|
|
117
|
+
placeholder="Send a message..."
|
|
118
|
+
value={textInput}
|
|
119
|
+
onChange={(e) => setTextInput(e.target.value)}
|
|
120
|
+
onKeyDown={(e) => {
|
|
121
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
122
|
+
e.preventDefault()
|
|
123
|
+
handleSend()
|
|
124
|
+
}
|
|
125
|
+
}}
|
|
126
|
+
/>
|
|
127
|
+
<div className="absolute bottom-4 right-4 flex items-center gap-3">
|
|
128
|
+
{onEndSession && (
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
onClick={onEndSession}
|
|
132
|
+
className="w-10 h-10 flex items-center justify-center rounded-full bg-background border border-border shadow-sm hover:bg-muted transition-colors group"
|
|
133
|
+
title="End session"
|
|
134
|
+
>
|
|
135
|
+
<PhoneOff className="w-5 h-5 text-foreground group-hover:text-destructive transition-colors" />
|
|
136
|
+
</button>
|
|
137
|
+
)}
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={handleSend}
|
|
141
|
+
disabled={!textInput.trim()}
|
|
142
|
+
className="w-10 h-10 flex items-center justify-center rounded-full bg-muted-foreground hover:bg-foreground disabled:opacity-50 disabled:cursor-not-allowed transition-colors text-background shadow-sm"
|
|
143
|
+
>
|
|
144
|
+
<Send className="w-5 h-5 ml-0.5" />
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
) : onEndSession ? (
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
onClick={onEndSession}
|
|
152
|
+
className="flex items-center justify-center gap-2 w-full px-6 py-3 bg-destructive/5 hover:bg-destructive/10 text-destructive border border-destructive/20 rounded-full font-medium transition-all"
|
|
153
|
+
>
|
|
154
|
+
<span className="w-2 h-2 bg-destructive rounded-full" />
|
|
155
|
+
End Session
|
|
156
|
+
</button>
|
|
157
|
+
) : null}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
{footer}
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Avatar as AvatarPrimitive } from "radix-ui"
|
|
5
|
+
|
|
6
|
+
import { cn } from "../lib/utils"
|
|
7
|
+
|
|
8
|
+
function Avatar({
|
|
9
|
+
className,
|
|
10
|
+
size = "default",
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
13
|
+
size?: "default" | "sm" | "lg"
|
|
14
|
+
}) {
|
|
15
|
+
return (
|
|
16
|
+
<AvatarPrimitive.Root
|
|
17
|
+
data-slot="avatar"
|
|
18
|
+
data-size={size}
|
|
19
|
+
className={cn(
|
|
20
|
+
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
|
|
21
|
+
className
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function AvatarImage({
|
|
29
|
+
className,
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
32
|
+
return (
|
|
33
|
+
<AvatarPrimitive.Image
|
|
34
|
+
data-slot="avatar-image"
|
|
35
|
+
className={cn("aspect-square size-full", className)}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function AvatarFallback({
|
|
42
|
+
className,
|
|
43
|
+
...props
|
|
44
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
45
|
+
return (
|
|
46
|
+
<AvatarPrimitive.Fallback
|
|
47
|
+
data-slot="avatar-fallback"
|
|
48
|
+
className={cn(
|
|
49
|
+
"bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
58
|
+
return (
|
|
59
|
+
<span
|
|
60
|
+
data-slot="avatar-badge"
|
|
61
|
+
className={cn(
|
|
62
|
+
"bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none",
|
|
63
|
+
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
64
|
+
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
65
|
+
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="avatar-group"
|
|
77
|
+
className={cn(
|
|
78
|
+
"*:data-[slot=avatar]:ring-background group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AvatarGroupCount({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<"div">) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot="avatar-group-count"
|
|
93
|
+
className={cn(
|
|
94
|
+
"bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
Avatar,
|
|
104
|
+
AvatarImage,
|
|
105
|
+
AvatarFallback,
|
|
106
|
+
AvatarBadge,
|
|
107
|
+
AvatarGroup,
|
|
108
|
+
AvatarGroupCount,
|
|
109
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { Slot } from "radix-ui"
|
|
4
|
+
|
|
5
|
+
import { cn } from "../lib/utils"
|
|
6
|
+
|
|
7
|
+
const badgeVariants = cva(
|
|
8
|
+
"inline-flex items-center justify-center rounded-md border border-transparent px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
9
|
+
{
|
|
10
|
+
variants: {
|
|
11
|
+
variant: {
|
|
12
|
+
default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
13
|
+
secondary:
|
|
14
|
+
"bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
15
|
+
destructive:
|
|
16
|
+
"bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
17
|
+
outline:
|
|
18
|
+
"border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
19
|
+
ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
20
|
+
link: "text-primary underline-offset-4 [a&]:hover:underline",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
defaultVariants: {
|
|
24
|
+
variant: "default",
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
function Badge({
|
|
30
|
+
className,
|
|
31
|
+
variant = "default",
|
|
32
|
+
asChild = false,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<"span"> &
|
|
35
|
+
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
36
|
+
const Comp = asChild ? Slot.Root : "span"
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<Comp
|
|
40
|
+
data-slot="badge"
|
|
41
|
+
data-variant={variant}
|
|
42
|
+
className={cn(badgeVariants({ variant }), className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { Badge, badgeVariants }
|