瀏覽代碼

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

David Hill 2 月之前
父節點
當前提交
a685e7a805

+ 26 - 2
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>

+ 32 - 0
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;
+}

+ 15 - 3
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>
   )
 }