Skip to content

Commit e1b4045

Browse files
Implement File and Folder components with highlighted and comment props
- Add custom File, Folder, and Files components with grid layout for proper icon alignment - Add highlighted prop to visually emphasize files/folders - Add comment prop to display explanatory text on the right - Add custom CSS for highlighting and comment styling - Fix icon alignment so file icons align with folder icons Co-Authored-By: Colton Berry <[email protected]>
1 parent 77f3f19 commit e1b4045

File tree

5 files changed

+214
-0
lines changed

5 files changed

+214
-0
lines changed

fern/assets/styles.css

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1164,3 +1164,26 @@ h1, h2, h3 {
11641164
}
11651165
}
11661166
/*** END -- HIGHLIGHTED FRAME STYLING ***/
1167+
1168+
/*** FILES COMPONENT STYLING ***/
1169+
.files-row-highlighted {
1170+
background-color: rgba(255, 235, 59, 0.15);
1171+
}
1172+
1173+
@media (prefers-color-scheme: dark) {
1174+
.files-row-highlighted {
1175+
background-color: rgba(255, 235, 59, 0.1);
1176+
}
1177+
}
1178+
1179+
.files-row-comment {
1180+
color: #6b7280;
1181+
opacity: 0.8;
1182+
}
1183+
1184+
@media (prefers-color-scheme: dark) {
1185+
.files-row-comment {
1186+
color: #9ca3af;
1187+
}
1188+
}
1189+
/*** END -- FILES COMPONENT STYLING ***/

fern/components/File.tsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React from 'react';
2+
3+
export interface FileProps {
4+
name: string;
5+
href?: string;
6+
highlighted?: boolean;
7+
comment?: string;
8+
className?: string;
9+
}
10+
11+
export const File: React.FC<FileProps> = ({
12+
name,
13+
href,
14+
highlighted = false,
15+
comment,
16+
className = ''
17+
}) => {
18+
const baseClasses = "grid items-center gap-2 py-1 px-2 rounded transition-colors";
19+
const highlightedClasses = highlighted ? "files-row-highlighted" : "";
20+
const gridClasses = "grid-cols-[24px_24px_1fr_auto]";
21+
const combinedClasses = `${baseClasses} ${gridClasses} ${highlightedClasses} ${className}`.trim();
22+
23+
const formattedComment = comment ? (comment.startsWith('#') ? comment : `# ${comment}`) : null;
24+
25+
const content = (
26+
<>
27+
{/* Empty spacer for chevron column */}
28+
<div className="w-6" />
29+
30+
{/* File icon */}
31+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-file flex-shrink-0 text-(color::--grayscale-a11)">
32+
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/>
33+
<path d="M14 2v4a2 2 0 0 0 2 2h4"/>
34+
</svg>
35+
36+
{/* File name */}
37+
<span className="text-default text-sm font-mono">{name}</span>
38+
39+
{/* Comment */}
40+
{formattedComment && (
41+
<span
42+
className="files-row-comment text-xs font-mono whitespace-nowrap overflow-hidden text-ellipsis"
43+
title={formattedComment}
44+
>
45+
{formattedComment}
46+
</span>
47+
)}
48+
</>
49+
);
50+
51+
if (href) {
52+
return (
53+
<a
54+
href={href}
55+
className={`${combinedClasses} hover:underline cursor-pointer no-underline`}
56+
>
57+
{content}
58+
</a>
59+
);
60+
}
61+
62+
return (
63+
<div className={combinedClasses}>
64+
{content}
65+
</div>
66+
);
67+
};

fern/components/Files.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
3+
export interface FilesProps {
4+
children: React.ReactNode;
5+
className?: string;
6+
}
7+
8+
export const Files: React.FC<FilesProps> = ({ children, className = '' }) => {
9+
return (
10+
<div className={`space-y-1 ${className}`.trim()}>
11+
{children}
12+
</div>
13+
);
14+
};

fern/components/Folder.tsx

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import React, { useState } from 'react';
2+
3+
export interface FolderProps {
4+
name: string;
5+
defaultOpen?: boolean;
6+
href?: string;
7+
highlighted?: boolean;
8+
comment?: string;
9+
children?: React.ReactNode;
10+
className?: string;
11+
}
12+
13+
export const Folder: React.FC<FolderProps> = ({
14+
name,
15+
defaultOpen = false,
16+
href,
17+
highlighted = false,
18+
comment,
19+
children,
20+
className = ''
21+
}) => {
22+
const [isOpen, setIsOpen] = useState(defaultOpen);
23+
24+
const baseClasses = "grid items-center gap-2 py-1 px-2 rounded transition-colors";
25+
const highlightedClasses = highlighted ? "files-row-highlighted" : "";
26+
const gridClasses = "grid-cols-[24px_24px_1fr_auto]";
27+
const combinedClasses = `${baseClasses} ${gridClasses} ${highlightedClasses}`.trim();
28+
29+
const handleToggle = () => {
30+
setIsOpen(!isOpen);
31+
};
32+
33+
const formattedComment = comment ? (comment.startsWith('#') ? comment : `# ${comment}`) : null;
34+
35+
const folderIcon = isOpen ? (
36+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-folder-open flex-shrink-0 text-(color::--grayscale-a11)">
37+
<path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/>
38+
</svg>
39+
) : (
40+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="lucide lucide-folder flex-shrink-0 text-(color::--grayscale-a11)">
41+
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>
42+
</svg>
43+
);
44+
45+
const caretIcon = (
46+
<svg
47+
xmlns="http://www.w3.org/2000/svg"
48+
width="16"
49+
height="16"
50+
viewBox="0 0 24 24"
51+
fill="none"
52+
stroke="currentColor"
53+
strokeWidth="2"
54+
strokeLinecap="round"
55+
strokeLinejoin="round"
56+
className={`lucide lucide-chevron-right flex-shrink-0 text-(color::--grayscale-a11) transition-transform ${isOpen ? 'rotate-90' : ''}`}
57+
style={{ transition: 'transform 0.2s' }}
58+
>
59+
<path d="m9 18 6-6-6-6"/>
60+
</svg>
61+
);
62+
63+
const content = (
64+
<>
65+
{caretIcon}
66+
{folderIcon}
67+
<span className="text-default text-sm font-mono">{name}</span>
68+
{formattedComment && (
69+
<span
70+
className="files-row-comment text-xs font-mono whitespace-nowrap overflow-hidden text-ellipsis"
71+
title={formattedComment}
72+
>
73+
{formattedComment}
74+
</span>
75+
)}
76+
</>
77+
);
78+
79+
const header = href ? (
80+
<a
81+
href={href}
82+
className={`${combinedClasses} hover:underline cursor-pointer no-underline`}
83+
>
84+
{content}
85+
</a>
86+
) : (
87+
<button
88+
onClick={handleToggle}
89+
className={`${combinedClasses} w-full text-left cursor-pointer border-0 bg-transparent`}
90+
aria-expanded={isOpen}
91+
type="button"
92+
>
93+
{content}
94+
</button>
95+
);
96+
97+
return (
98+
<div className={className}>
99+
{header}
100+
{isOpen && children && (
101+
<div className="pl-6 space-y-0.5 mt-0.5">
102+
{children}
103+
</div>
104+
)}
105+
</div>
106+
);
107+
};

fern/components/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { File } from './File';
2+
export { Folder } from './Folder';
3+
export { Files } from './Files';

0 commit comments

Comments
 (0)