Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 48 additions & 14 deletions apps/apollo-vertex/app/shadcn-components/card/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
import { Button } from '@/registry/button/button'
import { Input } from '@/registry/input/input'
import { Label } from '@/registry/label/label'
import { SelectableCardDemo } from './selectable-card-demo'

# Card

Displays a card with header, content, and footer.
Displays a card with header, content, and footer. The default variant uses a frosted glass effect. Use `variant="solid"` for a flat card with a solid background.

<div className="p-4 border rounded-lg mt-4">
<Card className="w-[350px]">
<div className="flex justify-center p-8 rounded-lg mt-4 bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-slate-900 dark:to-slate-800">
<Card className="w-[350px] gap-6 py-6">
<CardHeader>
<CardTitle>Create project</CardTitle>
<CardDescription>Deploy your new project in one-click.</CardDescription>
Expand All @@ -30,22 +31,22 @@ Displays a card with header, content, and footer.
</Card>
</div>

## Glass Variant
## Solid Variant

Use `variant="glass"` for a frosted glass effect with semi-transparent backgrounds and layered shadows.
Use `variant="solid"` for a flat card with a solid background and border.

<div className="p-8 rounded-lg mt-4 bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-slate-900 dark:to-slate-800">
<Card variant="glass" className="w-[350px]">
<div className="flex justify-center p-4 border rounded-lg mt-4">
<Card variant="solid" className="w-[350px] gap-6 py-6">
<CardHeader>
<CardTitle>Glass Card</CardTitle>
<CardDescription>A frosted glass effect with backdrop blur and layered shadows.</CardDescription>
<CardTitle>Solid Card</CardTitle>
<CardDescription>A flat card with a solid background.</CardDescription>
</CardHeader>
<CardContent>
<form>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-1.5">
<Label htmlFor="glass-name">Name</Label>
<Input id="glass-name" placeholder="Name of your project" />
<Label htmlFor="solid-name">Name</Label>
<Input id="solid-name" placeholder="Name of your project" />
</div>
</div>
</form>
Expand All @@ -58,10 +59,10 @@ Use `variant="glass"` for a frosted glass effect with semi-transparent backgroun
</div>

```tsx
<Card variant="glass">
<Card variant="solid">
<CardHeader>
<CardTitle>Glass Card</CardTitle>
<CardDescription>A frosted glass effect.</CardDescription>
<CardTitle>Solid Card</CardTitle>
<CardDescription>A flat card with a solid background.</CardDescription>
</CardHeader>
<CardContent>
<p>Card Content</p>
Expand All @@ -72,6 +73,26 @@ Use `variant="glass"` for a frosted glass effect with semi-transparent backgroun
</Card>
```

## Selectable

Cards can be made selectable with `selectable="standard"` or `selectable="ai"`. Selectable cards render as a `<button>` with `aria-pressed` for accessibility. They show a glow effect on hover and when selected.

<div className="mt-4">
<SelectableCardDemo />
</div>

```tsx
<Card selectable="standard" selected={selected} onClick={() => setSelected(!selected)}>
<CardTitle>Standard</CardTitle>
<CardDescription>Click to select this card.</CardDescription>
</Card>

<Card selectable="ai" selected={selected} onClick={() => setSelected(!selected)}>
<CardTitle>AI Mode</CardTitle>
<CardDescription>Uses an AI gradient glow when selected.</CardDescription>
</Card>
```

## Installation

```bash
Expand All @@ -88,6 +109,7 @@ import {
CardFooter,
CardHeader,
CardTitle,
GLASS_CLASSES,
} from "@/components/ui/card"
```

Expand All @@ -105,3 +127,15 @@ import {
</CardFooter>
</Card>
```

### GLASS_CLASSES

The `GLASS_CLASSES` constant is exported for reuse in other components that need the same frosted glass styling:

```tsx
import { GLASS_CLASSES } from "@/components/ui/card"

<div className={cn(GLASS_CLASSES, "p-4")}>
Glass morphism container
</div>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import { useState } from "react";

import { Card, CardDescription, CardTitle } from "@/registry/card/card";

export function SelectableCardDemo() {
const [standardSelected, setStandardSelected] = useState(false);
const [aiSelected, setAiSelected] = useState(false);

return (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 p-8 rounded-lg bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800">
<Card
selectable="standard"
selected={standardSelected}
onClick={() => setStandardSelected(!standardSelected)}
>
<CardTitle>Standard</CardTitle>
<CardDescription>
Click to select this card. Uses a primary color glow.
</CardDescription>
</Card>
<Card
selectable="ai"
selected={aiSelected}
onClick={() => setAiSelected(!aiSelected)}
>
<CardTitle>AI Mode</CardTitle>
<CardDescription>
Click to select this card. Uses an AI gradient glow.
</CardDescription>
</Card>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A carousel with motion and swipe built using Embla.
{[1, 2, 3, 4, 5].map((num) => (
<CarouselItem key={num}>
<div className="p-1">
<Card>
<Card variant="solid">
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{num}</span>
</CardContent>
Expand Down
4 changes: 2 additions & 2 deletions apps/apollo-vertex/app/shadcn-components/tabs/page.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A set of layered sections of content—known as tab panels—that are displayed
<TabsTrigger value="password">Password</TabsTrigger>
</TabsList>
<TabsContent value="account">
<Card>
<Card variant="solid" className="gap-6 py-6">
<CardHeader>
<CardTitle>Account</CardTitle>
<CardDescription>
Expand All @@ -38,7 +38,7 @@ A set of layered sections of content—known as tab panels—that are displayed
</Card>
</TabsContent>
<TabsContent value="password">
<Card>
<Card variant="solid" className="gap-6 py-6">
<CardHeader>
<CardTitle>Password</CardTitle>
<CardDescription>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ export function ThemeCustomizer() {

return (
<div className="space-y-6">
<Card className="p-6">
<Card variant="solid" className="p-6">
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
Expand Down Expand Up @@ -357,7 +357,7 @@ export function ThemeCustomizer() {
</div>
</Card>

<Card className="p-6">
<Card variant="solid" className="p-6">
<h3 className="text-lg font-semibold mb-4">Preview</h3>
<div className="space-y-4">
<div className="flex gap-2">
Expand Down
8 changes: 5 additions & 3 deletions apps/apollo-vertex/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@
"ease-in-out-circ": "cubic-bezier(0.785, 0.135, 0.15, 0.86)",
"--info-fg": "oklch(0.49 0.12 210)",
"--success-fg": "oklch(0.46 0.10 152)",
"--destructive-fg": "oklch(0.50 0.14 18)"
"--destructive-fg": "oklch(0.50 0.14 18)",
"ai-gradient": "linear-gradient(100.64deg, rgb(238, 224, 255) 8.29%, rgb(207, 217, 255) 88.73%)"
},
"dark": {
"background": "oklch(0.2100 0.0300 258.5000)",
Expand Down Expand Up @@ -225,7 +226,8 @@
"shadow-xl": "0 0px 12px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10)",
"shadow-2xl": "0 0px 12px 0px hsl(0 0% 0% / 0.25)",
"tracking-normal": "0em",
"spacing": "0.25rem"
"spacing": "0.25rem",
"ai-gradient": "linear-gradient(98.69deg, rgb(108, 90, 239) 8.79%, rgb(105, 199, 221) 91.48%)"
}
}
},
Expand Down Expand Up @@ -415,7 +417,7 @@
"name": "card",
"type": "registry:ui",
"title": "Card",
"description": "Displays a card with header, content, and footer.",
"description": "Displays a card with header, content, and footer. Supports solid, glass, and selectable variants.",
"files": [{ "path": "registry/card/card.tsx", "type": "registry:ui" }]
},
{
Expand Down
97 changes: 87 additions & 10 deletions apps/apollo-vertex/registry/card/card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,110 @@ import * as React from "react";

import { cn } from "@/lib/utils";

const cardVariants = cva("flex flex-col gap-6 py-6 text-card-foreground", {
export const GLASS_CLASSES = [
"bg-white/55 border border-white/80 rounded-2xl backdrop-blur-sm",
"shadow-[0_2px_16px_2px_rgba(0,0,0,0.05),inset_0_1px_0_0_rgba(255,255,255,0.6)]",
"dark:bg-white/[0.055] dark:border-white/[0.03]",
"dark:shadow-[0_2px_24px_2px_rgba(0,0,0,0.12),inset_0_1px_0_0_color-mix(in_srgb,var(--sidebar)_5%,transparent)]",
] as const;

const cardVariants = cva("flex flex-col text-card-foreground", {
variants: {
variant: {
default: "bg-card rounded-xl border",
glass: [
"bg-white/55 border border-white/80 rounded-2xl backdrop-blur-sm",
"shadow-[0_2px_16px_2px_rgba(0,0,0,0.05),inset_0_1px_0_0_rgba(255,255,255,0.6)]",
"dark:bg-white/[0.055] dark:border-white/[0.03]",
"dark:shadow-[0_2px_24px_2px_rgba(0,0,0,0.12),inset_0_1px_0_0_color-mix(in_srgb,var(--sidebar)_5%,transparent)]",
],
default: GLASS_CLASSES,
solid: "bg-card rounded-xl border",
glass: GLASS_CLASSES,
},
},
defaultVariants: {
variant: "default",
},
});

interface CardProps
extends React.ComponentProps<"div">,
VariantProps<typeof cardVariants> {
selectable?: "standard" | "ai";
selected?: boolean;
}

function Card({
className,
variant,
selectable,
selected = false,
children,
...props
}: React.ComponentProps<"div"> & VariantProps<typeof cardVariants>) {
}: CardProps) {
if (selectable) {
const isAi = selectable === "ai";

return (
<div
className="relative group h-full"
data-slot="card"
data-variant={variant}
data-selectable={selectable}
>
{/* Selected glow: gradient for ai, primary for standard */}
{isAi ? (
<div
className={cn(
"absolute inset-0 rounded-2xl pointer-events-none blur-xl",
"transition-opacity duration-150",
selected ? "opacity-100" : "opacity-0",
)}
style={{ background: "var(--ai-gradient)" }}
/>
) : (
<div
className={cn(
"absolute inset-0 rounded-2xl pointer-events-none blur-xl bg-primary-400/15 dark:bg-primary-400/30",
"transition-opacity duration-150",
selected ? "opacity-100" : "opacity-0",
)}
/>
)}

{/* Hover glow: always primary */}
<div
className={cn(
"absolute inset-0 rounded-2xl pointer-events-none blur-xl bg-primary-400/15 dark:bg-primary-400/50",
"opacity-0 transition-opacity duration-150",
!selected && "group-hover:opacity-100",
)}
/>

<button
{...(props as React.ComponentProps<"button">)}
type="button"
aria-pressed={selected}
className={cn(
"relative w-full h-full flex flex-col text-left p-5 text-card-foreground",
GLASS_CLASSES,
"transition-all duration-150 cursor-pointer",
"focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none",
selected
? "border-primary dark:border-primary bg-card dark:bg-card"
: "hover:bg-card dark:hover:bg-card",
className,
)}
>
{children}
</button>
</div>
);
}

return (
<div
data-slot="card"
data-variant={variant}
className={cn(cardVariants({ variant, className }))}
{...props}
/>
>
{children}
</div>
);
}

Expand Down Expand Up @@ -111,3 +186,5 @@ export {
CardDescription,
CardContent,
};

export type { CardProps };
2 changes: 1 addition & 1 deletion apps/apollo-vertex/templates/MyTemplate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/registry/card/card";

export function MyTemplate() {
return (
<Card>
<Card variant="solid" className="gap-6 py-6">
<CardHeader>
<CardTitle>My Template</CardTitle>
</CardHeader>
Expand Down
Loading
Loading