├── bun.lockb ├── app ├── favicon.ico ├── page.tsx ├── layout.tsx └── globals.css ├── public ├── icons │ ├── compositor.jpg │ ├── 21Arachnid.avif │ ├── LSilverPaint.avif │ ├── Paint_Black.avif │ ├── Paint_Blue.avif │ ├── Paint_White.avif │ ├── Red_Paint_R2.avif │ ├── Interior_Black.avif │ ├── Interior_Cream.avif │ ├── Interior_White.avif │ ├── Paint_StealthGrey.avif │ ├── ui_swat_s-wheel-y.png │ └── ui_swat_whl_tempest.avif ├── model_3_exterior_stealth_grey_main.jpg ├── model_3_exterior_stealth_grey_side.jpg ├── model_3_exterior_stealth_grey_perspective_back.jpg └── model_s_exterior_stealth_grey_tempest_wheels_main.jpg ├── next.config.mjs ├── postcss.config.mjs ├── lib └── utils.ts ├── .eslintrc.json ├── components ├── ui │ ├── skeleton.tsx │ ├── collapsible.tsx │ ├── label.tsx │ ├── separator.tsx │ ├── input.tsx │ ├── sonner.tsx │ ├── switch.tsx │ ├── badge.tsx │ ├── tooltip.tsx │ ├── toggle.tsx │ ├── avatar.tsx │ ├── toggle-group.tsx │ ├── button.tsx │ ├── card.tsx │ ├── breadcrumb.tsx │ ├── animated-grid-pattern.tsx │ ├── sheet.tsx │ ├── context-menu.tsx │ ├── dropdown-menu.tsx │ └── sidebar.tsx ├── desktop │ ├── Sidebar.tsx │ ├── EmptySpaceContextMenu.tsx │ ├── Tree.tsx │ ├── DragAndDropArea.tsx │ ├── DesktopWrapper.tsx │ ├── DraggableWindow.tsx │ ├── ItemModal.tsx │ └── DesktopItem.tsx └── magicui │ ├── dot-pattern.tsx │ ├── blur-fade.tsx │ ├── blur-fade-text.tsx │ └── dock.tsx ├── context └── desktop.tsx ├── components.json ├── .gitignore ├── hooks ├── use-mobile.tsx └── useDesktop.tsx ├── tsconfig.json ├── types └── desktop.tsx ├── package.json ├── README.md ├── config └── desktop.tsx └── tailwind.config.ts /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/bun.lockb -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/icons/compositor.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/compositor.jpg -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /public/icons/21Arachnid.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/21Arachnid.avif -------------------------------------------------------------------------------- /public/icons/LSilverPaint.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/LSilverPaint.avif -------------------------------------------------------------------------------- /public/icons/Paint_Black.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/Paint_Black.avif -------------------------------------------------------------------------------- /public/icons/Paint_Blue.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/Paint_Blue.avif -------------------------------------------------------------------------------- /public/icons/Paint_White.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/Paint_White.avif -------------------------------------------------------------------------------- /public/icons/Red_Paint_R2.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/Red_Paint_R2.avif -------------------------------------------------------------------------------- /public/icons/Interior_Black.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/Interior_Black.avif -------------------------------------------------------------------------------- /public/icons/Interior_Cream.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/Interior_Cream.avif -------------------------------------------------------------------------------- /public/icons/Interior_White.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/Interior_White.avif -------------------------------------------------------------------------------- /public/icons/Paint_StealthGrey.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/Paint_StealthGrey.avif -------------------------------------------------------------------------------- /public/icons/ui_swat_s-wheel-y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/ui_swat_s-wheel-y.png -------------------------------------------------------------------------------- /public/icons/ui_swat_whl_tempest.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/icons/ui_swat_whl_tempest.avif -------------------------------------------------------------------------------- /public/model_3_exterior_stealth_grey_main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/model_3_exterior_stealth_grey_main.jpg -------------------------------------------------------------------------------- /public/model_3_exterior_stealth_grey_side.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/model_3_exterior_stealth_grey_side.jpg -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import DesktopWrapper from "@/components/desktop/DesktopWrapper"; 2 | 3 | export default function Home() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /public/model_3_exterior_stealth_grey_perspective_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/model_3_exterior_stealth_grey_perspective_back.jpg -------------------------------------------------------------------------------- /public/model_s_exterior_stealth_grey_tempest_wheels_main.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/slowfound/desktop-simulator/HEAD/public/model_s_exterior_stealth_grey_tempest_wheels_main.jpg -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "plugins": ["@typescript-eslint"], 4 | "rules": { 5 | "@typescript-eslint/no-unused-vars": "off", 6 | "import/no-unresolved": "error", 7 | "import/named": "off" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /context/desktop.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from "react"; 2 | import type { DesktopContextValue } from "@/hooks/useDesktop"; 3 | 4 | export const DesktopContext = createContext(null); 5 | 6 | export const useDesktopContext = () => { 7 | const context = useContext(DesktopContext); 8 | if (!context) { 9 | throw new Error("useDesktopContext must be used within a DesktopProvider"); 10 | } 11 | return context; 12 | }; 13 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /hooks/use-mobile.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | const MOBILE_BREAKPOINT = 768 4 | 5 | export function useIsMobile() { 6 | const [isMobile, setIsMobile] = React.useState(undefined) 7 | 8 | React.useEffect(() => { 9 | const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) 10 | const onChange = () => { 11 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 12 | } 13 | mql.addEventListener("change", onChange) 14 | setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) 15 | return () => mql.removeEventListener("change", onChange) 16 | }, []) 17 | 18 | return !!isMobile 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /types/desktop.tsx: -------------------------------------------------------------------------------- 1 | export interface Item { 2 | id: string; 3 | name: string; 4 | type: "file" | "folder"; 5 | content?: Item[]; 6 | link?: string; 7 | locationId: string | null; 8 | path: string; 9 | } 10 | 11 | export interface WindowItem { 12 | id: string; 13 | itemId: string; 14 | item: Item; 15 | position: { x: number; y: number }; 16 | size: { width: number; height: number }; 17 | isMinimized: boolean; 18 | } 19 | 20 | export interface ModalState { 21 | open: boolean; 22 | type: "new" | "edit" | "rename" | "copy" | "cut" | null; 23 | itemType: "file" | "folder" | null; 24 | locationId: string | null; 25 | item: Item | null; 26 | } 27 | 28 | export interface DropResult { 29 | id: string; 30 | } 31 | 32 | export interface ClipboardItem { 33 | item: Item; 34 | operation: "copy" | "cut"; 35 | } 36 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ) 29 | Separator.displayName = SeparatorPrimitive.Root.displayName 30 | 31 | export { Separator } 32 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner } from "sonner" 5 | 6 | type ToasterProps = React.ComponentProps 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme() 10 | 11 | return ( 12 | 28 | ) 29 | } 30 | 31 | export { Toaster } 32 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | 3 | import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"; 4 | import type { Metadata } from "next"; 5 | import "./globals.css"; 6 | import { ThemeProvider } from "next-themes"; 7 | import { Toaster } from "@/components/ui/sonner"; 8 | import { Inter } from "next/font/google"; 9 | 10 | const inter = Inter({ subsets: ["latin"] }); 11 | 12 | export const metadata: Metadata = { 13 | title: "Desktop Simulator", 14 | description: "Drag and drop folders and files on a desktop simulator.", 15 | }; 16 | 17 | export default function RootLayout({ 18 | children, 19 | }: Readonly<{ 20 | children: React.ReactNode; 21 | }>) { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | {children} 29 | 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as SwitchPrimitives from "@radix-ui/react-switch"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | 26 | )); 27 | Switch.displayName = SwitchPrimitives.Root.displayName; 28 | 29 | export { Switch }; 30 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const TooltipProvider = TooltipPrimitive.Provider 9 | 10 | const Tooltip = TooltipPrimitive.Root 11 | 12 | const TooltipTrigger = TooltipPrimitive.Trigger 13 | 14 | const TooltipContent = React.forwardRef< 15 | React.ElementRef, 16 | React.ComponentPropsWithoutRef 17 | >(({ className, sideOffset = 4, ...props }, ref) => ( 18 | 19 | 28 | 29 | )) 30 | TooltipContent.displayName = TooltipPrimitive.Content.displayName 31 | 32 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 33 | -------------------------------------------------------------------------------- /components/desktop/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | import * as React from "react"; 4 | import { File, Folder } from "lucide-react"; 5 | 6 | import { 7 | Sidebar, 8 | SidebarContent, 9 | SidebarGroup, 10 | SidebarGroupContent, 11 | SidebarGroupLabel, 12 | SidebarMenu, 13 | SidebarMenuBadge, 14 | SidebarMenuButton, 15 | SidebarMenuItem, 16 | SidebarMenuSub, 17 | SidebarRail, 18 | } from "@/components/ui/sidebar"; 19 | import Tree from "@/components/desktop/Tree"; 20 | import type { Item } from "@/types/desktop"; 21 | 22 | export default function AppSidebar({ 23 | items, 24 | }: React.ComponentProps & { items: Item[] }) { 25 | return ( 26 | 27 | 28 | 29 | Files 30 | 31 | 32 | {items.map((item) => ( 33 | 34 | ))} 35 | 36 | 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /components/magicui/dot-pattern.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { useId } from "react"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | interface DotPatternProps { 7 | width?: any; 8 | height?: any; 9 | x?: any; 10 | y?: any; 11 | cx?: any; 12 | cy?: any; 13 | cr?: any; 14 | className?: string; 15 | [key: string]: any; 16 | } 17 | export function DotPattern({ 18 | width = 16, 19 | height = 16, 20 | x = 0, 21 | y = 0, 22 | cx = 1, 23 | cy = 1, 24 | cr = 1, 25 | className, 26 | ...props 27 | }: DotPatternProps) { 28 | const id = useId(); 29 | 30 | return ( 31 | 54 | ); 55 | } 56 | 57 | export default DotPattern; 58 | -------------------------------------------------------------------------------- /components/ui/toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TogglePrimitive from "@radix-ui/react-toggle" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const toggleVariants = cva( 10 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground", 11 | { 12 | variants: { 13 | variant: { 14 | default: "bg-transparent", 15 | outline: 16 | "border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground", 17 | }, 18 | size: { 19 | default: "h-9 px-3", 20 | sm: "h-8 px-2", 21 | lg: "h-10 px-3", 22 | }, 23 | }, 24 | defaultVariants: { 25 | variant: "default", 26 | size: "default", 27 | }, 28 | } 29 | ) 30 | 31 | const Toggle = React.forwardRef< 32 | React.ElementRef, 33 | React.ComponentPropsWithoutRef & 34 | VariantProps 35 | >(({ className, variant, size, ...props }, ref) => ( 36 | 41 | )) 42 | 43 | Toggle.displayName = TogglePrimitive.Root.displayName 44 | 45 | export { Toggle, toggleVariants } 46 | -------------------------------------------------------------------------------- /components/desktop/EmptySpaceContextMenu.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { File, Folder } from "lucide-react"; 5 | import { 6 | ContextMenu, 7 | ContextMenuTrigger, 8 | ContextMenuContent, 9 | ContextMenuItem, 10 | } from "@/components/ui/context-menu"; 11 | import { useDesktopContext } from "@/context/desktop"; 12 | 13 | interface EmptySpaceContextMenuProps { 14 | locationId?: string | null; 15 | children: React.ReactNode; 16 | } 17 | 18 | export const EmptySpaceContextMenu: React.FC = ({ 19 | locationId, 20 | children, 21 | }) => { 22 | const { handleCreateFile, handleCreateFolder, clipboard, pasteItem } = 23 | useDesktopContext(); 24 | 25 | return ( 26 | 27 | 28 |
e.stopPropagation()}>{children}
29 |
30 | 31 | handleCreateFile(locationId)}> 32 | 33 | New File 34 | 35 | handleCreateFolder(locationId)}> 36 | 37 | New Folder 38 | 39 | pasteItem(locationId)} 41 | disabled={!clipboard} 42 | > 43 | 44 | Paste 45 | 46 | 47 |
48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /components/desktop/Tree.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from "react"; 2 | import { 3 | SidebarMenuItem, 4 | SidebarMenuButton, 5 | SidebarMenuSub, 6 | } from "@/components/ui/sidebar"; 7 | import { 8 | Collapsible, 9 | CollapsibleContent, 10 | CollapsibleTrigger, 11 | } from "@/components/ui/collapsible"; 12 | import { ChevronRight, Folder, File } from "lucide-react"; 13 | 14 | import type { Item } from "@/types/desktop"; 15 | 16 | interface TreeProps { 17 | item: Item; 18 | } 19 | 20 | const Tree: React.FC = memo(({ item }) => { 21 | if (item.type === "folder" && item.content) { 22 | return ( 23 | 24 | 25 | 26 | 27 | 28 | 29 | {item.name} 30 | 31 | 32 | 33 | 34 | {item.content.map((subItem) => ( 35 | 36 | ))} 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | 44 | return ( 45 | 46 | 47 | {item.name} 48 | 49 | ); 50 | }); 51 | 52 | // Assigning displayName to the memoized component 53 | Tree.displayName = "Tree"; 54 | 55 | export default Tree; 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "desktop-simulator", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-avatar": "^1.1.1", 13 | "@radix-ui/react-collapsible": "^1.1.1", 14 | "@radix-ui/react-context-menu": "^2.2.2", 15 | "@radix-ui/react-dialog": "^1.1.2", 16 | "@radix-ui/react-dropdown-menu": "^2.1.2", 17 | "@radix-ui/react-icons": "^1.3.1", 18 | "@radix-ui/react-label": "^2.1.0", 19 | "@radix-ui/react-separator": "^1.1.0", 20 | "@radix-ui/react-slot": "^1.1.0", 21 | "@radix-ui/react-switch": "^1.1.1", 22 | "@radix-ui/react-toggle": "^1.1.0", 23 | "@radix-ui/react-toggle-group": "^1.1.0", 24 | "@radix-ui/react-tooltip": "^1.1.3", 25 | "class-variance-authority": "^0.7.0", 26 | "clsx": "^2.1.1", 27 | "framer-motion": "^11.11.11", 28 | "lucide-react": "^0.454.0", 29 | "next": "14.2.16", 30 | "next-themes": "^0.4.3", 31 | "react": "^18", 32 | "react-dnd": "^16.0.1", 33 | "react-dnd-html5-backend": "^16.0.1", 34 | "react-dom": "^18", 35 | "sonner": "^1.7.0", 36 | "tailwind-merge": "^2.5.4", 37 | "tailwindcss-animate": "^1.0.7", 38 | "uuid": "^11.0.2" 39 | }, 40 | "devDependencies": { 41 | "@types/node": "^20", 42 | "@types/react": "^18", 43 | "@types/react-dom": "^18", 44 | "eslint": "^8", 45 | "eslint-config-next": "14.2.16", 46 | "postcss": "^8", 47 | "tailwindcss": "^3.4.1", 48 | "typescript": "^5" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Image](https://github.com/user-attachments/assets/55d687c0-63d7-4d0d-b644-a88724673b86) 2 | 3 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 4 | 5 | ## Getting Started 6 | 7 | First, run the development server: 8 | 9 | ```bash 10 | npm run dev 11 | # or 12 | yarn dev 13 | # or 14 | pnpm dev 15 | # or 16 | bun dev 17 | ``` 18 | 19 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 20 | 21 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 33 | 34 | ## Deploy on Vercel 35 | 36 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 37 | 38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 39 | -------------------------------------------------------------------------------- /components/magicui/blur-fade.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AnimatePresence, motion, useInView, Variants } from "framer-motion"; 4 | import { useRef } from "react"; 5 | 6 | interface BlurFadeProps { 7 | children: React.ReactNode; 8 | className?: string; 9 | variant?: { 10 | hidden: { y: number }; 11 | visible: { y: number }; 12 | }; 13 | duration?: number; 14 | delay?: number; 15 | yOffset?: number; 16 | inView?: boolean; 17 | inViewMargin?: string; 18 | blur?: string; 19 | } 20 | const BlurFade = ({ 21 | children, 22 | className, 23 | variant, 24 | duration = 0.4, 25 | delay = 0, 26 | yOffset = 6, 27 | inView = false, 28 | inViewMargin = "-50px", 29 | blur = "6px", 30 | }: BlurFadeProps) => { 31 | const ref = useRef(null); 32 | const inViewResult = useInView(ref, { 33 | once: true, 34 | margin: inViewMargin as any, 35 | }); 36 | const isInView = !inView || inViewResult; 37 | const defaultVariants: Variants = { 38 | hidden: { y: yOffset, opacity: 0, filter: `blur(${blur})` }, 39 | visible: { y: -yOffset, opacity: 1, filter: `blur(0px)` }, 40 | }; 41 | const combinedVariants = variant || defaultVariants; 42 | return ( 43 | 44 | 57 | {children} 58 | 59 | 60 | ); 61 | }; 62 | 63 | export default BlurFade; 64 | -------------------------------------------------------------------------------- /components/desktop/DragAndDropArea.tsx: -------------------------------------------------------------------------------- 1 | import { useDrop } from "react-dnd"; 2 | import { DesktopItem } from "@/components/desktop/DesktopItem"; 3 | import type { Item } from "@/types/desktop"; 4 | import { useDesktopContext } from "@/context/desktop"; 5 | 6 | interface DragDropAreaProps { 7 | items: Item[]; 8 | locationId?: string | null; 9 | parentPath: string; 10 | } 11 | 12 | export const DragDropArea: React.FC = ({ 13 | items, 14 | locationId = null, 15 | parentPath, 16 | }) => { 17 | const { 18 | pasteItem, 19 | openWindow, 20 | setModalState, 21 | deleteItem, 22 | handleCopy, 23 | handleCut, 24 | } = useDesktopContext(); 25 | 26 | const [{ isOver, canDrop }, drop] = useDrop({ 27 | accept: "ITEM", 28 | drop: () => ({ id: locationId ?? null }), 29 | collect: (monitor) => ({ 30 | isOver: monitor.isOver({ shallow: true }), 31 | canDrop: monitor.canDrop(), 32 | }), 33 | }); 34 | 35 | return ( 36 |
{ 38 | drop(node as unknown as HTMLElement); 39 | }} 40 | className="relative w-full h-full" 41 | > 42 | {isOver && canDrop && ( 43 |
44 | )} 45 |
52 | {items.map((item) => ( 53 | 54 | ))} 55 |
56 |
57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /components/ui/toggle-group.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group" 5 | import { type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | import { toggleVariants } from "@/components/ui/toggle" 9 | 10 | const ToggleGroupContext = React.createContext< 11 | VariantProps 12 | >({ 13 | size: "default", 14 | variant: "default", 15 | }) 16 | 17 | const ToggleGroup = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef & 20 | VariantProps 21 | >(({ className, variant, size, children, ...props }, ref) => ( 22 | 27 | 28 | {children} 29 | 30 | 31 | )) 32 | 33 | ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName 34 | 35 | const ToggleGroupItem = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef & 38 | VariantProps 39 | >(({ className, children, variant, size, ...props }, ref) => { 40 | const context = React.useContext(ToggleGroupContext) 41 | 42 | return ( 43 | 54 | {children} 55 | 56 | ) 57 | }) 58 | 59 | ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName 60 | 61 | export { ToggleGroup, ToggleGroupItem } 62 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLParagraphElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |

53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |

61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /config/desktop.tsx: -------------------------------------------------------------------------------- 1 | import type { Item } from "@/types/desktop"; 2 | 3 | // Helper function to generate a random ID 4 | const generateId = (): string => { 5 | return Math.random().toString(36).substr(2, 9); 6 | }; 7 | 8 | // Helper function to generate a random name 9 | const generateName = (type: "folder" | "file"): string => { 10 | const prefixes = { 11 | folder: ["Folder", "Directory", "Project", "Archive"], 12 | file: ["Document", "Image", "Note", "Report"], 13 | }; 14 | const prefix = 15 | prefixes[type][Math.floor(Math.random() * prefixes[type].length)]; 16 | return `${prefix}_${Math.floor(Math.random() * 1000)}`; 17 | }; 18 | 19 | // Helper function to generate a random link (for files) 20 | const generateLink = (): string => { 21 | return `https://example.com/${Math.random().toString(36).substr(2, 9)}`; 22 | }; 23 | 24 | // Recursive function to generate a random file system structure 25 | const generateFileSystem = ( 26 | depth: number, 27 | maxDepth: number, 28 | minItemsPerFolder: number, 29 | maxItemsPerFolder: number, 30 | locationId: string | null = null, 31 | parentPath: string = "/desktop" 32 | ): Item[] => { 33 | if (depth > maxDepth) return []; 34 | 35 | const items: Item[] = []; 36 | const numItems = 37 | Math.floor(Math.random() * maxItemsPerFolder) + minItemsPerFolder; 38 | 39 | for (let i = 0; i < numItems; i++) { 40 | const isFolder = Math.random() > 0.5; // 50% chance to be a folder 41 | const id = generateId(); 42 | const name = generateName(isFolder ? "folder" : "file"); 43 | const path = `${parentPath}/${name}`; 44 | 45 | const item: Item = { 46 | id, 47 | name, 48 | type: isFolder ? "folder" : "file", 49 | locationId, 50 | path, 51 | }; 52 | 53 | if (isFolder) { 54 | item.content = generateFileSystem( 55 | depth + 1, 56 | maxDepth, 57 | minItemsPerFolder, 58 | maxItemsPerFolder, 59 | id, 60 | path 61 | ); 62 | } else { 63 | item.link = generateLink(); 64 | } 65 | 66 | items.push(item); 67 | } 68 | 69 | return items; 70 | }; 71 | 72 | // Function to generate the initial file system with random data 73 | export const generateInitialItems = (): Item[] => { 74 | return generateFileSystem(2, 6, 4, 8); // Adjust depth and maxItemsPerFolder as needed 75 | }; 76 | 77 | // Example usage 78 | export const initialItems: Item[] = generateInitialItems(); 79 | -------------------------------------------------------------------------------- /components/magicui/blur-fade-text.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { AnimatePresence, motion, Variants } from "framer-motion"; 5 | import { useMemo } from "react"; 6 | 7 | interface BlurFadeTextProps { 8 | text: string; 9 | className?: string; 10 | variant?: { 11 | hidden: { y: number }; 12 | visible: { y: number }; 13 | }; 14 | duration?: number; 15 | characterDelay?: number; 16 | delay?: number; 17 | yOffset?: number; 18 | animateByCharacter?: boolean; 19 | } 20 | const BlurFadeText = ({ 21 | text, 22 | className, 23 | variant, 24 | characterDelay = 0.03, 25 | delay = 0, 26 | yOffset = 8, 27 | animateByCharacter = false, 28 | }: BlurFadeTextProps) => { 29 | const defaultVariants: Variants = { 30 | hidden: { y: yOffset, opacity: 0, filter: "blur(8px)" }, 31 | visible: { y: -yOffset, opacity: 1, filter: "blur(0px)" }, 32 | }; 33 | const combinedVariants = variant || defaultVariants; 34 | const characters = useMemo(() => Array.from(text), [text]); 35 | 36 | if (animateByCharacter) { 37 | return ( 38 |
39 | 40 | {characters.map((char, i) => ( 41 | 55 | {char} 56 | 57 | ))} 58 | 59 |
60 | ); 61 | } 62 | 63 | return ( 64 |
65 | 66 | 78 | {text} 79 | 80 | 81 |
82 | ); 83 | }; 84 | 85 | export default BlurFadeText; 86 | -------------------------------------------------------------------------------- /components/desktop/DesktopWrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { DndProvider } from "react-dnd"; 5 | import { HTML5Backend } from "react-dnd-html5-backend"; 6 | import { DesktopContext } from "@/context/desktop"; 7 | import AppSidebar from "@/components/desktop/Sidebar"; 8 | import { DragDropArea } from "@/components/desktop/DragAndDropArea"; 9 | import { ItemModal } from "@/components/desktop/ItemModal"; 10 | import { DraggableWindow } from "@/components/desktop/DraggableWindow"; 11 | import { EmptySpaceContextMenu } from "@/components/desktop/EmptySpaceContextMenu"; 12 | import DotPattern from "@/components/magicui/dot-pattern"; 13 | import { cn } from "@/lib/utils"; 14 | import { useDesktop } from "@/hooks/useDesktop"; 15 | 16 | const DesktopWrapper: React.FC = () => { 17 | const desktopValues = useDesktop(); 18 | 19 | return ( 20 | 21 | 22 |
23 | 24 | 25 |
26 | 31 | 32 | item.locationId === null 35 | )} 36 | parentPath="/desktop" 37 | /> 38 | 39 | {desktopValues.windows.map((windowItem) => ( 40 | 41 | 42 | 47 | 48 | 49 | ))} 50 | 51 | 52 |
53 |
54 |
55 |
56 |
57 | ); 58 | }; 59 | 60 | export default DesktopWrapper; 61 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer utilities { 6 | .text-balance { 7 | text-wrap: balance; 8 | } 9 | } 10 | 11 | * { 12 | -webkit-user-select: none; /* Safari */ 13 | -moz-user-select: none; /* Firefox */ 14 | -ms-user-select: none; /* IE10+/Edge */ 15 | user-select: none; /* Standard */ 16 | } 17 | 18 | .context-menu { 19 | z-index: 1002; /* Higher than modals */ 20 | } 21 | 22 | @layer base { 23 | :root { 24 | --background: 0 0% 100%; 25 | --foreground: 0 0% 3.9%; 26 | --card: 0 0% 100%; 27 | --card-foreground: 0 0% 3.9%; 28 | --popover: 0 0% 100%; 29 | --popover-foreground: 0 0% 3.9%; 30 | --primary: 0 0% 9%; 31 | --primary-foreground: 0 0% 98%; 32 | --secondary: 0 0% 96.1%; 33 | --secondary-foreground: 0 0% 9%; 34 | --muted: 0 0% 96.1%; 35 | --muted-foreground: 0 0% 45.1%; 36 | --accent: 0 0% 96.1%; 37 | --accent-foreground: 0 0% 9%; 38 | --destructive: 0 84.2% 60.2%; 39 | --destructive-foreground: 0 0% 98%; 40 | --border: 0 0% 89.8%; 41 | --input: 0 0% 89.8%; 42 | --ring: 0 0% 3.9%; 43 | --chart-1: 12 76% 61%; 44 | --chart-2: 173 58% 39%; 45 | --chart-3: 197 37% 24%; 46 | --chart-4: 43 74% 66%; 47 | --chart-5: 27 87% 67%; 48 | --radius: 0.5rem; 49 | --sidebar-background: 0 0% 98%; 50 | --sidebar-foreground: 240 5.3% 26.1%; 51 | --sidebar-primary: 240 5.9% 10%; 52 | --sidebar-primary-foreground: 0 0% 98%; 53 | --sidebar-accent: 240 4.8% 95.9%; 54 | --sidebar-accent-foreground: 240 5.9% 10%; 55 | --sidebar-border: 220 13% 91%; 56 | --sidebar-ring: 217.2 91.2% 59.8%; 57 | } 58 | .dark { 59 | --background: 0 0% 3.9%; 60 | --foreground: 0 0% 98%; 61 | --card: 0 0% 0%; 62 | --card-foreground: 0 0% 98%; 63 | --popover: 0 0% 3.9%; 64 | --popover-foreground: 0 0% 98%; 65 | --primary: 0 0% 98%; 66 | --primary-foreground: 0 0% 9%; 67 | --secondary: 0 0% 14.9%; 68 | --secondary-foreground: 0 0% 98%; 69 | --muted: 0 0% 14.9%; 70 | --muted-foreground: 0 0% 63.9%; 71 | --accent: 0 0% 14.9%; 72 | --accent-foreground: 0 0% 98%; 73 | --destructive: 0 62.8% 30.6%; 74 | --destructive-foreground: 0 0% 98%; 75 | --border: 0 0% 14.9%; 76 | --input: 0 0% 14.9%; 77 | --ring: 0 0% 83.1%; 78 | --chart-1: 220 70% 50%; 79 | --chart-2: 160 60% 45%; 80 | --chart-3: 30 80% 55%; 81 | --chart-4: 280 65% 60%; 82 | --chart-5: 340 75% 55%; 83 | --sidebar-background: 240 5.9% 10%; 84 | --sidebar-foreground: 240 4.8% 95.9%; 85 | --sidebar-primary: 224.3 76.3% 48%; 86 | --sidebar-primary-foreground: 0 0% 100%; 87 | --sidebar-accent: 240 3.7% 15.9%; 88 | --sidebar-accent-foreground: 240 4.8% 95.9%; 89 | --sidebar-border: 240 3.7% 15.9%; 90 | --sidebar-ring: 217.2 91.2% 59.8%; 91 | } 92 | } 93 | 94 | @layer base { 95 | * { 96 | @apply border-border; 97 | } 98 | body { 99 | @apply bg-background text-foreground; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /components/desktop/DraggableWindow.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useCallback, useEffect } from "react"; 2 | import { Minimize, X } from "lucide-react"; 3 | import type { WindowItem } from "@/types/desktop"; 4 | import { useDesktopContext } from "@/context/desktop"; 5 | 6 | interface DraggableWindowProps { 7 | windowItem: WindowItem; 8 | children: React.ReactNode; 9 | } 10 | 11 | export const DraggableWindow: React.FC = ({ 12 | windowItem, 13 | children, 14 | }) => { 15 | const { closeWindow, minimizeWindow, moveWindow } = useDesktopContext(); 16 | const [isDragging, setIsDragging] = useState(false); 17 | const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); 18 | 19 | const handleMouseDown = (e: React.MouseEvent) => { 20 | setIsDragging(true); 21 | setDragStart({ 22 | x: e.clientX - windowItem.position.x, 23 | y: e.clientY - windowItem.position.y, 24 | }); 25 | }; 26 | 27 | const handleMouseMove = useCallback( 28 | (e: MouseEvent) => { 29 | if (isDragging) { 30 | moveWindow(windowItem.id, { 31 | x: e.clientX - dragStart.x, 32 | y: e.clientY - dragStart.y, 33 | }); 34 | } 35 | }, 36 | [isDragging, dragStart, moveWindow, windowItem.id] 37 | ); 38 | 39 | const handleMouseUp = useCallback(() => { 40 | setIsDragging(false); 41 | }, []); 42 | 43 | useEffect(() => { 44 | if (isDragging) { 45 | window.addEventListener("mousemove", handleMouseMove); 46 | window.addEventListener("mouseup", handleMouseUp); 47 | } 48 | return () => { 49 | window.removeEventListener("mousemove", handleMouseMove); 50 | window.removeEventListener("mouseup", handleMouseUp); 51 | }; 52 | }, [isDragging, handleMouseMove, handleMouseUp]); 53 | 54 | if (windowItem.isMinimized) return null; 55 | 56 | return ( 57 |
67 |
71 |

{windowItem.item.name}

72 |
73 | 79 | 85 |
86 |
87 |
91 | {children} 92 |
93 |
94 | ); 95 | }; 96 | -------------------------------------------------------------------------------- /components/magicui/dock.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { cva, type VariantProps } from "class-variance-authority"; 5 | import { motion, useMotionValue, useSpring, useTransform } from "framer-motion"; 6 | import React, { PropsWithChildren, useRef } from "react"; 7 | 8 | export interface DockProps extends VariantProps { 9 | className?: string; 10 | magnification?: number; 11 | distance?: number; 12 | children: React.ReactNode; 13 | } 14 | 15 | const DEFAULT_MAGNIFICATION = 60; 16 | const DEFAULT_DISTANCE = 140; 17 | 18 | const dockVariants = cva( 19 | "mx-auto w-max h-full p-2 flex items-end rounded-full border" 20 | ); 21 | 22 | const Dock = React.forwardRef( 23 | ( 24 | { 25 | className, 26 | children, 27 | magnification = DEFAULT_MAGNIFICATION, 28 | distance = DEFAULT_DISTANCE, 29 | ...props 30 | }, 31 | ref 32 | ) => { 33 | const mousex = useMotionValue(Infinity); 34 | 35 | const renderChildren = () => { 36 | return React.Children.map(children, (child: any) => { 37 | if (React.isValidElement(child)) { 38 | return React.cloneElement(child, { 39 | mousex, 40 | magnification, 41 | distance, 42 | } as DockIconProps); 43 | } 44 | return child; 45 | }); 46 | }; 47 | 48 | return ( 49 | mousex.set(e.pageX)} 52 | onMouseLeave={() => mousex.set(Infinity)} 53 | {...props} 54 | className={cn(dockVariants({ className }))} 55 | > 56 | {renderChildren()} 57 | 58 | ); 59 | } 60 | ); 61 | 62 | Dock.displayName = "Dock"; 63 | 64 | export interface DockIconProps { 65 | size?: number; 66 | magnification?: number; 67 | distance?: number; 68 | mousex?: any; 69 | className?: string; 70 | children?: React.ReactNode; 71 | props?: PropsWithChildren; 72 | } 73 | 74 | const DockIcon = ({ 75 | size, 76 | magnification = DEFAULT_MAGNIFICATION, 77 | distance = DEFAULT_DISTANCE, 78 | mousex, 79 | className, 80 | children, 81 | ...props 82 | }: DockIconProps) => { 83 | const ref = useRef(null); 84 | 85 | const distanceCalc = useTransform(mousex, (val: number) => { 86 | const bounds = ref.current?.getBoundingClientRect() ?? { x: 0, width: 0 }; 87 | return val - bounds.x - bounds.width / 2; 88 | }); 89 | 90 | let widthSync = useTransform( 91 | distanceCalc, 92 | [-distance, 0, distance], 93 | [40, magnification, 40] 94 | ); 95 | 96 | let width = useSpring(widthSync, { 97 | mass: 0.1, 98 | stiffness: 150, 99 | damping: 12, 100 | }); 101 | 102 | return ( 103 | 112 | {children} 113 | 114 | ); 115 | }; 116 | 117 | DockIcon.displayName = "DockIcon"; 118 | 119 | export { Dock, DockIcon, dockVariants }; 120 | -------------------------------------------------------------------------------- /components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cn } from "@/lib/utils"; 4 | import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons"; 5 | 6 | const Breadcrumb = React.forwardRef< 7 | HTMLElement, 8 | React.ComponentPropsWithoutRef<"nav"> & { 9 | separator?: React.ReactNode; 10 | } 11 | >(({ ...props }, ref) =>