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
1 change: 1 addition & 0 deletions apps/v4/content/docs/(root)/figma.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ description: Every component recreated in Figma. With customizable props, typogr
- [Shadcnblocks.com](https://www.shadcnblocks.com) - A Premium Shadcn Figma UI Kit with components, 500+ pro blocks, shadcn theme variables, light/dark mode and Figma MCP ready.
- [Obra shadcn/ui Pro](https://shadcn.obra.studio/products/obra-shadcn-ui-pro) by [Obra Studio](https://obra.studio/) - Focused on designers who need to get work done — the best designer experience for shadcn/ui within Figma. variable consistency with shadcn, plus custom components, Pro blocks, and a design-to-code plugin.
- [Shadcn Space](https://shadcnspace.com/figma) - A collection of beautifully designed 320+ blocks, 9+ templates, and 250+ components with a shadcn/ui-compatible Figma kit built for modern React and Next.js workflows. Design, prototype, and ship faster using Figma components that mirror real code architecture and production-ready implementation patterns.
- [Shadcn Design System](https://shadcnstore.com/) by [ShadcnStore](https://x.com/shadcnstore) - A complete Figma UI kit for shadcn/ui with components, variants, design tokens, production-ready blocks and templates. Built for fast workflows and pixel-perfect designer-to-developer handoff.
136 changes: 136 additions & 0 deletions apps/v4/content/docs/components/aria/hover-card.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
title: Hover Card
description: A popover that appears on hover, focus, or long press to preview content available behind a link.
base: aria
component: true
links:
doc: https://react-aria.adobe.com/PreviewTrigger
api: https://react-aria.adobe.com/PreviewTrigger#api
---

<ComponentPreview
styleName="aria-nova"
name="hover-card-demo"
previewClassName="h-80"
/>

## Installation

<CodeTabs>

<TabsList>
<TabsTrigger value="cli">Command</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>
<TabsContent value="cli">

```bash
npx shadcn@latest add hover-card
```

</TabsContent>

<TabsContent value="manual">

<Steps className="mb-0 pt-2">

<Step>Install the following dependencies:</Step>

```bash
npm install react-aria-components
```

<Step>Copy and paste the following code into your project.</Step>

<ComponentSource
name="hover-card"
title="components/ui/hover-card.tsx"
styleName="aria-nova"
/>

<Step>Update the import paths to match your project setup.</Step>

</Steps>

</TabsContent>

</CodeTabs>

## Usage

```tsx showLineNumbers
import { HoverCard, HoverCardTrigger } from "@/components/ui/hover-card"
```

```tsx showLineNumbers
<HoverCardTrigger>
<Button variant="link">Hover</Button>
<HoverCard>
The React Framework – created and maintained by @vercel.
</HoverCard>
</HoverCardTrigger>
```

## Composition

Use the following composition to build a `HoverCard`:

```text
HoverCardTrigger
├── Button
└── HoverCard
```

## Trigger Delays

Use `delay` and `closeDelay` on the `HoverCardTrigger` to control when the card
opens and closes.

```tsx showLineNumbers
<HoverCardTrigger delay={100} closeDelay={200}>
<Button variant="link">Hover</Button>
<HoverCard>Content</HoverCard>
</HoverCardTrigger>
```

## Positioning

Use the `placement` prop on `HoverCard` to control placement.

```tsx showLineNumbers
<HoverCardTrigger>
<Button variant="link">Hover</Button>
<HoverCard placement="top">Content</HoverCard>
</HoverCardTrigger>
```

## Basic

<ComponentPreview
styleName="aria-nova"
name="hover-card-demo"
previewClassName="h-80"
/>

## Sides

<ComponentPreview
styleName="aria-nova"
name="hover-card-sides"
previewClassName="h-[22rem]"
/>

## RTL

To enable RTL support in shadcn/ui, see the [RTL configuration guide](/docs/rtl).

<ComponentPreview
styleName="aria-nova"
name="hover-card-rtl"
direction="rtl"
previewClassName="h-80"
/>

## API Reference

See the [React Aria](https://react-aria.adobe.com/PreviewTrigger#api) documentation for more information.
1 change: 1 addition & 0 deletions apps/v4/content/docs/components/aria/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"empty",
"field",
"form",
"hover-card",
"input",
"input-group",
"input-otp",
Expand Down
24 changes: 24 additions & 0 deletions apps/v4/examples/__components__/aria.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,30 @@ export const Components: Record<string, any> = {
) || "file-upload-list"
return { default: mod.default || mod[exportName] }
}),
"hover-card-demo": React.lazy(async () => {
const mod = await import("@/examples/aria/hover-card-demo")
const exportName =
Object.keys(mod).find(
(key) => typeof mod[key] === "function" || typeof mod[key] === "object"
) || "hover-card-demo"
return { default: mod.default || mod[exportName] }
}),
"hover-card-rtl": React.lazy(async () => {
const mod = await import("@/examples/aria/hover-card-rtl")
const exportName =
Object.keys(mod).find(
(key) => typeof mod[key] === "function" || typeof mod[key] === "object"
) || "hover-card-rtl"
return { default: mod.default || mod[exportName] }
}),
"hover-card-sides": React.lazy(async () => {
const mod = await import("@/examples/aria/hover-card-sides")
const exportName =
Object.keys(mod).find(
(key) => typeof mod[key] === "function" || typeof mod[key] === "object"
) || "hover-card-sides"
return { default: mod.default || mod[exportName] }
}),
"input-badge": React.lazy(async () => {
const mod = await import("@/examples/aria/input-badge")
const exportName =
Expand Down
3 changes: 3 additions & 0 deletions apps/v4/examples/__components__/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,9 @@ const shards: Record<
"field-switch",
"field-textarea",
"file-upload-list",
"hover-card-demo",
"hover-card-rtl",
"hover-card-sides",
"input-badge",
"input-basic",
"input-button-group",
Expand Down
12 changes: 12 additions & 0 deletions apps/v4/examples/__index__.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2955,6 +2955,18 @@ export const ExamplesIndex: Record<string, Record<string, any>> = {
name: "file-upload-list",
filePath: "examples/aria/file-upload-list.tsx",
},
"hover-card-demo": {
name: "hover-card-demo",
filePath: "examples/aria/hover-card-demo.tsx",
},
"hover-card-rtl": {
name: "hover-card-rtl",
filePath: "examples/aria/hover-card-rtl.tsx",
},
"hover-card-sides": {
name: "hover-card-sides",
filePath: "examples/aria/hover-card-sides.tsx",
},
"input-badge": {
name: "input-badge",
filePath: "examples/aria/input-badge.tsx",
Expand Down
19 changes: 19 additions & 0 deletions apps/v4/examples/aria/hover-card-demo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"use client"

import { Button } from "@/styles/aria-nova/ui/button"
import { HoverCard, HoverCardTrigger } from "@/styles/aria-nova/ui/hover-card"

export default function HoverCardDemo() {
return (
<HoverCardTrigger delay={10} closeDelay={100}>
<Button variant="link">Hover Here</Button>
<HoverCard className="flex w-64 flex-col gap-0.5">
<div className="font-semibold">@nextjs</div>
<div>The React Framework – created and maintained by @vercel.</div>
<div className="mt-1 text-xs text-muted-foreground">
Joined December 2021
</div>
</HoverCard>
</HoverCardTrigger>
)
}
95 changes: 95 additions & 0 deletions apps/v4/examples/aria/hover-card-rtl.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"use client"

import {
useTranslation,
type Translations,
} from "@/components/language-selector"
import { Button } from "@/styles/aria-nova/ui-rtl/button"
import {
HoverCard,
HoverCardTrigger,
} from "@/styles/aria-nova/ui-rtl/hover-card"

const translations: Translations = {
en: {
dir: "ltr",
values: {
name: "Wireless Headphones",
price: "$99.99",
start: "Start",
left: "Left",
top: "Top",
bottom: "Bottom",
right: "Right",
end: "End",
},
},
ar: {
dir: "rtl",
values: {
name: "سماعات لاسلكية",
price: "٩٩.٩٩ $",
start: "بداية السطر",
left: "يسار",
top: "أعلى",
bottom: "أسفل",
right: "يمين",
end: "نهاية السطر",
},
},
he: {
dir: "rtl",
values: {
name: "אוזניות אלחוטיות",
price: "99.99 $",
start: "תחילת השורה",
left: "שמאל",
top: "למעלה",
bottom: "למטה",
right: "ימין",
end: "סוף השורה",
},
},
}

const physicalSides = ["left", "top", "bottom", "right"] as const
const logicalPlacements = ["start", "end"] as const

export function HoverCardRtl() {
const { dir, t } = useTranslation(translations, "ar")

return (
<div className="grid gap-4">
<div className="flex flex-wrap justify-center gap-2">
{physicalSides.map((side) => (
<HoverCardTrigger key={side} delay={10} closeDelay={100}>
<Button variant="outline">{t[side]}</Button>
<HoverCard
placement={side}
dir={dir}
className="flex w-64 flex-col gap-1"
>
<div className="font-semibold">{t.name}</div>
<div className="text-sm text-muted-foreground">{t.price}</div>
</HoverCard>
</HoverCardTrigger>
))}
</div>
<div className="flex flex-wrap justify-center gap-2">
{logicalPlacements.map((placement) => (
<HoverCardTrigger key={placement} delay={10} closeDelay={100}>
<Button variant="outline">{t[placement]}</Button>
<HoverCard
placement={placement}
dir={dir}
className="flex w-64 flex-col gap-1"
>
<div className="font-semibold">{t.name}</div>
<div className="text-sm text-muted-foreground">{t.price}</div>
</HoverCard>
</HoverCardTrigger>
))}
</div>
</div>
)
}
28 changes: 28 additions & 0 deletions apps/v4/examples/aria/hover-card-sides.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use client"

import { Button } from "@/styles/aria-nova/ui/button"
import { HoverCard, HoverCardTrigger } from "@/styles/aria-nova/ui/hover-card"

const HOVER_CARD_PLACEMENTS = ["left", "top", "bottom", "right"] as const

export function HoverCardSides() {
return (
<div className="flex flex-wrap justify-center gap-2">
{HOVER_CARD_PLACEMENTS.map((placement) => (
<HoverCardTrigger key={placement} delay={100} closeDelay={100}>
<Button variant="outline" className="capitalize">
{placement}
</Button>
<HoverCard placement={placement}>
<div className="flex flex-col gap-1">
<h4 className="font-medium">Hover Card</h4>
<p>
This hover card appears on the {placement} side of the trigger.
</p>
</div>
</HoverCard>
</HoverCardTrigger>
))}
</div>
)
}
10 changes: 5 additions & 5 deletions apps/v4/examples/aria/message-scroller-visibility.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
CardHeader,
CardTitle,
} from "@/styles/aria-rhea/ui/card"
import { HoverCard, HoverCardTrigger } from "@/styles/aria-rhea/ui/hover-card"
import { Message, MessageContent } from "@/styles/aria-rhea/ui/message"
import {
MessageScroller,
Expand All @@ -22,7 +23,6 @@ import {
useMessageScroller,
useMessageScrollerVisibility,
} from "@/styles/aria-rhea/ui/message-scroller"
import { Popover, PopoverTrigger } from "@/styles/aria-rhea/ui/popover"

const chat = createChat()
.user("Review the incident handoff and tell me what to read first.", {
Expand Down Expand Up @@ -127,7 +127,7 @@ function TranscriptOutline() {
const { currentAnchorId } = useMessageScrollerVisibility()

return (
<PopoverTrigger>
<HoverCardTrigger>
<Button
aria-label="Open transcript outline"
className="flex h-9 w-9 flex-col items-center justify-center gap-1 rounded-md transition-colors outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
Expand All @@ -140,7 +140,7 @@ function TranscriptOutline() {
/>
))}
</Button>
<Popover
<HoverCard
placement="left"
offset={-28}
className="flex w-64 flex-col gap-1 rounded-2xl p-1"
Expand All @@ -165,8 +165,8 @@ function TranscriptOutline() {
</span>
</button>
))}
</Popover>
</PopoverTrigger>
</HoverCard>
</HoverCardTrigger>
)
}

Expand Down
Loading
Loading