diff --git a/packages/app/src/components/controls/ControlExtensions/FiltersExtension.tsx b/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/FiltersExtension.tsx
similarity index 96%
rename from packages/app/src/components/controls/ControlExtensions/FiltersExtension.tsx
rename to packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/FiltersExtension.tsx
index f68f9cce..a2986275 100644
--- a/packages/app/src/components/controls/ControlExtensions/FiltersExtension.tsx
+++ b/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/FiltersExtension.tsx
@@ -3,7 +3,7 @@ import {
Tooltip,
TooltipContent,
TooltipTrigger,
-} from "../../shadcn/Tooltip.tsx";
+} from "../../../../shadcn/Tooltip.tsx";
import {
DropdownMenu,
DropdownMenuCheckboxItem,
@@ -11,8 +11,8 @@ import {
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
-} from "../../shadcn/Dropdownmenu.tsx";
-import { Button } from "../../shadcn/Button.tsx";
+} from "../../../../shadcn/Dropdownmenu.tsx";
+import { Button } from "../../../../shadcn/Button.tsx";
import { Funnel } from "lucide-react";
export default function FiltersExtension(props: {
diff --git a/packages/app/src/components/controls/ControlExtensions/GraphDepthExtension.tsx b/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/GraphDepthExtension.tsx
similarity index 91%
rename from packages/app/src/components/controls/ControlExtensions/GraphDepthExtension.tsx
rename to packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/GraphDepthExtension.tsx
index 19a71bd5..4d9c124c 100644
--- a/packages/app/src/components/controls/ControlExtensions/GraphDepthExtension.tsx
+++ b/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/GraphDepthExtension.tsx
@@ -3,17 +3,17 @@ import {
Tooltip,
TooltipContent,
TooltipTrigger,
-} from "../../shadcn/Tooltip.tsx";
+} from "../../../../shadcn/Tooltip.tsx";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuTrigger,
-} from "../../shadcn/Dropdownmenu.tsx";
-import { Button } from "../../shadcn/Button.tsx";
+} from "../../../../shadcn/Dropdownmenu.tsx";
+import { Button } from "../../../../shadcn/Button.tsx";
import { Settings2 } from "lucide-react";
-import { Slider } from "../../shadcn/Slider.tsx";
-import { Input } from "../../shadcn/Input.tsx";
-import { Label } from "../../shadcn/Label.tsx";
+import { Slider } from "../../../../shadcn/Slider.tsx";
+import { Input } from "../../../../shadcn/Input.tsx";
+import { Label } from "../../../../shadcn/Label.tsx";
export default function GraphDepthExtension(props: {
busy: boolean;
diff --git a/packages/app/src/components/controls/ControlExtensions/MetricsExtension.tsx b/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/MetricsExtension.tsx
similarity index 94%
rename from packages/app/src/components/controls/ControlExtensions/MetricsExtension.tsx
rename to packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/MetricsExtension.tsx
index 5fa2d339..677df98e 100644
--- a/packages/app/src/components/controls/ControlExtensions/MetricsExtension.tsx
+++ b/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/MetricsExtension.tsx
@@ -12,14 +12,14 @@ import {
Tooltip,
TooltipContent,
TooltipTrigger,
-} from "../../shadcn/Tooltip.tsx";
-import { Button } from "../../shadcn/Button.tsx";
+} from "../../../../shadcn/Tooltip.tsx";
+import { Button } from "../../../../shadcn/Button.tsx";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
-} from "../../shadcn/Dropdownmenu.tsx";
+} from "../../../../shadcn/Dropdownmenu.tsx";
// Extension for the controls in the project view
export default function MetricsExtension(props: {
diff --git a/packages/app/src/components/controls/Controls.tsx b/packages/app/src/components/DependencyVisualizer/components/controls/Controls.tsx
similarity index 94%
rename from packages/app/src/components/controls/Controls.tsx
rename to packages/app/src/components/DependencyVisualizer/components/controls/Controls.tsx
index ffdbbea0..b52fcf0c 100644
--- a/packages/app/src/components/controls/Controls.tsx
+++ b/packages/app/src/components/DependencyVisualizer/components/controls/Controls.tsx
@@ -1,6 +1,10 @@
import type { ReactNode } from "react";
-import { Button } from "../shadcn/Button.tsx";
-import { Tooltip, TooltipContent, TooltipTrigger } from "../shadcn/Tooltip.tsx";
+import { Button } from "../../../shadcn/Button.tsx";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from "../../../shadcn/Tooltip.tsx";
import type { Core } from "cytoscape";
import { Focus, Network, ZoomIn, ZoomOut } from "lucide-react";
diff --git a/packages/app/src/components/detailsPanes/alertBadge.tsx b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/AlertBadge.tsx
similarity index 100%
rename from packages/app/src/components/detailsPanes/alertBadge.tsx
rename to packages/app/src/components/DependencyVisualizer/components/detailsPanes/AlertBadge.tsx
diff --git a/packages/app/src/components/detailsPanes/FileDetailsPane.tsx b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/FileDetailsPane.tsx
similarity index 74%
rename from packages/app/src/components/detailsPanes/FileDetailsPane.tsx
rename to packages/app/src/components/DependencyVisualizer/components/detailsPanes/FileDetailsPane.tsx
index 9fa24ec2..bbe735bd 100644
--- a/packages/app/src/components/detailsPanes/FileDetailsPane.tsx
+++ b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/FileDetailsPane.tsx
@@ -4,16 +4,21 @@ import {
SheetContent,
SheetHeader,
SheetTitle,
- SheetTrigger,
-} from "../shadcn/Sheet.tsx";
-import { Card, CardContent, CardHeader, CardTitle } from "../shadcn/Card.tsx";
+} from "../../../shadcn/Sheet.tsx";
+import {
+ Card,
+ CardContent,
+ CardHeader,
+ CardTitle,
+} from "../../../shadcn/Card.tsx";
import { Code, File, Pickaxe, SearchCode } from "lucide-react";
-import { ScrollArea } from "../shadcn/Scrollarea.tsx";
-import { Button } from "../shadcn/Button.tsx";
-import { Link } from "react-router";
+import { ScrollArea } from "../../../shadcn/Scrollarea.tsx";
+import { Button } from "../../../shadcn/Button.tsx";
+import { Link, useSearchParams } from "react-router";
import DisplayNameWithTooltip from "../DisplayNameWithTootip.tsx";
-import Metrics from "./metrics.tsx";
-import AlertBadge from "./alertBadge.tsx";
+import Metrics from "./Metrics.tsx";
+import AlertBadge from "./AlertBadge.tsx";
+import SymbolExtractionDialog from "../SymbolExtractionDialog.tsx";
export default function FileDetailsPane(props: {
context: {
@@ -21,28 +26,14 @@ export default function FileDetailsPane(props: {
fileAuditManifest: FileAuditManifest;
} | undefined;
onClose: () => void;
- onAddSymbolsForExtraction: (filePath: string, symbolIds: string[]) => void;
}) {
- function markAllSymbolsForExtraction() {
- if (!props.context?.fileDependencyManifest) {
- return;
- }
+ const [searchParams] = useSearchParams();
- props.onAddSymbolsForExtraction(
- props.context?.fileDependencyManifest.filePath,
- Object.keys(props.context.fileDependencyManifest.symbols),
- );
- }
-
- function markSymbolForExtraction(symbolId: string) {
- if (!props.context?.fileDependencyManifest) {
- return;
- }
-
- props.onAddSymbolsForExtraction(
- props.context.fileDependencyManifest.filePath,
- [symbolId],
- );
+ function getToFileLink(filePath: string) {
+ const newSearchParams = new URLSearchParams(searchParams);
+ newSearchParams.set("fileId", filePath);
+ newSearchParams.delete("instanceId");
+ return `?${newSearchParams.toString()}`;
}
return (
@@ -51,8 +42,7 @@ export default function FileDetailsPane(props: {
open={props.context !== undefined}
onOpenChange={() => props.onClose()}
>
-
-
+
@@ -70,24 +60,25 @@ export default function FileDetailsPane(props: {
onClick={props.onClose}
>
View graph for this file
-
+
+
@@ -161,7 +152,7 @@ export default function FileDetailsPane(props: {
-
+
+
void;
- onAddSymbolsForExtraction: (filePath: string, symbolIds: string[]) => void;
}) {
- function markSymbolForExtraction(symbolId: string) {
- if (!props.context?.fileDependencyManifest) {
- return;
- }
+ const [searchParams] = useSearchParams();
- props.onAddSymbolsForExtraction(
- props.context.fileDependencyManifest.filePath,
- [symbolId],
- );
+ function getToSymbolLink(filePath: string, symbolId: string) {
+ const newSearchParams = new URLSearchParams(searchParams);
+ newSearchParams.set("fileId", filePath);
+ newSearchParams.set("instanceId", symbolId);
+ return `?${newSearchParams.toString()}`;
}
return (
@@ -48,7 +51,10 @@ export default function SymbolDetailsPane(props: {
onOpenChange={() => props.onClose()}
>
-
+
@@ -74,32 +80,24 @@ export default function SymbolDetailsPane(props: {
onClick={props.onClose}
>
View graph for this symbol
-
+
+
);
}
diff --git a/packages/app/src/pages/audit/file/instance/index.tsx b/packages/app/src/components/DependencyVisualizer/visualizers/SymbolVisualizer.tsx
similarity index 74%
rename from packages/app/src/pages/audit/file/instance/index.tsx
rename to packages/app/src/components/DependencyVisualizer/visualizers/SymbolVisualizer.tsx
index 6526ea95..8a5099c1 100644
--- a/packages/app/src/pages/audit/file/instance/index.tsx
+++ b/packages/app/src/components/DependencyVisualizer/visualizers/SymbolVisualizer.tsx
@@ -1,35 +1,31 @@
import { useEffect, useRef, useState } from "react";
-import Controls from "../../../../components/controls/Controls.tsx";
-import GraphDepthExtension from "../../../../components/controls/ControlExtensions/GraphDepthExtension.tsx";
-import SymbolContextMenu from "../../../../components/contextMenu/SymbolContextMenu.tsx";
-import {
- useNavigate,
- useOutletContext,
- useParams,
- useSearchParams,
-} from "react-router";
-import type { AuditContext } from "../../base.tsx";
-import SymbolDetailsPane from "../../../../components/detailsPanes/SymbolDetailsPane.tsx";
-import { useTheme } from "../../../../contexts/ThemeProvider.tsx";
+import Controls from "../components/controls/Controls.tsx";
+import GraphDepthExtension from "../components/controls/ControlExtensions/GraphDepthExtension.tsx";
+import SymbolContextMenu from "../components/contextMenu/SymbolContextMenu.tsx";
+import { useNavigate, useSearchParams } from "react-router";
+import type { VisualizerContext } from "../DependencyVisualizer.tsx";
+import SymbolDetailsPane from "../components/detailsPanes/SymbolDetailsPane.tsx";
+import { useTheme } from "../../../contexts/ThemeProvider.tsx";
import type {
FileAuditManifest,
FileDependencyManifest,
SymbolAuditManifest,
SymbolDependencyManifest,
} from "@napi/shared";
-import { SymbolDependencyVisualizer } from "../../../../helpers/cytoscape/symbolDependencyVisualizer/index.ts";
-
-export default function AuditInstancePage() {
+import { SymbolDependencyVisualizer } from "../cytoscape/symbolDependencyVisualizer/index.ts";
+
+export default function SymbolVisualizer(
+ props: VisualizerContext & {
+ fileId: string;
+ instanceId: string;
+ },
+) {
const navigate = useNavigate();
const { theme } = useTheme();
- const params = useParams<{ file: string; instance: string }>();
-
const [searchParams, setSearchParams] = useSearchParams();
- const context = useOutletContext();
-
const containerRef = useRef(null);
const [busy, setBusy] = useState(true);
const [symbolVisualizer, setSymbolVisualizer] = useState<
@@ -81,18 +77,18 @@ export default function AuditInstancePage() {
useEffect(() => {
setBusy(true);
- if (!params.file || !params.instance) {
+ if (!props.fileId || !props.instanceId) {
return;
}
const symbolVisualizer = new SymbolDependencyVisualizer(
containerRef.current as HTMLElement,
- params.file,
- params.instance,
+ props.fileId,
+ props.instanceId,
dependencyDepth,
dependentDepth,
- context.dependencyManifest,
- context.auditManifest,
+ props.dependencyManifest,
+ props.auditManifest,
{
theme: theme,
onAfterNodeRightClick: (value: {
@@ -101,7 +97,7 @@ export default function AuditInstancePage() {
symbolId: string;
}) => {
const fileDependencyManifest =
- context.dependencyManifest[value.filePath];
+ props.dependencyManifest[value.filePath];
const symbolDependencyManifest =
fileDependencyManifest.symbols[value.symbolId];
setContextMenu({
@@ -118,7 +114,7 @@ export default function AuditInstancePage() {
symbolId,
);
const urlEncodedSymbolName =
- `/audit/${urlEncodedFileName}/${urlEncodedSymbolId}`;
+ `/${urlEncodedFileName}/${urlEncodedSymbolId}`;
navigate(urlEncodedSymbolName);
},
@@ -135,23 +131,23 @@ export default function AuditInstancePage() {
setSymbolVisualizer(undefined);
};
}, [
- context.dependencyManifest,
- context.auditManifest,
- params.file,
- params.instance,
+ props.dependencyManifest,
+ props.auditManifest,
+ props.fileId,
+ props.instanceId,
dependencyDepth,
dependentDepth,
]);
useEffect(() => {
if (symbolVisualizer) {
- if (context.highlightedCytoscapeRef) {
- symbolVisualizer.highlightNode(context.highlightedCytoscapeRef);
+ if (props.highlightedCytoscapeRef) {
+ symbolVisualizer.highlightNode(props.highlightedCytoscapeRef);
} else {
symbolVisualizer.unhighlightNodes();
}
}
- }, [context.highlightedCytoscapeRef]);
+ }, [props.highlightedCytoscapeRef]);
// Hook to update the theme in the graph
useEffect(() => {
@@ -169,12 +165,12 @@ export default function AuditInstancePage() {
symbolVisualizer?.layoutGraph(symbolVisualizer.cy)}
>
setContextMenu(undefined)}
onOpenDetails={(filePath, symbolId) => {
- const fileDependencyManifest = context.dependencyManifest[filePath];
+ const fileDependencyManifest = props.dependencyManifest[filePath];
const symbolDependencyManifest =
fileDependencyManifest.symbols[symbolId];
- const fileAuditManifest = context.auditManifest[filePath];
+ const fileAuditManifest = props.auditManifest[filePath];
const symbolAuditManifest = fileAuditManifest.symbols[symbolId];
setDetailsPane({
fileDependencyManifest,
@@ -208,9 +204,6 @@ export default function AuditInstancePage() {
setDetailsPane(undefined)}
- onAddSymbolsForExtraction={(filePath, symbolIds) => {
- context.onAddSymbolsForExtraction(filePath, symbolIds);
- }}
/>
);
diff --git a/packages/app/src/components/shadcn/Alert.tsx b/packages/app/src/components/shadcn/Alert.tsx
index f80fec6d..9ef9269b 100644
--- a/packages/app/src/components/shadcn/Alert.tsx
+++ b/packages/app/src/components/shadcn/Alert.tsx
@@ -1,16 +1,16 @@
-import * as React from "react";
+import type * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils.ts";
const alertVariants = cva(
- "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",
+ "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
{
variants: {
variant: {
- default: "bg-background text-foreground",
+ default: "bg-card text-card-foreground",
destructive:
- "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
+ "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90",
},
},
defaultVariants: {
@@ -19,41 +19,48 @@ const alertVariants = cva(
},
);
-const Alert = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes & VariantProps
->(({ className, variant, ...props }, ref) => (
-
-));
-Alert.displayName = "Alert";
+function Alert({
+ className,
+ variant,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ );
+}
-const AlertTitle = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-AlertTitle.displayName = "AlertTitle";
+function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const AlertDescription = React.forwardRef<
- HTMLParagraphElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-AlertDescription.displayName = "AlertDescription";
+function AlertDescription({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
export { Alert, AlertDescription, AlertTitle };
diff --git a/packages/app/src/components/shadcn/Breadcrumb.tsx b/packages/app/src/components/shadcn/Breadcrumb.tsx
index 05f4a7f6..3de86be9 100644
--- a/packages/app/src/components/shadcn/Breadcrumb.tsx
+++ b/packages/app/src/components/shadcn/Breadcrumb.tsx
@@ -1,108 +1,102 @@
-import * as React from "react";
+import type * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { ChevronRight, MoreHorizontal } from "lucide-react";
import { cn } from "../../lib/utils.ts";
-const Breadcrumb = React.forwardRef<
- HTMLElement,
- React.ComponentPropsWithoutRef<"nav"> & {
- separator?: React.ReactNode;
- }
->(({ ...props }, ref) => );
-Breadcrumb.displayName = "Breadcrumb";
+function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
+ return ;
+}
-const BreadcrumbList = React.forwardRef<
- HTMLOListElement,
- React.ComponentPropsWithoutRef<"ol">
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbList.displayName = "BreadcrumbList";
+function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
+ return (
+
+ );
+}
-const BreadcrumbItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentPropsWithoutRef<"li">
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbItem.displayName = "BreadcrumbItem";
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
-const BreadcrumbLink = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentPropsWithoutRef<"a"> & {
- asChild?: boolean;
- }
->(({ asChild, className, ...props }, ref) => {
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean;
+}) {
const Comp = asChild ? Slot : "a";
return (
);
-});
-BreadcrumbLink.displayName = "BreadcrumbLink";
+}
-const BreadcrumbPage = React.forwardRef<
- HTMLSpanElement,
- React.ComponentPropsWithoutRef<"span">
->(({ className, ...props }, ref) => (
-
-));
-BreadcrumbPage.displayName = "BreadcrumbPage";
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
-const BreadcrumbSeparator = ({
+function BreadcrumbSeparator({
children,
className,
...props
-}: React.ComponentProps<"li">) => (
- svg]:w-3.5 [&>svg]:h-3.5", className)}
- {...props}
- >
- {children ?? }
-
-);
-BreadcrumbSeparator.displayName = "BreadcrumbSeparator";
+}: React.ComponentProps<"li">) {
+ return (
+ svg]:size-3.5", className)}
+ {...props}
+ >
+ {children ?? }
+
+ );
+}
-const BreadcrumbEllipsis = ({
+function BreadcrumbEllipsis({
className,
...props
-}: React.ComponentProps<"span">) => (
-
-
- More
-
-);
-BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More
+
+ );
+}
export {
Breadcrumb,
diff --git a/packages/app/src/components/shadcn/Button.tsx b/packages/app/src/components/shadcn/Button.tsx
index e6687a33..692c5c04 100644
--- a/packages/app/src/components/shadcn/Button.tsx
+++ b/packages/app/src/components/shadcn/Button.tsx
@@ -1,30 +1,31 @@
-import * as React from "react";
+import type * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils.ts";
const buttonVariants = cva(
- "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",
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default:
- "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
- "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
- "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
- "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
- ghost: "hover:bg-accent hover:text-accent-foreground",
+ "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
- default: "h-9 px-4 py-2",
- sm: "h-8 rounded-md px-3 text-xs",
- lg: "h-10 rounded-md px-8",
- icon: "h-9 w-9",
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
},
},
defaultVariants: {
@@ -34,25 +35,27 @@ const buttonVariants = cva(
},
);
-export interface ButtonProps
- extends
- React.ButtonHTMLAttributes,
- VariantProps {
- asChild?: boolean;
-}
+function Button({
+ className,
+ variant,
+ size,
+ asChild = false,
+ ...props
+}:
+ & React.ComponentProps<"button">
+ & VariantProps
+ & {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot : "button";
-const Button = React.forwardRef(
- ({ className, variant, size, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : "button";
- return (
-
- );
- },
-);
-Button.displayName = "Button";
+ return (
+
+ );
+}
export { Button, buttonVariants };
diff --git a/packages/app/src/components/shadcn/Card.tsx b/packages/app/src/components/shadcn/Card.tsx
index a24eaad5..fe5171b8 100644
--- a/packages/app/src/components/shadcn/Card.tsx
+++ b/packages/app/src/components/shadcn/Card.tsx
@@ -1,80 +1,89 @@
-import * as React from "react";
+import type * as React from "react";
import { cn } from "../../lib/utils.ts";
-const Card = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-Card.displayName = "Card";
+function Card({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const CardHeader = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardHeader.displayName = "CardHeader";
+function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const CardTitle = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardTitle.displayName = "CardTitle";
+function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const CardDescription = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardDescription.displayName = "CardDescription";
+function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const CardContent = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardContent.displayName = "CardContent";
+function CardAction({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const CardFooter = React.forwardRef<
- HTMLDivElement,
- React.HTMLAttributes
->(({ className, ...props }, ref) => (
-
-));
-CardFooter.displayName = "CardFooter";
+function CardContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
export {
Card,
+ CardAction,
CardContent,
CardDescription,
CardFooter,
diff --git a/packages/app/src/components/shadcn/Dialog.tsx b/packages/app/src/components/shadcn/Dialog.tsx
new file mode 100644
index 00000000..75b094ca
--- /dev/null
+++ b/packages/app/src/components/shadcn/Dialog.tsx
@@ -0,0 +1,143 @@
+"use client";
+
+import type * as React from "react";
+import * as DialogPrimitive from "@radix-ui/react-dialog";
+import { XIcon } from "lucide-react";
+
+import { cn } from "../../lib/utils.ts";
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean;
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ );
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+};
diff --git a/packages/app/src/components/shadcn/Dropdownmenu.tsx b/packages/app/src/components/shadcn/Dropdownmenu.tsx
index 7e539898..ae9f0b76 100644
--- a/packages/app/src/components/shadcn/Dropdownmenu.tsx
+++ b/packages/app/src/components/shadcn/Dropdownmenu.tsx
@@ -1,186 +1,242 @@
"use client";
-import * as React from "react";
+import type * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
-import { Check, ChevronRight, Circle } from "lucide-react";
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { cn } from "../../lib/utils.ts";
-const DropdownMenu = DropdownMenuPrimitive.Root;
-
-const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
-
-const DropdownMenuGroup = DropdownMenuPrimitive.Group;
-
-const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
-
-const DropdownMenuSub = DropdownMenuPrimitive.Sub;
-
-const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
-
-const DropdownMenuSubTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, children, ...props }, ref) => (
-
- {children}
-
-
-));
-DropdownMenuSubTrigger.displayName =
- DropdownMenuPrimitive.SubTrigger.displayName;
-
-const DropdownMenuSubContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DropdownMenuSubContent.displayName =
- DropdownMenuPrimitive.SubContent.displayName;
-
-const DropdownMenuContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 4, ...props }, ref) => (
-
- ) {
+ return ;
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function DropdownMenuGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+ variant?: "default" | "destructive";
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
-
-));
-DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
-
-const DropdownMenuItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, ...props }, ref) => (
- svg]:size-4 [&>svg]:shrink-0",
- inset && "pl-8",
- className,
- )}
- {...props}
- />
-));
-DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
-
-const DropdownMenuCheckboxItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, checked, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-DropdownMenuCheckboxItem.displayName =
- DropdownMenuPrimitive.CheckboxItem.displayName;
-
-const DropdownMenuRadioItem = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
-
-
-
-
- {children}
-
-));
-DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
-
-const DropdownMenuLabel = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef & {
- inset?: boolean;
- }
->(({ className, inset, ...props }, ref) => (
-
-));
-DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
-
-const DropdownMenuSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
-
-const DropdownMenuShortcut = ({
+ );
+}
+
+function DropdownMenuSeparator({
className,
...props
-}: React.HTMLAttributes) => {
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
return (
);
-};
-DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
+}
+
+function DropdownMenuSub({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
export {
DropdownMenu,
diff --git a/packages/app/src/components/shadcn/Input.tsx b/packages/app/src/components/shadcn/Input.tsx
index a7a053d5..867627a1 100644
--- a/packages/app/src/components/shadcn/Input.tsx
+++ b/packages/app/src/components/shadcn/Input.tsx
@@ -1,22 +1,21 @@
-import * as React from "react";
+import type * as React from "react";
import { cn } from "../../lib/utils.ts";
-const Input = React.forwardRef>(
- ({ className, type, ...props }, ref) => {
- return (
-
- );
- },
-);
-Input.displayName = "Input";
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ );
+}
export { Input };
diff --git a/packages/app/src/components/shadcn/Label.tsx b/packages/app/src/components/shadcn/Label.tsx
index c7fd301a..0fedd649 100644
--- a/packages/app/src/components/shadcn/Label.tsx
+++ b/packages/app/src/components/shadcn/Label.tsx
@@ -1,26 +1,24 @@
"use client";
-import * as React from "react";
+import type * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
-import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils.ts";
-const labelVariants = cva(
- "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
-);
-
-const Label = React.forwardRef<
- React.ElementRef,
- & React.ComponentPropsWithoutRef
- & VariantProps
->(({ className, ...props }, ref) => (
-
-));
-Label.displayName = LabelPrimitive.Root.displayName;
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
export { Label };
diff --git a/packages/app/src/components/shadcn/README.md b/packages/app/src/components/shadcn/README.md
deleted file mode 100644
index 49d8ff28..00000000
--- a/packages/app/src/components/shadcn/README.md
+++ /dev/null
@@ -1,18 +0,0 @@
-# Shadcn UI Components
-
-These components are imported from [shadcn/ui](https://ui.shadcn.com/) and
-should not be modified directly.
-
-## Usage
-
-These components are part of our design system and follow the shadcn/ui
-implementation pattern. They are carefully styled and configured to work with
-our application's theme.
-
-## Important Notes
-
-Modification of these files can have consequenses on other shadcn/ui components.
-Make sure to double check before mofifying these
-
-For more information on how to use or customize these components, refer to the
-[shadcn/ui documentation](https://ui.shadcn.com/docs).
diff --git a/packages/app/src/components/shadcn/Resizable.tsx b/packages/app/src/components/shadcn/Resizable.tsx
deleted file mode 100644
index 0da61fec..00000000
--- a/packages/app/src/components/shadcn/Resizable.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-"use client";
-
-import { GripVertical } from "lucide-react";
-import * as ResizablePrimitive from "react-resizable-panels";
-
-import { cn } from "../../lib/utils.ts";
-
-const ResizablePanelGroup = ({
- className,
- ...props
-}: React.ComponentProps) => (
-
-);
-
-const ResizablePanel = ResizablePrimitive.Panel;
-
-const ResizableHandle = ({
- withHandle,
- className,
- ...props
-}: React.ComponentProps & {
- withHandle?: boolean;
-}) => (
- div]:rotate-90",
- className,
- )}
- {...props}
- >
- {withHandle && (
-
-
-
- )}
-
-);
-
-export { ResizableHandle, ResizablePanel, ResizablePanelGroup };
diff --git a/packages/app/src/components/shadcn/Scrollarea.tsx b/packages/app/src/components/shadcn/Scrollarea.tsx
index 4364d158..b08e67cb 100644
--- a/packages/app/src/components/shadcn/Scrollarea.tsx
+++ b/packages/app/src/components/shadcn/Scrollarea.tsx
@@ -1,48 +1,58 @@
"use client";
-import * as React from "react";
+import type * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "../../lib/utils.ts";
-const ScrollArea = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, children, ...props }, ref) => (
-
-
- {children}
-
-
-
-
-));
-ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
+function ScrollArea({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+
+ );
+}
-const ScrollBar = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, orientation = "vertical", ...props }, ref) => (
-
-
-
-));
-ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
+function ScrollBar({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
export { ScrollArea, ScrollBar };
diff --git a/packages/app/src/components/shadcn/Separator.tsx b/packages/app/src/components/shadcn/Separator.tsx
index 13268e40..0d612faa 100644
--- a/packages/app/src/components/shadcn/Separator.tsx
+++ b/packages/app/src/components/shadcn/Separator.tsx
@@ -1,31 +1,28 @@
"use client";
-import * as React from "react";
+import type * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "../../lib/utils.ts";
-const Separator = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(
- (
- { className, orientation = "horizontal", decorative = true, ...props },
- ref,
- ) => (
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
- ),
-);
-Separator.displayName = SeparatorPrimitive.Root.displayName;
+ );
+}
export { Separator };
diff --git a/packages/app/src/components/shadcn/Sheet.tsx b/packages/app/src/components/shadcn/Sheet.tsx
index 05103d46..a8fc7991 100644
--- a/packages/app/src/components/shadcn/Sheet.tsx
+++ b/packages/app/src/components/shadcn/Sheet.tsx
@@ -1,133 +1,131 @@
"use client";
-import * as React from "react";
+import type * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
-import { cva, type VariantProps } from "class-variance-authority";
-import { X } from "lucide-react";
+import { XIcon } from "lucide-react";
import { cn } from "../../lib/utils.ts";
-const Sheet = SheetPrimitive.Root;
+function Sheet({ ...props }: React.ComponentProps) {
+ return ;
+}
-const SheetTrigger = SheetPrimitive.Trigger;
+function SheetTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
-const SheetClose = SheetPrimitive.Close;
+function SheetClose({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
-const SheetPortal = SheetPrimitive.Portal;
+function SheetPortal({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
-const SheetOverlay = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
+function SheetOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
-const sheetVariants = cva(
- "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
- {
- variants: {
- side: {
- top:
- "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
- bottom:
- "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
- left:
- "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
- right:
- "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
- },
- },
- defaultVariants: {
- side: "right",
- },
- },
-);
+function SheetContent({
+ className,
+ children,
+ side = "right",
+ ...props
+}: React.ComponentProps & {
+ side?: "top" | "right" | "bottom" | "left";
+}) {
+ return (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+ );
+}
-interface SheetContentProps
- extends
- React.ComponentPropsWithoutRef,
- VariantProps {}
+function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
-const SheetContent = React.forwardRef<
- React.ElementRef,
- SheetContentProps
->(({ side = "right", className, children, ...props }, ref) => (
-
-
- ) {
+ return (
+
-
-
- Close
-
- {children}
-
-
-));
-SheetContent.displayName = SheetPrimitive.Content.displayName;
+ />
+ );
+}
-const SheetHeader = ({
+function SheetTitle({
className,
...props
-}: React.HTMLAttributes
) => (
-
-);
-SheetHeader.displayName = "SheetHeader";
+}: React.ComponentProps) {
+ return (
+
+ );
+}
-const SheetFooter = ({
+function SheetDescription({
className,
...props
-}: React.HTMLAttributes) => (
-
-);
-SheetFooter.displayName = "SheetFooter";
-
-const SheetTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetTitle.displayName = SheetPrimitive.Title.displayName;
-
-const SheetDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-SheetDescription.displayName = SheetPrimitive.Description.displayName;
+}: React.ComponentProps) {
+ return (
+
+ );
+}
export {
Sheet,
@@ -136,8 +134,6 @@ export {
SheetDescription,
SheetFooter,
SheetHeader,
- SheetOverlay,
- SheetPortal,
SheetTitle,
SheetTrigger,
};
diff --git a/packages/app/src/components/shadcn/Sidebar.tsx b/packages/app/src/components/shadcn/Sidebar.tsx
index 3e40a9be..27327ba6 100644
--- a/packages/app/src/components/shadcn/Sidebar.tsx
+++ b/packages/app/src/components/shadcn/Sidebar.tsx
@@ -3,7 +3,7 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
-import { PanelLeft } from "lucide-react";
+import { PanelLeftIcon } from "lucide-react";
import { useIsMobile } from "./hooks/use-mobile.tsx";
import { cn } from "../../lib/utils.ts";
@@ -53,272 +53,245 @@ function useSidebar() {
return context;
}
-const SidebarProvider = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div"> & {
- defaultOpen?: boolean;
- open?: boolean;
- onOpenChange?: (open: boolean) => void;
- }
->(
- (
- {
- defaultOpen = true,
- open: openProp,
- onOpenChange: setOpenProp,
- className,
- style,
- children,
- ...props
+function SidebarProvider({
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ defaultOpen?: boolean;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+}) {
+ const isMobile = useIsMobile();
+ const [openMobile, setOpenMobile] = React.useState(false);
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen);
+ const open = openProp ?? _open;
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === "function" ? value(open) : value;
+ if (setOpenProp) {
+ setOpenProp(openState);
+ } else {
+ _setOpen(openState);
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie =
+ `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
},
- ref,
- ) => {
- const isMobile = useIsMobile();
- const [openMobile, setOpenMobile] = React.useState(false);
-
- // This is the internal state of the sidebar.
- // We use openProp and setOpenProp for control from outside the component.
- const [_open, _setOpen] = React.useState(defaultOpen);
- const open = openProp ?? _open;
- const setOpen = React.useCallback(
- (value: boolean | ((value: boolean) => boolean)) => {
- const openState = typeof value === "function" ? value(open) : value;
- if (setOpenProp) {
- setOpenProp(openState);
- } else {
- _setOpen(openState);
- }
-
- // This sets the cookie to keep the sidebar state.
- document.cookie =
- `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
- },
- [setOpenProp, open],
- );
-
- // Helper to toggle the sidebar.
- const toggleSidebar = React.useCallback(() => {
- return isMobile
- ? setOpenMobile((open) => !open)
- : setOpen((open) => !open);
- }, [isMobile, setOpen, setOpenMobile]);
-
- // Adds a keyboard shortcut to toggle the sidebar.
- React.useEffect(() => {
- const handleKeyDown = (event: KeyboardEvent) => {
- if (
- event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
- (event.metaKey || event.ctrlKey)
- ) {
- event.preventDefault();
- toggleSidebar();
- }
- };
-
- globalThis.addEventListener("keydown", handleKeyDown);
- return () => globalThis.removeEventListener("keydown", handleKeyDown);
- }, [toggleSidebar]);
-
- // We add a state so that we can do data-state="expanded" or "collapsed".
- // This makes it easier to style the sidebar with Tailwind classes.
- const state = open ? "expanded" : "collapsed";
-
- const contextValue = React.useMemo(
- () => ({
- state,
- open,
- setOpen,
- isMobile,
- openMobile,
- setOpenMobile,
- toggleSidebar,
- }),
- [
- state,
- open,
- setOpen,
- isMobile,
- openMobile,
- setOpenMobile,
- toggleSidebar,
- ],
- );
+ [setOpenProp, open],
+ );
- return (
-
-
-
- {children}
-
-
-
- );
- },
-);
-SidebarProvider.displayName = "SidebarProvider";
-
-const Sidebar = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div"> & {
- side?: "left" | "right";
- variant?: "sidebar" | "floating" | "inset";
- collapsible?: "offcanvas" | "icon" | "none";
- }
->(
- (
- {
- side = "left",
- variant = "sidebar",
- collapsible = "offcanvas",
- className,
- children,
- ...props
- },
- ref,
- ) => {
- const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
+ }, [isMobile, setOpen, setOpenMobile]);
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (
+ event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
+ (event.metaKey || event.ctrlKey)
+ ) {
+ event.preventDefault();
+ toggleSidebar();
+ }
+ };
+
+ globalThis.addEventListener("keydown", handleKeyDown);
+ return () => globalThis.removeEventListener("keydown", handleKeyDown);
+ }, [toggleSidebar]);
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? "expanded" : "collapsed";
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar,
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar],
+ );
- if (collapsible === "none") {
- return (
+ return (
+
+
{children}
- );
- }
-
- if (isMobile) {
- return (
-
-
-
- Sidebar
- Displays the mobile sidebar.
-
- {children}
-
-
- );
- }
+
+
+ );
+}
+function Sidebar({
+ side = "left",
+ variant = "sidebar",
+ collapsible = "offcanvas",
+ className,
+ children,
+ ...props
+}: React.ComponentProps<"div"> & {
+ side?: "left" | "right";
+ variant?: "sidebar" | "floating" | "inset";
+ collapsible?: "offcanvas" | "icon" | "none";
+}) {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
+
+ if (collapsible === "none") {
return (
+ {children}
+
+ );
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ );
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
- {/* This is what handles the sidebar gap on desktop */}
-
-
- {children}
-
+ {children}
- );
- },
-);
-Sidebar.displayName = "Sidebar";
+
+ );
+}
-const SidebarTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentProps
->(({ className, onClick, ...props }, ref) => {
+function SidebarTrigger({
+ className,
+ onClick,
+ ...props
+}: React.ComponentProps) {
const { toggleSidebar } = useSidebar();
return (
);
-});
-SidebarTrigger.displayName = "SidebarTrigger";
+}
-const SidebarRail = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<"button">
->(({ className, ...props }, ref) => {
+function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
const { toggleSidebar } = useSidebar();
return (
);
-});
-SidebarRail.displayName = "SidebarRail";
+}
-const SidebarInset = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"main">
->(({ className, ...props }, ref) => {
+function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
return (
);
-});
-SidebarInset.displayName = "SidebarInset";
+}
-const SidebarInput = React.forwardRef<
- React.ElementRef,
- React.ComponentProps
->(({ className, ...props }, ref) => {
+function SidebarInput({
+ className,
+ ...props
+}: React.ComponentProps) {
return (
);
-});
-SidebarInput.displayName = "SidebarInput";
+}
-const SidebarHeader = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => {
+function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
);
-});
-SidebarHeader.displayName = "SidebarHeader";
+}
-const SidebarFooter = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => {
+function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
);
-});
-SidebarFooter.displayName = "SidebarFooter";
+}
-const SidebarSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentProps
->(({ className, ...props }, ref) => {
+function SidebarSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
return (
);
-});
-SidebarSeparator.displayName = "SidebarSeparator";
+}
-const SidebarContent = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => {
+function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
return (
);
-});
-SidebarContent.displayName = "SidebarContent";
+}
-const SidebarGroup = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => {
+function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
);
-});
-SidebarGroup.displayName = "SidebarGroup";
+}
-const SidebarGroupLabel = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div"> & { asChild?: boolean }
->(({ className, asChild = false, ...props }, ref) => {
+function SidebarGroupLabel({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "div";
return (
svg]:size-4 [&>svg]:shrink-0",
+ "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className,
)}
{...props}
/>
);
-});
-SidebarGroupLabel.displayName = "SidebarGroupLabel";
+}
-const SidebarGroupAction = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<"button"> & { asChild?: boolean }
->(({ className, asChild = false, ...props }, ref) => {
+function SidebarGroupAction({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "button";
return (
svg]:size-4 [&>svg]:shrink-0",
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 after:md:hidden",
+ "after:absolute after:-inset-2 md:after:hidden",
"group-data-[collapsible=icon]:hidden",
className,
)}
{...props}
/>
);
-});
-SidebarGroupAction.displayName = "SidebarGroupAction";
-
-const SidebarGroupContent = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => (
-
-));
-SidebarGroupContent.displayName = "SidebarGroupContent";
-
-const SidebarMenu = React.forwardRef<
- HTMLUListElement,
- React.ComponentProps<"ul">
->(({ className, ...props }, ref) => (
-
-));
-SidebarMenu.displayName = "SidebarMenu";
-
-const SidebarMenuItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentProps<"li">
->(({ className, ...props }, ref) => (
-
-));
-SidebarMenuItem.displayName = "SidebarMenuItem";
+}
+
+function SidebarGroupContent({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ );
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
const sidebarMenuButtonVariants = cva(
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
+ "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
{
variants: {
variant: {
@@ -538,7 +482,7 @@ const sidebarMenuButtonVariants = cva(
size: {
default: "h-8 text-sm",
sm: "h-7 text-xs",
- lg: "h-12 text-sm group-data-[collapsible=icon]:!p-0",
+ lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
},
},
defaultVariants: {
@@ -548,123 +492,117 @@ const sidebarMenuButtonVariants = cva(
},
);
-const SidebarMenuButton = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<"button"> & {
- asChild?: boolean;
- isActive?: boolean;
- tooltip?: string | React.ComponentProps;
- } & VariantProps
->(
- (
- {
- asChild = false,
- isActive = false,
- variant = "default",
- size = "default",
- tooltip,
- className,
- ...props
- },
- ref,
- ) => {
- const Comp = asChild ? Slot : "button";
- const { isMobile, state } = useSidebar();
-
- const button = (
-
- );
-
- if (!tooltip) {
- return button;
- }
+function SidebarMenuButton({
+ asChild = false,
+ isActive = false,
+ variant = "default",
+ size = "default",
+ tooltip,
+ className,
+ ...props
+}: React.ComponentProps<"button"> & {
+ asChild?: boolean;
+ isActive?: boolean;
+ tooltip?: string | React.ComponentProps;
+} & VariantProps) {
+ const Comp = asChild ? Slot : "button";
+ const { isMobile, state } = useSidebar();
- if (typeof tooltip === "string") {
- tooltip = {
- children: tooltip,
- };
- }
+ const button = (
+
+ );
- return (
-
- {button}
-
-
- );
- },
-);
-SidebarMenuButton.displayName = "SidebarMenuButton";
+ if (!tooltip) {
+ return button;
+ }
-const SidebarMenuAction = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<"button"> & {
- asChild?: boolean;
- showOnHover?: boolean;
+ if (typeof tooltip === "string") {
+ tooltip = {
+ children: tooltip,
+ };
}
->(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
+
+ return (
+
+ {button}
+
+
+ );
+}
+
+function SidebarMenuAction({
+ className,
+ asChild = false,
+ showOnHover = false,
+ ...props
+}: React.ComponentProps<"button"> & {
+ asChild?: boolean;
+ showOnHover?: boolean;
+}) {
const Comp = asChild ? Slot : "button";
return (
svg]:size-4 [&>svg]:shrink-0",
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground peer-hover/menu-button:text-sidebar-accent-foreground absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 outline-hidden transition-transform focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
// Increases the hit area of the button on mobile.
- "after:absolute after:-inset-2 after:md:hidden",
+ "after:absolute after:-inset-2 md:after:hidden",
"peer-data-[size=sm]/menu-button:top-1",
"peer-data-[size=default]/menu-button:top-1.5",
"peer-data-[size=lg]/menu-button:top-2.5",
"group-data-[collapsible=icon]:hidden",
showOnHover &&
- "group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
+ "peer-data-[active=true]/menu-button:text-sidebar-accent-foreground group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 md:opacity-0",
className,
)}
{...props}
/>
);
-});
-SidebarMenuAction.displayName = "SidebarMenuAction";
-
-const SidebarMenuBadge = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div">
->(({ className, ...props }, ref) => (
-
-));
-SidebarMenuBadge.displayName = "SidebarMenuBadge";
-
-const SidebarMenuSkeleton = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<"div"> & {
- showIcon?: boolean;
- }
->(({ className, showIcon = false, ...props }, ref) => {
+}
+
+function SidebarMenuBadge({
+ className,
+ ...props
+}: React.ComponentProps<"div">) {
+ return (
+
+ );
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ showIcon?: boolean;
+}) {
// Random width between 50 to 90%.
const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`;
@@ -672,7 +610,7 @@ const SidebarMenuSkeleton = React.forwardRef<
return (
)}
);
-});
-SidebarMenuSkeleton.displayName = "SidebarMenuSkeleton";
-
-const SidebarMenuSub = React.forwardRef<
- HTMLUListElement,
- React.ComponentProps<"ul">
->(({ className, ...props }, ref) => (
-
-));
-SidebarMenuSub.displayName = "SidebarMenuSub";
-
-const SidebarMenuSubItem = React.forwardRef<
- HTMLLIElement,
- React.ComponentProps<"li">
->(({ ...props }, ref) => );
-SidebarMenuSubItem.displayName = "SidebarMenuSubItem";
-
-const SidebarMenuSubButton = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentProps<"a"> & {
- asChild?: boolean;
- size?: "sm" | "md";
- isActive?: boolean;
- }
->(({ asChild = false, size = "md", isActive, className, ...props }, ref) => {
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubItem({
+ className,
+ ...props
+}: React.ComponentProps<"li">) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubButton({
+ asChild = false,
+ size = "md",
+ isActive = false,
+ className,
+ ...props
+}: React.ComponentProps<"a"> & {
+ asChild?: boolean;
+ size?: "sm" | "md";
+ isActive?: boolean;
+}) {
const Comp = asChild ? Slot : "a";
return (
span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
+ "text-sidebar-foreground ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground [&>svg]:text-sidebar-accent-foreground flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 outline-hidden focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
size === "sm" && "text-xs",
size === "md" && "text-sm",
@@ -745,8 +691,7 @@ const SidebarMenuSubButton = React.forwardRef<
{...props}
/>
);
-});
-SidebarMenuSubButton.displayName = "SidebarMenuSubButton";
+}
export {
Sidebar,
diff --git a/packages/app/src/components/shadcn/Skeleton.tsx b/packages/app/src/components/shadcn/Skeleton.tsx
index 20cc560d..89b225cc 100644
--- a/packages/app/src/components/shadcn/Skeleton.tsx
+++ b/packages/app/src/components/shadcn/Skeleton.tsx
@@ -1,12 +1,10 @@
import { cn } from "../../lib/utils.ts";
-function Skeleton({
- className,
- ...props
-}: React.HTMLAttributes) {
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
);
diff --git a/packages/app/src/components/shadcn/Slider.tsx b/packages/app/src/components/shadcn/Slider.tsx
index 556abc10..07138256 100644
--- a/packages/app/src/components/shadcn/Slider.tsx
+++ b/packages/app/src/components/shadcn/Slider.tsx
@@ -5,24 +5,62 @@ import * as SliderPrimitive from "@radix-ui/react-slider";
import { cn } from "../../lib/utils.ts";
-const Slider = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-
-
-
-));
-Slider.displayName = SliderPrimitive.Root.displayName;
+function Slider({
+ className,
+ defaultValue,
+ value,
+ min = 0,
+ max = 100,
+ ...props
+}: React.ComponentProps) {
+ const _values = React.useMemo(
+ () =>
+ Array.isArray(value)
+ ? value
+ : Array.isArray(defaultValue)
+ ? defaultValue
+ : [min, max],
+ [value, defaultValue, min, max],
+ );
+
+ return (
+
+
+
+
+ {Array.from(
+ { length: _values.length },
+ (_, index) => (
+
+ ),
+ )}
+
+ );
+}
export { Slider };
diff --git a/packages/app/src/components/shadcn/Sonner.tsx b/packages/app/src/components/shadcn/Sonner.tsx
new file mode 100644
index 00000000..2c5a2874
--- /dev/null
+++ b/packages/app/src/components/shadcn/Sonner.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import { useTheme } from "next-themes";
+import { Toaster as Sonner, type ToasterProps } from "sonner";
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = "system" } = useTheme();
+
+ return (
+
+ );
+};
+
+export { Toaster };
diff --git a/packages/app/src/components/shadcn/Toast.tsx b/packages/app/src/components/shadcn/Toast.tsx
deleted file mode 100644
index 87861ed9..00000000
--- a/packages/app/src/components/shadcn/Toast.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-"use client";
-
-import * as React from "react";
-import * as ToastPrimitives from "@radix-ui/react-toast";
-import { cva, type VariantProps } from "class-variance-authority";
-import { X } from "lucide-react";
-
-import { cn } from "../../lib/utils.ts";
-
-const ToastProvider = ToastPrimitives.Provider;
-
-const ToastViewport = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
-
-const toastVariants = cva(
- "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
- {
- variants: {
- variant: {
- default: "border bg-background text-foreground",
- destructive:
- "destructive group border-destructive bg-destructive text-destructive-foreground",
- },
- },
- defaultVariants: {
- variant: "default",
- },
- },
-);
-
-const Toast = React.forwardRef<
- React.ElementRef,
- & React.ComponentPropsWithoutRef
- & VariantProps
->(({ className, variant, ...props }, ref) => {
- return (
-
- );
-});
-Toast.displayName = ToastPrimitives.Root.displayName;
-
-const ToastAction = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-ToastAction.displayName = ToastPrimitives.Action.displayName;
-
-const ToastClose = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-
-
-));
-ToastClose.displayName = ToastPrimitives.Close.displayName;
-
-const ToastTitle = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-ToastTitle.displayName = ToastPrimitives.Title.displayName;
-
-const ToastDescription = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, ...props }, ref) => (
-
-));
-ToastDescription.displayName = ToastPrimitives.Description.displayName;
-
-type ToastProps = React.ComponentPropsWithoutRef;
-
-type ToastActionElement = React.ReactElement;
-
-export {
- Toast,
- ToastAction,
- type ToastActionElement,
- ToastClose,
- ToastDescription,
- type ToastProps,
- ToastProvider,
- ToastTitle,
- ToastViewport,
-};
diff --git a/packages/app/src/components/shadcn/Toaster.tsx b/packages/app/src/components/shadcn/Toaster.tsx
deleted file mode 100644
index 39d06d71..00000000
--- a/packages/app/src/components/shadcn/Toaster.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client";
-
-import { useToast } from "./hooks/use-toast.tsx";
-import {
- Toast,
- ToastClose,
- ToastDescription,
- ToastProvider,
- ToastTitle,
- ToastViewport,
-} from "./Toast.tsx";
-
-export function Toaster() {
- const { toasts } = useToast();
-
- return (
-
- {toasts.map(function ({ id, title, description, action, ...props }) {
- return (
-
-
- {title && {title}}
- {description && (
- {description}
- )}
-
- {action}
-
-
- );
- })}
-
-
- );
-}
diff --git a/packages/app/src/components/shadcn/Tooltip.tsx b/packages/app/src/components/shadcn/Tooltip.tsx
index 7e2d3805..e8d6446d 100644
--- a/packages/app/src/components/shadcn/Tooltip.tsx
+++ b/packages/app/src/components/shadcn/Tooltip.tsx
@@ -1,32 +1,61 @@
"use client";
-import * as React from "react";
+import type * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "../../lib/utils.ts";
-const TooltipProvider = TooltipPrimitive.Provider;
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
-const Tooltip = TooltipPrimitive.Root;
+function Tooltip({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
-const TooltipTrigger = TooltipPrimitive.Trigger;
+function TooltipTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
-const TooltipContent = React.forwardRef<
- React.ElementRef,
- React.ComponentPropsWithoutRef
->(({ className, sideOffset = 4, ...props }, ref) => (
-
-
-
-));
-TooltipContent.displayName = TooltipPrimitive.Content.displayName;
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
diff --git a/packages/app/src/components/shadcn/hooks/use-toast.tsx b/packages/app/src/components/shadcn/hooks/use-toast.tsx
deleted file mode 100644
index 266573c8..00000000
--- a/packages/app/src/components/shadcn/hooks/use-toast.tsx
+++ /dev/null
@@ -1,191 +0,0 @@
-"use client";
-
-// Inspired by react-hot-toast library
-import * as React from "react";
-
-import type { ToastActionElement, ToastProps } from "../Toast.tsx";
-
-const TOAST_LIMIT = 1;
-const TOAST_REMOVE_DELAY = 1000000;
-
-type ToasterToast = ToastProps & {
- id: string;
- title?: React.ReactNode;
- description?: React.ReactNode;
- action?: ToastActionElement;
-};
-
-const actionTypes = {
- ADD_TOAST: "ADD_TOAST",
- UPDATE_TOAST: "UPDATE_TOAST",
- DISMISS_TOAST: "DISMISS_TOAST",
- REMOVE_TOAST: "REMOVE_TOAST",
-} as const;
-
-let count = 0;
-
-function genId() {
- count = (count + 1) % Number.MAX_SAFE_INTEGER;
- return count.toString();
-}
-
-type ActionType = typeof actionTypes;
-
-type Action =
- | {
- type: ActionType["ADD_TOAST"];
- toast: ToasterToast;
- }
- | {
- type: ActionType["UPDATE_TOAST"];
- toast: Partial;
- }
- | {
- type: ActionType["DISMISS_TOAST"];
- toastId?: ToasterToast["id"];
- }
- | {
- type: ActionType["REMOVE_TOAST"];
- toastId?: ToasterToast["id"];
- };
-
-interface State {
- toasts: ToasterToast[];
-}
-
-const toastTimeouts = new Map>();
-
-const addToRemoveQueue = (toastId: string) => {
- if (toastTimeouts.has(toastId)) {
- return;
- }
-
- const timeout = setTimeout(() => {
- toastTimeouts.delete(toastId);
- dispatch({
- type: "REMOVE_TOAST",
- toastId: toastId,
- });
- }, TOAST_REMOVE_DELAY);
-
- toastTimeouts.set(toastId, timeout);
-};
-
-export const reducer = (state: State, action: Action): State => {
- switch (action.type) {
- case "ADD_TOAST":
- return {
- ...state,
- toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
- };
-
- case "UPDATE_TOAST":
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === action.toast.id ? { ...t, ...action.toast } : t
- ),
- };
-
- case "DISMISS_TOAST": {
- const { toastId } = action;
-
- // ! Side effects ! - This could be extracted into a dismissToast() action,
- // but I'll keep it here for simplicity
- if (toastId) {
- addToRemoveQueue(toastId);
- } else {
- state.toasts.forEach((toast) => {
- addToRemoveQueue(toast.id);
- });
- }
-
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === toastId || toastId === undefined
- ? {
- ...t,
- open: false,
- }
- : t
- ),
- };
- }
- case "REMOVE_TOAST":
- if (action.toastId === undefined) {
- return {
- ...state,
- toasts: [],
- };
- }
- return {
- ...state,
- toasts: state.toasts.filter((t) => t.id !== action.toastId),
- };
- }
-};
-
-const listeners: Array<(state: State) => void> = [];
-
-let memoryState: State = { toasts: [] };
-
-function dispatch(action: Action) {
- memoryState = reducer(memoryState, action);
- listeners.forEach((listener) => {
- listener(memoryState);
- });
-}
-
-type Toast = Omit;
-
-function toast({ ...props }: Toast) {
- const id = genId();
-
- const update = (props: ToasterToast) =>
- dispatch({
- type: "UPDATE_TOAST",
- toast: { ...props, id },
- });
- const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
-
- dispatch({
- type: "ADD_TOAST",
- toast: {
- ...props,
- id,
- open: true,
- onOpenChange: (open) => {
- if (!open) dismiss();
- },
- },
- });
-
- return {
- id: id,
- dismiss,
- update,
- };
-}
-
-function useToast() {
- const [state, setState] = React.useState(memoryState);
-
- React.useEffect(() => {
- listeners.push(setState);
- return () => {
- const index = listeners.indexOf(setState);
- if (index > -1) {
- listeners.splice(index, 1);
- }
- };
- }, [state]);
-
- return {
- ...state,
- toast,
- dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
- };
-}
-
-export { toast, useToast };
diff --git a/packages/app/src/main.tsx b/packages/app/src/main.tsx
index 5a5526e1..b2ea5b59 100644
--- a/packages/app/src/main.tsx
+++ b/packages/app/src/main.tsx
@@ -2,36 +2,15 @@
import { StrictMode } from "react";
import ReactDOM from "react-dom/client";
-import { createBrowserRouter, Navigate, RouterProvider } from "react-router";
-import BaseAuditPage from "./pages/audit/base.tsx";
-import AuditPage from "./pages/audit/index.tsx";
-import AuditFilePage from "./pages/audit/file/index.tsx";
-import AuditInstancePage from "./pages/audit/file/instance/index.tsx";
-import { Toaster } from "./components/shadcn/Toaster.tsx";
+import { createBrowserRouter, RouterProvider } from "react-router";
+import { Toaster } from "./components/shadcn/Sonner.tsx";
import { ThemeProvider } from "./contexts/ThemeProvider.tsx";
+import IndexPage from "./pages/index.tsx";
const router = createBrowserRouter([
{
path: "/",
- element: ,
- },
- {
- path: "/audit",
- element: ,
- children: [
- {
- path: "/audit",
- element: ,
- },
- {
- path: "/audit/:file",
- element: ,
- },
- {
- path: "/audit/:file/:instance",
- element: ,
- },
- ],
+ element: ,
},
]);
@@ -44,8 +23,8 @@ if (!rootElement) {
ReactDOM.createRoot(rootElement).render(
+
-
,
);
diff --git a/packages/app/src/pages/audit/base.tsx b/packages/app/src/pages/audit/base.tsx
deleted file mode 100644
index 2b49fc75..00000000
--- a/packages/app/src/pages/audit/base.tsx
+++ /dev/null
@@ -1,779 +0,0 @@
-import { useEffect, useState } from "react";
-import { Link, Outlet, useParams } from "react-router";
-import {
- getAuditManifest,
- getDependencyManifest,
- runExtraction,
-} from "../../service/api/index.ts";
-import type {
- AuditManifest,
- DependencyManifest,
- SymbolsToExtract,
-} from "@napi/shared";
-import {
- Sidebar,
- SidebarContent,
- SidebarGroup,
- SidebarGroupLabel,
- SidebarHeader,
- SidebarProvider,
- SidebarRail,
- SidebarTrigger,
-} from "../../components/shadcn/Sidebar.tsx";
-import {
- Breadcrumb,
- BreadcrumbItem,
- BreadcrumbLink,
- BreadcrumbList,
- BreadcrumbSeparator,
-} from "../../components/shadcn/Breadcrumb.tsx";
-import { Button } from "../../components/shadcn/Button.tsx";
-import { Skeleton } from "../../components/shadcn/Skeleton.tsx";
-import { Input } from "../../components/shadcn/Input.tsx";
-import {
- Tooltip,
- TooltipContent,
- TooltipTrigger,
-} from "../../components/shadcn/Tooltip.tsx";
-import {
- ChevronDown,
- ChevronRight,
- CircleMinus,
- Code,
- File,
- Loader,
- Moon,
- Pickaxe,
- ScanEye,
- SearchCode,
- Sun,
-} from "lucide-react";
-import {
- ResizableHandle,
- ResizablePanel,
- ResizablePanelGroup,
-} from "../../components/shadcn/Resizable.tsx";
-import { ScrollArea, ScrollBar } from "../../components/shadcn/Scrollarea.tsx";
-import { useToast } from "../../components/shadcn/hooks/use-toast.tsx";
-import { useTheme } from "../../contexts/ThemeProvider.tsx";
-import {
- Card,
- CardContent,
- CardHeader,
- CardTitle,
-} from "../../components/shadcn/Card.tsx";
-import DisplayNameWithTooltip from "../../components/DisplayNameWithTootip.tsx";
-
-export interface AuditContext {
- busy: boolean;
- dependencyManifest: DependencyManifest;
- auditManifest: AuditManifest;
- highlightedCytoscapeRef: {
- filePath: string;
- symbolId: string | undefined;
- } | undefined;
- onAddSymbolsForExtraction: (
- filePath: string,
- symbolIds: string[],
- ) => void;
-}
-
-export default function BaseAuditPage() {
- const { theme, setTheme } = useTheme();
-
- const { file, instance } = useParams();
-
- const { toast } = useToast();
-
- const [busy, setBusy] = useState(true);
-
- const [auditManifest, setAuditManifest] = useState({});
- const [dependencyManifest, setDependencyManifest] = useState<
- DependencyManifest
- >({});
-
- const [highlightedCytoscapeRef, setHighlightedCytoscapeRef] = useState<
- {
- filePath: string;
- symbolId: string | undefined;
- } | undefined
- >(undefined);
-
- const [symbolsToExtract, setSymbolsToExtract] = useState(
- [],
- );
-
- async function extractSymbols() {
- setBusy(true);
- const extractionToast = toast({
- title: "Extracting symbols",
- description: "This may take a while...",
- });
- try {
- await runExtraction(symbolsToExtract);
- extractionToast.update({
- id: extractionToast.id,
- description: "Symbols extracted successfully",
- });
- } catch (_error) {
- extractionToast.update({
- id: extractionToast.id,
- description: "Failed to extract symbols",
- variant: "destructive",
- });
- } finally {
- setBusy(false);
- }
- }
-
- useEffect(() => {
- async function handleOnLoad() {
- setBusy(true);
- const allPromiseToast = toast({
- title: "Loading manifests",
- description: "This may take a while...",
- });
- try {
- const dependencyManifestPromise = getDependencyManifest();
- const auditManifestPromise = getAuditManifest();
-
- const allPromise = Promise.all([
- dependencyManifestPromise,
- auditManifestPromise,
- ]);
-
- const [dependencyManifest, auditManifest] = await allPromise;
-
- setDependencyManifest(dependencyManifest);
- setAuditManifest(auditManifest);
-
- allPromiseToast.update({
- id: allPromiseToast.id,
- description: "Manifests loaded successfully",
- });
- } catch (_error) {
- allPromiseToast.update({
- id: allPromiseToast.id,
- description: "Failed to load manifests",
- variant: "destructive",
- });
- } finally {
- setBusy(false);
- }
- }
-
- handleOnLoad();
- }, []);
-
- return (
-
- {
- if (!node.fileId) return;
- const newRef = {
- filePath: node.fileId,
- symbolId: node.symbolId,
- };
- // If the new ref is the same as the current ref, we un set it (unhighlight)
- if (
- highlightedCytoscapeRef?.filePath === newRef.filePath &&
- highlightedCytoscapeRef?.symbolId === newRef.symbolId
- ) {
- setHighlightedCytoscapeRef(undefined);
- } else {
- setHighlightedCytoscapeRef(newRef);
- }
- }}
- toDetails={(node: ExplorerNodeData) => {
- if (node.symbolId && node.fileId) {
- return `/audit/${encodeURIComponent(node.fileId)}/${
- encodeURIComponent(node.symbolId)
- }`;
- } else if (node.fileId) {
- return `/audit/${encodeURIComponent(node.fileId)}`;
- } else {
- return "/audit";
- }
- }}
- symbolsToExtract={symbolsToExtract}
- onUpdateSymbolsToExtract={setSymbolsToExtract}
- onExtractSymbols={extractSymbols}
- />
-
-
-
-
- "/audit"}
- fileId={file}
- toFileIdLink={(fileId) => `/audit/${encodeURIComponent(fileId)}`}
- instanceId={instance}
- toInstanceIdLink={(fileId, instanceId) =>
- `/audit/${encodeURIComponent(fileId)}/${
- encodeURIComponent(instanceId)
- }`}
- />
-
-
-
-
- {
- const newSymbolsToExtract = [...symbolsToExtract];
- for (const symbolId of symbolIds) {
- // Check if there's an existing entry for this file
- const existingIndex = newSymbolsToExtract.findIndex(
- (s) => s.filePath === filePath,
- );
-
- if (existingIndex === -1) {
- // No existing entry for this file, create a new one
- newSymbolsToExtract.push({ filePath, symbols: [symbolId] });
- } else {
- // File exists, check if symbol is already included
- if (
- !newSymbolsToExtract[existingIndex].symbols.includes(
- symbolId,
- )
- ) {
- newSymbolsToExtract[existingIndex].symbols.push(symbolId);
- }
- }
- }
- setSymbolsToExtract(newSymbolsToExtract);
- },
- } as AuditContext}
- />
-
-
-
- );
-}
-
-function BreadcrumbNav(props: {
- toProjectLink: () => string;
- fileId: string | undefined;
- toFileIdLink: (fileId: string) => string;
- instanceId: string | undefined;
- toInstanceIdLink: (fileId: string, instanceId: string) => string;
-}) {
- return (
-
-
-
-
- Project
-
-
- {props.fileId && (
- <>
-
-
-
-
- {props.fileId}
-
-
-
- {props.instanceId && (
- <>
-
-
-
-
- {props.instanceId}
-
-
-
- >
- )}
- >
- )}
-
-
- );
-}
-
-interface ExplorerNodeData {
- id: string;
- displayName: string;
- fileId?: string;
- symbolId?: string;
- children: Map;
-}
-
-function FileExplorerSidebar(props: {
- busy: boolean;
- dependencyManifest: DependencyManifest;
- auditManifest: AuditManifest;
- onHighlightInCytoscape: (node: ExplorerNodeData) => void;
- toDetails: (node: ExplorerNodeData) => string;
- symbolsToExtract: SymbolsToExtract;
- onUpdateSymbolsToExtract: (symbolsToExtract: SymbolsToExtract) => void;
- onExtractSymbols: () => void;
-}) {
- const [search, setSearch] = useState("");
-
- const [explorerTree, setExplorerTree] = useState();
-
- // Build the explorer tree when the dependency manifest changes
- useEffect(() => {
- const tree = buildExplorerTree(props.dependencyManifest, search);
- setExplorerTree(tree);
- }, [props.dependencyManifest, search]);
-
- function buildExplorerTree(
- dependencyManifest: DependencyManifest,
- search: string,
- ): ExplorerNodeData | undefined {
- const getExplorerNodeId = (filePath: string, instanceId?: string) => {
- if (instanceId) {
- return `${filePath}#${instanceId}`;
- }
- return filePath;
- };
-
- const root: ExplorerNodeData = {
- id: "root",
- displayName: "Project",
- children: new Map(),
- };
-
- // Filter function to check if a string matches the search term
- const matchesSearch = (text: string): boolean => {
- if (!search) return true;
- return text.toLowerCase().includes(search.toLowerCase());
- };
-
- // Track if any nodes match the search to avoid empty results
- let hasMatchingNodes = false;
-
- for (const fileDependencyManifest of Object.values(dependencyManifest)) {
- const filePath = fileDependencyManifest.filePath;
- const fileName = filePath.split("/").pop() || "";
- const fileMatchesSearch = matchesSearch(fileName);
-
- // Check if any symbols match the search
- const matchingSymbols = Object.keys(fileDependencyManifest.symbols)
- .filter(
- (symbolId) => matchesSearch(symbolId),
- );
-
- // Skip this file if neither the file nor any symbols match the search
- if (search && !fileMatchesSearch && matchingSymbols.length === 0) {
- continue;
- }
-
- hasMatchingNodes = true;
-
- const parts = filePath.split("/");
- let currentNode: ExplorerNodeData = root;
- for (const part of parts) {
- const id = getExplorerNodeId(part);
- if (!currentNode.children.has(id)) {
- currentNode.children.set(id, {
- id: id,
- displayName: part,
- children: new Map(),
- });
- }
- currentNode = currentNode.children.get(id)!;
- }
- currentNode.fileId = getExplorerNodeId(filePath);
-
- // Only add symbols that match the search or if no search is provided
- for (const instanceId of Object.keys(fileDependencyManifest.symbols)) {
- if (!search || matchesSearch(instanceId)) {
- const id = getExplorerNodeId(filePath, instanceId);
- currentNode.children.set(id, {
- id: id,
- displayName: instanceId,
- fileId: filePath,
- symbolId: instanceId,
- children: new Map(),
- });
- }
- }
- }
-
- // If no nodes match the search, return an empty tree
- if (search && !hasMatchingNodes) {
- return undefined;
- }
-
- const flattenTree = (node: ExplorerNodeData): ExplorerNodeData => {
- // First recursively flatten all children
- if (node.children.size > 0) {
- const flattenedChildren = new Map();
- for (const [id, child] of node.children) {
- const flattenedChild = flattenTree(child);
- flattenedChildren.set(id, flattenedChild);
- }
- node.children = flattenedChildren;
- }
-
- // Then check if this node has exactly one child that can be merged
- while (node.children.size === 1) {
- const childEntry = Array.from(node.children.entries())[0];
- const child = childEntry[1];
-
- // Skip if the child has symbols (is a leaf node)
- if (child.fileId) {
- break;
- }
-
- // Merge child's name into parent
- node.displayName = `${node.displayName}/${child.displayName}`;
- // Update the parent's id to child's id
- node.id = child.id;
- // Replace parent's children with child's children
- node.children = child.children;
- }
-
- return node;
- };
-
- // Flatten nodes that have only one child
- return flattenTree(root);
- }
-
- function removeSymbolsFromExtraction(filePath: string, symbolIds: string[]) {
- const newSymbolsToExtract = props.symbolsToExtract.map(
- (symbolToExtract) => {
- if (symbolToExtract.filePath === filePath) {
- // Only filter out the specific symbol IDs from this file's symbols array
- return {
- ...symbolToExtract,
- symbols: symbolToExtract.symbols.filter(
- (symbolId) => !symbolIds.includes(symbolId),
- ),
- };
- }
- return symbolToExtract;
- },
- ).filter((symbolToExtract) => symbolToExtract.symbols.length > 0); // Remove entries with no symbols left
-
- props.onUpdateSymbolsToExtract(newSymbolsToExtract);
- }
-
- return (
-
-
-
-
- NanoAPI
-
-
-
-
- {props.busy
- ? (
-
- {Array.from({ length: 10 }).map((_, index) => (
-
- ))}
-
- )
- : (
-
-
-
-
-
-
- setSearch(e.target.value)}
- placeholder="Search"
- />
-
-
-
- Search for a file or symbol.
-
- The search will find partial matches in both symbol
- names and file paths.
-
-
-
-
-
- {!explorerTree
- ? (
-
- No Matching files found
-
- )
- : (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
- Symbol Extraction
-
-
- {props.symbolsToExtract.length === 0
- ? (
-
- No symbols marked for extraction yet
-
- )
- : (
-
- {props.symbolsToExtract.map((symbolToExtract) => (
-
-
-
-
-
-
-
-
-
-
-
-
- {symbolToExtract.symbols.map((symbol) => (
-
-
-
-
-
-
-
- ))}
-
-
-
- ))}
-
-
- )}
-
-
-
-
-
-
- )}
-
-
-
- );
-}
-
-function ExplorerNode(props: {
- node: ExplorerNodeData;
- level: number;
- onHighlightInCytoscape: (node: ExplorerNodeData) => void;
- toDetails: (node: ExplorerNodeData) => string;
-}) {
- const [showChildren, setShowChildren] = useState(false);
-
- const type: "folder" | "file" | "symbol" = props.node.symbolId
- ? "symbol"
- : props.node.fileId
- ? "file"
- : "folder";
-
- return (
-
- {(() => {
- switch (type) {
- case "folder":
- return (
-
- );
- case "file":
- return (
-
-
-
-
-
-
-
- Highlight in graph
-
-
-
-
-
-
-
- View graph for this file
-
-
-
- );
- case "symbol":
- return (
-
-
-
-
-
-
-
-
-
-
-
- Highlight in graph
-
-
-
-
-
-
-
- View graph for this symbol
-
-
-
-
- );
- }
- })()}
- {showChildren &&
- Array.from(props.node.children.values()).map((child) => (
-
- ))}
-
- );
-}
diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx
new file mode 100644
index 00000000..c74be87a
--- /dev/null
+++ b/packages/app/src/pages/index.tsx
@@ -0,0 +1,65 @@
+import { useEffect, useState } from "react";
+import {
+ getAuditManifest,
+ getDependencyManifest,
+} from "../service/api/index.ts";
+import type { AuditManifest, DependencyManifest } from "@napi/shared";
+import { toast } from "sonner";
+import DependencyVisualizer from "../components/DependencyVisualizer/DependencyVisualizer.tsx";
+import { Loader } from "lucide-react";
+import { useTheme } from "../contexts/ThemeProvider.tsx";
+
+export default function IndexPage() {
+ const [auditManifest, setAuditManifest] = useState(
+ undefined,
+ );
+ const [dependencyManifest, setDependencyManifest] = useState<
+ DependencyManifest | undefined
+ >(undefined);
+
+ const { theme } = useTheme();
+
+ useEffect(() => {
+ async function handleOnLoad() {
+ const dependencyManifestPromise = getDependencyManifest();
+ const auditManifestPromise = getAuditManifest();
+
+ const allPromise = Promise.all([
+ dependencyManifestPromise,
+ auditManifestPromise,
+ ]);
+
+ toast.promise(allPromise, {
+ loading: "Loading manifests",
+ success: "Manifests loaded successfully",
+ error: "Failed to load manifests",
+ });
+
+ const [dependencyManifest, auditManifest] = await allPromise;
+
+ setDependencyManifest(dependencyManifest);
+ setAuditManifest(auditManifest);
+ }
+
+ handleOnLoad();
+ }, []);
+
+ return (
+
+ {auditManifest && dependencyManifest
+ ? (
+
+ )
+ : (
+
+ )}
+
+ );
+}
diff --git a/packages/app/src/service/api/index.ts b/packages/app/src/service/api/index.ts
index dc015293..c68f1b85 100644
--- a/packages/app/src/service/api/index.ts
+++ b/packages/app/src/service/api/index.ts
@@ -1,8 +1,4 @@
-import type {
- AuditManifest,
- DependencyManifest,
- SymbolsToExtract,
-} from "@napi/shared";
+import type { AuditManifest, DependencyManifest } from "@napi/shared";
export async function getDependencyManifest() {
const response = await fetch("/api/dependency-manifest/", {
@@ -31,21 +27,3 @@ export async function getAuditManifest() {
return await responseBody;
}
-
-export async function runExtraction(
- symbolsToExtract: SymbolsToExtract,
-): Promise<{ success: boolean }> {
- const response = await fetch("/api/extractSymbol/", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(symbolsToExtract),
- });
-
- if (!response.ok || response.status !== 200) {
- throw new Error("Failed to run extraction");
- }
-
- return await response.json();
-}
diff --git a/packages/app/src/styles/global.css b/packages/app/src/styles/global.css
index a28618e3..a7d9572d 100644
--- a/packages/app/src/styles/global.css
+++ b/packages/app/src/styles/global.css
@@ -1,5 +1,3 @@
-/* IMPORTANT: This file is used by shadcn/ui to generate the theme. Do not modify this file directly. */
-
@import "tailwindcss";
@import "tw-animate-css";
@@ -21,8 +19,7 @@
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
- /* was same as destructive here before. I changed it. */
- --destructive-foreground: oklch(0.985 0 0);
+ --destructive-foreground: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
@@ -57,8 +54,8 @@
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
- --destructive: oklch(0.577 0.245 27.325);
- --destructive-foreground: oklch(0.85 0.3 25.331);
+ --destructive: oklch(0.396 0.141 25.723);
+ --destructive-foreground: oklch(0.637 0.237 25.331);
--border: oklch(0.269 0 0);
--input: oklch(0.269 0 0);
--ring: oklch(0.439 0 0);
@@ -123,26 +120,28 @@
body {
@apply bg-background text-foreground;
}
+}
+@layer base {
:root {
- --sidebar-background: 0 0% 98%;
- --sidebar-foreground: 240 5.3% 26.1%;
- --sidebar-primary: 240 5.9% 10%;
- --sidebar-primary-foreground: 0 0% 98%;
- --sidebar-accent: 240 4.8% 95.9%;
- --sidebar-accent-foreground: 240 5.9% 10%;
- --sidebar-border: 220 13% 91%;
- --sidebar-ring: 217.2 91.2% 59.8%;
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
}
.dark {
- --sidebar-background: 240 5.9% 10%;
- --sidebar-foreground: 240 4.8% 95.9%;
- --sidebar-primary: 224.3 76.3% 48%;
- --sidebar-primary-foreground: 0 0% 100%;
- --sidebar-accent: 240 3.7% 15.9%;
- --sidebar-accent-foreground: 240 4.8% 95.9%;
- --sidebar-border: 240 3.7% 15.9%;
- --sidebar-ring: 217.2 91.2% 59.8%;
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.439 0 0);
}
}
diff --git a/packages/cli/src/api/index.ts b/packages/cli/src/api/index.ts
index 24685676..eac22300 100644
--- a/packages/cli/src/api/index.ts
+++ b/packages/cli/src/api/index.ts
@@ -1,31 +1,13 @@
import { Router } from "@oak/oak";
import type { z } from "zod";
import type { localConfigSchema } from "../config/localConfig.ts";
-import {
- getFilesFromDirectory,
- writeFilesToDirectory,
-} from "../helpers/fileSystem/index.ts";
-import { getExtensionsForLanguage } from "../helpers/fileSystem/index.ts";
-import { generateDependencyManifest } from "../manifest/dependencyManifest/index.ts";
import { generateAuditManifest } from "../manifest/auditManifest/index.ts";
-import { extractSymbols } from "../symbolExtractor/index.ts";
-import { join } from "@std/path";
-import { extractSymbolPayloadSchema } from "./types.ts";
+import type { DependencyManifest } from "@napi/shared";
export function getApi(
- workDir: string,
napiConfig: z.infer,
+ dependencyManifest: DependencyManifest,
) {
- const fileExtensions = getExtensionsForLanguage(napiConfig.language);
-
- const files = getFilesFromDirectory(workDir, {
- includes: napiConfig.project.include,
- excludes: napiConfig.project.exclude,
- extensions: fileExtensions,
- logMessages: true,
- });
-
- const dependencyManifest = generateDependencyManifest(files, napiConfig);
const auditManifest = generateAuditManifest(dependencyManifest, napiConfig);
const api = new Router();
@@ -42,43 +24,5 @@ export function getApi(
ctx.response.body = auditManifest;
});
- api.post("/api/extractSymbol", async (ctx) => {
- const body = await ctx.request.body.json();
-
- const parsedPayload = extractSymbolPayloadSchema.safeParse(body);
-
- if (!parsedPayload.success) {
- ctx.response.status = 400;
- ctx.response.body = {
- success: false,
- error: parsedPayload.error,
- };
- return;
- }
-
- const symbolsToExtract = new Map<
- string,
- { filePath: string; symbols: Set }
- >();
- for (const { filePath, symbols } of parsedPayload.data) {
- symbolsToExtract.set(filePath, { filePath, symbols: new Set(symbols) });
- }
-
- const extractedFileMap = extractSymbols(
- files,
- dependencyManifest,
- symbolsToExtract,
- napiConfig,
- );
-
- const outputDir = join(workDir, napiConfig.outDir);
- writeFilesToDirectory(extractedFileMap, outputDir);
-
- ctx.response.status = 200;
- ctx.response.body = {
- success: true,
- };
- });
-
return api;
}
diff --git a/packages/cli/src/api/types.ts b/packages/cli/src/api/types.ts
deleted file mode 100644
index f13066ee..00000000
--- a/packages/cli/src/api/types.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import z from "npm:zod";
-
-export const extractSymbolPayloadSchema = z.array(
- z.object({
- filePath: z.string(),
- symbols: z.array(z.string()),
- }),
-);
diff --git a/packages/cli/src/cli/handlers/audit/index.ts b/packages/cli/src/cli/handlers/audit/index.ts
deleted file mode 100644
index 050424da..00000000
--- a/packages/cli/src/cli/handlers/audit/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import viewHandler from "./view.ts";
-import type { Argv, InferredOptionTypes } from "npm:yargs";
-import type { globalOptions } from "../../helpers/options.ts";
-
-function builder(
- yargs: Argv<
- Omit