Lead the files browser's folders with a tinted type icon
Each folder row in /admin/files now opens with its type as an icon in a box tinted with its own hue (Bots ember, Workspaces teal, Computers cobalt, Media violet, MCP servers moss, Database amber), so the picker reads at a glance and the row's width goes to the name. The box carries the type as its title, so hovering still names it.
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
Bot,
|
||||||
|
Database,
|
||||||
|
Images,
|
||||||
|
Layers,
|
||||||
|
Monitor,
|
||||||
|
Plug,
|
||||||
|
type LucideIcon,
|
||||||
|
} from "lucide-react";
|
||||||
|
import type { StorageVolume } from "@/src/utils/storage-volumes";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The icon each volume's folder rows lead with in the files browser's
|
||||||
|
* picker, imported one by one (never by name: a string icon ships every
|
||||||
|
* icon). Route-local so the shared, server-safe storage utils stay free
|
||||||
|
* of lucide.
|
||||||
|
*/
|
||||||
|
export const StorageVolumeIcons: Record<StorageVolume, LucideIcon> = {
|
||||||
|
bots: Bot,
|
||||||
|
workspaces: Layers,
|
||||||
|
computers: Monitor,
|
||||||
|
media: Images,
|
||||||
|
mcp: Plug,
|
||||||
|
db: Database,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tinted box each volume icon sits in: a soft wash and icon color from
|
||||||
|
* the hue palette, one hue per volume so the picker reads at a glance.
|
||||||
|
* Static class names only, so Tailwind keeps them.
|
||||||
|
*/
|
||||||
|
export const StorageVolumeIconClasses: Record<StorageVolume, string> = {
|
||||||
|
bots: "bg-hue-ember/15 text-hue-ember dark:bg-hue-ember/25",
|
||||||
|
workspaces: "bg-hue-teal/15 text-hue-teal dark:bg-hue-teal/25",
|
||||||
|
computers: "bg-hue-cobalt/15 text-hue-cobalt dark:bg-hue-cobalt/25",
|
||||||
|
media: "bg-hue-violet/15 text-hue-violet dark:bg-hue-violet/25",
|
||||||
|
mcp: "bg-hue-moss/15 text-hue-moss dark:bg-hue-moss/25",
|
||||||
|
db: "bg-hue-amber/15 text-hue-amber dark:bg-hue-amber/25",
|
||||||
|
};
|
||||||
@@ -3,6 +3,10 @@ import Span from "@moduletrace/twui/layout/Span";
|
|||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
import type { StorageSpecOption } from "@/src/types";
|
import type { StorageSpecOption } from "@/src/types";
|
||||||
import { storageSpecHref, storageVolumeLabel } from "@/src/utils/storage-volumes";
|
import { storageSpecHref, storageVolumeLabel } from "@/src/utils/storage-volumes";
|
||||||
|
import {
|
||||||
|
StorageVolumeIconClasses,
|
||||||
|
StorageVolumeIcons,
|
||||||
|
} from "../(data)/storage-volume-icons";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
option: StorageSpecOption;
|
option: StorageSpecOption;
|
||||||
@@ -12,14 +16,19 @@ type Props = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* One folder in the files browser's picker: the bot, workspace, computer,
|
* One folder in the files browser's picker: the bot, workspace, computer,
|
||||||
* media library, MCP server or database it is, named the way the crew
|
* media library, MCP server or database it is, its type an icon in a tinted
|
||||||
* names it.
|
* box and its name beside it. The box carries the type as its title, so
|
||||||
|
* hovering names it without spending the row's width on a label.
|
||||||
*/
|
*/
|
||||||
export default function FilesVolumeRow({ option, selected, onOpen }: Props) {
|
export default function FilesVolumeRow({ option, selected, onOpen }: Props) {
|
||||||
|
const volume = option.spec.volume;
|
||||||
|
const Icon = StorageVolumeIcons[volume];
|
||||||
|
const type_label = storageVolumeLabel({ volume });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={storageSpecHref({ spec: option.spec })}
|
href={storageSpecHref({ spec: option.spec })}
|
||||||
title={`${storageVolumeLabel({ volume: option.spec.volume })}: ${option.label}`}
|
title={`${type_label}: ${option.label}`}
|
||||||
aria-current={selected ? "page" : undefined}
|
aria-current={selected ? "page" : undefined}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -31,9 +40,17 @@ export default function FilesVolumeRow({ option, selected, onOpen }: Props) {
|
|||||||
selected && "bg-ink/5 hover:bg-ink/5",
|
selected && "bg-ink/5 hover:bg-ink/5",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Span className="truncate text-[13px] font-bold leading-5">{option.label}</Span>
|
<Span
|
||||||
<Span className="ml-auto shrink-0 text-[12px] leading-4 text-ink-secondary">
|
title={type_label}
|
||||||
{storageVolumeLabel({ volume: option.spec.volume })}
|
className={twMerge(
|
||||||
|
"size-6 shrink-0 flex items-center justify-center rounded-default-sm",
|
||||||
|
StorageVolumeIconClasses[volume],
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Icon size={14} aria-hidden />
|
||||||
|
</Span>
|
||||||
|
<Span className="min-w-0 flex-1 truncate text-[13px] font-bold leading-5">
|
||||||
|
{option.label}
|
||||||
</Span>
|
</Span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user