Skip to content

Commit 804412f

Browse files
authored
Merge pull request #12 from IgorD-lab/feat/finder
feat: added portfolio window, moved window types from window componen…
2 parents a8ab204 + 20f0e0b commit 804412f

File tree

7 files changed

+119
-15
lines changed

7 files changed

+119
-15
lines changed

package-lock.json

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"dependencies": {
1515
"@gsap/react": "^2.1.2",
1616
"@tailwindcss/vite": "^4.1.18",
17+
"clsx": "^2.1.1",
1718
"dayjs": "^1.11.19",
1819
"gsap": "^3.14.2",
1920
"immer": "^11.1.4",

src/App.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import gsap from 'gsap';
22
import { Draggable } from 'gsap/Draggable';
33

44
import { Navbar, Welcome, Dock } from '#components';
5-
import { Terminal, Safari, Resume } from '#windows';
5+
import { Terminal, Safari, Resume, Finder } from '#windows';
66

77
gsap.registerPlugin(Draggable);
88

@@ -16,6 +16,7 @@ const App = () => {
1616
<Terminal />
1717
<Safari />
1818
<Resume />
19+
<Finder />
1920
</main>
2021
);
2122
};

src/store/location.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
11
import { create } from 'zustand';
22
import { immer } from 'zustand/middleware/immer';
33
import { locations } from '#constants';
4-
5-
// Extract the type of a single location (e.g., the 'work' or 'trash' object)
6-
type Location = (typeof locations)[keyof typeof locations];
4+
import { FinderItem } from '#types'; // Use your new alias/path
75

86
interface LocationState {
9-
activeLocation: Location; // Strictly typed to your constants
10-
setActiveLocation: (location: Location | null) => void;
7+
activeLocation: FinderItem;
8+
setActiveLocation: (location: FinderItem | null) => void;
119
resetActiveLocation: () => void;
1210
}
1311

14-
const DEFAULT_LOCATION = locations.work;
12+
const DEFAULT_LOCATION = locations.work as FinderItem;
1513

16-
// Global state management for file explorer/projects
1714
const useLocationStore = create<LocationState>()(
1815
immer((set) => ({
1916
activeLocation: DEFAULT_LOCATION,
20-
21-
setActiveLocation: (location = null) =>
17+
setActiveLocation: (location) =>
2218
set((state) => {
23-
if (location) {
24-
state.activeLocation = location;
25-
}
19+
if (location) state.activeLocation = location as FinderItem;
2620
}),
27-
2821
resetActiveLocation: () =>
2922
set((state) => {
3023
state.activeLocation = DEFAULT_LOCATION;

src/types/index.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// TODO: move all types here
2+
export type WindowID =
3+
| 'finder'
4+
| 'contact'
5+
| 'resume'
6+
| 'safari'
7+
| 'photos'
8+
| 'terminal'
9+
| 'txtfile'
10+
| 'imgfile';
11+
12+
export interface FinderItem {
13+
id: number | string;
14+
name: string;
15+
icon: string;
16+
kind: 'folder' | 'file';
17+
type?: string;
18+
fileType?: string;
19+
position?: string;
20+
imageUrl?: string;
21+
href?: string;
22+
children?: FinderItem[];
23+
}

src/windows/Finder.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { WindowControls } from '#components';
2+
import { locations } from '#constants';
3+
import WindowWrapper from '#hoc/WindowWrapper';
4+
import useLocationStore from '#store/location';
5+
import useWindowStore from '#store/window';
6+
import { clsx } from 'clsx';
7+
import { Search } from 'lucide-react';
8+
import { FinderItem, WindowID } from '#types';
9+
10+
const Finder = () => {
11+
const { openWindow } = useWindowStore();
12+
const { activeLocation, setActiveLocation } = useLocationStore();
13+
14+
const openItem = (item: FinderItem) => {
15+
if (item.fileType === 'pdf') return openWindow('resume');
16+
if (item.kind === 'folder') return setActiveLocation(item);
17+
if (['fig', 'url'].includes(item.fileType ?? '') && item.href)
18+
return window.open(item.href, '_blank');
19+
20+
const windowId = `${item.fileType}${item.kind}` as WindowID;
21+
openWindow(windowId, item);
22+
};
23+
24+
const renderList = (name: string, items: FinderItem[]) => (
25+
<div>
26+
<h3>{name}</h3>
27+
28+
<ul>
29+
{items.map((item) => (
30+
<li
31+
key={item.id}
32+
onClick={() => setActiveLocation(item)}
33+
className={clsx(
34+
item.id === activeLocation?.id ? 'active' : 'not-active',
35+
)}
36+
>
37+
<img src={item.icon} className="w-4" alt={item.name} />
38+
<p className="text-sm font-medium truncate">{item.name}</p>
39+
</li>
40+
))}
41+
</ul>
42+
</div>
43+
);
44+
45+
return (
46+
<>
47+
<div id="window-header">
48+
<WindowControls target="finder" />
49+
<Search className="icon" />
50+
</div>{' '}
51+
<div className="bg-white flex h-full">
52+
<div className="sidebar">
53+
<ul>
54+
{renderList('Favorites', Object.values(locations) as FinderItem[])}
55+
</ul>
56+
<ul>
57+
{renderList(
58+
'Work',
59+
(locations.work.children as FinderItem[]) || [],
60+
)}
61+
</ul>
62+
</div>
63+
64+
<ul className="content">
65+
{(activeLocation?.children ?? []).map((item) => (
66+
<li
67+
key={item.id}
68+
className={item.position}
69+
onClick={() => openItem(item)}
70+
>
71+
{' '}
72+
<img src={item.icon} alt={item.name} />
73+
<p> {item.name} </p>
74+
</li>
75+
))}
76+
</ul>
77+
</div>
78+
</>
79+
);
80+
};
81+
82+
const FinderWindow = WindowWrapper(Finder, 'finder');
83+
84+
export default FinderWindow;

src/windows/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Terminal from '#windows/Terminal';
22
import Safari from '#windows/Safari';
33
import Resume from '#windows/Resume';
4+
import Finder from '#windows/Finder';
45

5-
export { Terminal, Safari, Resume };
6+
export { Terminal, Safari, Resume, Finder };

0 commit comments

Comments
 (0)