Theme and default functionality
This commit is contained in:
+89
-62
@@ -46,72 +46,99 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--radius: 0.625rem;
|
--radius: 0.75rem;
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.145 0 0);
|
--background: oklch(0.97 0.01 240);
|
||||||
--card: oklch(1 0 0);
|
--foreground: oklch(0.22 0.02 240);
|
||||||
--card-foreground: oklch(0.145 0 0);
|
|
||||||
|
--card: oklch(0.99 0.005 240);
|
||||||
|
--card-foreground: oklch(0.22 0.02 240);
|
||||||
|
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
--popover-foreground: oklch(0.22 0.02 240);
|
||||||
--primary: oklch(0.205 0 0);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
/* Primary = architectural blue */
|
||||||
--secondary: oklch(0.97 0 0);
|
--primary: oklch(0.55 0.16 250);
|
||||||
--secondary-foreground: oklch(0.205 0 0);
|
--primary-foreground: oklch(0.98 0.01 250);
|
||||||
--muted: oklch(0.97 0 0);
|
|
||||||
--muted-foreground: oklch(0.556 0 0);
|
--secondary: oklch(0.93 0.02 240);
|
||||||
--accent: oklch(0.97 0 0);
|
--secondary-foreground: oklch(0.28 0.02 240);
|
||||||
--accent-foreground: oklch(0.205 0 0);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--muted: oklch(0.94 0.015 240);
|
||||||
--border: oklch(0.922 0 0);
|
--muted-foreground: oklch(0.55 0.02 240);
|
||||||
--input: oklch(0.922 0 0);
|
|
||||||
--ring: oklch(0.708 0 0);
|
/* Accent = subtle violet for graph highlights */
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
--accent: oklch(0.92 0.03 270);
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
--accent-foreground: oklch(0.30 0.04 270);
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--destructive: oklch(0.62 0.22 25);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
--border: oklch(0.90 0.015 240);
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--input: oklch(0.90 0.015 240);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--ring: oklch(0.55 0.16 250);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.97 0 0);
|
/* Charts (clean + modern) */
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--chart-1: oklch(0.55 0.16 250);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--chart-2: oklch(0.60 0.14 200);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--chart-3: oklch(0.62 0.18 280);
|
||||||
|
--chart-4: oklch(0.70 0.15 140);
|
||||||
|
--chart-5: oklch(0.65 0.20 30);
|
||||||
|
|
||||||
|
--sidebar: oklch(0.95 0.01 240);
|
||||||
|
--sidebar-foreground: oklch(0.25 0.02 240);
|
||||||
|
--sidebar-primary: oklch(0.55 0.16 250);
|
||||||
|
--sidebar-primary-foreground: oklch(0.98 0.01 250);
|
||||||
|
--sidebar-accent: oklch(0.92 0.02 250);
|
||||||
|
--sidebar-accent-foreground: oklch(0.30 0.02 250);
|
||||||
|
--sidebar-border: oklch(0.88 0.015 240);
|
||||||
|
--sidebar-ring: oklch(0.55 0.16 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.145 0 0);
|
--background: oklch(0.17 0.02 250);
|
||||||
--foreground: oklch(0.985 0 0);
|
--foreground: oklch(0.94 0.01 240);
|
||||||
--card: oklch(0.205 0 0);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
--card: oklch(0.21 0.02 250);
|
||||||
--popover: oklch(0.205 0 0);
|
--card-foreground: oklch(0.94 0.01 240);
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.922 0 0);
|
--popover: oklch(0.23 0.02 250);
|
||||||
--primary-foreground: oklch(0.205 0 0);
|
--popover-foreground: oklch(0.94 0.01 240);
|
||||||
--secondary: oklch(0.269 0 0);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
/* Strong but not neon */
|
||||||
--muted: oklch(0.269 0 0);
|
--primary: oklch(0.65 0.17 250);
|
||||||
--muted-foreground: oklch(0.708 0 0);
|
--primary-foreground: oklch(0.12 0.02 250);
|
||||||
--accent: oklch(0.269 0 0);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
--secondary: oklch(0.27 0.02 250);
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--secondary-foreground: oklch(0.94 0.01 240);
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
--muted: oklch(0.25 0.02 250);
|
||||||
--ring: oklch(0.556 0 0);
|
--muted-foreground: oklch(0.70 0.02 240);
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
/* Accent for graph edges / highlights */
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
--accent: oklch(0.60 0.18 280);
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
--accent-foreground: oklch(0.96 0.01 240);
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.205 0 0);
|
--destructive: oklch(0.68 0.20 25);
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
--border: oklch(0.30 0.02 250);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--input: oklch(0.30 0.02 250);
|
||||||
--sidebar-accent: oklch(0.269 0 0);
|
--ring: oklch(0.65 0.17 250);
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
/* Chart colors (balanced + readable on dark canvas) */
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--chart-1: oklch(0.65 0.17 250);
|
||||||
|
--chart-2: oklch(0.70 0.14 180);
|
||||||
|
--chart-3: oklch(0.72 0.18 300);
|
||||||
|
--chart-4: oklch(0.75 0.15 140);
|
||||||
|
--chart-5: oklch(0.72 0.20 40);
|
||||||
|
|
||||||
|
--sidebar: oklch(0.19 0.02 250);
|
||||||
|
--sidebar-foreground: oklch(0.92 0.01 240);
|
||||||
|
--sidebar-primary: oklch(0.65 0.17 250);
|
||||||
|
--sidebar-primary-foreground: oklch(0.12 0.02 250);
|
||||||
|
--sidebar-accent: oklch(0.27 0.02 250);
|
||||||
|
--sidebar-accent-foreground: oklch(0.92 0.01 240);
|
||||||
|
--sidebar-border: oklch(0.30 0.02 250);
|
||||||
|
--sidebar-ring: oklch(0.65 0.17 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -6,14 +6,19 @@ import { useWorkspace } from "@/hooks/workspace";
|
|||||||
import { ChevronRight } from "lucide-react";
|
import { ChevronRight } from "lucide-react";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { EntityId } from "@/lib/types";
|
import { Route } from "@/lib/types";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
createDefaultWorkspace,
|
||||||
|
saveWorkspaceToLocalStorage,
|
||||||
|
} from "@/lib/workspace";
|
||||||
|
|
||||||
export default function Workspace() {
|
export default function Workspace() {
|
||||||
const [currentWorkspace, setCurrentWorkspace] = useState<string>();
|
const [currentWorkspace, setCurrentWorkspace] = useState<string>();
|
||||||
|
|
||||||
const { workspace, loadWorkspace } = useWorkspace();
|
const { workspace, loadWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const [route, setRoute] = useState<EntityId>();
|
const [route, setRoute] = useState<Route>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentWorkspace(Cookies.get("workspace"));
|
setCurrentWorkspace(Cookies.get("workspace"));
|
||||||
@@ -21,6 +26,8 @@ export default function Workspace() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadWorkspace(currentWorkspace);
|
loadWorkspace(currentWorkspace);
|
||||||
|
if (currentWorkspace) Cookies.set("workspace", currentWorkspace);
|
||||||
|
else Cookies.remove("workspace");
|
||||||
}, [currentWorkspace]);
|
}, [currentWorkspace]);
|
||||||
|
|
||||||
return workspace ? (
|
return workspace ? (
|
||||||
@@ -30,12 +37,23 @@ export default function Workspace() {
|
|||||||
<header className="w-full border-b-2 pb-3 pt-2 hover:bg-accent/20 flex items-center text-foreground/90">
|
<header className="w-full border-b-2 pb-3 pt-2 hover:bg-accent/20 flex items-center text-foreground/90">
|
||||||
{workspace.name}
|
{workspace.name}
|
||||||
<ChevronRight className="h-5" />
|
<ChevronRight className="h-5" />
|
||||||
{workspace.entities[route || ""]?.name}
|
{workspace.entities[route.entity || ""]?.name}
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
</SidebarTrigger>
|
</SidebarTrigger>
|
||||||
|
|
||||||
<Router workspace={workspace} />
|
<Router workspace={workspace} />
|
||||||
</AppSidebar>
|
</AppSidebar>
|
||||||
) : null;
|
) : (
|
||||||
|
<main
|
||||||
|
className="flex h-svh items-center justify-center"
|
||||||
|
onClick={() => {
|
||||||
|
const w = createDefaultWorkspace();
|
||||||
|
saveWorkspaceToLocalStorage(w);
|
||||||
|
setCurrentWorkspace(w.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button>Create a new workspace</Button>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../ui/dropdown-menu";
|
} from "../ui/dropdown-menu";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
import { EntityId, SetState, Workspace } from "@/lib/types";
|
import { EntityId, Route, SetState, Workspace } from "@/lib/types";
|
||||||
|
|
||||||
export default function AppSidebar({
|
export default function AppSidebar({
|
||||||
children,
|
children,
|
||||||
@@ -25,7 +25,7 @@ export default function AppSidebar({
|
|||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
setRoute: SetState<EntityId | undefined>;
|
setRoute: SetState<Route>;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
} from "../ui/sidebar";
|
} from "../ui/sidebar";
|
||||||
import { Collapsible, CollapsibleContent } from "../ui/collapsible";
|
import { Collapsible, CollapsibleContent } from "../ui/collapsible";
|
||||||
import { ChevronDown } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
import { EntityId, SetState, Workspace } from "@/lib/types";
|
import { EntityId, Route, SetState, Workspace } from "@/lib/types";
|
||||||
|
|
||||||
export type SidebarEntity =
|
export type SidebarEntity =
|
||||||
| {
|
| {
|
||||||
@@ -29,7 +29,7 @@ export default function EntitySwitch({
|
|||||||
}: {
|
}: {
|
||||||
entity: SidebarEntity;
|
entity: SidebarEntity;
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
setRoute: SetState<EntityId | undefined>;
|
setRoute: SetState<Route>;
|
||||||
}) {
|
}) {
|
||||||
if (entity.kind === "group") {
|
if (entity.kind === "group") {
|
||||||
return (
|
return (
|
||||||
@@ -65,7 +65,7 @@ export default function EntitySwitch({
|
|||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setRoute(entity.id);
|
setRoute({ workspace: workspace.id, entity: entity.id });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{workspace.entities[entity.id].name}
|
{workspace.entities[entity.id].name}
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ export type EntityId = string;
|
|||||||
export type TagId = string;
|
export type TagId = string;
|
||||||
export type TaskId = string;
|
export type TaskId = string;
|
||||||
|
|
||||||
|
export interface Route {
|
||||||
|
workspace?: string;
|
||||||
|
entity?: EntityId;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Workspace {
|
export interface Workspace {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user