diff options
| author | David Hill <[email protected]> | 2026-02-17 18:23:04 +0000 |
|---|---|---|
| committer | David Hill <[email protected]> | 2026-02-17 18:23:04 +0000 |
| commit | a685e7a805454110d92ed4da5a3799a15ea1bcb9 (patch) | |
| tree | 2de6f2118ae06d5de65390109e5c5c29a538cb50 | |
| parent | ce7484b4f5c3de1b83db4223052bdf9ce4c0cfb9 (diff) | |
| download | opencode-a685e7a805454110d92ed4da5a3799a15ea1bcb9.tar.gz opencode-a685e7a805454110d92ed4da5a3799a15ea1bcb9.zip | |
tui: show monochrome file icons by default in tree view, revealing colors on hover to reduce visual clutter and help users focus on code content
| -rw-r--r-- | packages/app/src/components/file-tree.tsx | 28 | ||||
| -rw-r--r-- | packages/ui/src/components/file-icon.css | 32 | ||||
| -rw-r--r-- | packages/ui/src/components/file-icon.tsx | 18 |
3 files changed, 73 insertions, 5 deletions
diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx index 758f5a83f..1f09642ac 100644 --- a/packages/app/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -447,12 +447,13 @@ export default function FileTree(props: { }) return ( - <div class={`flex flex-col gap-0.5 ${props.class ?? ""}`}> + <div data-component="filetree" class={`flex flex-col gap-0.5 ${props.class ?? ""}`}> <For each={nodes()}> {(node) => { const expanded = () => file.tree.state(node.path)?.expanded ?? false const deep = () => deeps().get(node.path) ?? -1 const kind = () => visibleKind(node, kinds(), marks()) + const active = () => !!kind() && !node.ignored return ( <Switch> @@ -530,7 +531,30 @@ export default function FileTree(props: { onClick={() => props.onFileClick?.(node)} > <div class="w-4 shrink-0" /> - <FileIcon node={node} class="text-icon-weak size-4" /> + <Switch> + <Match when={node.ignored}> + <FileIcon + node={node} + class="size-4 filetree-icon filetree-icon--mono" + style="color: var(--icon-weak-base)" + mono + /> + </Match> + <Match when={active()}> + <FileIcon + node={node} + class="size-4 filetree-icon filetree-icon--mono" + style={kindTextColor(kind()!)} + mono + /> + </Match> + <Match when={!node.ignored}> + <span class="filetree-iconpair size-4"> + <FileIcon node={node} class="size-4 filetree-icon filetree-icon--color" /> + <FileIcon node={node} class="size-4 filetree-icon filetree-icon--mono" mono /> + </span> + </Match> + </Switch> </FileTreeNode> </FileTreeNodeTooltip> </Match> diff --git a/packages/ui/src/components/file-icon.css b/packages/ui/src/components/file-icon.css index e650f6dc7..078352096 100644 --- a/packages/ui/src/components/file-icon.css +++ b/packages/ui/src/components/file-icon.css @@ -3,3 +3,35 @@ width: 16px; height: 16px; } + +/* + File tree: show monochrome weak icons by default. + On hover, show the original file-type colors. +*/ +[data-component="filetree"] .filetree-icon--mono { + color: var(--icon-base); +} + +[data-component="filetree"] .filetree-iconpair { + position: relative; + display: inline-flex; + width: 16px; + height: 16px; +} + +[data-component="filetree"] .filetree-iconpair [data-component="file-icon"] { + position: absolute; + inset: 0; +} + +[data-component="filetree"] .filetree-iconpair .filetree-icon--color { + opacity: 0; +} + +[data-component="filetree"]:hover .filetree-iconpair .filetree-icon--color { + opacity: 1; +} + +[data-component="filetree"]:hover .filetree-iconpair .filetree-icon--mono { + opacity: 0; +} diff --git a/packages/ui/src/components/file-icon.tsx b/packages/ui/src/components/file-icon.tsx index cadb7ba83..405cbe163 100644 --- a/packages/ui/src/components/file-icon.tsx +++ b/packages/ui/src/components/file-icon.tsx @@ -1,16 +1,20 @@ import type { Component, JSX } from "solid-js" -import { createMemo, splitProps } from "solid-js" +import { createMemo, splitProps, Show } from "solid-js" import sprite from "./file-icons/sprite.svg" import type { IconName } from "./file-icons/types" +let filter = 0 + export type FileIconProps = JSX.GSVGAttributes<SVGSVGElement> & { node: { path: string; type: "file" | "directory" } expanded?: boolean + mono?: boolean } export const FileIcon: Component<FileIconProps> = (props) => { - const [local, rest] = splitProps(props, ["node", "class", "classList", "expanded"]) + const [local, rest] = splitProps(props, ["node", "class", "classList", "expanded", "mono"]) const name = createMemo(() => chooseIconName(local.node.path, local.node.type, local.expanded || false)) + const id = `file-icon-mono-${filter++}` return ( <svg data-component="file-icon" @@ -20,7 +24,15 @@ export const FileIcon: Component<FileIconProps> = (props) => { [local.class ?? ""]: !!local.class, }} > - <use href={`${sprite}#${name()}`} /> + <Show when={local.mono}> + <defs> + <filter id={id} color-interpolation-filters="sRGB"> + <feFlood flood-color="currentColor" result="flood" /> + <feComposite in="flood" in2="SourceAlpha" operator="in" /> + </filter> + </defs> + </Show> + <use href={`${sprite}#${name()}`} filter={local.mono ? `url(#${id})` : undefined} /> </svg> ) } |
