Scale up radix-mira component sizes for a portfolio context

The new shadcn 'radix-mira' style targets dense dashboards. For a personal
academic site the ultra-compact defaults (10px badge, 12px button/card,
14px card title) read as broken. Updated to readable sizes:

- card.tsx: text-xs/relaxed → text-sm base; CardTitle text-sm font-medium
  → text-base font-semibold leading-tight; CardDescription text-xs → text-sm
- badge.tsx: text-[0.625rem] h-5 px-2 → text-xs h-6 px-2.5
- button.tsx: text-xs h-7 px-2 → text-sm h-9 px-4 (default size)
- tabs.tsx: TabsTrigger text-xs px-1.5 → text-sm px-3; TabsContent
  text-xs/relaxed removed; TabsList h-8 → h-10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-11 01:34:15 -04:00
parent 4dc9d5db9b
commit 1e4704ed3f
4 changed files with 244 additions and 173 deletions
+31 -19
View File
@@ -1,37 +1,49 @@
import * as React from "react"; import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"; import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils"
const badgeVariants = cva( const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", "group/badge inline-flex h-6 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2.5 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
{ {
variants: { variants: {
variant: { variant: {
default: default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary: secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", "bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive: destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", "bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
outline: "text-foreground", outline:
"border-border bg-input/20 text-foreground dark:bg-input/30 [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
}, },
}, }
); )
export interface BadgeProps function Badge({
extends className,
React.HTMLAttributes<HTMLDivElement>, variant = "default",
VariantProps<typeof badgeVariants> {} asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span"
function Badge({ className, variant, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant }), className)} {...props} /> <Comp
); data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
)
} }
export { Badge, badgeVariants }; export { Badge, badgeVariants }
+40 -33
View File
@@ -1,58 +1,65 @@
import * as React from "react"; import * as React from "react"
import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"
import { cva, type VariantProps } from "class-variance-authority"; import { Slot } from "radix-ui"
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xl 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", "group/button inline-flex shrink-0 items-center justify-center rounded-md border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
{ {
variants: { variants: {
variant: { variant: {
default: default: "bg-primary text-primary-foreground hover:bg-primary/80",
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline: outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", "border-border hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:bg-input/30",
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost:
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
destructive:
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
link: "text-primary underline-offset-4 hover:underline", link: "text-primary underline-offset-4 hover:underline",
}, },
size: { size: {
default: "h-9 px-4 py-2", default:
sm: "h-8 px-3 text-xs", "h-9 gap-2 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3 [&_svg:not([class*='size-'])]:size-4",
lg: "h-10 px-8", xs: "h-6 gap-1 rounded-sm px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
icon: "h-9 w-9 rounded-full", sm: "h-8 gap-1.5 px-3 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3.5",
lg: "h-10 gap-2 px-6 text-base has-data-[icon=inline-end]:pr-4 has-data-[icon=inline-start]:pl-4 [&_svg:not([class*='size-'])]:size-5",
icon: "size-9 [&_svg:not([class*='size-'])]:size-4",
"icon-xs": "size-6 rounded-sm [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8 [&_svg:not([class*='size-'])]:size-3.5",
"icon-lg": "size-10 [&_svg:not([class*='size-'])]:size-5",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: "default",
size: "default", size: "default",
}, },
}, }
); )
export interface ButtonProps function Button({
extends className,
React.ButtonHTMLAttributes<HTMLButtonElement>, variant = "default",
VariantProps<typeof buttonVariants> { size = "default",
asChild?: boolean; asChild = false,
} ...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
return ( return (
<Comp <Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props} {...props}
/> />
); )
}, }
);
Button.displayName = "Button";
export { Button, buttonVariants }; export { Button, buttonVariants }
+69 -52
View File
@@ -1,83 +1,100 @@
import * as React from "react"; import * as React from "react"
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils"
const Card = React.forwardRef< function Card({
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"overflow-hidden rounded-3xl border border-border/60 bg-background/80 text-card-foreground shadow-sm backdrop-blur-xl",
className, className,
size = "default",
...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
return (
<div
data-slot="card"
data-size={size}
className={cn(
"group/card flex flex-col gap-4 overflow-hidden rounded-lg bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 *:[img:first-child]:rounded-t-lg *:[img:last-child]:rounded-b-lg",
className
)} )}
{...props} {...props}
/> />
)); )
Card.displayName = "Card"; }
const CardHeader = React.forwardRef< function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="card-header"
className={cn("flex flex-col space-y-1.5 p-6", className)} className={cn(
"group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-lg px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
className
)}
{...props} {...props}
/> />
)); )
CardHeader.displayName = "CardHeader"; }
const CardTitle = React.forwardRef< function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="card-title"
className={cn("font-semibold leading-none tracking-tight", className)} className={cn("font-heading text-base font-semibold leading-tight", className)}
{...props} {...props}
/> />
)); )
CardTitle.displayName = "CardTitle"; }
const CardDescription = React.forwardRef< function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="card-description"
className={cn("text-sm text-muted-foreground", className)} className={cn("text-sm text-muted-foreground", className)}
{...props} {...props}
/> />
)); )
CardDescription.displayName = "CardDescription"; }
const CardContent = React.forwardRef< function CardAction({ className, ...props }: React.ComponentProps<"div">) {
HTMLDivElement, return (
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div <div
ref={ref} data-slot="card-action"
className={cn("flex items-center p-6 pt-0", className)} className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props} {...props}
/> />
)); )
CardFooter.displayName = "CardFooter"; }
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn(
"flex items-center rounded-b-lg px-4 group-data-[size=sm]/card:px-3 [.border-t]:pt-4 group-data-[size=sm]/card:[.border-t]:pt-3",
className
)}
{...props}
/>
)
}
export { export {
Card, Card,
CardHeader, CardHeader,
CardFooter, CardFooter,
CardTitle, CardTitle,
CardAction,
CardDescription, CardDescription,
CardContent, CardContent,
}; }
+72 -37
View File
@@ -1,55 +1,90 @@
"use client"; "use client"
import * as React from "react"; import * as React from "react"
import * as TabsPrimitive from "@radix-ui/react-tabs"; import { cva, type VariantProps } from "class-variance-authority"
import { Tabs as TabsPrimitive } from "radix-ui"
import { cn } from "~/lib/utils"; import { cn } from "~/lib/utils"
const Tabs = TabsPrimitive.Root; function Tabs({
className,
orientation = "horizontal",
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
data-orientation={orientation}
className={cn(
"group/tabs flex gap-2 data-horizontal:flex-col",
className
)}
{...props}
/>
)
}
const TabsList = React.forwardRef< const tabsListVariants = cva(
React.ElementRef<typeof TabsPrimitive.List>, "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-10 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> {
>(({ className, ...props }, ref) => ( variants: {
variant: {
default: "bg-muted",
line: "gap-1 bg-transparent",
},
},
defaultVariants: {
variant: "default",
},
}
)
function TabsList({
className,
variant = "default",
...props
}: React.ComponentProps<typeof TabsPrimitive.List> &
VariantProps<typeof tabsListVariants>) {
return (
<TabsPrimitive.List <TabsPrimitive.List
ref={ref} data-slot="tabs-list"
className={cn( data-variant={variant}
"inline-flex h-9 items-center justify-center rounded-xl border border-border/50 bg-background/80 p-1 text-muted-foreground shadow-sm backdrop-blur-md", className={cn(tabsListVariants({ variant }), className)}
className,
)}
{...props} {...props}
/> />
)); )
TabsList.displayName = TabsPrimitive.List.displayName; }
const TabsTrigger = React.forwardRef< function TabsTrigger({
React.ElementRef<typeof TabsPrimitive.Trigger>, className,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
ref={ref} data-slot="tabs-trigger"
className={cn( 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", "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-3 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:py-2 hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className, "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
className
)} )}
{...props} {...props}
/> />
)); )
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; }
const TabsContent = React.forwardRef< function TabsContent({
React.ElementRef<typeof TabsPrimitive.Content>, className,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> ...props
>(({ className, ...props }, ref) => ( }: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content <TabsPrimitive.Content
ref={ref} data-slot="tabs-content"
className={cn( className={cn("flex-1 outline-none", className)}
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className,
)}
{...props} {...props}
/> />
)); )
TabsContent.displayName = TabsPrimitive.Content.displayName; }
export { Tabs, TabsList, TabsTrigger, TabsContent }; export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }