Shape
A set of shapes that can mask an image and morph between each other.
Preview
Loading...
import { Shape } from "@/components/ui/Shape";
export function ShapeDemo() {
return (
<div className="flex flex-wrap items-center gap-1">
<Shape name="circle" />
<Shape name="square" />
<Shape name="diamond" />
<Shape name="arch" />
<Shape name="pill" />
</div>
);
}Installation
Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces
pnpm dlx shadcn@latest add @aura/shapeManual
Copy and paste the class names utility into your utils/class-names.ts file.
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Copy and paste the Shape component into your components/ui/Shape.tsx file.
"use client";
import * as React from "react";
import { cn } from "@/utils/class-names";
const shapePaths = {
circle: "circle(50% at 50% 50%)",
square: "inset(0% round 12%)",
diamond: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
arch: "polygon(0% 100%, 0% 42%, 18% 14%, 50% 0%, 82% 14%, 100% 42%, 100% 100%)",
pill: "inset(18% 0% round 999px)",
} as const;
type ShapeName = keyof typeof shapePaths;
function Shape({
className,
name = "circle",
size = "default",
...props
}: React.ComponentProps<"div"> & {
name?: ShapeName;
size?: "sm" | "default" | "lg";
}) {
return (
<div
data-slot="shape"
data-shape={name}
data-size={size}
style={{ clipPath: shapePaths[name] }}
className={cn(
"bg-accent-9 transition duration-300 ease-in-out motion-reduce:transition-none",
size === "sm" && "size-3",
size === "default" && "size-8",
size === "lg" && "size-12",
className,
)}
{...props}
/>
);
}
function ShapeMorph({
names = ["circle", "diamond", "arch", "pill", "square"],
interval = 900,
}: {
names?: ShapeName[];
interval?: number;
}) {
const [index, setIndex] = React.useState(0);
React.useEffect(() => {
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
if (query.matches) return;
const timer = window.setInterval(() => {
setIndex((current) => (current + 1) % names.length);
}, interval);
return () => window.clearInterval(timer);
}, [interval, names.length]);
return <Shape name={names[index]} size="sm" />;
}
export { Shape, ShapeMorph, shapePaths };
export type { ShapeName };
Usage
Mask
export const Mask = () => {
return <Shape name="arch" size="lg" role="img" aria-label="Arch mask" />;
};