feat(11-02): replace 1113 old CSS variable references with Paperclip design tokens
- Replace all bg-[color:var(--old)] with bg-background/bg-card/bg-muted/bg-primary etc. - Replace all text-[color:var(--old)] with text-foreground/text-muted-foreground/text-primary etc. - Replace all border-[color:var(--line)] with border-border - Replace all inline var(--old) with var(--new) equivalents - Replace all rounded-[6px]/rounded-[8px] with rounded-lg - Replace .surface class with bg-card border border-border shadow-sm - Replace .focus-ring class with focus-visible Tailwind utilities - Replace .mono class with font-mono - Replace data-theme attribute with class-based dark mode - 99 files updated, ~1265 lines changed, zero old references remaining
This commit is contained in:
1 parent
2ed5dd3388
commit
06ea4c6aec
99 files changed
+1265
-1265
No files matched your search
@@ -47,12 +47,12 @@ export function ChannelHeader({
|
||||
const t = useT()
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3 border-b border-[color:var(--line)] bg-[color:var(--panel)] px-4 py-2.5">
|
||||
<div className="flex items-center gap-3 border-b border-border bg-card px-4 py-2.5">
|
||||
{!showSidebar ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onShowSidebar}
|
||||
className="rounded-[4px] p-1 text-[color:var(--muted)] transition hover:text-[color:var(--text)]"
|
||||
className="rounded-[4px] p-1 text-muted-foreground transition hover:text-foreground"
|
||||
aria-label="Show sidebar"
|
||||
>
|
||||
<CaretDown size={12} className="rotate-90" />
|
||||
@@ -61,15 +61,15 @@ export function ChannelHeader({
|
||||
|
||||
{activeChannel ? (
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="truncate text-[13px] font-semibold text-[color:var(--text)]">{activeChannel.label}</div>
|
||||
<div className="truncate text-[13px] font-semibold text-foreground">{activeChannel.label}</div>
|
||||
{activeChannel.kind === "task" && activeTask ? (
|
||||
<button type="button" onClick={() => onNavigateToTask(activeTask.id)} className="mt-0.5 text-[11px] font-medium text-[color:var(--accent)] transition hover:underline">
|
||||
<button type="button" onClick={() => onNavigateToTask(activeTask.id)} className="mt-0.5 text-[11px] font-medium text-primary transition hover:underline">
|
||||
Open task
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-[13px] text-[color:var(--muted)]">{t("comm.selectChannel")}</span>
|
||||
<span className="text-[13px] text-muted-foreground">{t("comm.selectChannel")}</span>
|
||||
)}
|
||||
|
||||
<div className="ml-auto w-full max-w-[320px]">
|
||||
|
||||
@@ -7,11 +7,11 @@ import type { ChannelDescriptor, TaskDiscussionDescriptor } from "./ChannelHeade
|
||||
import { useT } from "../../i18n"
|
||||
|
||||
const channelConfig: Record<MessageChannel, { label: string; icon: React.ElementType; color: string }> = {
|
||||
direct: { label: "Direct Messages", icon: EnvelopeSimple, color: "var(--accent)" },
|
||||
department: { label: "Department", icon: Buildings, color: "var(--muted-strong)" },
|
||||
company: { label: "Company-wide", icon: Megaphone, color: "var(--accent)" },
|
||||
project: { label: "Project", icon: Hash, color: "var(--success)" },
|
||||
incident: { label: "Incident", icon: Fire, color: "var(--danger)" },
|
||||
direct: { label: "Direct Messages", icon: EnvelopeSimple, color: "var(--primary)" },
|
||||
department: { label: "Department", icon: Buildings, color: "var(--muted-foreground)" },
|
||||
company: { label: "Company-wide", icon: Megaphone, color: "var(--primary)" },
|
||||
project: { label: "Project", icon: Hash, color: "var(--primary)" },
|
||||
incident: { label: "Incident", icon: Fire, color: "var(--destructive)" },
|
||||
}
|
||||
|
||||
function ChannelItem({
|
||||
@@ -38,15 +38,15 @@ function ChannelItem({
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
aria-current={active ? "page" : undefined}
|
||||
className={`group relative flex w-full items-center gap-2.5 rounded-[8px] px-3 py-2 text-left transition duration-150 ${
|
||||
active ? "bg-[color:var(--accent-soft)]" : "hover:bg-[color:var(--panel-soft)]/50"
|
||||
className={`group relative flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left transition duration-150 ${
|
||||
active ? "bg-accent" : "hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
{descriptor.channel === "direct" && agent ? (
|
||||
<AgentAvatar agent={agent} size="sm" />
|
||||
) : (
|
||||
<div
|
||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[6px]"
|
||||
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg"
|
||||
style={{ backgroundColor: `${config.color}18` }}
|
||||
>
|
||||
<Icon size={14} weight={active ? "fill" : "regular"} style={{ color: config.color }} />
|
||||
@@ -56,21 +56,21 @@ function ChannelItem({
|
||||
<span
|
||||
className={`min-w-0 flex-1 truncate text-[13px] ${
|
||||
active
|
||||
? "font-semibold text-[color:var(--text)]"
|
||||
? "font-semibold text-foreground"
|
||||
: unreadCount > 0
|
||||
? "font-semibold text-[color:var(--text)]"
|
||||
: "font-medium text-[color:var(--muted-strong)]"
|
||||
? "font-semibold text-foreground"
|
||||
: "font-medium text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
{descriptor.label}
|
||||
</span>
|
||||
|
||||
{hasUrgent ? (
|
||||
<span className="h-2 w-2 shrink-0 rounded-full bg-[color:var(--danger)]" />
|
||||
<span className="h-2 w-2 shrink-0 rounded-full bg-destructive" />
|
||||
) : unreadCount > 0 ? (
|
||||
<span
|
||||
className="flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full px-1 text-[9px] font-bold text-[color:var(--text-on-accent)]"
|
||||
style={{ backgroundColor: "var(--accent)" }}
|
||||
className="flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full px-1 text-[9px] font-bold text-primary-foreground"
|
||||
style={{ backgroundColor: "var(--primary)" }}
|
||||
>
|
||||
{unreadCount > 99 ? "99+" : unreadCount}
|
||||
</span>
|
||||
@@ -108,13 +108,13 @@ export function ChannelSidebar({
|
||||
animate={{ width: 260, opacity: 1 }}
|
||||
exit={{ width: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="flex h-full shrink-0 flex-col overflow-hidden border-r border-[color:var(--line)] bg-[color:var(--panel)]"
|
||||
className="flex h-full shrink-0 flex-col overflow-hidden border-r border-border bg-card"
|
||||
style={{ width: 260 }}
|
||||
>
|
||||
<div className="flex-1 overflow-y-auto px-2 py-3">
|
||||
{channelGroups.map((group) => (
|
||||
<div key={group.type} className="mb-3">
|
||||
<div className="mb-1 px-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]">
|
||||
<div className="mb-1 px-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
{group.label}
|
||||
</div>
|
||||
{group.items.map((ch) => {
|
||||
@@ -136,7 +136,7 @@ export function ChannelSidebar({
|
||||
|
||||
{taskDiscussionChannels.length > 0 ? (
|
||||
<div className="mb-3">
|
||||
<div className="mb-1 px-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]">
|
||||
<div className="mb-1 px-2 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground">
|
||||
{t("comm.taskDiscussions")}
|
||||
</div>
|
||||
{taskDiscussionChannels.map((discussion) => (
|
||||
@@ -144,14 +144,14 @@ export function ChannelSidebar({
|
||||
key={discussion.key}
|
||||
type="button"
|
||||
onClick={() => onSelectChannel(discussion.key)}
|
||||
className={`group flex w-full items-center gap-2.5 rounded-[8px] px-3 py-2 text-left transition duration-150 ${
|
||||
selectedChannelKey === discussion.key ? "bg-[color:var(--panel-soft)]" : "hover:bg-[color:var(--panel-soft)]/50"
|
||||
className={`group flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left transition duration-150 ${
|
||||
selectedChannelKey === discussion.key ? "bg-muted" : "hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[6px]" style={{ backgroundColor: "var(--accent-soft)" }}>
|
||||
<Hash size={14} weight={selectedChannelKey === discussion.key ? "fill" : "regular"} style={{ color: "var(--accent)" }} />
|
||||
<div className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg" style={{ backgroundColor: "var(--primary)" }}>
|
||||
<Hash size={14} weight={selectedChannelKey === discussion.key ? "fill" : "regular"} style={{ color: "var(--primary)" }} />
|
||||
</div>
|
||||
<span className={`min-w-0 flex-1 truncate text-[13px] ${selectedChannelKey === discussion.key ? "font-semibold text-[color:var(--text)]" : "font-medium text-[color:var(--muted-strong)]"}`}>
|
||||
<span className={`min-w-0 flex-1 truncate text-[13px] ${selectedChannelKey === discussion.key ? "font-semibold text-foreground" : "font-medium text-muted-foreground"}`}>
|
||||
{discussion.label}
|
||||
</span>
|
||||
</button>
|
||||
@@ -160,14 +160,14 @@ export function ChannelSidebar({
|
||||
) : null}
|
||||
|
||||
{totalChannels === 0 ? (
|
||||
<div className="px-3 py-6 text-center text-[11px] text-[color:var(--muted)]">
|
||||
<div className="px-3 py-6 text-center text-[11px] text-muted-foreground">
|
||||
{t("comm.noActiveDepartments")}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="border-t border-[color:var(--line)] px-4 py-2">
|
||||
<div className="flex items-center justify-between text-[11px] text-[color:var(--muted)]">
|
||||
<div className="border-t border-border px-4 py-2">
|
||||
<div className="flex items-center justify-between text-[11px] text-muted-foreground">
|
||||
<span>{totalChannels} channels</span>
|
||||
<span>{agents.length} agents</span>
|
||||
</div>
|
||||
@@ -201,29 +201,29 @@ export function FilterBar({
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="relative min-w-0 flex-1">
|
||||
<MagnifyingGlass size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-[color:var(--muted)]" />
|
||||
<MagnifyingGlass size={14} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" />
|
||||
<input
|
||||
type="text"
|
||||
value={filters.search}
|
||||
onChange={(e) => onChangeFilters({ ...filters, search: e.target.value })}
|
||||
onKeyDown={onSearchKeyDown}
|
||||
placeholder={t("comm.searchMessages")}
|
||||
className="w-full rounded-[6px] border border-[color:var(--line)] bg-[color:var(--panel)] py-1.5 pl-8 pr-3 text-[11px] text-[color:var(--text)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-1"
|
||||
style={{ focusRingColor: "var(--accent)" } as React.CSSProperties}
|
||||
className="w-full rounded-lg border border-border bg-card py-1.5 pl-8 pr-3 text-[11px] text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-1"
|
||||
style={{ focusRingColor: "var(--primary)" } as React.CSSProperties}
|
||||
/>
|
||||
</div>
|
||||
{scopeToggle}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
className={`flex items-center gap-1.5 rounded-[6px] border px-2.5 py-1.5 text-[11px] font-medium transition ${
|
||||
className={`flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-[11px] font-medium transition ${
|
||||
expanded || filters.channelType !== "all" || filters.priority !== "all" || filters.unreadOnly
|
||||
? "border-transparent text-[color:var(--text-on-accent)]"
|
||||
: "border-[color:var(--line)] text-[color:var(--muted-strong)] hover:border-[color:var(--line-strong)]"
|
||||
? "border-transparent text-primary-foreground"
|
||||
: "border-border text-muted-foreground hover:border-border"
|
||||
}`}
|
||||
style={
|
||||
expanded || filters.channelType !== "all" || filters.priority !== "all" || filters.unreadOnly
|
||||
? { backgroundColor: "var(--accent)" }
|
||||
? { backgroundColor: "var(--primary)" }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
@@ -243,7 +243,7 @@ export function FilterBar({
|
||||
<select
|
||||
value={filters.channelType}
|
||||
onChange={(e) => onChangeFilters({ ...filters, channelType: e.target.value as MessageChannel | "all" })}
|
||||
className="rounded-[6px] border border-[color:var(--line)] bg-[color:var(--panel)] px-2 py-1 text-[11px] font-medium text-[color:var(--text)]"
|
||||
className="rounded-lg border border-border bg-card px-2 py-1 text-[11px] font-medium text-foreground"
|
||||
>
|
||||
<option value="all">{t("comm.allChannels")}</option>
|
||||
<option value="direct">{t("comm.direct")}</option>
|
||||
@@ -255,7 +255,7 @@ export function FilterBar({
|
||||
<select
|
||||
value={filters.priority}
|
||||
onChange={(e) => onChangeFilters({ ...filters, priority: e.target.value })}
|
||||
className="rounded-[6px] border border-[color:var(--line)] bg-[color:var(--panel)] px-2 py-1 text-[11px] font-medium text-[color:var(--text)]"
|
||||
className="rounded-lg border border-border bg-card px-2 py-1 text-[11px] font-medium text-foreground"
|
||||
>
|
||||
<option value="all">{t("comm.allPriorities")}</option>
|
||||
<option value="urgent">{t("comm.urgent")}</option>
|
||||
@@ -266,9 +266,9 @@ export function FilterBar({
|
||||
type="button"
|
||||
onClick={() => onChangeFilters({ ...filters, unreadOnly: !filters.unreadOnly })}
|
||||
className={`rounded-full px-2.5 py-1.5 text-[11px] font-semibold transition ${
|
||||
filters.unreadOnly ? "text-[color:var(--text-on-accent)]" : "bg-[color:var(--panel-soft)] text-[color:var(--muted-strong)]"
|
||||
filters.unreadOnly ? "text-primary-foreground" : "bg-muted text-muted-foreground"
|
||||
}`}
|
||||
style={filters.unreadOnly ? { backgroundColor: "var(--accent)" } : undefined}
|
||||
style={filters.unreadOnly ? { backgroundColor: "var(--primary)" } : undefined}
|
||||
>
|
||||
{t("comm.unreadOnly")}
|
||||
</button>
|
||||
@@ -276,7 +276,7 @@ export function FilterBar({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChangeFilters({ search: filters.search, channelType: "all", priority: "all", unreadOnly: false })}
|
||||
className="text-[11px] font-medium text-[color:var(--muted)] underline transition hover:text-[color:var(--text)]"
|
||||
className="text-[11px] font-medium text-muted-foreground underline transition hover:text-foreground"
|
||||
>
|
||||
{t("comm.clearFilters")}
|
||||
</button>
|
||||
|
||||
@@ -22,7 +22,7 @@ interface CommunicationHubProps {
|
||||
function SearchScopeToggle({ scope, onChangeScope }: { scope: "global" | "channel"; onChangeScope: (scope: "global" | "channel") => void }) {
|
||||
return (
|
||||
<div
|
||||
className="inline-flex items-center gap-0.5 rounded-full bg-[color:var(--panel)] p-0.5"
|
||||
className="inline-flex items-center gap-0.5 rounded-full bg-card p-0.5"
|
||||
role="radiogroup"
|
||||
aria-label="Search scope"
|
||||
>
|
||||
@@ -34,8 +34,8 @@ function SearchScopeToggle({ scope, onChangeScope }: { scope: "global" | "channe
|
||||
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onChangeScope("global") } }}
|
||||
className={`rounded-full px-2.5 py-1.5 text-[11px] font-semibold transition-colors duration-150 ${
|
||||
scope === "global"
|
||||
? "bg-[color:var(--accent)] text-[color:var(--text-on-accent)]"
|
||||
: "bg-[color:var(--panel-soft)] text-[color:var(--muted-strong)]"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
All Channels
|
||||
@@ -48,8 +48,8 @@ function SearchScopeToggle({ scope, onChangeScope }: { scope: "global" | "channe
|
||||
onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onChangeScope("channel") } }}
|
||||
className={`rounded-full px-2.5 py-1.5 text-[11px] font-semibold transition-colors duration-150 ${
|
||||
scope === "channel"
|
||||
? "bg-[color:var(--accent)] text-[color:var(--text-on-accent)]"
|
||||
: "bg-[color:var(--panel-soft)] text-[color:var(--muted-strong)]"
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-muted text-muted-foreground"
|
||||
}`}
|
||||
>
|
||||
This Channel
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useT } from "../../i18n"
|
||||
|
||||
function SendButton({ sending, disabled, onClick, label }: { sending: boolean; disabled: boolean; onClick: () => void; label: string }) {
|
||||
return (
|
||||
<button type="button" onClick={onClick} disabled={disabled || sending} className="flex h-[40px] w-[40px] shrink-0 items-center justify-center rounded-[8px] bg-[color:var(--accent)] text-[color:var(--text-on-accent)] transition hover:brightness-90 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none" aria-label={label}>
|
||||
<button type="button" onClick={onClick} disabled={disabled || sending} className="flex h-[40px] w-[40px] shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground transition hover:brightness-90 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none" aria-label={label}>
|
||||
{sending ? <svg className="h-4 w-4 animate-spin" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="2" opacity="0.3" /><path d="M14 8a6 6 0 0 0-6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /></svg> : <PaperPlaneTilt size={18} weight="fill" />}
|
||||
</button>
|
||||
)
|
||||
@@ -49,30 +49,30 @@ export function MessageComposer({ agents, companyId, channel, channelTargetId, d
|
||||
const fromAgent = agents.find((a) => a.id === fromAgentId)
|
||||
|
||||
return (
|
||||
<div className="border-t border-[color:var(--line)] bg-[color:var(--panel)] px-4 py-3">
|
||||
<div className="border-t border-border bg-card px-4 py-3">
|
||||
{sendError ? <div className="mb-3"><InlineNotice message={sendError} /></div> : null}
|
||||
{channel === "direct" && !directRecipientId ? <div className="mb-2"><InlineNotice message="Select a direct conversation partner before sending a DM." /></div> : null}
|
||||
<AnimatePresence>
|
||||
{replyTo ? (
|
||||
<motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} exit={{ opacity: 0, height: 0 }} className="mb-2 flex items-center gap-2 overflow-hidden rounded-[6px] bg-[color:var(--panel-soft)] px-3 py-2">
|
||||
<ArrowBendUpLeft size={12} className="text-[color:var(--accent)]" />
|
||||
<span className="min-w-0 flex-1 truncate text-[11px] text-[color:var(--muted-strong)]">Replying to <span className="font-semibold">{agents.find((a) => a.id === replyTo.fromAgentId)?.name ?? "agent"}</span>{replyTo.subject ? `: ${replyTo.subject}` : ""}</span>
|
||||
<button type="button" onClick={onCancelReply} className="shrink-0 rounded-[4px] p-0.5 text-[color:var(--muted)] transition hover:text-[color:var(--text)]"><X size={12} /></button>
|
||||
<motion.div initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} exit={{ opacity: 0, height: 0 }} className="mb-2 flex items-center gap-2 overflow-hidden rounded-lg bg-muted px-3 py-2">
|
||||
<ArrowBendUpLeft size={12} className="text-primary" />
|
||||
<span className="min-w-0 flex-1 truncate text-[11px] text-muted-foreground">Replying to <span className="font-semibold">{agents.find((a) => a.id === replyTo.fromAgentId)?.name ?? "agent"}</span>{replyTo.subject ? `: ${replyTo.subject}` : ""}</span>
|
||||
<button type="button" onClick={onCancelReply} className="shrink-0 rounded-[4px] p-0.5 text-muted-foreground transition hover:text-foreground"><X size={12} /></button>
|
||||
</motion.div>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
<AgentAvatar agent={fromAgent} size="sm" />
|
||||
<select value={fromAgentId} onChange={(e) => setFromAgentId(e.target.value)} className="min-w-0 rounded-[6px] border border-[color:var(--line)] bg-[color:var(--panel)] px-2 py-1 text-[11px] font-medium text-[color:var(--text)]">
|
||||
<select value={fromAgentId} onChange={(e) => setFromAgentId(e.target.value)} className="min-w-0 rounded-lg border border-border bg-card px-2 py-1 text-[11px] font-medium text-foreground">
|
||||
{agents.map((a) => <option key={a.id} value={a.id}>{a.name} ({a.role})</option>)}
|
||||
</select>
|
||||
{!replyTo ? <input type="text" value={subject} onChange={(e) => setSubject(e.target.value)} placeholder="Subject (optional)" className="min-w-0 flex-1 rounded-[6px] border border-[color:var(--line)] bg-[color:var(--panel)] px-2.5 py-1 text-[11px] text-[color:var(--text)] placeholder:text-[color:var(--muted)]" /> : null}
|
||||
{!replyTo ? <input type="text" value={subject} onChange={(e) => setSubject(e.target.value)} placeholder="Subject (optional)" className="min-w-0 flex-1 rounded-lg border border-border bg-card px-2.5 py-1 text-[11px] text-foreground placeholder:text-muted-foreground" /> : null}
|
||||
</div>
|
||||
<div className="flex items-end gap-2">
|
||||
<textarea ref={textareaRef} value={body} onChange={(e) => setBody(e.target.value)} onKeyDown={handleKeyDown} placeholder="Type a message..." rows={2} className="min-h-[40px] min-w-0 flex-1 resize-none rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel-soft)] px-3 py-2 text-[13px] text-[color:var(--text)] placeholder:text-[color:var(--muted)] focus:border-transparent focus:outline-none focus:ring-2" style={{ focusRingColor: "var(--accent)" } as React.CSSProperties} />
|
||||
<textarea ref={textareaRef} value={body} onChange={(e) => setBody(e.target.value)} onKeyDown={handleKeyDown} placeholder="Type a message..." rows={2} className="min-h-[40px] min-w-0 flex-1 resize-none rounded-lg border border-border bg-muted px-3 py-2 text-[13px] text-foreground placeholder:text-muted-foreground focus:border-transparent focus:outline-none focus:ring-2" style={{ focusRingColor: "var(--primary)" } as React.CSSProperties} />
|
||||
<SendButton sending={sending} disabled={!body.trim() || !fromAgentId || (channel === "direct" && !directRecipientId)} onClick={() => void handleSend()} label="Send message" />
|
||||
</div>
|
||||
<div className="mt-1 text-[11px] text-[color:var(--muted)]">Press {navigator.platform.includes("Mac") ? "Cmd" : "Ctrl"}+Enter to send</div>
|
||||
<div className="mt-1 text-[11px] text-muted-foreground">Press {navigator.platform.includes("Mac") ? "Cmd" : "Ctrl"}+Enter to send</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -84,10 +84,10 @@ export function TaskDiscussionComposer({ companyId, taskId, onSent }: { companyI
|
||||
try { unwrap(await window.agentCompany.addComment({ companyId, taskId, authorName: "Board", body: body.trim() })); setBody(""); onSent() } catch (e) { setSendError(e instanceof Error ? e.message : "Failed to send.") } finally { setSending(false) }
|
||||
}, [body, companyId, taskId, onSent])
|
||||
return (
|
||||
<div className="border-t border-[color:var(--line)] bg-[color:var(--panel)] px-4 py-3">
|
||||
<div className="border-t border-border bg-card px-4 py-3">
|
||||
{sendError ? <div className="mb-3"><InlineNotice message={sendError} /></div> : null}
|
||||
<div className="flex items-end gap-2">
|
||||
<textarea value={body} onChange={(e) => setBody(e.target.value)} placeholder="Reply in task discussion..." rows={2} className="min-h-[40px] min-w-0 flex-1 resize-none rounded-[8px] border border-[color:var(--line)] bg-[color:var(--panel-soft)] px-3 py-2 text-[13px] text-[color:var(--text)] placeholder:text-[color:var(--muted)] focus:border-transparent focus:outline-none focus:ring-2" style={{ focusRingColor: "var(--accent)" } as React.CSSProperties} />
|
||||
<textarea value={body} onChange={(e) => setBody(e.target.value)} placeholder="Reply in task discussion..." rows={2} className="min-h-[40px] min-w-0 flex-1 resize-none rounded-lg border border-border bg-muted px-3 py-2 text-[13px] text-foreground placeholder:text-muted-foreground focus:border-transparent focus:outline-none focus:ring-2" style={{ focusRingColor: "var(--primary)" } as React.CSSProperties} />
|
||||
<SendButton sending={sending} disabled={!body.trim()} onClick={() => void handleSend()} label="Send task comment" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,7 +96,7 @@ export function TaskDiscussionComposer({ companyId, taskId, onSent }: { companyI
|
||||
|
||||
export function NoChannelComposer() {
|
||||
const t = useT()
|
||||
return <div className="border-t border-[color:var(--line)] bg-[color:var(--panel)] px-4 py-2.5"><div className="flex items-center gap-2 text-[11px] text-[color:var(--muted)]"><ChatCircle size={14} /><span>{t("comm.selectChannelSend")}</span></div></div>
|
||||
return <div className="border-t border-border bg-card px-4 py-2.5"><div className="flex items-center gap-2 text-[11px] text-muted-foreground"><ChatCircle size={14} /><span>{t("comm.selectChannelSend")}</span></div></div>
|
||||
}
|
||||
|
||||
export function ComposeModal({ open, onClose, agents, projects, companyId, onSent }: { open: boolean; onClose: () => void; agents: AgentRecord[]; projects?: ProjectRecord[]; companyId: string; onSent: () => void }) {
|
||||
@@ -130,8 +130,8 @@ export function ComposeModal({ open, onClose, agents, projects, companyId, onSen
|
||||
<TextArea label={t("comm.message")} value={body} onChange={setBody} placeholder={t("comm.writeMessage")} rows={5} />
|
||||
<Select label="Priority" value={priority} onChange={(v) => setPriority(v as MessagePriority)} options={[{ value: "low", label: "Low" }, { value: "normal", label: "Normal" }, { value: "urgent", label: "Urgent" }]} />
|
||||
<div className="flex justify-end gap-3 pt-2">
|
||||
<button type="button" onClick={onClose} className="rounded-[8px] px-3 py-2 text-[13px] font-medium text-[color:var(--muted)] transition hover:bg-[color:var(--panel-soft)]">{t("common.cancel")}</button>
|
||||
<button type="button" onClick={() => void handleSend()} disabled={!body.trim() || !subject.trim() || !fromAgentId || (channel === "direct" && !toAgentId) || ((channel === "department" || channel === "project") && !channelTargetId) || sending} className="inline-flex items-center gap-2 rounded-[8px] px-3 py-2 text-[13px] font-semibold bg-[color:var(--accent)] text-[color:var(--text-on-accent)] transition hover:brightness-90 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none">
|
||||
<button type="button" onClick={onClose} className="rounded-lg px-3 py-2 text-[13px] font-medium text-muted-foreground transition hover:bg-muted">{t("common.cancel")}</button>
|
||||
<button type="button" onClick={() => void handleSend()} disabled={!body.trim() || !subject.trim() || !fromAgentId || (channel === "direct" && !toAgentId) || ((channel === "department" || channel === "project") && !channelTargetId) || sending} className="inline-flex items-center gap-2 rounded-lg px-3 py-2 text-[13px] font-semibold bg-primary text-primary-foreground transition hover:brightness-90 active:translate-y-px disabled:opacity-40 disabled:pointer-events-none">
|
||||
{sending ? <svg className="h-4 w-4 animate-spin" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="6" stroke="currentColor" strokeWidth="2" opacity="0.3" /><path d="M14 8a6 6 0 0 0-6-6" stroke="currentColor" strokeWidth="2" strokeLinecap="round" /></svg> : <PaperPlaneTilt size={14} weight="fill" />}{t("comm.sendMessage")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -14,14 +14,14 @@ import { DEPARTMENT_COLORS } from "@shared/constants"
|
||||
|
||||
|
||||
const priorityConfig: Record<string, { label: string; color: string; bg: string }> = {
|
||||
urgent: { label: "Urgent", color: "var(--danger)", bg: "var(--danger-soft)" },
|
||||
normal: { label: "Normal", color: "var(--muted-strong)", bg: "var(--panel-soft)" },
|
||||
low: { label: "Low", color: "var(--muted)", bg: "var(--panel-soft)" },
|
||||
urgent: { label: "Urgent", color: "var(--destructive)", bg: "var(--destructive)" },
|
||||
normal: { label: "Normal", color: "var(--muted-foreground)", bg: "var(--muted-foreground)" },
|
||||
low: { label: "Low", color: "var(--muted-foreground)", bg: "var(--muted-foreground)" },
|
||||
}
|
||||
|
||||
function getDepartmentColor(department: DepartmentId | null | undefined): string {
|
||||
if (!department) return "var(--muted)"
|
||||
return DEPARTMENT_COLORS[department] ?? "var(--muted)"
|
||||
if (!department) return "var(--muted-foreground)"
|
||||
return DEPARTMENT_COLORS[department] ?? "var(--muted-foreground)"
|
||||
}
|
||||
|
||||
export const AgentAvatar = memo(function AgentAvatar({
|
||||
@@ -32,7 +32,7 @@ export const AgentAvatar = memo(function AgentAvatar({
|
||||
size?: "sm" | "md" | "lg"
|
||||
}) {
|
||||
const sizeClass = size === "sm" ? "h-7 w-7 text-[10px]" : size === "lg" ? "h-11 w-11 text-[14px]" : "h-9 w-9 text-[11px]"
|
||||
const color = agent ? getDepartmentColor(agent.department) : "var(--muted)"
|
||||
const color = agent ? getDepartmentColor(agent.department) : "var(--muted-foreground)"
|
||||
const initials = agent ? getAgentInitials(agent.name) : "?"
|
||||
|
||||
return (
|
||||
@@ -60,19 +60,19 @@ export const MessageBubble = memo(function MessageBubble({
|
||||
}) {
|
||||
const isUnread = !message.readAt
|
||||
const isUrgent = message.priority === "urgent"
|
||||
const deptColor = agent ? getDepartmentColor(agent.department) : "var(--muted)"
|
||||
const deptColor = agent ? getDepartmentColor(agent.department) : "var(--muted-foreground)"
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className={`group relative flex gap-3 rounded-[8px] px-4 py-3 transition ${
|
||||
className={`group relative flex gap-3 rounded-lg px-4 py-3 transition ${
|
||||
isThread ? "ml-11" : ""
|
||||
} ${
|
||||
isUnread
|
||||
? "bg-[color:var(--panel)]"
|
||||
: "hover:bg-[color:var(--panel-soft)]/40"
|
||||
? "bg-card"
|
||||
: "hover:bg-muted/40"
|
||||
}`}
|
||||
>
|
||||
<AgentAvatar agent={agent} />
|
||||
@@ -83,7 +83,7 @@ export const MessageBubble = memo(function MessageBubble({
|
||||
{agent?.name ?? "Unknown Agent"}
|
||||
</span>
|
||||
{agent?.title ? (
|
||||
<span className="truncate text-[11px] text-[color:var(--muted)]">{agent.title}</span>
|
||||
<span className="truncate text-[11px] text-muted-foreground">{agent.title}</span>
|
||||
) : null}
|
||||
{agent?.department ? (
|
||||
<span
|
||||
@@ -93,7 +93,7 @@ export const MessageBubble = memo(function MessageBubble({
|
||||
{agent.department.replace("_", " ")}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="whitespace-nowrap shrink-0 text-[11px] text-[color:var(--muted)]">
|
||||
<span className="whitespace-nowrap shrink-0 text-[11px] text-muted-foreground">
|
||||
{formatMessageTime(message.createdAt)}
|
||||
</span>
|
||||
{message.priority !== "normal" ? (
|
||||
@@ -109,23 +109,23 @@ export const MessageBubble = memo(function MessageBubble({
|
||||
</span>
|
||||
) : null}
|
||||
{isUnread ? (
|
||||
<Circle size={8} weight="fill" className="shrink-0" style={{ color: "var(--accent)" }} aria-label="Unread" />
|
||||
<Circle size={8} weight="fill" className="shrink-0" style={{ color: "var(--primary)" }} aria-label="Unread" />
|
||||
) : (
|
||||
<CheckCircle
|
||||
size={10}
|
||||
weight="fill"
|
||||
className="shrink-0 text-[color:var(--muted)] opacity-0 group-hover:opacity-60 group-focus-within:opacity-60"
|
||||
className="shrink-0 text-muted-foreground opacity-0 group-hover:opacity-60 group-focus-within:opacity-60"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{message.subject ? (
|
||||
<div className="mt-0.5 break-words text-[13px] font-medium text-[color:var(--text)]">
|
||||
<div className="mt-0.5 break-words text-[13px] font-medium text-foreground">
|
||||
{message.subject}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="mt-1 break-words whitespace-pre-wrap text-[13px] leading-relaxed text-[color:var(--muted-strong)]">
|
||||
<div className="mt-1 break-words whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground">
|
||||
{message.body}
|
||||
</div>
|
||||
|
||||
@@ -139,7 +139,7 @@ export const MessageBubble = memo(function MessageBubble({
|
||||
type="button"
|
||||
onClick={onReply}
|
||||
className="flex items-center gap-1.5 text-[11px] font-medium transition hover:opacity-80"
|
||||
style={{ color: "var(--accent)" }}
|
||||
style={{ color: "var(--primary)" }}
|
||||
>
|
||||
<ChatCircle size={12} />
|
||||
{replyCount} {replyCount === 1 ? "reply" : "replies"}
|
||||
@@ -148,7 +148,7 @@ export const MessageBubble = memo(function MessageBubble({
|
||||
<button
|
||||
type="button"
|
||||
onClick={onReply}
|
||||
className="flex items-center gap-1 rounded-[6px] px-2 py-0.5 text-[11px] font-medium text-[color:var(--muted)] opacity-0 transition hover:bg-[color:var(--panel-soft)] hover:text-[color:var(--text)] group-hover:opacity-100"
|
||||
className="flex items-center gap-1 rounded-lg px-2 py-0.5 text-[11px] font-medium text-muted-foreground opacity-0 transition hover:bg-muted hover:text-foreground group-hover:opacity-100"
|
||||
>
|
||||
<ArrowBendUpLeft size={10} />
|
||||
Reply
|
||||
@@ -167,25 +167,25 @@ export function TaskCommentBubble({
|
||||
agent: AgentRecord | undefined
|
||||
}) {
|
||||
const isBoard = !comment.authorAgentId
|
||||
const deptColor = agent ? getDepartmentColor(agent.department) : "var(--muted)"
|
||||
const deptColor = agent ? getDepartmentColor(agent.department) : "var(--muted-foreground)"
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 6 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.18 }}
|
||||
className="group relative flex gap-3 rounded-[8px] bg-[color:var(--panel)] px-4 py-3"
|
||||
className="group relative flex gap-3 rounded-lg bg-card px-4 py-3"
|
||||
>
|
||||
<AgentAvatar agent={agent} />
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate text-[13px] font-semibold" style={{ color: isBoard ? "var(--accent)" : deptColor }}>
|
||||
<span className="truncate text-[13px] font-semibold" style={{ color: isBoard ? "var(--primary)" : deptColor }}>
|
||||
{comment.authorName}
|
||||
</span>
|
||||
{agent?.title ? <span className="truncate text-[11px] text-[color:var(--muted)]">{agent.title}</span> : null}
|
||||
<span className="whitespace-nowrap shrink-0 text-[11px] text-[color:var(--muted)]">{formatMessageTime(comment.createdAt)}</span>
|
||||
{agent?.title ? <span className="truncate text-[11px] text-muted-foreground">{agent.title}</span> : null}
|
||||
<span className="whitespace-nowrap shrink-0 text-[11px] text-muted-foreground">{formatMessageTime(comment.createdAt)}</span>
|
||||
</div>
|
||||
<div className="mt-1 break-words whitespace-pre-wrap text-[13px] leading-relaxed text-[color:var(--muted-strong)]">
|
||||
<div className="mt-1 break-words whitespace-pre-wrap text-[13px] leading-relaxed text-muted-foreground">
|
||||
{comment.body}
|
||||
</div>
|
||||
</div>
|
||||
@@ -207,9 +207,9 @@ function AttachmentList({ json }: { json: string }) {
|
||||
{items.map((item, i) => (
|
||||
<span
|
||||
key={`${item.id}-${i}`}
|
||||
className="inline-flex items-center gap-1 rounded-[6px] bg-[color:var(--panel-soft)] px-2 py-1 text-[11px] font-medium text-[color:var(--muted-strong)]"
|
||||
className="inline-flex items-center gap-1 rounded-lg bg-muted px-2 py-1 text-[11px] font-medium text-muted-foreground"
|
||||
>
|
||||
<At size={10} className="text-[color:var(--muted)]" />
|
||||
<At size={10} className="text-muted-foreground" />
|
||||
{item.title || `${item.type}:${item.id.slice(0, 8)}`}
|
||||
</span>
|
||||
))}
|
||||
|
||||
@@ -92,8 +92,8 @@ export function MessageList({
|
||||
if (!activeChannel) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center text-center">
|
||||
<ChatCircle size={40} className="mb-3 text-[color:var(--muted)]" />
|
||||
<div className="text-[14px] font-medium text-[color:var(--muted-strong)]">
|
||||
<ChatCircle size={40} className="mb-3 text-muted-foreground" />
|
||||
<div className="text-[14px] font-medium text-muted-foreground">
|
||||
{t("comm.selectChannelView")}
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,8 +103,8 @@ export function MessageList({
|
||||
if (activeChannel.kind === "task" && taskDiscussionComments.length === 0) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center text-center">
|
||||
<Tray size={36} className="mb-3 text-[color:var(--muted)]" />
|
||||
<div className="text-[14px] font-medium text-[color:var(--muted-strong)]">
|
||||
<Tray size={36} className="mb-3 text-muted-foreground" />
|
||||
<div className="text-[14px] font-medium text-muted-foreground">
|
||||
{t("comm.noDiscussionYet")}
|
||||
</div>
|
||||
</div>
|
||||
@@ -114,14 +114,14 @@ export function MessageList({
|
||||
if (activeChannel.kind !== "task" && filteredMessages.length === 0) {
|
||||
return (
|
||||
<div className="flex h-full flex-col items-center justify-center text-center">
|
||||
<Tray size={36} className="mb-3 text-[color:var(--muted)]" />
|
||||
<div className="text-[14px] font-medium text-[color:var(--muted-strong)]">
|
||||
<Tray size={36} className="mb-3 text-muted-foreground" />
|
||||
<div className="text-[14px] font-medium text-muted-foreground">
|
||||
{filters.search || filters.unreadOnly || filters.priority !== "all"
|
||||
? t("comm.noMatchFilters")
|
||||
: t("comm.noMessagesChannel")}
|
||||
</div>
|
||||
{(filters.search || filters.unreadOnly || filters.priority !== "all") ? (
|
||||
<div className="mt-1 text-[13px] text-[color:var(--muted)]">{t("comm.adjustFilters")}</div>
|
||||
<div className="mt-1 text-[13px] text-muted-foreground">{t("comm.adjustFilters")}</div>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
@@ -159,7 +159,7 @@ export function MessageList({
|
||||
width: "100%",
|
||||
transform: `translateY(${virtualRow.start}px)`,
|
||||
}}
|
||||
className={`focus-ring outline-none ${virtualRow.index === focusedIndex ? "ring-1 ring-[color:var(--accent)] ring-inset" : ""}`}
|
||||
className={`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 outline-none ${virtualRow.index === focusedIndex ? "ring-1 ring-[color:var(--primary)] ring-inset" : ""}`}
|
||||
>
|
||||
{item.type === "comment" ? (
|
||||
<TaskCommentBubble
|
||||
|
||||
@@ -9,11 +9,11 @@ import { EmptyState, useReducedMotion } from "../ui"
|
||||
import { useRovingTabIndex } from "../../lib/keyboard"
|
||||
|
||||
const channelConfig: Record<MessageChannel, { label: string; icon: React.ElementType; color: string }> = {
|
||||
direct: { label: "Direct", icon: EnvelopeSimple, color: "var(--accent)" },
|
||||
department: { label: "Department", icon: Buildings, color: "var(--muted-strong)" },
|
||||
company: { label: "Company", icon: Megaphone, color: "var(--accent)" },
|
||||
project: { label: "Project", icon: Hash, color: "var(--success)" },
|
||||
incident: { label: "Incident", icon: Fire, color: "var(--danger)" },
|
||||
direct: { label: "Direct", icon: EnvelopeSimple, color: "var(--primary)" },
|
||||
department: { label: "Department", icon: Buildings, color: "var(--muted-foreground)" },
|
||||
company: { label: "Company", icon: Megaphone, color: "var(--primary)" },
|
||||
project: { label: "Project", icon: Hash, color: "var(--primary)" },
|
||||
incident: { label: "Incident", icon: Fire, color: "var(--destructive)" },
|
||||
}
|
||||
|
||||
function highlightMatch(text: string, query: string): React.ReactNode {
|
||||
@@ -23,7 +23,7 @@ function highlightMatch(text: string, query: string): React.ReactNode {
|
||||
const parts = text.split(regex)
|
||||
return parts.map((part, i) =>
|
||||
regex.test(part)
|
||||
? <mark key={i} className="rounded-[2px] bg-[color:var(--warn-soft)] px-0.5 text-[color:var(--text)]">{part}</mark>
|
||||
? <mark key={i} className="rounded-[2px] bg-yellow-100 dark:bg-yellow-900/50 px-0.5 text-foreground">{part}</mark>
|
||||
: part
|
||||
)
|
||||
}
|
||||
@@ -31,11 +31,11 @@ function highlightMatch(text: string, query: string): React.ReactNode {
|
||||
function SkeletonRow() {
|
||||
return (
|
||||
<div className="flex gap-3 px-3 py-3" style={{ height: 72 }}>
|
||||
<div className="h-7 w-7 shrink-0 animate-pulse rounded-full bg-[color:var(--panel-soft)]" />
|
||||
<div className="h-7 w-7 shrink-0 animate-pulse rounded-full bg-muted" />
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
|
||||
<div className="h-3 w-32 animate-pulse rounded bg-[color:var(--panel-soft)]" />
|
||||
<div className="h-3 w-48 animate-pulse rounded bg-[color:var(--panel-soft)]" />
|
||||
<div className="h-3 w-64 animate-pulse rounded bg-[color:var(--panel-soft)]" />
|
||||
<div className="h-3 w-32 animate-pulse rounded bg-muted" />
|
||||
<div className="h-3 w-48 animate-pulse rounded bg-muted" />
|
||||
<div className="h-3 w-64 animate-pulse rounded bg-muted" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -97,7 +97,7 @@ export function SearchResults({ results, query, loading, agents, onNavigateToRes
|
||||
return (
|
||||
<motion.div className="flex h-full flex-col" {...animationProps}>
|
||||
<div className="shrink-0 px-3 py-2">
|
||||
<span className="text-[11px] font-semibold text-[color:var(--muted)]">
|
||||
<span className="text-[11px] font-semibold text-muted-foreground">
|
||||
{results.length} result{results.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
</div>
|
||||
@@ -144,7 +144,7 @@ export function SearchResults({ results, query, loading, agents, onNavigateToRes
|
||||
width: "100%",
|
||||
transform: `translateY(${virtualRow.start}px)`,
|
||||
}}
|
||||
className={`cursor-pointer px-3 py-2 transition-colors duration-100 hover:bg-[color:var(--panel-soft)] focus:outline-none ${virtualRow.index === focusedIndex ? "ring-1 ring-inset ring-[color:var(--accent)]" : ""}`}
|
||||
className={`cursor-pointer px-3 py-2 transition-colors duration-100 hover:bg-muted focus:outline-none ${virtualRow.index === focusedIndex ? "ring-1 ring-inset ring-[color:var(--primary)]" : ""}`}
|
||||
>
|
||||
<div className="flex items-start gap-2.5">
|
||||
<AgentAvatar agent={agent} size="sm" />
|
||||
@@ -152,7 +152,7 @@ export function SearchResults({ results, query, loading, agents, onNavigateToRes
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* Row 1: Agent name, channel badge, timestamp */}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate text-[11px] font-semibold text-[color:var(--text)]">
|
||||
<span className="truncate text-[11px] font-semibold text-foreground">
|
||||
{agent?.name ?? "Unknown Agent"}
|
||||
</span>
|
||||
<span
|
||||
@@ -162,18 +162,18 @@ export function SearchResults({ results, query, loading, agents, onNavigateToRes
|
||||
<ChannelIcon size={10} weight="bold" />
|
||||
{config.label}
|
||||
</span>
|
||||
<span className="ml-auto whitespace-nowrap shrink-0 font-mono text-[11px] text-[color:var(--muted)]">
|
||||
<span className="ml-auto whitespace-nowrap shrink-0 font-mono text-[11px] text-muted-foreground">
|
||||
{formatMessageTime(message.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Row 2: Subject */}
|
||||
<div className="mt-0.5 truncate text-[13px] font-semibold text-[color:var(--text)]">
|
||||
<div className="mt-0.5 truncate text-[13px] font-semibold text-foreground">
|
||||
{highlightMatch(message.subject, query)}
|
||||
</div>
|
||||
|
||||
{/* Row 3: Body snippet */}
|
||||
<div className="mt-0.5 line-clamp-2 break-words text-[13px] leading-relaxed text-[color:var(--muted)]">
|
||||
<div className="mt-0.5 line-clamp-2 break-words text-[13px] leading-relaxed text-muted-foreground">
|
||||
{highlightMatch(message.body, query)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user