diff --git a/apps/web/src/components/agents/agent-detail-sheet.tsx b/apps/web/src/components/agents/agent-detail-sheet.tsx index 7f3bc3a..b5fdfc4 100644 --- a/apps/web/src/components/agents/agent-detail-sheet.tsx +++ b/apps/web/src/components/agents/agent-detail-sheet.tsx @@ -1,60 +1,27 @@ -import { Link } from '@tanstack/react-router' -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { toast } from 'sonner' -import { Check, CircleAlertIcon, Copy, ShieldOff } from 'lucide-react' -import type { Agent } from '@evofw/shared' -import { - agentPreviewQueryOptions, - agentsQueryOptions, -} from '@/queries' -import { apiFetch } from '@/lib/api' -import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' -import { - AgentPlatformIcon, - platformLabel, -} from '@/components/agents/agent-platform-icon' -import { StatusBadge } from '@/components/status-badge' -import { DetailPanel } from '@/components/reui-kit' -import { Badge } from '@/components/reui/badge' -import { - Alert, - AlertDescription, - AlertTitle, -} from '@/components/reui/alert' +import { useQuery } from '@tanstack/react-query' +import { XIcon } from 'lucide-react' +import { AgentDetailView } from '@/components/agents/agent-detail-view' +import { agentQueryOptions } from '@/queries' import { Button } from '@evofw/ui/components/button' +import { ScrollArea } from '@evofw/ui/components/scroll-area' import { Sheet, + SheetClose, SheetContent, SheetDescription, + SheetFooter, SheetHeader, SheetTitle, } from '@evofw/ui/components/sheet' -import { Skeleton } from '@evofw/ui/components/skeleton' /** - * Agent preview sheet — VPS topology detail DNA, wider for policy summary. - * Preview: https://reui.io/preview/base/components/c-sheet-1 - * DNA: vps-tracker VpsDetailSheet + * Wide agent detail Sheet — inventory-9 / CRM-4 shell + SA3 body. + * Preview: https://reui.io/preview/base/solution-inventory-9 + * · https://reui.io/preview/base/solution-crm-4 + * · https://reui.io/preview/base/solution-agents-3 + * · https://reui.io/preview/base/components/c-sheet-1 */ -function Row({ label, value }: { label: string; value: string }) { - return ( -
- {label} - {value} -
- ) -} - -const packetFmt = new Intl.NumberFormat('ru-RU') - -function formatWhen(iso?: string | null): string { - if (!iso) return '—' - const d = new Date(iso) - if (Number.isNaN(d.getTime())) return iso - return d.toLocaleString('ru-RU') -} - type AgentDetailSheetProps = { agentId: string | null open: boolean @@ -66,257 +33,61 @@ export function AgentDetailSheet({ open, onOpenChange, }: AgentDetailSheetProps) { - const qc = useQueryClient() - const { copyToClipboard } = useCopyToClipboard() - const agentsQ = useQuery({ - ...agentsQueryOptions(), - enabled: open, - }) - const agent = (agentsQ.data?.items ?? []).find((a) => a.id === agentId) as - | Agent - | undefined - - const previewQ = useQuery({ - ...agentPreviewQueryOptions(agentId ?? ''), + const agentQ = useQuery({ + ...agentQueryOptions(agentId ?? ''), enabled: open && Boolean(agentId), }) - const approve = useMutation({ - mutationFn: () => - apiFetch(`/api/v1/agents/${agentId}/approve`, { method: 'POST' }), - onSuccess: () => { - toast.success('Агент одобрен') - void qc.invalidateQueries({ queryKey: ['agents'] }) - }, - onError: (e: Error) => toast.error(e.message), - }) - - const revoke = useMutation({ - mutationFn: () => - apiFetch(`/api/v1/agents/${agentId}/revoke`, { method: 'POST' }), - onSuccess: () => { - toast.success('Агент отозван') - void qc.invalidateQueries({ queryKey: ['agents'] }) - }, - onError: (e: Error) => toast.error(e.message), - }) - - const subtitle = agent - ? [ - agent.hostname, - platformLabel(agent.platform), - `gen ${agent.policy_generation}`, - ] - .filter(Boolean) - .join(' · ') - : 'Агент не найден' + const title = agentQ.data?.name ?? 'Агент' return ( - -
- {agent ? ( - - ) : null} -
- - {agent?.name ?? 'Агент'} - - {subtitle} -
+ +
+ + {title} + + +
+ + Политика, apply counters и identity агента +
- {!agentId || agentsQ.isLoading ? ( -
- - -
- ) : !agent ? ( -

- Агент удалён или недоступен. -

- ) : ( -
-
- - - {agent.default_action === 'drop' ? 'Drop' : 'Accept'} - -
- - {agent.last_apply_error ? ( - - - Ошибка apply - {agent.last_apply_error} - +
+ + {agentId && open ? ( + ) : null} + +
- - -
- - - - - -
-
- - -
- - {previewQ.isLoading ? ( -
- -
- ) : previewQ.data ? ( - <> - - - - - - - ) : ( - - )} -
-
- - -
- - - - - - - -
-
-
- -
-
- {agent.status === 'pending' ? ( - - ) : null} - {agent.status === 'approved' ? ( - - ) : null} - {agent.install_curl ? ( - - ) : null} -
- -
-
- )} + } + /> + ) diff --git a/apps/web/src/components/agents/agent-detail-view.tsx b/apps/web/src/components/agents/agent-detail-view.tsx new file mode 100644 index 0000000..5b6e6c7 --- /dev/null +++ b/apps/web/src/components/agents/agent-detail-view.tsx @@ -0,0 +1,302 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { toast } from 'sonner' +import { useRef, useState } from 'react' +import { + BanIcon, + CheckCircle2Icon, + CircleAlertIcon, + ClockIcon, + Copy, + CopyPlusIcon, + CpuIcon, + MoreHorizontalIcon, + ShieldPlusIcon, + TerminalIcon, +} from 'lucide-react' +import { DetailPanel } from '@/components/reui-kit' +import { + Alert, + AlertDescription, + AlertTitle, +} from '@/components/reui/alert' +import { Badge } from '@/components/reui/badge' +import { StatusBadge } from '@/components/status-badge' +import { + AgentPlatformIcon, + platformLabel, +} from '@/components/agents/agent-platform-icon' +import { AgentPolicySetsSortable } from '@/components/agents/agent-policy-sets-sortable' +import { AgentPolicyTrace } from '@/components/agents/agent-policy-trace' +import { AgentFactsPanel } from '@/components/agents/agent-facts-panel' +import { AgentEffectiveCidrs } from '@/components/agents/agent-effective-cidrs' +import { + AgentCloneSetsSheet, + AgentOverrideSheet, +} from '@/components/agents/agent-settings-sheets' +import { + agentPreviewQueryOptions, + agentQueryOptions, +} from '@/queries' +import { apiFetch } from '@/lib/api' +import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard' +import { Button } from '@evofw/ui/components/button' +import { Skeleton } from '@evofw/ui/components/skeleton' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@evofw/ui/components/dropdown-menu' + +/** + * Full agent detail body — SA3 DNA for Sheet (and redirect target). + * Preview: https://reui.io/preview/base/solution-agents-3 + * · https://reui.io/preview/base/stats-12 + * · https://reui.io/preview/base/form-7 + */ + +type AgentDetailViewProps = { + agentId: string +} + +export function AgentDetailView({ agentId }: AgentDetailViewProps) { + const qc = useQueryClient() + const { copyToClipboard } = useCopyToClipboard() + const agentQ = useQuery(agentQueryOptions(agentId)) + const previewQ = useQuery(agentPreviewQueryOptions(agentId)) + const installRef = useRef(null) + const [overrideOpen, setOverrideOpen] = useState(false) + const [cloneOpen, setCloneOpen] = useState(false) + + const revoke = useMutation({ + mutationFn: () => + apiFetch(`/api/v1/agents/${agentId}/revoke`, { method: 'POST' }), + onSuccess: () => { + toast.success('Агент отозван') + void qc.invalidateQueries({ queryKey: ['agents'] }) + }, + onError: (e: Error) => toast.error(e.message), + }) + + const approve = useMutation({ + mutationFn: () => + apiFetch(`/api/v1/agents/${agentId}/approve`, { method: 'POST' }), + onSuccess: () => { + toast.success('Агент одобрен') + void qc.invalidateQueries({ queryKey: ['agents'] }) + }, + onError: (e: Error) => toast.error(e.message), + }) + + const a = agentQ.data + + if (agentQ.isLoading || !a) { + return ( +
+ + + +
+ ) + } + + if (agentQ.isError) { + return ( +
+ + + Ошибка загрузки + + {agentQ.error?.message ?? 'Не удалось загрузить агента'} + + +
+ ) + } + + const headerDesc = [ + a.hostname, + platformLabel(a.platform), + `gen ${a.policy_generation}`, + a.default_action === 'drop' ? 'default Drop' : 'default Accept', + ] + .filter(Boolean) + .join(' · ') + + return ( + <> +
+ + + + + + {a.default_action === 'drop' ? 'Drop' : 'Accept'} + + {a.status === 'pending' ? ( + + ) : null} + {a.status === 'approved' ? ( + + ) : null} + {a.install_curl ? ( + + ) : null} + + + } + > + + + + setOverrideOpen(true)}> + + IP override + + setCloneOpen(true)}> + + Копировать наборы + + {a.install_curl ? ( + { + copyToClipboard(a.install_curl!) + toast.success('Скопировано') + installRef.current?.scrollIntoView({ + behavior: 'smooth', + }) + }} + > + + Install curl + + ) : null} + + + + } + /> + + {a.last_apply_error ? ( + + + Ошибка apply + {a.last_apply_error} + + ) : null} + + , + iconClassName: 'text-warning', + label: 'Dropped', + description: String(a.last_apply_packets_dropped ?? 0), + hint: 'сумма counters', + variant: 'warning', + }, + { + id: 'accepted', + icon: , + iconClassName: 'text-success', + label: 'Accepted', + description: String(a.last_apply_packets_accepted ?? 0), + hint: 'сумма counters', + }, + { + id: 'kernel', + icon: , + iconClassName: 'text-info', + label: 'Kernel', + description: a.last_apply_kernel_method ?? '—', + }, + { + id: 'apply', + icon: , + iconClassName: 'text-primary', + label: 'Last apply', + description: a.last_apply_at ?? '—', + }, + ]} + /> + + +
+
+
+ +
+ +
+ +
+ +
+ + +
+
+
+
+ + + + + ) +} diff --git a/apps/web/src/components/agents/agent-fleet-data-grid.tsx b/apps/web/src/components/agents/agent-fleet-data-grid.tsx new file mode 100644 index 0000000..f53676d --- /dev/null +++ b/apps/web/src/components/agents/agent-fleet-data-grid.tsx @@ -0,0 +1,390 @@ +import { useCallback, useMemo, type MouseEvent, type ReactNode } from 'react' +import type { ColumnDef } from '@tanstack/react-table' +import { Check, Copy, PanelRight } from 'lucide-react' +import type { Agent } from '@evofw/shared' +import type { Filter, FilterFieldConfig } from '@/components/reui/filters' +import { ResourcePage } from '@/components/reui-kit' +import { Badge } from '@/components/reui/badge' +import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header' +import { + DataGridMutedCell, + DataGridPrimaryCell, +} from '@/components/data-grid-cell' +import { StatusBadge } from '@/components/status-badge' +import { + AgentPlatformIcon, + platformLabel, +} from '@/components/agents/agent-platform-icon' +import { Button } from '@evofw/ui/components/button' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@evofw/ui/components/tooltip' + +/** + * Fleet triage DataGrid — firewall ops density. + * Preview: https://reui.io/preview/base/data-grid-filtering-2 + * · https://reui.io/preview/base/solution-agents-2 + * · https://reui.io/preview/base/solution-agents-1 + */ + +const packetFmt = new Intl.NumberFormat('ru-RU', { + notation: 'compact', + maximumFractionDigits: 1, +}) + +const seenFmt = new Intl.DateTimeFormat('ru-RU', { + day: '2-digit', + month: '2-digit', + hour: '2-digit', + minute: '2-digit', +}) + +function formatPackets(n: number | undefined, hasApply: boolean): string { + if (!hasApply || n === undefined) return '—' + return packetFmt.format(n) +} + +function formatAgentSeen(iso: string | null | undefined): string { + if (!iso) return '—' + const t = Date.parse(iso) + if (Number.isNaN(t)) return '—' + return seenFmt.format(t) +} + +export type AgentFleetDataGridProps = { + data: Agent[] + filterFields: FilterFieldConfig[] + filters: Filter[] + onFiltersChange: (filters: Filter[]) => void + onClearFilters?: () => void + getFilterFieldValue: (item: Agent, field: string) => unknown + searchQuery: string + onSearchChange: (query: string) => void + getSearchText: (item: Agent) => string + tabs: { id: string; label: string }[] + activeTab: string + onTabChange: (tabId: string) => void + tabFilter: (item: Agent, tabId: string) => boolean + isLoading?: boolean + isError?: boolean + error?: Error | null + onRetry?: () => void + emptyAction?: ReactNode + toolbarExtra?: ReactNode + onSelect: (id: string) => void + onApprove: (id: string) => void + approvePending?: boolean + onCopyInstall: (curl: string) => void +} + +export function AgentFleetDataGrid({ + data, + filterFields, + filters, + onFiltersChange, + onClearFilters, + getFilterFieldValue, + searchQuery, + onSearchChange, + getSearchText, + tabs, + activeTab, + onTabChange, + tabFilter, + isLoading, + isError, + error, + onRetry, + emptyAction, + toolbarExtra, + onSelect, + onApprove, + approvePending, + onCopyInstall, +}: AgentFleetDataGridProps) { + const handleCopy = useCallback( + (curl: string, e?: MouseEvent) => { + e?.stopPropagation() + onCopyInstall(curl) + }, + [onCopyInstall], + ) + + const columns: ColumnDef[] = useMemo( + () => [ + { + accessorKey: 'name', + size: 260, + minSize: 180, + maxSize: 420, + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const a = row.original + return ( +
+ + +
+ ) + }, + }, + { + accessorKey: 'status', + size: 110, + minSize: 100, + maxSize: 130, + header: ({ column }) => ( + + ), + cell: ({ row }) => , + }, + { + id: 'default_action', + size: 100, + minSize: 90, + maxSize: 120, + accessorFn: (row) => row.default_action, + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const drop = row.original.default_action === 'drop' + return ( + + {drop ? 'Drop' : 'Accept'} + + ) + }, + }, + { + id: 'apply', + size: 100, + minSize: 90, + maxSize: 120, + accessorFn: (row) => row.last_apply_status ?? '', + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const a = row.original + if (a.last_apply_error) { + return ( + + + } + > + + error + + + + {a.last_apply_error} + + + ) + } + if (!a.last_apply_status && !a.last_apply_at) { + return + } + return ( + + {a.last_apply_status ?? 'ok'} + + ) + }, + }, + { + id: 'dropped', + size: 90, + minSize: 80, + maxSize: 110, + accessorFn: (row) => row.last_apply_packets_dropped ?? -1, + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const a = row.original + const hasApply = Boolean(a.last_apply_at || a.last_apply_status) + const text = formatPackets(a.last_apply_packets_dropped, hasApply) + if (text === '—') { + return + } + return ( + + {text} + + ) + }, + }, + { + id: 'accepted', + size: 90, + minSize: 80, + maxSize: 110, + accessorFn: (row) => row.last_apply_packets_accepted ?? -1, + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const a = row.original + const hasApply = Boolean(a.last_apply_at || a.last_apply_status) + const text = formatPackets(a.last_apply_packets_accepted, hasApply) + if (text === '—') { + return + } + return ( + + {text} + + ) + }, + }, + { + accessorKey: 'last_seen_at', + size: 140, + minSize: 120, + maxSize: 180, + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const a = row.original + const short = formatAgentSeen(a.last_seen_at) + if (!a.last_seen_at || short === '—') { + return + } + return ( + + ) + }, + }, + { + id: 'gen', + size: 70, + minSize: 60, + maxSize: 90, + accessorFn: (row) => row.policy_generation, + header: ({ column }) => ( + + ), + cell: ({ row }) => ( + + {row.original.policy_generation} + + ), + }, + { + id: 'actions', + size: 120, + minSize: 120, + maxSize: 120, + enableSorting: false, + enableResizing: false, + header: () => Действия, + cell: ({ row }) => { + const a = row.original + return ( +
+ {a.status === 'pending' ? ( + + ) : null} + {a.install_curl ? ( + + ) : null} + +
+ ) + }, + }, + ], + [approvePending, handleCopy, onApprove, onSelect], + ) + + return ( + r.id} + filterFields={filterFields} + filters={filters} + onFiltersChange={onFiltersChange} + onClearFilters={onClearFilters} + getFilterFieldValue={getFilterFieldValue} + searchQuery={searchQuery} + onSearchChange={onSearchChange} + searchPlaceholder="Поиск агентов…" + getSearchText={getSearchText} + tableLayout={{ width: 'fixed', columnsResizable: true }} + columnPinning={{ right: ['actions'] }} + onRowClick={(row) => onSelect(row.id)} + tabs={tabs} + activeTab={activeTab} + onTabChange={onTabChange} + tabFilter={tabFilter} + isLoading={isLoading} + isError={isError} + error={error} + onRetry={onRetry} + toolbarExtra={toolbarExtra} + emptyState={{ + title: 'Нет агентов', + description: + 'Создайте агента — он появится в списке как Invited с командой установки.', + action: emptyAction, + }} + /> + ) +} diff --git a/apps/web/src/components/blocks/solution-agents-2/components/data.tsx b/apps/web/src/components/blocks/solution-agents-2/components/data.tsx new file mode 100644 index 0000000..30f2fe4 --- /dev/null +++ b/apps/web/src/components/blocks/solution-agents-2/components/data.tsx @@ -0,0 +1,469 @@ +import { type ReactNode } from "react" +import { CircleCheckIcon, InfoIcon, CreditCardIcon, SendIcon, DatabaseIcon, BookOpenIcon, SparklesIcon, FileTextIcon } from "lucide-react" + +export type RunStatusId = "running" | "waiting" | "failed" | "completed" + +export type AgentName = + | "Refund Resolver" + | "Outreach Composer" + | "Data Steward" + | "Invoice Reconciler" + | "Lead Enricher" + | "Contract Summarizer" + +export type RunAttention = + | "Escalated" + | "Needs approval" + | "Retrying" + | "On track" + | "Done" + +export type RunEnvironment = "Production" | "Staging" | "Development" + +export interface RunOwner { + id: string + name: string + initials: string + avatarSrc?: string + role: string +} + +export interface RunStatusGroup { + id: RunStatusId + label: "Running" | "Waiting" | "Failed" | "Completed" + summary: string + focus: string + order: number +} + +export interface AgentRun { + id: string + runKey: string + title: string + context: string + statusId: RunStatusId + agent: AgentName + environment: RunEnvironment + attention: RunAttention + owner: RunOwner | null + owners: RunOwner[] + stepProgress: number | null + startedAt: string + startedLabel: string +} + +export interface RunGroupRow { + kind: "group" + id: string + group: RunStatusGroup + subRows?: RunItemRow[] +} + +export interface RunItemRow { + kind: "run" + id: string + group: RunStatusGroup + run: AgentRun +} + +export type RunTableRow = RunGroupRow | RunItemRow + +export const RUN_STATUS_ORDER: RunStatusId[] = [ + "running", + "waiting", + "failed", + "completed", +] + +export const RUN_ATTENTION_OPTIONS: RunAttention[] = [ + "Escalated", + "Needs approval", + "Retrying", + "On track", + "Done", +] + +export const TOAST_SUCCESS_ICON = ( +
{ - setDetailOpen(open) - if (!open) setDetailAgentId(null) + if (!open) setSearch({ agent: '' }) }} /> diff --git a/apps/web/src/routes/_auth/index.tsx b/apps/web/src/routes/_auth/index.tsx index 1cd8f23..99d9830 100644 --- a/apps/web/src/routes/_auth/index.tsx +++ b/apps/web/src/routes/_auth/index.tsx @@ -161,8 +161,8 @@ function DashboardPage() { {a.name} @@ -236,8 +236,8 @@ function DashboardPage() { Pending: {a.name} Открыть diff --git a/packages/ui/src/components/collapsible.tsx b/packages/ui/src/components/collapsible.tsx index 488fb33..4b242f7 100644 --- a/packages/ui/src/components/collapsible.tsx +++ b/packages/ui/src/components/collapsible.tsx @@ -1,5 +1,3 @@ -"use client" - import { Collapsible as CollapsiblePrimitive } from "@base-ui/react/collapsible" function Collapsible({ ...props }: CollapsiblePrimitive.Root.Props) { diff --git a/packages/ui/src/components/combobox.tsx b/packages/ui/src/components/combobox.tsx new file mode 100644 index 0000000..e47ad7a --- /dev/null +++ b/packages/ui/src/components/combobox.tsx @@ -0,0 +1,297 @@ +"use client" + +import * as React from "react" +import { Combobox as ComboboxPrimitive } from "@base-ui/react" + +import { cn } from "@evofw/ui/lib/utils" +import { Button } from "@evofw/ui/components/button" +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput, +} from "@evofw/ui/components/input-group" +import { ChevronDownIcon, XIcon, CheckIcon } from "lucide-react" + +const Combobox = ComboboxPrimitive.Root + +function ComboboxValue({ ...props }: ComboboxPrimitive.Value.Props) { + return +} + +function ComboboxTrigger({ + className, + children, + ...props +}: ComboboxPrimitive.Trigger.Props) { + return ( + + {children} + + + ) +} + +function ComboboxClear({ className, ...props }: ComboboxPrimitive.Clear.Props) { + return ( + } + className={cn(className)} + {...props} + > + + + ) +} + +function ComboboxInput({ + className, + children, + disabled = false, + showTrigger = true, + showClear = false, + ...props +}: ComboboxPrimitive.Input.Props & { + showTrigger?: boolean + showClear?: boolean +}) { + return ( + + } + {...props} + /> + + {showTrigger && ( + } + data-slot="input-group-button" + className="group-has-data-[slot=combobox-clear]/input-group:hidden data-pressed:bg-transparent" + disabled={disabled} + /> + )} + {showClear && } + + {children} + + ) +} + +function ComboboxContent({ + className, + side = "bottom", + sideOffset = 6, + align = "start", + alignOffset = 0, + anchor, + ...props +}: ComboboxPrimitive.Popup.Props & + Pick< + ComboboxPrimitive.Positioner.Props, + "side" | "align" | "sideOffset" | "alignOffset" | "anchor" + >) { + return ( + + + + + + ) +} + +function ComboboxList({ className, ...props }: ComboboxPrimitive.List.Props) { + return ( + + ) +} + +function ComboboxItem({ + className, + children, + ...props +}: ComboboxPrimitive.Item.Props) { + return ( + + {children} + + } + > + + + + ) +} + +function ComboboxGroup({ className, ...props }: ComboboxPrimitive.Group.Props) { + return ( + + ) +} + +function ComboboxLabel({ + className, + ...props +}: ComboboxPrimitive.GroupLabel.Props) { + return ( + + ) +} + +function ComboboxCollection({ ...props }: ComboboxPrimitive.Collection.Props) { + return ( + + ) +} + +function ComboboxEmpty({ className, ...props }: ComboboxPrimitive.Empty.Props) { + return ( + + ) +} + +function ComboboxSeparator({ + className, + ...props +}: ComboboxPrimitive.Separator.Props) { + return ( + + ) +} + +function ComboboxChips({ + className, + ...props +}: React.ComponentPropsWithRef & + ComboboxPrimitive.Chips.Props) { + return ( + + ) +} + +function ComboboxChip({ + className, + children, + showRemove = true, + ...props +}: ComboboxPrimitive.Chip.Props & { + showRemove?: boolean +}) { + return ( + + {children} + {showRemove && ( + } + className="-ml-1 opacity-50 hover:opacity-100" + data-slot="combobox-chip-remove" + > + + + )} + + ) +} + +function ComboboxChipsInput({ + className, + ...props +}: ComboboxPrimitive.Input.Props) { + return ( + + ) +} + +function useComboboxAnchor() { + return React.useRef(null) +} + +export { + Combobox, + ComboboxInput, + ComboboxContent, + ComboboxList, + ComboboxItem, + ComboboxGroup, + ComboboxLabel, + ComboboxCollection, + ComboboxEmpty, + ComboboxSeparator, + ComboboxChips, + ComboboxChip, + ComboboxChipsInput, + ComboboxTrigger, + ComboboxValue, + useComboboxAnchor, +} diff --git a/packages/ui/src/components/field.tsx b/packages/ui/src/components/field.tsx index c65c1dc..e72c2bb 100644 --- a/packages/ui/src/components/field.tsx +++ b/packages/ui/src/components/field.tsx @@ -1,5 +1,3 @@ -"use client" - import { useMemo } from "react" import { cva, type VariantProps } from "class-variance-authority" diff --git a/packages/ui/src/components/progress.tsx b/packages/ui/src/components/progress.tsx index 65899df..15c744c 100644 --- a/packages/ui/src/components/progress.tsx +++ b/packages/ui/src/components/progress.tsx @@ -1,3 +1,5 @@ +"use client" + import { Progress as ProgressPrimitive } from "@base-ui/react/progress" import { cn } from "@evofw/ui/lib/utils" diff --git a/packages/ui/src/components/scroll-area.tsx b/packages/ui/src/components/scroll-area.tsx index 8aa01ec..ff28786 100644 --- a/packages/ui/src/components/scroll-area.tsx +++ b/packages/ui/src/components/scroll-area.tsx @@ -1,3 +1,5 @@ +"use client" + import * as React from "react" import { ScrollArea as ScrollAreaPrimitive } from "@base-ui/react/scroll-area" diff --git a/packages/ui/src/components/sheet.tsx b/packages/ui/src/components/sheet.tsx index 3196be4..0dd8617 100644 --- a/packages/ui/src/components/sheet.tsx +++ b/packages/ui/src/components/sheet.tsx @@ -1,3 +1,5 @@ +"use client" + import * as React from "react" import { Dialog as SheetPrimitive } from "@base-ui/react/dialog" @@ -92,7 +94,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { return (
) diff --git a/packages/ui/src/components/switch.tsx b/packages/ui/src/components/switch.tsx index e7f4375..1d5271c 100644 --- a/packages/ui/src/components/switch.tsx +++ b/packages/ui/src/components/switch.tsx @@ -1,3 +1,5 @@ +"use client" + import { Switch as SwitchPrimitive } from "@base-ui/react/switch" import { cn } from "@evofw/ui/lib/utils"