11"use client" ;
22
33import * as React from "react" ;
4- import { LoaderCircle } from "lucide-react" ;
4+ import { ChevronLeft , Download , ExternalLink , LoaderCircle , Trash2 } from "lucide-react" ;
55import { useTranslations } from "next-intl" ;
66
7- import { ChevronLeft } from "lucide-react" ;
8-
9- import { ExternalLink } from "@/components/animate-ui/icons/external-link" ;
10- import { Download } from "@/components/animate-ui/icons/download" ;
11- import { Trash2 } from "@/components/animate-ui/icons/trash-2" ;
127import { formatBytes , formatDateTime , resolveFileExtension , resolveFileIcon } from "@/shared/lib/file-display" ;
138import type { FilePreviewState } from "@/features/files/hooks/use-file-preview" ;
149import { Button } from "@/components/ui/button" ;
1510import { resolveFileProcessingBadge , resolveFileProcessingToneClass } from "@/shared/lib/file-processing" ;
1611import type { FileObjectDTO } from "@/shared/api/file.types" ;
1712import { Avatar , AvatarImage , AvatarFallback } from "@/components/ui/avatar" ;
1813import { useAppLocale } from "@/i18n/app-i18n-provider" ;
14+ import { cn } from "@/lib/utils" ;
1915
2016type ContentHeaderProps = {
2117 file : FileObjectDTO | null ;
@@ -52,10 +48,10 @@ export function ContentHeader({
5248 onDeleteRequest,
5349 onToggleRagOptOut,
5450} : ContentHeaderProps ) {
51+ const tCommon = useTranslations ( "common.actions" ) ;
5552 const t = useTranslations ( "files" ) ;
5653 const tStatus = useTranslations ( "files.status" ) ;
5754 const { locale } = useAppLocale ( ) ;
58- const [ hoveredAction , setHoveredAction ] = React . useState < "open" | "download" | "delete" | null > ( null ) ;
5955
6056 if ( ! file ) {
6157 return null ;
@@ -65,6 +61,7 @@ export function ContentHeader({
6561 const fileTypeLabel = resolveRawFileTypeLabel ( file ) ;
6662 const isReady = preview . status === "ready" ;
6763 const thumbnail = isReady && preview . isImage ? preview . objectURL : null ;
64+ const avatarFallback = file . fileName . slice ( 0 , 2 ) . toUpperCase ( ) || "?" ;
6865 const processingBadge = resolveFileProcessingBadge ( {
6966 fileCategory : file . fileCategory ,
7067 processingStatus : file . processingStatus ,
@@ -80,15 +77,23 @@ export function ContentHeader({
8077 < div className = "flex min-w-0 shrink-0 items-center justify-between gap-4 border-b border-border/40 px-3 py-4 md:px-6" >
8178 < div className = "flex min-w-0 flex-1 items-center gap-3" >
8279 { onBack ? (
83- < Button type = "button" variant = "ghost" size = "icon" className = "size-7 shrink-0 md:hidden" onClick = { onBack } >
80+ < Button
81+ type = "button"
82+ variant = "ghost"
83+ size = "icon"
84+ className = "-ml-1 size-8 shrink-0 rounded-md text-muted-foreground hover:bg-muted hover:text-foreground md:hidden"
85+ aria-label = { tCommon ( "back" ) }
86+ onClick = { onBack }
87+ >
8488 < ChevronLeft className = "size-4" />
8589 </ Button >
8690 ) : null }
87- < div className = "flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-md bg-background/70" >
91+
92+ < div className = { cn ( "flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-md bg-background/70" , onBack && "hidden md:flex" ) } >
8893 { thumbnail ? (
8994 < Avatar className = "h-7 w-7 rounded-md" >
9095 < AvatarImage src = { thumbnail } alt = { file . fileName } />
91- < AvatarFallback className = "text-base font-medium rounded-md bg-background text-foreground" > { file . fileName . charAt ( 0 ) . toUpperCase ( ) + file . fileName . charAt ( 1 ) . toUpperCase ( ) } </ AvatarFallback >
96+ < AvatarFallback className = "rounded-md bg-background text-base font-medium text- foreground" > { avatarFallback } </ AvatarFallback >
9297 </ Avatar >
9398 ) : (
9499 React . createElement ( fileIcon , { className : "size-5 text-muted-foreground" } )
@@ -106,25 +111,31 @@ export function ContentHeader({
106111 { formatBytes ( file . sizeBytes ) }
107112 </ p >
108113 < span
109- className = { `inline-flex rounded-md border px-1.5 py-0.5 text-[10px] font-medium ${ resolveFileProcessingToneClass ( processingBadge . tone ) } ` }
114+ className = { cn (
115+ "inline-flex rounded-md border px-1.5 py-0.5 text-[10px] font-medium" ,
116+ resolveFileProcessingToneClass ( processingBadge . tone ) ,
117+ ) }
110118 title = { processingBadge . detail }
111119 >
112120 { processingBadge . label }
113121 </ span >
114122 { file . embedStatus === "ready" ? (
115- < button
123+ < Button
116124 type = "button"
125+ variant = "ghost"
126+ size = "sm"
117127 onClick = { ( ) => onToggleRagOptOut ( file . fileID , file . ragOptOut ) }
118128 title = { file . ragOptOut ? t ( "rag.disabledTitle" ) : t ( "rag.enabledTitle" ) }
119- className = { `inline-flex items-center gap-0.5 rounded-md border px-1.5 py-0.5 text-[10px] font-medium transition-colors ${
129+ className = { cn (
130+ "h-auto gap-0.5 rounded-md border px-1.5 py-0.5 text-[10px] font-medium shadow-none" ,
120131 file . ragOptOut
121132 ? "border-border/50 text-muted-foreground/60 hover:border-border hover:text-muted-foreground"
122- : "border-emerald-500/30 bg-emerald-50/50 text-emerald-700 hover:bg-emerald-50 dark:border-emerald-500/20 dark:bg-emerald-950/20 dark:text-emerald-400"
123- } ` }
133+ : "border-primary/25 bg-primary/10 text-primary hover:bg-primary/15" ,
134+ ) }
124135 >
125136 < span > ⚡</ span >
126137 < span > { file . ragOptOut ? t ( "rag.disabled" ) : t ( "rag.enabled" ) } </ span >
127- </ button >
138+ </ Button >
128139 ) : null }
129140 </ div >
130141 </ div >
@@ -140,10 +151,8 @@ export function ContentHeader({
140151 disabled = { ! isReady }
141152 aria-label = { t ( "actions.open" ) }
142153 title = { t ( "actions.open" ) }
143- onMouseEnter = { ( ) => setHoveredAction ( "open" ) }
144- onMouseLeave = { ( ) => setHoveredAction ( ( current ) => ( current === "open" ? null : current ) ) }
145154 >
146- < ExternalLink className = "size-3.5" strokeWidth = { 1.6 } animate = { hoveredAction === "open" ? "default" : false } />
155+ < ExternalLink className = "size-3.5" strokeWidth = { 1.6 } />
147156 </ Button >
148157 < Button
149158 type = "button"
@@ -154,10 +163,8 @@ export function ContentHeader({
154163 disabled = { ! isReady }
155164 aria-label = { t ( "actions.download" ) }
156165 title = { t ( "actions.download" ) }
157- onMouseEnter = { ( ) => setHoveredAction ( "download" ) }
158- onMouseLeave = { ( ) => setHoveredAction ( ( current ) => ( current === "download" ? null : current ) ) }
159166 >
160- < Download className = "size-3.5" strokeWidth = { 1.6 } animate = { hoveredAction === "download" ? "default-loop" : false } />
167+ < Download className = "size-3.5" strokeWidth = { 1.6 } />
161168 </ Button >
162169 < Button
163170 type = "button"
@@ -168,10 +175,8 @@ export function ContentHeader({
168175 disabled = { deleting }
169176 aria-label = { t ( "actions.delete" ) }
170177 title = { t ( "actions.delete" ) }
171- onMouseEnter = { ( ) => setHoveredAction ( "delete" ) }
172- onMouseLeave = { ( ) => setHoveredAction ( ( current ) => ( current === "delete" ? null : current ) ) }
173178 >
174- { deleting ? < LoaderCircle className = "size-3.5 animate-spin" strokeWidth = { 1.6 } /> : < Trash2 className = "size-3.5" strokeWidth = { 1.6 } animate = { hoveredAction === "delete" ? "default" : false } /> }
179+ { deleting ? < LoaderCircle className = "size-3.5 animate-spin" strokeWidth = { 1.6 } /> : < Trash2 className = "size-3.5" strokeWidth = { 1.6 } /> }
175180 </ Button >
176181 </ div >
177182 </ div >
0 commit comments