This repository has no description
7.4 kB
253 lines
1import type { Meta, StoryObj } from '@storybook/nextjs-vite';
2import { useState } from 'react';
3import { CollectionAccessType } from '@semble/types';
4import type { Collection } from '@semble/types';
5import UrlCard from './UrlCard';
6import UrlCardSkeleton from './Skeleton.UrlCard';
7import { AuthContext } from '@/hooks/useAuth';
8import type { AuthContextType } from '@/hooks/useAuth';
9
10const mockAuthUser = {
11 id: 'did:plc:mock123',
12 name: 'Test User',
13 handle: 'test.user',
14 avatarUrl: 'https://i.pravatar.cc/150?u=testuser',
15};
16
17const mockAuthValue: AuthContextType = {
18 user: mockAuthUser as any,
19 isAuthenticated: true,
20 isLoading: false,
21 refreshAuth: async () => {},
22 logout: async () => {},
23};
24
25const cardAuthor = {
26 id: 'did:plc:author456',
27 name: 'Jane Researcher',
28 handle: 'jane.researcher',
29 avatarUrl: 'https://i.pravatar.cc/150?u=jane',
30 description: 'Science writer and open-access advocate.',
31 followerCount: 214,
32 followingCount: 63,
33};
34
35const collectionAuthor = {
36 id: 'did:plc:colauthor789',
37 name: 'Alex Curator',
38 handle: 'alex.curator',
39 avatarUrl: 'https://i.pravatar.cc/150?u=alex',
40};
41
42const baseCollection: Collection = {
43 id: 'col-story-001',
44 uri: 'at://did:plc:colauthor789/app.semble.collection/3juzt2xkr5c2a',
45 name: 'Web Development Resources',
46 author: collectionAuthor,
47 description: 'A curated set of useful web development articles and tools.',
48 accessType: CollectionAccessType.CLOSED,
49 cardCount: 24,
50 createdAt: '2024-09-15T12:00:00.000Z',
51 updatedAt: '2025-06-20T09:30:00.000Z',
52 isFollowing: false,
53 followerCount: 42,
54};
55
56const baseCardContent = {
57 url: 'https://example.com/understanding-design-tokens',
58 title: 'Understanding Design Tokens: A Practical Guide',
59 description:
60 'Design tokens are the atoms of a design system — small, named entities that store visual-design decisions such as color, typography, and spacing.',
61 imageUrl: 'https://picsum.photos/seed/urlcard1/400/225',
62 siteName: 'Example Blog',
63 type: 'article',
64 retrievedAt: '2025-06-01T10:00:00.000Z',
65};
66
67const meta: Meta<typeof UrlCard> = {
68 title: 'Features/Cards/UrlCard',
69 component: UrlCard,
70 decorators: [
71 (Story) => (
72 <AuthContext value={mockAuthValue}>
73 <div style={{ maxWidth: 400 }}>
74 <Story />
75 </div>
76 </AuthContext>
77 ),
78 ],
79 args: {
80 id: 'card-default-001',
81 url: 'https://example.com/understanding-design-tokens',
82 uri: 'at://did:plc:author456/app.semble.card/3k1abc',
83 cardContent: baseCardContent,
84 urlLibraryCount: 5,
85 urlIsInLibrary: false,
86 urlConnectionCount: 2,
87 urlIsConnected: false,
88 showAuthor: false,
89 },
90};
91
92export default meta;
93
94type Story = StoryObj<typeof UrlCard>;
95
96export const Default: Story = {};
97
98export const WithContributor: Story = {
99 args: {
100 id: 'card-author-002',
101 showAuthor: true,
102 cardAuthor: cardAuthor,
103 authorHandle: cardAuthor.handle,
104 },
105};
106
107export const WithNote: Story = {
108 args: {
109 id: 'card-note-003',
110 note: {
111 id: 'note-001',
112 text: 'This is an excellent primer on design tokens. Highly recommend reading the section on naming conventions — it changed how I structure my own token files.',
113 },
114 },
115};
116
117export const InLibrary: Story = {
118 args: {
119 id: 'card-library-004',
120 urlIsInLibrary: true,
121 urlLibraryCount: 37,
122 cardContent: {
123 ...baseCardContent,
124 url: 'https://example.com/state-of-css-2025',
125 title: 'State of CSS 2025 Survey Results',
126 description:
127 'An overview of the most popular CSS features, frameworks, and tools according to thousands of developers worldwide.',
128 imageUrl: 'https://picsum.photos/seed/urlcard2/400/225',
129 siteName: 'State of CSS',
130 },
131 },
132};
133
134export const Connected: Story = {
135 args: {
136 id: 'card-connected-005',
137 urlIsConnected: true,
138 urlConnectionCount: 12,
139 cardContent: {
140 ...baseCardContent,
141 url: 'https://example.com/react-server-components',
142 title: 'React Server Components Explained',
143 description:
144 'A deep dive into how React Server Components work under the hood, when to use them, and common pitfalls to avoid.',
145 imageUrl: 'https://picsum.photos/seed/urlcard3/400/225',
146 siteName: 'React Blog',
147 },
148 },
149};
150
151export const WithCollection: Story = {
152 args: {
153 id: 'card-collection-006',
154 currentCollection: baseCollection,
155 cardContent: {
156 ...baseCardContent,
157 url: 'https://example.com/responsive-layout-patterns',
158 title: 'Modern Responsive Layout Patterns',
159 description:
160 'Move beyond media queries. Learn how to build truly fluid layouts using CSS Grid, Container Queries, and clamp().',
161 imageUrl: 'https://picsum.photos/seed/urlcard4/400/225',
162 siteName: 'CSS Tricks',
163 },
164 },
165};
166
167export const HighCounts: Story = {
168 args: {
169 id: 'card-highcounts-007',
170 urlLibraryCount: 2847,
171 urlIsInLibrary: true,
172 urlConnectionCount: 593,
173 urlIsConnected: true,
174 showAuthor: true,
175 cardAuthor: cardAuthor,
176 authorHandle: cardAuthor.handle,
177 cardContent: {
178 ...baseCardContent,
179 url: 'https://example.com/attention-is-all-you-need',
180 title: 'Attention Is All You Need',
181 description:
182 'The dominant sequence transduction models are based on complex recurrent or convolutional neural networks that include an encoder and a decoder.',
183 imageUrl: 'https://picsum.photos/seed/urlcard5/400/225',
184 siteName: 'arXiv',
185 author: 'Vaswani et al.',
186 },
187 note: {
188 id: 'note-002',
189 text: 'The foundational transformer paper. Still worth re-reading every few months.',
190 },
191 },
192};
193
194const ownedCollection: Collection = {
195 ...baseCollection,
196 id: 'col-story-pin-001',
197 uri: 'at://did:plc:mock123/app.semble.collection/pinstory',
198 name: 'My Reading List',
199 author: {
200 id: mockAuthUser.id,
201 name: mockAuthUser.name,
202 handle: mockAuthUser.handle,
203 avatarUrl: mockAuthUser.avatarUrl,
204 },
205};
206
207export const PinUnpinInCollection: Story = {
208 render: (args) => {
209 const [isPinned, setIsPinned] = useState(false);
210 return (
211 <UrlCard
212 {...args}
213 isPinnedInCollection={isPinned}
214 onTogglePinInCollection={() => setIsPinned((prev) => !prev)}
215 />
216 );
217 },
218 args: {
219 id: 'card-pin-toggle-008',
220 currentCollection: ownedCollection,
221 cardContent: {
222 ...baseCardContent,
223 url: 'https://example.com/css-cascade-layers',
224 title: 'A Practical Look at CSS Cascade Layers',
225 description:
226 'Cascade layers give you explicit control over CSS specificity ordering — here is when they help and when they get in the way.',
227 imageUrl: 'https://picsum.photos/seed/urlcard-pin/400/225',
228 siteName: 'Example Blog',
229 },
230 },
231};
232
233export const PinnedInCollection: Story = {
234 args: {
235 id: 'card-pinned-009',
236 currentCollection: ownedCollection,
237 isPinnedInCollection: true,
238 onTogglePinInCollection: () => {},
239 cardContent: {
240 ...baseCardContent,
241 url: 'https://example.com/the-tao-of-css',
242 title: 'The Tao of CSS',
243 description:
244 'A meditation on simplicity, specificity, and the long road from inline styles to design systems.',
245 imageUrl: 'https://picsum.photos/seed/urlcard-pinned/400/225',
246 siteName: 'Example Blog',
247 },
248 },
249};
250
251export const Skeleton: Story = {
252 render: () => <UrlCardSkeleton />,
253};