15 |
16 |
17 |
18 |
19 |
23 |
24 |
25 |
26 |
Task Node
27 |
89 |
90 | {/* definition of each one earlist start, latest start, earlist finish, latest finish */}
91 |
92 |
93 | -
94 | Earliest Start: The earliest time a task can start
95 |
96 | -
97 | Latest Start: The latest time a task can start without
98 | delaying the project
99 |
100 | -
101 | Earliest Finish: The earliest time a task can finish
102 |
103 | -
104 | Latest Finish: The latest time a task can finish
105 | without delaying the project
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | );
115 | }
116 |
--------------------------------------------------------------------------------
/src/components/PertDetails.tsx:
--------------------------------------------------------------------------------
1 | import { memo } from "react";
2 | import { ScrollArea } from "@/components/ui/scroll-area";
3 | import { usePert, setSelectedTask, Task } from "@@/lib/main";
4 | import { Separator } from "@/components/ui/separator";
5 | import { Input } from "./ui/input";
6 |
7 | export const PertDetails = memo(({ selectedTask }: { selectedTask: Task | null }) => {
8 | const { projectDuration, criticalPaths } = usePert();
9 |
10 | return (
11 |
12 |
13 |
Project Duration
14 |
{projectDuration} days
15 |
16 |
17 |
18 |
Critical Paths
19 |
20 | {criticalPaths.map((cp, index) => (
21 | -
22 | {cp.map((p, i) => (
23 |
24 | {p.text}
25 | {i < cp.length - 1 && " → "}
26 |
27 | ))}
28 |
29 | ))}
30 |
31 |
32 |
33 |
34 |
Select Task by Key
35 | setSelectedTask(e.target.value ?? null)}
39 | />
40 |
41 |
42 |
43 |
Selected Task
44 | {!selectedTask &&
Click on a task to view its details
}
45 | {selectedTask && (
46 |
47 |
{selectedTask.text}
48 |
Key: {selectedTask.key}
49 |
Duration: {selectedTask.duration} day(s)
50 |
Depends on: {selectedTask.dependsOn?.join(", ")}
51 |
Early Start: {selectedTask.earlyStart}
52 |
Early Finish: {selectedTask.earlyFinish}
53 |
Late Start: {selectedTask.lateStart}
54 |
Late Finish: {selectedTask.lateFinish}
55 |
Level: {selectedTask.level}
56 |
Critical: {selectedTask.critical ? "Yes" : "No"}
57 |
Free Float: {selectedTask.freeFloat}
58 |
Total Float: {selectedTask.totalFloat}
59 |
60 | )}
61 |
62 |
63 | );
64 | });
65 |
--------------------------------------------------------------------------------
/src/components/Sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { Card } from "@/components/ui/card";
2 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
3 |
4 | import { PertStyles, Task } from "@@/lib/main";
5 | import { memo, useState } from "react";
6 | import { StyleOptions } from "./StyleOptions";
7 | import { PertDetails } from "./PertDetails";
8 |
9 | type SidebarProps = PertStyles & {
10 | onStyleChange: (key: keyof PertStyles, value: any) => void;
11 | selectedTask: Task | null;
12 | };
13 |
14 | export const Sidebar = memo(
15 | ({ onStyleChange, selectedTask, ...styles }: SidebarProps) => {
16 | const [tab, setActiveTab] = useState("details");
17 |
18 | return (
19 |
20 |
26 |
27 |
28 | Styles
29 |
30 |
31 | Details
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | );
43 | }
44 | );
45 |
--------------------------------------------------------------------------------
/src/components/StyleOptions.tsx:
--------------------------------------------------------------------------------
1 | import { FontSize, PertStyles } from "@@/lib/main";
2 | import { memo, useCallback } from "react";
3 | import {
4 | Select,
5 | SelectContent,
6 | SelectItem,
7 | SelectTrigger,
8 | SelectValue,
9 | } from "@/components/ui/select";
10 |
11 | import { Label } from "@/components/ui/label";
12 | import { Switch } from "@/components/ui/switch";
13 | import { Slider } from "@/components/ui/slider";
14 | import { ScrollArea } from "@/components/ui/scroll-area";
15 | import { Color } from "./Color";
16 | import { CopyDrawer } from "./CopyDrawer";
17 | import { Separator } from "./ui/separator";
18 |
19 | const fontFamilies = [
20 | "Arial",
21 | "Roboto",
22 | "Helvetica",
23 | "Times New Roman",
24 | "sans-serif",
25 | "system-ui",
26 | ];
27 |
28 | const fontSizes: FontSize[] = ["sm", "md", "lg", "xl", "2xl", "3xl"];
29 |
30 | type StyleOptionsProps = PertStyles & {
31 | onStyleChange: (key: keyof PertStyles, value: any) => void;
32 | };
33 |
34 | export const StyleOptions = memo(({ onStyleChange, ...styles }: StyleOptionsProps) => {
35 | const handleChange = useCallback(
36 | (key: keyof PertStyles, value: any) => {
37 | onStyleChange(key, value);
38 | },
39 | [onStyleChange]
40 | );
41 |
42 | return (
43 |
44 |
45 |
46 |
47 |
Chart Properties
48 |
49 |
50 |
51 | {/* Grid Settings */}
52 |
53 |
Chart Settings
54 |
55 |
56 |
57 |
62 |
63 |
64 |
65 | handleChange("disableGrid", checked)}
69 | />
70 |
71 |
72 |
73 |
74 |
79 |
80 |
81 |
82 | {/* Task Node Settings */}
83 |
84 |
Task Node Settings
85 |
86 |
87 | handleChange("taskSize", value)}
93 | />
94 |
95 |
96 |
97 |
112 |
113 |
114 |
115 |
130 |
131 |
132 |
133 |
134 |
139 |
140 |
141 |
142 |
143 |
148 |
149 |
150 |
151 | {/* Border Settings */}
152 |
153 |
Border Settings
154 |
155 |
156 | handleChange("borderWidth", value)}
162 | />
163 |
164 |
165 |
166 | handleChange("selectedBorderWidth", value)}
172 | />
173 |
174 |
175 |
176 | handleChange("hoverBorderWidth", value)}
182 | />
183 |
184 |
185 |
186 |
187 |
188 |
193 |
194 |
195 |
196 |
197 |
198 |
203 |
204 |
205 |
206 | {/* Arrow Settings */}
207 |
208 |
Arrow Settings
209 |
210 |
211 | handleChange("arrowWidth", value)}
217 | />
218 |
219 |
220 |
221 |
222 |
223 |
228 |
229 |
230 |
231 |
232 |
233 |
238 |
239 |
240 |
241 | {/* Gap Settings */}
242 |
243 |
Gap Settings
244 |
245 |
246 |
252 | handleChange("gap", { ...styles.gap, x: value })
253 | }
254 | />
255 |
256 |
257 |
258 |
264 | handleChange("gap", { ...styles.gap, y: value })
265 | }
266 | />
267 |
268 |
269 |
270 |
271 |
272 | );
273 | });
274 |
--------------------------------------------------------------------------------
/src/components/TasksDrawer.tsx:
--------------------------------------------------------------------------------
1 | import { memo, useState } from "react";
2 | import {
3 | Drawer,
4 | DrawerContent,
5 | DrawerDescription,
6 | DrawerHeader,
7 | DrawerTitle,
8 | DrawerTrigger,
9 | } from "@/components/ui/drawer";
10 | import { Button } from "@/components/ui/button";
11 | import { Textarea } from "@/components/ui/textarea";
12 | import { TaskInput } from "@@/lib/main";
13 | import { initTasks } from "@/constants/initStates";
14 | import { AlertCircle } from "lucide-react";
15 |
16 | interface TasksDrawerProps {
17 | setTasks: (tasks: TaskInput[]) => void;
18 | }
19 |
20 | export const TasksDrawer = memo(({ setTasks }: TasksDrawerProps) => {
21 | const [jsonInput, setJsonInput] = useState(
22 | `[\n${initTasks.map((obj) => ` ${JSON.stringify(obj)}`).join(",\n")}\n]`
23 | );
24 | const [error, setError] = useState
(null);
25 |
26 | const handleJsonChange = (value: string) => {
27 | setJsonInput(value);
28 | setError(null);
29 |
30 | if (!value.trim()) {
31 | return;
32 | }
33 |
34 | try {
35 | const parsedJson = JSON.parse(value);
36 | if (!Array.isArray(parsedJson)) {
37 | throw new Error("Input must be an array of tasks");
38 | }
39 | setTasks(parsedJson);
40 | } catch (err) {
41 | setError(err instanceof Error ? err.message : "Invalid JSON format");
42 | }
43 | };
44 |
45 | return (
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | Edit Tasks
55 |
56 | Paste your tasks JSON array below. Example format:
57 | {`[ { "key": "1", "duration": 5, "text": "A", "dependsOn": ["8", "10", "11"] } ]`}
58 |
59 |
60 |
78 |
79 |
80 |
81 |
82 | );
83 | });
84 |
--------------------------------------------------------------------------------
/src/components/mode-toggle.tsx:
--------------------------------------------------------------------------------
1 | import { Moon, Sun } from "lucide-react";
2 | import { Button } from "@/components/ui/button";
3 | import { useTheme } from "@/components/theme-provider";
4 |
5 | export function ModeToggle() {
6 | const { setTheme, theme } = useTheme();
7 |
8 | return (
9 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from "react";
2 |
3 | type Theme = "dark" | "light" | "system";
4 |
5 | type ThemeProviderProps = {
6 | children: React.ReactNode;
7 | defaultTheme?: Theme;
8 | storageKey?: string;
9 | };
10 |
11 | type ThemeProviderState = {
12 | theme: Theme;
13 | setTheme: (theme: Theme) => void;
14 | };
15 |
16 | const initialState: ThemeProviderState = {
17 | theme: "system",
18 | setTheme: () => null,
19 | };
20 |
21 | const ThemeProviderContext = createContext(initialState);
22 |
23 | export function ThemeProvider({
24 | children,
25 | defaultTheme = "dark",
26 | storageKey = "vite-ui-theme",
27 | ...props
28 | }: ThemeProviderProps) {
29 | const [theme, setTheme] = useState(
30 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
31 | );
32 |
33 | useEffect(() => {
34 | const root = window.document.documentElement;
35 |
36 | root.classList.remove("light", "dark");
37 |
38 | if (theme === "system") {
39 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
40 | ? "dark"
41 | : "light";
42 |
43 | root.classList.add(systemTheme);
44 | return;
45 | }
46 |
47 | root.classList.add(theme);
48 | }, [theme]);
49 |
50 | const value = {
51 | theme,
52 | setTheme: (theme: Theme) => {
53 | localStorage.setItem(storageKey, theme);
54 | setTheme(theme);
55 | },
56 | };
57 |
58 | return (
59 |
60 | {children}
61 |
62 | );
63 | }
64 |
65 | export const useTheme = () => {
66 | const context = useContext(ThemeProviderContext);
67 |
68 | if (context === undefined)
69 | throw new Error("useTheme must be used within a ThemeProvider");
70 |
71 | return context;
72 | };
73 |
--------------------------------------------------------------------------------
/src/components/ui/alert.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 alertVariants = cva(
7 | "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/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 | HTMLDivElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
41 | ))
42 | CardTitle.displayName = "CardTitle"
43 |
44 | const CardDescription = React.forwardRef<
45 | HTMLDivElement,
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 |
--------------------------------------------------------------------------------
/src/components/ui/drawer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Drawer as DrawerPrimitive } from "vaul"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Drawer = ({
7 | shouldScaleBackground = true,
8 | ...props
9 | }: React.ComponentProps) => (
10 |
14 | )
15 | Drawer.displayName = "Drawer"
16 |
17 | const DrawerTrigger = DrawerPrimitive.Trigger
18 |
19 | const DrawerPortal = DrawerPrimitive.Portal
20 |
21 | const DrawerClose = DrawerPrimitive.Close
22 |
23 | const DrawerOverlay = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
34 |
35 | const DrawerContent = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, children, ...props }, ref) => (
39 |
40 |
41 |
49 |
50 | {children}
51 |
52 |
53 | ))
54 | DrawerContent.displayName = "DrawerContent"
55 |
56 | const DrawerHeader = ({
57 | className,
58 | ...props
59 | }: React.HTMLAttributes) => (
60 |
64 | )
65 | DrawerHeader.displayName = "DrawerHeader"
66 |
67 | const DrawerFooter = ({
68 | className,
69 | ...props
70 | }: React.HTMLAttributes) => (
71 |
75 | )
76 | DrawerFooter.displayName = "DrawerFooter"
77 |
78 | const DrawerTitle = React.forwardRef<
79 | React.ElementRef,
80 | React.ComponentPropsWithoutRef
81 | >(({ className, ...props }, ref) => (
82 |
90 | ))
91 | DrawerTitle.displayName = DrawerPrimitive.Title.displayName
92 |
93 | const DrawerDescription = React.forwardRef<
94 | React.ElementRef,
95 | React.ComponentPropsWithoutRef
96 | >(({ className, ...props }, ref) => (
97 |
102 | ))
103 | DrawerDescription.displayName = DrawerPrimitive.Description.displayName
104 |
105 | export {
106 | Drawer,
107 | DrawerPortal,
108 | DrawerOverlay,
109 | DrawerTrigger,
110 | DrawerClose,
111 | DrawerContent,
112 | DrawerHeader,
113 | DrawerFooter,
114 | DrawerTitle,
115 | DrawerDescription,
116 | }
117 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Input = React.forwardRef>(
6 | ({ className, type, ...props }, ref) => {
7 | return (
8 |
17 | )
18 | }
19 | )
20 | Input.displayName = "Input"
21 |
22 | export { Input }
23 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const labelVariants = cva(
8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9 | )
10 |
11 | const Label = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef &
14 | VariantProps
15 | >(({ className, ...props }, ref) => (
16 |
21 | ))
22 | Label.displayName = LabelPrimitive.Root.displayName
23 |
24 | export { Label }
25 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as PopoverPrimitive from "@radix-ui/react-popover"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Popover = PopoverPrimitive.Root
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger
9 |
10 | const PopoverAnchor = PopoverPrimitive.Anchor
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ))
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName
30 |
31 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
32 |
--------------------------------------------------------------------------------
/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const ScrollArea = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, children, ...props }, ref) => (
10 |
15 |
16 | {children}
17 |
18 |
19 |
20 |
21 | ))
22 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
23 |
24 | const ScrollBar = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, orientation = "vertical", ...props }, ref) => (
28 |
41 |
42 |
43 | ))
44 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
45 |
46 | export { ScrollArea, ScrollBar }
47 |
--------------------------------------------------------------------------------
/src/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as SelectPrimitive from "@radix-ui/react-select";
5 | import { Check, ChevronDown, ChevronUp } from "lucide-react";
6 |
7 | import { cn } from "@/lib/utils";
8 |
9 | const Select = SelectPrimitive.Root;
10 |
11 | const SelectGroup = SelectPrimitive.Group;
12 |
13 | const SelectValue = SelectPrimitive.Value;
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 | span]:line-clamp-1",
23 | className
24 | )}
25 | {...props}
26 | >
27 | {children}
28 |
29 |
30 |
31 |
32 | ));
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
34 |
35 | const SelectScrollUpButton = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
44 |
45 |
46 | ));
47 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
48 |
49 | const SelectScrollDownButton = React.forwardRef<
50 | React.ElementRef,
51 | React.ComponentPropsWithoutRef
52 | >(({ className, ...props }, ref) => (
53 |
58 |
59 |
60 | ));
61 | SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName;
62 |
63 | const SelectContent = React.forwardRef<
64 | React.ElementRef,
65 | React.ComponentPropsWithoutRef
66 | >(({ className, children, position = "popper", ...props }, ref) => (
67 |
68 |
79 |
80 |
87 | {children}
88 |
89 |
90 |
91 |
92 | ));
93 | SelectContent.displayName = SelectPrimitive.Content.displayName;
94 |
95 | const SelectLabel = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, ...props }, ref) => (
99 |
104 | ));
105 | SelectLabel.displayName = SelectPrimitive.Label.displayName;
106 |
107 | const SelectItem = React.forwardRef<
108 | React.ElementRef,
109 | React.ComponentPropsWithoutRef
110 | >(({ className, children, ...props }, ref) => (
111 |
119 |
120 |
121 |
122 |
123 |
124 | {children}
125 |
126 | ));
127 | SelectItem.displayName = SelectPrimitive.Item.displayName;
128 |
129 | const SelectSeparator = React.forwardRef<
130 | React.ElementRef,
131 | React.ComponentPropsWithoutRef
132 | >(({ className, ...props }, ref) => (
133 |
138 | ));
139 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
140 |
141 | export {
142 | Select,
143 | SelectGroup,
144 | SelectValue,
145 | SelectTrigger,
146 | SelectContent,
147 | SelectLabel,
148 | SelectItem,
149 | SelectSeparator,
150 | SelectScrollUpButton,
151 | SelectScrollDownButton,
152 | };
153 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Separator = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(
10 | (
11 | { className, orientation = "horizontal", decorative = true, ...props },
12 | ref
13 | ) => (
14 |
25 | )
26 | )
27 | Separator.displayName = SeparatorPrimitive.Root.displayName
28 |
29 | export { Separator }
30 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SliderPrimitive from "@radix-ui/react-slider"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Slider = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 |
21 |
22 |
23 |
24 |
25 | ))
26 | Slider.displayName = SliderPrimitive.Root.displayName
27 |
28 | export { Slider }
29 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as SwitchPrimitives from "@radix-ui/react-switch"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Switch = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 |
23 |
24 | ))
25 | Switch.displayName = SwitchPrimitives.Root.displayName
26 |
27 | export { Switch }
28 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as TabsPrimitive from "@radix-ui/react-tabs"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Tabs = TabsPrimitive.Root
7 |
8 | const TabsList = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | TabsList.displayName = TabsPrimitive.List.displayName
22 |
23 | const TabsTrigger = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
35 | ))
36 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
37 |
38 | const TabsContent = React.forwardRef<
39 | React.ElementRef,
40 | React.ComponentPropsWithoutRef
41 | >(({ className, ...props }, ref) => (
42 |
50 | ))
51 | TabsContent.displayName = TabsPrimitive.Content.displayName
52 |
53 | export { Tabs, TabsList, TabsTrigger, TabsContent }
54 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Textarea = React.forwardRef<
6 | HTMLTextAreaElement,
7 | React.ComponentProps<"textarea">
8 | >(({ className, ...props }, ref) => {
9 | return (
10 |
18 | )
19 | })
20 | Textarea.displayName = "Textarea"
21 |
22 | export { Textarea }
23 |
--------------------------------------------------------------------------------
/src/constants/initStates.ts:
--------------------------------------------------------------------------------
1 | import { PertStyles, TaskInput } from "@@/lib/main";
2 |
3 | export const initTasks: TaskInput[] = [
4 | { key: "1", duration: 5, text: "A", dependsOn: ["8", "9", "10"] },
5 | { key: "2", duration: 4, text: "B", dependsOn: ["9", "10"] },
6 | { key: "3", duration: 2, text: "C" },
7 | { key: "4", duration: 3, text: "D", dependsOn: ["5", "8"] },
8 | { key: "5", duration: 6, text: "E", dependsOn: ["3"] },
9 | { key: "6", duration: 8, text: "F" },
10 | { key: "7", duration: 3, text: "G" },
11 | { key: "8", duration: 5, text: "H", dependsOn: ["3", "6", "7"] },
12 | { key: "9", duration: 5, text: "J", dependsOn: ["3", "6"] },
13 | { key: "10", duration: 2, text: "K", dependsOn: ["6", "7"] },
14 | ];
15 |
16 | export const initialStyles: PertStyles = {
17 | disableGrid: false,
18 | taskSize: 100,
19 | fontFamily: "system-ui",
20 | fontSize: "md",
21 | textColor: "#000000",
22 | chartBackground: "#ffffff00",
23 | taskBackground: "#aaaeff",
24 | gridColor: "#83838350",
25 | borderWidth: 1,
26 | selectedBorderWidth: 3,
27 | hoverBorderWidth: 2,
28 | borderColor: "#615f77",
29 | selectedBorderColor: "#6868ff",
30 | criticalColor: "#ff9147",
31 | arrowColor: "#615f77",
32 | arrowWidth: 2,
33 | gap: { x: 100, y: 100 },
34 | };
35 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | :root {
7 | --background: 0 0% 100%;
8 | --foreground: 240 10% 3.9%;
9 | --card: 0 0% 100%;
10 | --card-foreground: 240 10% 3.9%;
11 | --popover: 0 0% 100%;
12 | --popover-foreground: 240 10% 3.9%;
13 | --primary: 240 5.9% 10%;
14 | --primary-foreground: 0 0% 98%;
15 | --secondary: 240 4.8% 95.9%;
16 | --secondary-foreground: 240 5.9% 10%;
17 | --muted: 240 4.8% 95.9%;
18 | --muted-foreground: 240 3.8% 46.1%;
19 | --accent: 240 4.8% 95.9%;
20 | --accent-foreground: 240 5.9% 10%;
21 | --destructive: 0 84.2% 60.2%;
22 | --destructive-foreground: 0 0% 98%;
23 | --border: 240 5.9% 90%;
24 | --input: 240 5.9% 90%;
25 | --ring: 240 10% 3.9%;
26 | --chart-1: 12 76% 61%;
27 | --chart-2: 173 58% 39%;
28 | --chart-3: 197 37% 24%;
29 | --chart-4: 43 74% 66%;
30 | --chart-5: 27 87% 67%;
31 | --radius: 0.5rem
32 | }
33 |
34 | .dark {
35 | --background: 240 10% 3.9%;
36 | --foreground: 0 0% 98%;
37 | --card: 240 10% 3.9%;
38 | --card-foreground: 0 0% 98%;
39 | --popover: 240 10% 3.9%;
40 | --popover-foreground: 0 0% 98%;
41 | --primary: 0 0% 98%;
42 | --primary-foreground: 240 5.9% 10%;
43 | --secondary: 240 3.7% 15.9%;
44 | --secondary-foreground: 0 0% 98%;
45 | --muted: 240 3.7% 15.9%;
46 | --muted-foreground: 240 5% 64.9%;
47 | --accent: 240 3.7% 15.9%;
48 | --accent-foreground: 0 0% 98%;
49 | --destructive: 0 62.8% 30.6%;
50 | --destructive-foreground: 0 0% 98%;
51 | --border: 240 3.7% 15.9%;
52 | --input: 240 3.7% 15.9%;
53 | --ring: 240 4.9% 83.9%;
54 | --chart-1: 220 70% 50%;
55 | --chart-2: 160 60% 45%;
56 | --chart-3: 30 80% 55%;
57 | --chart-4: 280 65% 60%;
58 | --chart-5: 340 75% 55%
59 | }
60 | }
61 |
62 | @layer base {
63 | * {
64 | @apply border-border;
65 | }
66 |
67 | body {
68 | @apply bg-background text-foreground;
69 | }
70 | }
--------------------------------------------------------------------------------
/src/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 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from "react";
2 | import { createRoot } from "react-dom/client";
3 | import App from "./App.tsx";
4 | import { ThemeProvider } from "@/components/theme-provider";
5 | import { PertProvider } from "@@/lib/main";
6 |
7 | createRoot(document.getElementById("root")!).render(
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | export default {
3 | darkMode: ["class"],
4 | content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
5 | theme: {
6 | extend: {
7 | borderRadius: {
8 | lg: 'var(--radius)',
9 | md: 'calc(var(--radius) - 2px)',
10 | sm: 'calc(var(--radius) - 4px)'
11 | },
12 | colors: {
13 | background: 'hsl(var(--background))',
14 | foreground: 'hsl(var(--foreground))',
15 | card: {
16 | DEFAULT: 'hsl(var(--card))',
17 | foreground: 'hsl(var(--card-foreground))'
18 | },
19 | popover: {
20 | DEFAULT: 'hsl(var(--popover))',
21 | foreground: 'hsl(var(--popover-foreground))'
22 | },
23 | primary: {
24 | DEFAULT: 'hsl(var(--primary))',
25 | foreground: 'hsl(var(--primary-foreground))'
26 | },
27 | secondary: {
28 | DEFAULT: 'hsl(var(--secondary))',
29 | foreground: 'hsl(var(--secondary-foreground))'
30 | },
31 | muted: {
32 | DEFAULT: 'hsl(var(--muted))',
33 | foreground: 'hsl(var(--muted-foreground))'
34 | },
35 | accent: {
36 | DEFAULT: 'hsl(var(--accent))',
37 | foreground: 'hsl(var(--accent-foreground))'
38 | },
39 | destructive: {
40 | DEFAULT: 'hsl(var(--destructive))',
41 | foreground: 'hsl(var(--destructive-foreground))'
42 | },
43 | border: 'hsl(var(--border))',
44 | input: 'hsl(var(--input))',
45 | ring: 'hsl(var(--ring))',
46 | chart: {
47 | '1': 'hsl(var(--chart-1))',
48 | '2': 'hsl(var(--chart-2))',
49 | '3': 'hsl(var(--chart-3))',
50 | '4': 'hsl(var(--chart-4))',
51 | '5': 'hsl(var(--chart-5))'
52 | }
53 | }
54 | }
55 | },
56 | plugins: [require("tailwindcss-animate")],
57 | };
58 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "useDefineForClassFields": true,
5 | "lib": ["ES2020", "DOM", "DOM.Iterable"],
6 | "module": "ESNext",
7 | "skipLibCheck": true,
8 | "paths": {
9 | "react-pert": ["./lib/main.ts"],
10 | "@/*": ["./src/*"],
11 | "@@/*": ["./*"]
12 | },
13 | "baseUrl": ".",
14 |
15 | /* Bundler mode */
16 | "moduleResolution": "bundler",
17 | "allowImportingTsExtensions": true,
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx",
22 |
23 | /* Linting */
24 | "strict": true,
25 | "noUnusedLocals": true,
26 | "noUnusedParameters": true,
27 | "noFallthroughCasesInSwitch": true
28 | },
29 | "include": ["src", "lib"],
30 | "references": [{ "path": "./tsconfig.node.json" }]
31 | }
32 |
--------------------------------------------------------------------------------
/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5 | "skipLibCheck": true,
6 | "module": "ESNext",
7 | "moduleResolution": "bundler",
8 | "allowSyntheticDefaultImports": true,
9 | "strict": true
10 | },
11 | "include": ["vite.config.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import { resolve } from "path";
4 | import dts from "vite-plugin-dts";
5 | import tsConfigPaths from "vite-tsconfig-paths";
6 | import { libInjectCss } from "vite-plugin-lib-inject-css";
7 | const appConfig = {
8 | plugins: [react()],
9 | resolve: {
10 | alias: {
11 | "@": resolve(__dirname, "./src"),
12 | "@@": resolve(__dirname, "./"),
13 | },
14 | },
15 | };
16 | const packageConfig = {
17 | plugins: [
18 | react(),
19 | tsConfigPaths(),
20 | dts({ rollupTypes: true, include: ["lib", "src/vite-env.d.ts"] }),
21 | libInjectCss(),
22 | ],
23 | build: {
24 | copyPublicDir: false,
25 | lib: {
26 | entry: resolve(__dirname, "lib/main.ts"),
27 | name: "react-pert",
28 | fileName: "index",
29 | },
30 | rollupOptions: {
31 | external: ["react", "react-dom", "react/jsx-runtime"],
32 | output: {
33 | globals: {
34 | react: "React",
35 | "react-dom": "ReactDOM",
36 | "react/jsx-runtime": "react/jsx-runtime",
37 | },
38 | },
39 | },
40 | },
41 | };
42 |
43 | export default defineConfig(({ mode }) => {
44 | return mode === "lib" ? packageConfig : appConfig;
45 | });
46 |
--------------------------------------------------------------------------------