Files
snippet-pastebin/src/components/ui/fab.tsx
johndoe6345789 1cbcb2051f feat: implement Material Design 3 styles for buttons and dialogs
- Revamped button styles to align with Material Design 3 guidelines, including new variants (filled, tonal, elevated, outlined, text, and icon buttons).
- Enhanced button states with hover, focus, and active effects, incorporating opacity transitions and background color changes.
- Updated dialog styles to reflect MD3 design principles, including scrim overlay, dialog content, headers, footers, and close buttons.
- Introduced animations for dialogs, dropdowns, and snackbars to improve user experience.
- Refined dropdown menu and select component styles, ensuring consistency with MD3 aesthetics.
2026-01-19 23:41:56 +00:00

173 lines
4.8 KiB
TypeScript

'use client'
import { ComponentProps, forwardRef } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
/**
* Material Design 3 Floating Action Button (FAB)
*
* Variants:
* - primary: Primary container color (default)
* - secondary: Secondary container color
* - tertiary: Tertiary container color
* - surface: Surface container color
*
* Sizes:
* - small: 40x40 (MD3 Small FAB)
* - default: 56x56 (MD3 FAB)
* - large: 96x96 (MD3 Large FAB)
*/
const fabVariants = cva(
// Base styles
[
"inline-flex items-center justify-center",
"rounded-2xl font-medium",
"transition-all duration-200",
"disabled:pointer-events-none disabled:opacity-[0.38]",
"[&_svg]:pointer-events-none",
"shrink-0 [&_svg]:shrink-0",
"outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"relative overflow-hidden select-none",
"shadow-lg hover:shadow-xl active:shadow-md",
// State layer
"before:absolute before:inset-0 before:rounded-[inherit] before:bg-current before:opacity-0 before:transition-opacity before:duration-200",
"hover:before:opacity-[0.08] focus-visible:before:opacity-[0.12] active:before:opacity-[0.12]",
].join(" "),
{
variants: {
variant: {
// Primary container (default)
primary: [
"bg-primary text-primary-foreground",
"focus-visible:ring-primary",
].join(" "),
// Secondary container
secondary: [
"bg-secondary text-secondary-foreground",
"focus-visible:ring-secondary",
].join(" "),
// Tertiary container
tertiary: [
"bg-accent text-accent-foreground",
"focus-visible:ring-accent",
].join(" "),
// Surface container
surface: [
"bg-card text-primary",
"focus-visible:ring-primary",
].join(" "),
},
size: {
// MD3 Small FAB - 40x40
small: "size-10 rounded-xl [&_svg]:size-5",
// MD3 FAB - 56x56
default: "size-14 rounded-2xl [&_svg]:size-6",
// MD3 Large FAB - 96x96
large: "size-24 rounded-[28px] [&_svg]:size-9",
},
},
defaultVariants: {
variant: "primary",
size: "default",
},
}
)
interface FABProps
extends ComponentProps<"button">,
VariantProps<typeof fabVariants> {
icon: React.ReactNode
}
const FAB = forwardRef<HTMLButtonElement, FABProps>(
({ className, variant, size, icon, ...props }, ref) => {
return (
<button
ref={ref}
data-slot="fab"
className={cn(fabVariants({ variant, size }), className)}
{...props}
>
{icon}
</button>
)
}
)
FAB.displayName = "FAB"
/**
* Extended FAB with label
*/
interface ExtendedFABProps
extends ComponentProps<"button">,
VariantProps<typeof fabVariants> {
icon?: React.ReactNode
label: string
}
const extendedFabVariants = cva(
// Base styles
[
"inline-flex items-center justify-center gap-3",
"h-14 px-4 rounded-2xl font-medium text-sm",
"transition-all duration-200",
"disabled:pointer-events-none disabled:opacity-[0.38]",
"[&_svg]:pointer-events-none [&_svg]:size-6",
"shrink-0 [&_svg]:shrink-0",
"outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
"relative overflow-hidden select-none",
"shadow-lg hover:shadow-xl active:shadow-md",
// State layer
"before:absolute before:inset-0 before:rounded-[inherit] before:bg-current before:opacity-0 before:transition-opacity before:duration-200",
"hover:before:opacity-[0.08] focus-visible:before:opacity-[0.12] active:before:opacity-[0.12]",
].join(" "),
{
variants: {
variant: {
primary: [
"bg-primary text-primary-foreground",
"focus-visible:ring-primary",
].join(" "),
secondary: [
"bg-secondary text-secondary-foreground",
"focus-visible:ring-secondary",
].join(" "),
tertiary: [
"bg-accent text-accent-foreground",
"focus-visible:ring-accent",
].join(" "),
surface: [
"bg-card text-primary",
"focus-visible:ring-primary",
].join(" "),
},
},
defaultVariants: {
variant: "primary",
},
}
)
const ExtendedFAB = forwardRef<HTMLButtonElement, ExtendedFABProps>(
({ className, variant, icon, label, ...props }, ref) => {
return (
<button
ref={ref}
data-slot="extended-fab"
className={cn(extendedFabVariants({ variant }), className)}
{...props}
>
{icon}
<span>{label}</span>
</button>
)
}
)
ExtendedFAB.displayName = "ExtendedFAB"
export { FAB, ExtendedFAB, fabVariants, extendedFabVariants }