From d204e6fb84117301e808f7da10c23a19586448c5 Mon Sep 17 00:00:00 2001 From: Jake Howell Date: Fri, 27 Feb 2026 22:53:09 +1100 Subject: [PATCH] feat: implement file icons in `` (#22369) This pull-request takes our icons shown in the sidebar tree and shows them alongside the names of the files in the `Source Code` page of our templates. Also does a quick de-mui of this page. image --- .../TemplateFiles/TemplateFileIcon.tsx | 77 +++++++++++++++ .../TemplateFiles/TemplateFileTree.tsx | 75 +------------- .../templates/TemplateFiles/TemplateFiles.tsx | 99 ++++++------------- 3 files changed, 112 insertions(+), 139 deletions(-) create mode 100644 site/src/modules/templates/TemplateFiles/TemplateFileIcon.tsx diff --git a/site/src/modules/templates/TemplateFiles/TemplateFileIcon.tsx b/site/src/modules/templates/TemplateFiles/TemplateFileIcon.tsx new file mode 100644 index 0000000000..3d9e5b874d --- /dev/null +++ b/site/src/modules/templates/TemplateFiles/TemplateFileIcon.tsx @@ -0,0 +1,77 @@ +import { DockerIcon } from "components/Icons/DockerIcon"; +import { + BracesIcon, + FileCodeIcon, + FileIcon, + FolderIcon, + TerminalIcon, +} from "lucide-react"; +import type { ComponentProps, ElementType, FC } from "react"; + +const FileTypeTerraform: FC> = (props) => ( + + file_type_terraform + + + + + +); + +const FileTypeMarkdown: FC> = (props) => ( + + + + + +); + +export const getTemplateFileIcon = ( + filename: string, + isFolder: boolean, +): ElementType => { + if (isFolder) { + return FolderIcon; + } + if (filename.endsWith(".tf")) { + return FileTypeTerraform; + } + if (filename.endsWith(".md")) { + return FileTypeMarkdown; + } + if (filename.endsWith("Dockerfile")) { + return DockerIcon; + } + if (filename.endsWith(".sh")) { + return TerminalIcon; + } + if (filename.endsWith(".json")) { + return BracesIcon; + } + if (filename.endsWith(".yaml") || filename.endsWith(".yml")) { + return FileCodeIcon; + } + // Default icon for files without a specific icon. + return FileIcon; +}; diff --git a/site/src/modules/templates/TemplateFiles/TemplateFileTree.tsx b/site/src/modules/templates/TemplateFiles/TemplateFileTree.tsx index 929b781135..ba6692bbf0 100644 --- a/site/src/modules/templates/TemplateFiles/TemplateFileTree.tsx +++ b/site/src/modules/templates/TemplateFiles/TemplateFileTree.tsx @@ -2,24 +2,10 @@ import { css } from "@emotion/react"; import Menu from "@mui/material/Menu"; import MenuItem from "@mui/material/MenuItem"; import { SimpleTreeView, TreeItem } from "@mui/x-tree-view"; -import { DockerIcon } from "components/Icons/DockerIcon"; -import { - BracesIcon, - ChevronDownIcon, - ChevronRightIcon, - FileCodeIcon, - FileIcon, - FolderIcon, - TerminalIcon, -} from "lucide-react"; -import { - type CSSProperties, - type ElementType, - type FC, - type JSX, - useState, -} from "react"; +import { ChevronDownIcon, ChevronRightIcon } from "lucide-react"; +import { type CSSProperties, type FC, type JSX, useState } from "react"; import type { FileTree } from "utils/filetree"; +import { getTemplateFileIcon } from "./TemplateFileIcon"; const isFolder = (content?: FileTree | string): content is FileTree => typeof content === "object"; @@ -94,29 +80,11 @@ export const TemplateFileTree: FC = ({ ); } - let icon: ElementType | undefined; - if (isFolder(content)) { - icon = FolderIcon; - } else if (filename.endsWith(".tf")) { - icon = FileTypeTerraform; - } else if (filename.endsWith(".md")) { - icon = FileTypeMarkdown; - } else if (filename.endsWith("Dockerfile")) { - icon = DockerIcon; - } else if (filename.endsWith(".sh")) { - icon = TerminalIcon; - } else if (filename.endsWith(".json")) { - icon = BracesIcon; - } else if (filename.endsWith(".yaml") || filename.endsWith(".yml")) { - icon = FileCodeIcon; - } else { - // Default icon for files without a specific icon. - icon = FileIcon; - } + const templateFileIcon = getTemplateFileIcon(filename, isFolder(content)); return ( = ({ ); }; -const FileTypeTerraform: FC = () => ( - - file_type_terraform - - - - - -); - -const FileTypeMarkdown: FC = () => ( - - - - - -); - const expandablePaths = (path: string) => { const paths = path.split("/"); const result = []; diff --git a/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx b/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx index e23a427620..1a213f4753 100644 --- a/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx +++ b/site/src/modules/templates/TemplateFiles/TemplateFiles.tsx @@ -1,13 +1,13 @@ -import { type Interpolation, type Theme, useTheme } from "@emotion/react"; import { SyntaxHighlighter } from "components/SyntaxHighlighter/SyntaxHighlighter"; import set from "lodash/set"; import { EditIcon } from "lucide-react"; import { linkToTemplate, useLinks } from "modules/navigation"; import { type FC, useCallback, useMemo } from "react"; -import { Link } from "react-router"; +import { Link as RouterLink } from "react-router"; import { cn } from "utils/cn"; import type { FileTree } from "utils/filetree"; import type { TemplateVersionFiles } from "utils/templateVersion"; +import { getTemplateFileIcon } from "./TemplateFileIcon"; import { TemplateFileTree } from "./TemplateFileTree"; interface TemplateFilesProps { @@ -29,7 +29,6 @@ export const TemplateFiles: FC = ({ baseFiles, }) => { const getLink = useLinks(); - const theme = useTheme(); const fileInfo = useCallback( (filename: string) => { @@ -61,8 +60,8 @@ export const TemplateFiles: FC = ({ return (
-
-
+
+
{ @@ -75,9 +74,7 @@ export const TemplateFiles: FC = ({ const hasDiff = fileInfo(path).hasDiff; return ( - + {filename} ); @@ -85,42 +82,42 @@ export const TemplateFiles: FC = ({ />
-
+
{Object.keys(currentFiles) .sort((a, b) => a.localeCompare(b)) .map((filename) => { + const TemplateFileIcon = getTemplateFileIcon(filename, false); const info = fileInfo(filename); return ( -
-
- - {filename} - +
+
+
+ + + {filename} + +
-
- + Edit - +
{ const numberOfLines = (content: string) => { return content.split("\n").length; }; - -const styles = { - sidebar: (theme) => ({ - width: 240, - flexShrink: 0, - borderRadius: 8, - overflow: "auto", - border: `1px solid ${theme.palette.divider}`, - padding: "4px 0", - position: "sticky", - top: 32, - }), - - files: { - display: "flex", - flexDirection: "column", - gap: 16, - flex: 1, - }, - - filePanel: (theme) => ({ - borderRadius: 8, - border: `1px solid ${theme.palette.divider}`, - overflow: "hidden", - }), - - fileHeader: (theme) => ({ - padding: "8px 16px", - borderBottom: `1px solid ${theme.palette.divider}`, - fontSize: 13, - fontWeight: 500, - display: "flex", - gap: 8, - alignItems: "center", - }), -} satisfies Record>;