Skip to content

Commit c531355

Browse files
authored
feat(registry): Add polaroid card component (#42)
1 parent f0f867b commit c531355

5 files changed

Lines changed: 208 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script setup lang="ts">
2+
import { PolaroidCard, type PolaroidCardImage } from "~/registry/blocks/polaroid-card";
3+
4+
const images: Array<{
5+
image: PolaroidCardImage;
6+
caption?: string;
7+
rotation: "left" | "right" | "none";
8+
interactive?: boolean;
9+
}> = [
10+
{
11+
image: {
12+
src: "https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=320&q=80",
13+
alt: "Cabin beside a lake",
14+
},
15+
caption: "Weekend cabin",
16+
rotation: "left",
17+
},
18+
{
19+
image: {
20+
src: "https://images.unsplash.com/photo-1500534314209-a25ddb2bd429?auto=format&fit=crop&w=320&q=80",
21+
alt: "Desert road at sunset",
22+
},
23+
rotation: "right",
24+
},
25+
{
26+
image: {
27+
src: "https://images.unsplash.com/photo-1495567720989-cebdbdd97913?auto=format&fit=crop&w=320&q=80",
28+
alt: "Mountain ridge above clouds",
29+
},
30+
caption: "Static layout",
31+
rotation: "none",
32+
interactive: false,
33+
},
34+
];
35+
</script>
36+
37+
<template>
38+
<div class="overflow-hidden rounded-xl border bg-muted/30 p-8">
39+
<div class="mx-auto flex max-w-2xl flex-col items-center gap-8 sm:flex-row sm:justify-center sm:gap-6">
40+
<PolaroidCard
41+
v-for="item in images"
42+
:key="item.image.src"
43+
:image="item.image"
44+
:caption="item.caption"
45+
:rotation="item.rotation"
46+
:interactive="item.interactive"
47+
/>
48+
</div>
49+
</div>
50+
</template>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<script setup lang="ts">
2+
import type { HTMLAttributes } from "vue";
3+
import { computed } from "vue";
4+
import { cn } from "@/lib/utils";
5+
6+
export interface PolaroidCardImage {
7+
src: string;
8+
alt: string;
9+
}
10+
11+
export interface PolaroidCardProps {
12+
/** Image source and accessible alt text supplied by the consuming app */
13+
image: PolaroidCardImage;
14+
/** Optional handwritten-style caption. Falls back to image.alt when omitted. */
15+
caption?: string;
16+
/** Fixed card rotation for standalone placement */
17+
rotation?: "left" | "right" | "none";
18+
/** Enable hover lift and straightening interaction */
19+
interactive?: boolean;
20+
/** Additional CSS classes for the root card */
21+
class?: HTMLAttributes["class"];
22+
}
23+
24+
const props = withDefaults(defineProps<PolaroidCardProps>(), {
25+
rotation: "left",
26+
interactive: true,
27+
});
28+
29+
const captionText = computed(() => props.caption ?? props.image.alt);
30+
31+
const rotationClass = computed(() => {
32+
if (props.rotation === "right") return "rotate-5";
33+
if (props.rotation === "none") return "rotate-0";
34+
return "-rotate-5";
35+
});
36+
37+
const interactiveClass = computed(() => {
38+
if (!props.interactive) return "";
39+
if (props.rotation === "right") return "hover:z-10 hover:translate-x-4 hover:scale-105 hover:rotate-0";
40+
if (props.rotation === "left") return "hover:z-10 hover:-translate-x-4 hover:scale-105 hover:rotate-0";
41+
return "hover:z-10 hover:scale-105";
42+
});
43+
</script>
44+
45+
<template>
46+
<figure
47+
data-slot="polaroid-card"
48+
:data-rotation="rotation"
49+
:data-interactive="interactive"
50+
:class="cn(
51+
'inline-flex flex-col bg-white p-2 text-black drop-shadow-2xl transition-transform duration-300 ease-in-out',
52+
rotationClass,
53+
interactiveClass,
54+
props.class,
55+
)"
56+
>
57+
<img
58+
:src="image.src"
59+
:alt="image.alt"
60+
class="aspect-square size-32 object-cover"
61+
>
62+
<figcaption class="mt-2 w-32 text-center font-serif text-xs font-medium text-black">
63+
{{ captionText }}
64+
</figcaption>
65+
</figure>
66+
</template>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as PolaroidCard, type PolaroidCardImage, type PolaroidCardProps } from "./PolaroidCard.vue";
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
---
2+
title: PolaroidCard
3+
description: Render a tilted photo card with app-owned image data and explicit presentation controls.
4+
category: content
5+
---
6+
7+
::component-preview
8+
---
9+
name: PolaroidCardDemo
10+
---
11+
::
12+
13+
## Installation
14+
15+
```bash
16+
npx shadcn-vue@latest add "https://ui.stackhacker.io/r/polaroid-card.json"
17+
```
18+
19+
## Usage
20+
21+
```vue
22+
<script setup lang="ts">
23+
import { PolaroidCard, type PolaroidCardImage } from "@/components/polaroid-card";
24+
25+
const image: PolaroidCardImage = {
26+
src: "/images/studio-desk.jpg",
27+
alt: "Studio desk with open notebooks",
28+
};
29+
</script>
30+
31+
<template>
32+
<PolaroidCard
33+
:image="image"
34+
caption="Studio notes"
35+
rotation="right"
36+
/>
37+
</template>
38+
```
39+
40+
## App-Owned Media
41+
42+
`PolaroidCard` owns the photo-paper styling, rotation, caption rendering, and optional hover movement. Your app owns image storage, image optimization, alt text, captions, ordering, links, lightboxes, and gallery layout.
43+
44+
When `caption` is omitted, the component renders `image.alt` as the visible caption. Pass an explicit caption when the display label should differ from the accessible image description.
45+
46+
Use `:interactive="false"` when the card appears in a dense static layout or inside another interactive surface.
47+
48+
## Examples
49+
50+
### Default
51+
52+
::component-preview
53+
---
54+
name: PolaroidCardDemo
55+
---
56+
::
57+
58+
### Static Card
59+
60+
```vue
61+
<PolaroidCard
62+
:image="image"
63+
rotation="none"
64+
:interactive="false"
65+
/>
66+
```
67+
68+
## API Reference
69+
70+
### Props
71+
72+
| Prop | Type | Default | Description |
73+
|------|------|---------|-------------|
74+
| `image` | `PolaroidCardImage` || Image source and accessible alt text supplied by your app. |
75+
| `caption` | `string` | `image.alt` | Optional visible caption. Falls back to the image alt text when omitted. |
76+
| `rotation` | `"left" \| "right" \| "none"` | `"left"` | Fixed card rotation for standalone placement. |
77+
| `interactive` | `boolean` | `true` | Enables hover lift, translation, scale, and straightening. |
78+
| `class` | `HTMLAttributes["class"]` || Additional CSS classes for the root card. |
79+
80+
### Types
81+
82+
```ts
83+
interface PolaroidCardImage {
84+
src: string;
85+
alt: string;
86+
}
87+
```

scripts/registry-verify.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ const expectedItems: Record<string, ExpectedRegistryItem> = {
9999
dependencies: [],
100100
registryDependencies: [],
101101
},
102+
"polaroid-card": {
103+
dependencies: [],
104+
registryDependencies: [],
105+
},
102106
"docs-pager": {
103107
dependencies: ["@lucide/vue"],
104108
registryDependencies: [],

0 commit comments

Comments
 (0)