Skip to content

Commit

Permalink
chore: update link implementation and usages in tables app-wide (#787)
Browse files Browse the repository at this point in the history
- Updated link component to optionally use button classes
- Fixed link alignment in tables (Agents, Workflows)
- Updated webhooks actions to use dropdown instead of popover

Signed-off-by: Ryan Hopper-Lowe <ryan@acorn.io>
  • Loading branch information
ryanhopperlowe authored Dec 5, 2024
1 parent b13c3a8 commit 9e69b19
Show file tree
Hide file tree
Showing 11 changed files with 164 additions and 185 deletions.
2 changes: 1 addition & 1 deletion ui/admin/app/components/errors/Error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function Error({ error }: { error: Error }) {
<Link
as="button"
className="w-full"
buttonVariant="secondary"
variant="secondary"
to="/"
>
<HomeIcon /> Go home
Expand Down
4 changes: 2 additions & 2 deletions ui/admin/app/components/thread/ThreadMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ export function ThreadMeta({
<Link
to={assistantLink}
as="button"
buttonVariant="ghost"
buttonSize="icon"
variant="ghost"
size="icon"
>
{isAgent ? (
<EditIcon className="w-4 h-4" />
Expand Down
70 changes: 36 additions & 34 deletions ui/admin/app/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,44 @@ import * as React from "react";

import { cn } from "~/lib/utils";

export const ButtonClasses = {
base: "inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none hover:shadow-inner 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",
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/80",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/80",
outline:
"border border-input bg-background shadow-sm hover:bg-muted/80",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-secondary hover:text-secondary-foreground",
accent: "bg-accent text-accent-foreground shadow-sm hover:bg-accent/80",
link: "text-primary hover:text-primary/70 underline-offset-4 hover:underline shadow-none hover:shadow-none",
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-none hover:shadow-inner 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",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/80",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/80",
outline:
"border border-input bg-background shadow-sm hover:bg-muted/80",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-secondary hover:text-secondary-foreground",
accent: "bg-accent text-accent-foreground shadow-sm hover:bg-accent/80",
link: "text-primary hover:text-primary/70 underline-offset-4 hover:underline shadow-none hover:shadow-none",
},
size: {
none: "",
default: "h-9 px-4 py-2",
badge: "text-xs py-0.5 px-2",
sm: "h-8 px-3 text-xs",
lg: "h-10 px-8",
icon: "h-9 w-9 min-w-9 min-h-9 [&_svg]:size-[1.375rem]",
},
shape: {
none: "",
default: "rounded-md",
pill: "rounded-full",
},
},
size: {
default: "h-9 px-4 py-2",
badge: "text-xs py-0.5 px-2",
sm: "h-8 px-3 text-xs",
lg: "h-10 px-8",
icon: "h-9 w-9 min-w-9 min-h-9 [&_svg]:size-[1.375rem]",
defaultVariants: {
variant: "default",
size: "default",
shape: "pill",
},
shape: {
default: "rounded-md",
pill: "rounded-full",
},
},
defaultVariants: {
variant: "default",
size: "default",
shape: "pill",
},
} as const;

const buttonVariants = cva(ButtonClasses.base, ButtonClasses);
}
);

export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof buttonVariants> & {
Expand Down
58 changes: 21 additions & 37 deletions ui/admin/app/components/ui/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,46 @@ import {
LinkProps as RemixLinkProps,
} from "@remix-run/react";
import { VariantProps, cva } from "class-variance-authority";
import { forwardRef } from "react";

import { cn } from "~/lib/utils";

import { ButtonClasses } from "~/components/ui/button";
import { buttonVariants } from "~/components/ui/button";

const linkVariants = cva("", {
variants: {
as: {
button: cn(ButtonClasses.base, "flex flex-row items-center gap-2"),
default: ButtonClasses.variants.variant.link,
default: buttonVariants({
variant: "link",
size: "none",
shape: "none",
}),
button: "flex flex-row items-center gap-2",
div: "",
},
buttonVariant: ButtonClasses.variants.variant,
buttonSize: ButtonClasses.variants.size,
buttonShape: ButtonClasses.variants.shape,
},
defaultVariants: {
as: "default",
},
});

type LinkVariants = VariantProps<typeof linkVariants>;
type ButtonVariants = VariantProps<typeof buttonVariants>;

export type LinkProps = RemixLinkProps & LinkVariants;
export type LinkProps = RemixLinkProps & LinkVariants & ButtonVariants;

export function Link({
as,
buttonVariant,
buttonSize,
buttonShape,
className,
...rest
}: LinkProps) {
const buttonVariants = getButtonVariants({
as,
buttonVariant,
buttonSize,
buttonShape,
});

return (
export const Link = forwardRef<HTMLAnchorElement, LinkProps>(
({ as, variant, size, shape, className, ...rest }, ref) => (
<RemixLink
{...rest}
className={linkVariants({ as, ...buttonVariants, className })}
ref={ref}
className={cn(
linkVariants({ as }),
as === "button" && buttonVariants({ variant, size, shape }),
className
)}
/>
);

function getButtonVariants(props: LinkVariants) {
if (props.as !== "button") return {};
)
);

return {
buttonVariant:
props.buttonVariant ?? ButtonClasses.defaultVariants.variant,
buttonSize: props.buttonSize ?? ButtonClasses.defaultVariants.size,
buttonShape:
props.buttonShape ?? ButtonClasses.defaultVariants.shape,
};
}
}
Link.displayName = "Link";
55 changes: 55 additions & 0 deletions ui/admin/app/components/webhooks/WebhookActions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { EllipsisIcon } from "lucide-react";
import { $path } from "remix-routes";

import { Webhook } from "~/lib/model/webhooks";

import { Button } from "~/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "~/components/ui/dropdown-menu";
import { Link } from "~/components/ui/link";
import { DeleteWebhook } from "~/components/webhooks/DeleteWebhook";

export function WebhookActions({ webhook }: { webhook: Webhook }) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={(e) => e.stopPropagation()}
>
<EllipsisIcon />
</Button>
</DropdownMenuTrigger>

<DropdownMenuContent
className="w-48 p-2 flex flex-col gap-1"
side="bottom"
align="end"
onClick={(e) => e.stopPropagation()}
>
<DropdownMenuGroup>
<Link
to={$path("/webhooks/:webhook", {
webhook: webhook.id,
})}
as="div"
>
<DropdownMenuItem>Edit</DropdownMenuItem>
</Link>

<DeleteWebhook id={webhook.id}>
<DropdownMenuItem onSelect={(e) => e.preventDefault()}>
Delete
</DropdownMenuItem>
</DeleteWebhook>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
);
}
2 changes: 1 addition & 1 deletion ui/admin/app/components/workflow/DeleteWorkflow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function DeleteWorkflowButton({ id }: DeleteWorkflowButtonProps) {
confirmProps={{ variant: "destructive", children: "Delete" }}
description="This action cannot be undone."
>
<TooltipTrigger asChild>
<TooltipTrigger onClick={(e) => e.stopPropagation()} asChild>
<Button
variant="ghost"
size="icon"
Expand Down
7 changes: 5 additions & 2 deletions ui/admin/app/components/workflow/WorkflowView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,18 @@ export function WorkflowViewYaml({ workflow }: WorkflowViewProps) {
return (
<Tooltip>
<Dialog>
<DialogTrigger asChild>
<DialogTrigger asChild onClick={(e) => e.stopPropagation()}>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<ReaderIcon />
</Button>
</TooltipTrigger>
</DialogTrigger>

<DialogContent className="h-[80vh] max-w-[80vw]">
<DialogContent
className="h-[80vh] max-w-[80vw]"
onClick={(e) => e.stopPropagation()}
>
<DialogTitle>{workflow.name}</DialogTitle>
<DialogDescription>Workflow</DialogDescription>

Expand Down
46 changes: 20 additions & 26 deletions ui/admin/app/routes/_auth.agents._index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PlusIcon } from "@radix-ui/react-icons";
import { Link, useNavigate } from "@remix-run/react";
import { useNavigate } from "@remix-run/react";
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
import { SquarePen } from "lucide-react";
import { useMemo } from "react";
Expand All @@ -16,6 +16,7 @@ import { TypographyH2, TypographyP } from "~/components/Typography";
import { DeleteAgent } from "~/components/agent/DeleteAgent";
import { DataTable } from "~/components/composed/DataTable";
import { Button } from "~/components/ui/button";
import { Link } from "~/components/ui/link";
import {
Tooltip,
TooltipContent,
Expand Down Expand Up @@ -119,23 +120,15 @@ export default function Agents() {
header: "Threads",
cell: (info) => (
<div className="flex gap-2 items-center">
<Button
asChild
variant="link"
className="underline"
<Link
to={$path("/threads", {
agentId: info.row.original.id,
from: "agents",
})}
className="px-0"
>
<Link
to={$path("/threads", {
agentId: info.row.original.id,
from: "agents",
})}
className="px-0"
>
<TypographyP>
{info.getValue() || 0} Threads
</TypographyP>
</Link>
</Button>
{info.getValue() || 0} Threads
</Link>
</div>
),
}
Expand All @@ -155,15 +148,16 @@ export default function Agents() {
<div className="flex gap-2 justify-end">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" asChild>
<Link
to={$path("/agents/:agent", {
agent: row.original.id,
})}
>
<SquarePen />
</Link>
</Button>
<Link
to={$path("/agents/:agent", {
agent: row.original.id,
})}
as="button"
size="icon"
variant="ghost"
>
<SquarePen />
</Link>
</TooltipTrigger>

<TooltipContent>
Expand Down
2 changes: 1 addition & 1 deletion ui/admin/app/routes/_auth.users.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { Link } from "@remix-run/react";
import { ColumnDef, createColumnHelper } from "@tanstack/react-table";
import { useMemo } from "react";
import { $path } from "remix-routes";
Expand All @@ -12,6 +11,7 @@ import { pluralize, timeSince } from "~/lib/utils";

import { TypographyH2, TypographyP } from "~/components/Typography";
import { DataTable } from "~/components/composed/DataTable";
import { Link } from "~/components/ui/link";

export async function clientLoader() {
const users = await preload(
Expand Down
Loading

0 comments on commit 9e69b19

Please sign in to comment.