1
0
Fork 0

feat(ui): enhance shadcn dialog, tabs, tooltip, and command components

- Add showCloseButton prop to DialogContent for modal workflows
- Add line variant to TabsList via class-variance-authority
- Add arrowClassName prop to TooltipContent for arrow styling
- Add optional title prop to CommandDialog for accessibility
This commit is contained in:
Jason Fraley 2026-08-17 22:18:31 -04:00
parent ae732496ed
commit 452aa392bd
4 changed files with 49 additions and 17 deletions

View file

@ -6,7 +6,7 @@ import { Command as CommandPrimitive } from "cmdk";
import { Search } from "lucide-react";
import { cn } from "@/lib/utils";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog";
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
@ -23,10 +23,15 @@ const Command = React.forwardRef<
));
Command.displayName = CommandPrimitive.displayName;
const CommandDialog = ({ children, ...props }: DialogProps) => {
const CommandDialog = ({
children,
title,
...props
}: DialogProps & { title?: React.ReactNode }) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0">
{title && <DialogTitle className="sr-only">{title}</DialogTitle>}
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>

View file

@ -31,8 +31,10 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & {
showCloseButton?: boolean;
}
>(({ className, children, showCloseButton = true, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
@ -44,10 +46,12 @@ const DialogContent = React.forwardRef<
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
{showCloseButton && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
)}
</DialogPrimitive.Content>
</DialogPortal>
));

View file

@ -2,21 +2,36 @@
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const Tabs = TabsPrimitive.Root;
const tabsListVariants = cva(
"inline-flex items-center justify-center text-muted-foreground",
{
variants: {
variant: {
default: "h-9 rounded-lg bg-muted p-1",
line: "h-9 w-full justify-start gap-1 border-b border-border bg-transparent p-0 group",
},
},
defaultVariants: {
variant: "default",
},
},
);
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> &
VariantProps<typeof tabsListVariants>
>(({ className, variant, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
className,
)}
data-variant={variant ?? "default"}
className={cn(tabsListVariants({ variant, className }))}
{...props}
/>
));
@ -30,6 +45,9 @@ const TabsTrigger = React.forwardRef<
ref={ref}
className={cn(
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow",
// "line" variant: underline-style triggers (applies only when the parent
// TabsList has data-variant="line" + the `group` class from the cva above).
"group-data-[variant=line]:rounded-none group-data-[variant=line]:border-b-2 group-data-[variant=line]:border-transparent group-data-[variant=line]:bg-transparent group-data-[variant=line]:px-4 group-data-[variant=line]:py-2 group-data-[variant=line]:shadow-none group-data-[variant=line]:data-[state=active]:border-primary group-data-[variant=line]:data-[state=active]:bg-transparent group-data-[variant=line]:data-[state=active]:text-foreground group-data-[variant=line]:data-[state=active]:shadow-none",
className,
)}
{...props}

View file

@ -13,8 +13,10 @@ const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & {
arrowClassName?: string;
}
>(({ className, sideOffset = 4, arrowClassName, children, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
@ -24,7 +26,10 @@ const TooltipContent = React.forwardRef<
className,
)}
{...props}
/>
>
{arrowClassName && <TooltipPrimitive.Arrow className={arrowClassName} />}
{children}
</TooltipPrimitive.Content>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;