| 197 | }; |
| 198 | |
| 199 | export const FileTreeFile = ({ |
| 200 | path, |
| 201 | name, |
| 202 | icon, |
| 203 | className, |
| 204 | children, |
| 205 | ...props |
| 206 | }: FileTreeFileProps) => { |
| 207 | const { selectedPath, onSelect } = useContext(FileTreeContext); |
| 208 | const isSelected = selectedPath === path; |
| 209 | |
| 210 | const handleClick = useCallback(() => { |
| 211 | onSelect?.(path); |
| 212 | }, [onSelect, path]); |
| 213 | |
| 214 | const handleKeyDown = useCallback( |
| 215 | (e: React.KeyboardEvent) => { |
| 216 | if (e.key === "Enter" || e.key === " ") { |
| 217 | onSelect?.(path); |
| 218 | } |
| 219 | }, |
| 220 | [onSelect, path] |
| 221 | ); |
| 222 | |
| 223 | const fileContextValue = useMemo(() => ({ name, path }), [name, path]); |
| 224 | |
| 225 | return ( |
| 226 | <FileTreeFileContext.Provider value={fileContextValue}> |
| 227 | <div |
| 228 | className={cn( |
| 229 | "flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50", |
| 230 | isSelected && "bg-muted", |
| 231 | className |
| 232 | )} |
| 233 | onClick={handleClick} |
| 234 | onKeyDown={handleKeyDown} |
| 235 | role="treeitem" |
| 236 | tabIndex={0} |
| 237 | {...props} |
| 238 | > |
| 239 | {children ?? ( |
| 240 | <> |
| 241 | {/* Spacer for alignment */} |
| 242 | <span className="size-4" /> |
| 243 | <FileTreeIcon> |
| 244 | {icon ?? <FileIcon className="size-4 text-muted-foreground" />} |
| 245 | </FileTreeIcon> |
| 246 | <FileTreeName>{name}</FileTreeName> |
| 247 | </> |
| 248 | )} |
| 249 | </div> |
| 250 | </FileTreeFileContext.Provider> |
| 251 | ); |
| 252 | }; |
| 253 | |
| 254 | export type FileTreeIconProps = HTMLAttributes<HTMLSpanElement>; |
| 255 | |