Cardstandard
관련 내용을 한 상자에 담는 카드(card)입니다. shadcn base-nova 의 원본을 그대로 담은 아이템이며 `div` 로 그립니다.
설치
pnpm dlx shadcn@4.23.0 add @uikit/cardDefault
"use client"
import * as React from "react"
import { Example, ExampleWrapper,} from "@/components/example"import { Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage,} from "@/components/ui/avatar"import { Button } from "@/components/ui/button"import { Card, CardAction, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"import { Field, FieldGroup, FieldLabel } from "@/components/ui/field"import { Input } from "@/components/ui/input"import { ToggleGroup, ToggleGroupItem,} from "@/components/ui/toggle-group"import { CaptionsIcon, PlusIcon } from "lucide-react"
const spacingOptions = [ { className: "[--card-spacing:--spacing(4)]", label: "16px", value: "4", }, { className: "[--card-spacing:--spacing(5)]", label: "20px", value: "5", }, { className: "[--card-spacing:--spacing(6)]", label: "24px", value: "6", }, { className: "[--card-spacing:--spacing(8)]", label: "32px", value: "8", },]
export default function CardExample() { return ( <ExampleWrapper> <CardDefault /> <CardSmall /> <CardContentEdgeToEdge /> <CardCustomSpacing /> <CardHeaderWithBorder /> <CardFooterWithBorder /> <CardHeaderWithBorderSmall /> <CardFooterWithBorderSmall /> <CardWithImage /> <CardWithImageSmall /> <CardLogin /> <CardMeetingNotes /> </ExampleWrapper> )}
function CardContentEdgeToEdge() { return ( <Example title="Content Edge to Edge"> <Card className="mx-auto w-full max-w-sm"> <CardHeader> <CardTitle>Terms of Service</CardTitle> <CardDescription> Review the terms before accepting the agreement. </CardDescription> </CardHeader> <CardContent className="-mb-(--card-spacing) px-0"> <div tabIndex={0} className="max-h-48 space-y-4 overflow-y-scroll border-t bg-muted/50 px-(--card-spacing) py-4 text-sm leading-relaxed"> <p> These terms govern your use of the workspace, including access to shared documents, project files, and collaboration tools. </p> <p> You are responsible for the content you upload and for ensuring that your team has the appropriate permissions to view or edit it. </p> <p> We may update features or limits as the service evolves. When those changes materially affect your workflow, we will notify your workspace administrators. </p> <p> By continuing, you agree to keep your account credentials secure and to follow your organization's acceptable use policies. </p> </div> </CardContent> <CardFooter className="justify-end gap-2"> <Button variant="outline">Decline</Button> <Button>Accept</Button> </CardFooter> </Card> </Example> )}
function CardCustomSpacing() { const [spacing, setSpacing] = React.useState("4") const selectedSpacing = spacingOptions.find( (option) => option.value === spacing )
return ( <Example title="Custom Spacing"> <div className="mx-auto grid w-full max-w-sm gap-4"> <ToggleGroup value={[spacing]} onValueChange={(value) => { if (value[0]) { setSpacing(value[0]) } }} variant="outline" size="sm" className="justify-center" > {spacingOptions.map((option) => ( <ToggleGroupItem key={option.value} value={option.value}> {option.label} </ToggleGroupItem> ))} </ToggleGroup> <Card className={selectedSpacing?.className}> <CardHeader> <CardTitle>Release Health</CardTitle> <CardDescription> Track readiness across launch signals. </CardDescription> </CardHeader> <CardContent> <div className="grid gap-2 rounded-lg bg-muted/50 p-3 text-sm style-lyra:rounded-none style-sera:rounded-none"> <div className="flex items-center justify-between gap-2"> <span className="text-muted-foreground">Checks passed</span> <span className="font-medium">24 / 26</span> </div> <div className="flex items-center justify-between gap-2"> <span className="text-muted-foreground">Open blockers</span> <span className="font-medium">2</span> </div> </div> </CardContent> </Card> </div> </Example> )}
function CardLogin() { return ( <Example title="Login"> <Card className="mx-auto w-full max-w-sm"> <CardHeader> <CardTitle>Login to your account</CardTitle> <CardDescription> Enter your email below to login to your account </CardDescription> </CardHeader> <CardContent> <form> <FieldGroup> <Field> <FieldLabel htmlFor="email">Email</FieldLabel> <Input id="email" type="email" placeholder="m@example.com" required /> </Field> <Field> <div className="flex items-center"> <FieldLabel htmlFor="password">Password</FieldLabel> <a href="#" className="ml-auto inline-block underline-offset-4 hover:underline" > Forgot your password? </a> </div> <Input id="password" type="password" required /> </Field> </FieldGroup> </form> </CardContent> <CardFooter className="flex-col gap-2"> <Button type="submit" className="w-full"> Login </Button> <Button variant="outline" className="w-full"> Login with Google </Button> <div className="mt-4 text-center"> Don't have an account?{" "} <a href="#" className="underline underline-offset-4"> Sign up </a> </div> </CardFooter> </Card> </Example> )}
function CardMeetingNotes() { return ( <Example title="Meeting Notes"> <Card className="mx-auto w-full max-w-sm"> <CardHeader> <CardTitle>Meeting Notes</CardTitle> <CardDescription> Transcript from the meeting with the client. </CardDescription> <CardAction> <Button variant="outline" size="sm"> <CaptionsIcon data-icon="inline-start" /> Transcribe </Button> </CardAction> </CardHeader> <CardContent> <p> Client requested dashboard redesign with focus on mobile responsiveness. </p> <ol className="mt-4 flex list-decimal flex-col gap-2 pl-6"> <li>New analytics widgets for daily/weekly metrics</li> <li>Simplified navigation menu</li> <li>Dark mode support</li> <li>Timeline: 6 weeks</li> <li>Follow-up meeting scheduled for next Tuesday</li> </ol> </CardContent> <CardFooter> <AvatarGroup> <Avatar> <AvatarImage src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%208%208'%3E%3Crect%20width='8'%20height='8'%20fill='%2364748b'/%3E%3C/svg%3E" alt="@shadcn" /> <AvatarFallback>CN</AvatarFallback> </Avatar> <Avatar> <AvatarImage src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%208%208'%3E%3Crect%20width='8'%20height='8'%20fill='%230f766e'/%3E%3C/svg%3E" alt="@maxleiter" /> <AvatarFallback>LR</AvatarFallback> </Avatar> <Avatar> <AvatarImage src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%208%208'%3E%3Crect%20width='8'%20height='8'%20fill='%237c3aed'/%3E%3C/svg%3E" alt="@evilrabbit" /> <AvatarFallback>ER</AvatarFallback> </Avatar> <AvatarGroupCount>+8</AvatarGroupCount> </AvatarGroup> </CardFooter> </Card> </Example> )}
function CardWithImage() { return ( <Example title="With Image"> <Card size="default" className="relative mx-auto w-full max-w-sm pt-0"> <div className="absolute inset-0 z-30 aspect-video bg-primary opacity-50 mix-blend-color" /> <img src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%208%206'%3E%3Crect%20width='8'%20height='6'%20fill='%2394a3b8'/%3E%3C/svg%3E" alt="Photo by mymind on Unsplash" title="Photo by mymind on Unsplash" className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale" /> <CardHeader> <CardTitle>Beautiful Landscape</CardTitle> <CardDescription> A stunning view that captures the essence of natural beauty. </CardDescription> </CardHeader> <CardFooter> <Button className="w-full"> <PlusIcon data-icon="inline-start" /> Button </Button> </CardFooter> </Card> </Example> )}
function CardWithImageSmall() { return ( <Example title="With Image (Small)"> <Card size="sm" className="relative mx-auto w-full max-w-sm pt-0"> <div className="absolute inset-0 z-30 aspect-video bg-primary opacity-50 mix-blend-color" /> <img src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%208%206'%3E%3Crect%20width='8'%20height='6'%20fill='%2394a3b8'/%3E%3C/svg%3E" alt="Photo by mymind on Unsplash" title="Photo by mymind on Unsplash" className="relative z-20 aspect-video w-full object-cover brightness-60 grayscale" /> <CardHeader> <CardTitle>Beautiful Landscape</CardTitle> <CardDescription> A stunning view that captures the essence of natural beauty. </CardDescription> </CardHeader> <CardFooter> <Button size="sm" className="w-full"> <PlusIcon data-icon="inline-start" /> Button </Button> </CardFooter> </Card> </Example> )}
function CardHeaderWithBorder() { return ( <Example title="Header with Border"> <Card className="mx-auto w-full max-w-sm"> <CardHeader className="border-b"> <CardTitle>Header with Border</CardTitle> <CardDescription> This is a card with a header that has a bottom border. </CardDescription> </CardHeader> <CardContent> <p> The header has a border-b class applied, creating a visual separation between the header and content sections. </p> </CardContent> </Card> </Example> )}
function CardFooterWithBorder() { return ( <Example title="Footer with Border"> <Card className="mx-auto w-full max-w-sm"> <CardContent> <p> The footer has a border-t class applied, creating a visual separation between the content and footer sections. </p> </CardContent> <CardFooter className="border-t"> <Button variant="outline" className="w-full"> Footer with Border </Button> </CardFooter> </Card> </Example> )}
function CardDefault() { return ( <Example title="Default Size"> <Card size="default" className="mx-auto w-full max-w-sm"> <CardHeader> <CardTitle>Default Card</CardTitle> <CardDescription> This card uses the default size variant. </CardDescription> </CardHeader> <CardContent> <p> The card component supports a size prop that defaults to "default" for standard spacing and sizing. </p> </CardContent> <CardFooter> <Button variant="outline" className="w-full"> Action </Button> </CardFooter> </Card> </Example> )}
function CardSmall() { return ( <Example title="Small Size"> <Card size="sm" className="mx-auto w-full max-w-sm"> <CardHeader> <CardTitle>Small Card</CardTitle> <CardDescription> This card uses the small size variant. </CardDescription> </CardHeader> <CardContent> <p> The card component supports a size prop that can be set to "sm" for a more compact appearance. </p> </CardContent> <CardFooter> <Button variant="outline" size="sm" className="w-full"> Action </Button> </CardFooter> </Card> </Example> )}
function CardHeaderWithBorderSmall() { return ( <Example title="Header with Border (Small)"> <Card size="sm" className="mx-auto w-full max-w-sm"> <CardHeader className="border-b"> <CardTitle>Header with Border</CardTitle> <CardDescription> This is a small card with a header that has a bottom border. </CardDescription> </CardHeader> <CardContent> <p> The header has a border-b class applied, creating a visual separation between the header and content sections. </p> </CardContent> </Card> </Example> )}
function CardFooterWithBorderSmall() { return ( <Example title="Footer with Border (Small)"> <Card size="sm" className="mx-auto w-full max-w-sm"> <CardContent> <p> The footer has a border-t class applied, creating a visual separation between the content and footer sections. </p> </CardContent> <CardFooter className="border-t"> <Button variant="outline" size="sm" className="w-full"> Footer with Border </Button> </CardFooter> </Card> </Example> )}