This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

chore: text card stories

+252 -1
+189
src/webapp/features/cards/components/textCard/TextCard.stories.tsx
··· 1 + import type { Meta, StoryObj } from '@storybook/nextjs-vite'; 2 + import { CollectionAccessType } from '@semble/types'; 3 + import type { Collection } from '@semble/types'; 4 + import TextCard from './TextCard'; 5 + import { AuthContext } from '@/hooks/useAuth'; 6 + import type { AuthContextType } from '@/hooks/useAuth'; 7 + 8 + const mockAuthUser = { 9 + id: 'did:plc:mock123', 10 + name: 'Test User', 11 + handle: 'test.user', 12 + avatarUrl: 'https://i.pravatar.cc/150?u=testuser', 13 + }; 14 + 15 + const mockAuthValue: AuthContextType = { 16 + user: mockAuthUser as any, 17 + isAuthenticated: true, 18 + isLoading: false, 19 + refreshAuth: async () => {}, 20 + logout: async () => {}, 21 + }; 22 + 23 + const cardAuthor = { 24 + id: 'did:plc:author456', 25 + name: 'Jane Researcher', 26 + handle: 'jane.researcher', 27 + avatarUrl: 'https://i.pravatar.cc/150?u=jane', 28 + description: 'Science writer and open-access advocate.', 29 + followerCount: 214, 30 + followingCount: 63, 31 + }; 32 + 33 + const baseCollection: Collection = { 34 + id: 'col-story-001', 35 + uri: 'at://did:plc:colauthor789/app.semble.collection/3juzt2xkr5c2a', 36 + name: 'Research Notes', 37 + author: { 38 + id: 'did:plc:colauthor789', 39 + name: 'Alex Curator', 40 + handle: 'alex.curator', 41 + avatarUrl: 'https://i.pravatar.cc/150?u=alex', 42 + }, 43 + description: 'A curated collection of research notes and insights.', 44 + accessType: CollectionAccessType.CLOSED, 45 + cardCount: 24, 46 + createdAt: '2024-09-15T12:00:00.000Z', 47 + updatedAt: '2025-06-20T09:30:00.000Z', 48 + isFollowing: false, 49 + followerCount: 31, 50 + }; 51 + 52 + const baseCardContent = { 53 + url: 'https://example.com/article-1', 54 + title: 'How to Actually Enjoy Cooking at Home', 55 + siteName: 'The Kitchn', 56 + type: 'article', 57 + retrievedAt: '2025-06-01T10:00:00.000Z', 58 + }; 59 + 60 + const meta: Meta<typeof TextCard> = { 61 + title: 'Features/Cards/TextCard', 62 + component: TextCard, 63 + decorators: [ 64 + (Story) => ( 65 + <AuthContext.Provider value={mockAuthValue}> 66 + <div style={{ maxWidth: 360 }}> 67 + <Story /> 68 + </div> 69 + </AuthContext.Provider> 70 + ), 71 + ], 72 + args: { 73 + id: 'card-txt-001', 74 + cardContent: baseCardContent, 75 + urlLibraryCount: 5, 76 + urlIsInLibrary: false, 77 + urlConnectionCount: 2, 78 + urlIsConnected: false, 79 + }, 80 + }; 81 + 82 + export default meta; 83 + 84 + type Story = StoryObj<typeof TextCard>; 85 + 86 + export const Default: Story = { 87 + args: { 88 + text: "<p>The trick to enjoying cooking is to stop treating every meal like a project. Pick three recipes you actually like, rotate them during the week, and save the experiments for weekends when there's no pressure.</p>", 89 + }, 90 + }; 91 + 92 + export const RichFormatting: Story = { 93 + args: { 94 + id: 'card-txt-rich', 95 + text: `<h3>Things I Changed This Year</h3> 96 + <p>I finally stopped buying groceries without a <strong>rough plan</strong> for the week. It sounds obvious, but it cut my food waste in <em>half</em>.</p> 97 + <ul> 98 + <li>Check what's already in the <strong>fridge</strong> before shopping</li> 99 + <li>Buy versatile ingredients that work in multiple meals</li> 100 + <li>Freeze anything you won't use in two days</li> 101 + </ul> 102 + <blockquote> 103 + <p>"The best meal plan is the one you actually stick to."</p> 104 + </blockquote> 105 + <p>Also started keeping a running note on my phone with a list of <code>go-to meals</code> so I never blank at the store again.</p>`, 106 + cardContent: { 107 + ...baseCardContent, 108 + url: 'https://example.com/simple-meal-planning', 109 + title: 'Simple Meal Planning for Busy People', 110 + siteName: 'Budget Bytes', 111 + }, 112 + }, 113 + }; 114 + 115 + export const LongText: Story = { 116 + args: { 117 + id: 'card-txt-long', 118 + text: `<p>I've been thinking a lot about how we pick up habits without realizing it. Most of the things I do every morning — checking my phone, making coffee in a specific order, even the route I walk to the train — started without any conscious decision.</p> 119 + <p>What's interesting is that <strong>small disruptions</strong> to routine can feel weirdly unsettling. When my usual coffee shop closed for renovation last month, it threw off my whole morning for a week. I started arriving at work later and feeling groggy until noon.</p> 120 + <p>Eventually I found a new place two blocks further, and now I actually prefer it. The walk is longer but I pass through a park, which turns out to be a much nicer way to start the day than staring at traffic.</p> 121 + <p>The lesson I keep re-learning: the things that feel like inconveniences in the moment often push you toward something better, but only if you let them.</p> 122 + <p>I wonder how many other parts of my routine could use a gentle shake-up. Maybe it's time to audit the defaults I've been living on autopilot.</p>`, 123 + cardContent: { 124 + ...baseCardContent, 125 + url: 'https://example.com/daily-habits', 126 + title: 'The Hidden Power of Everyday Routines', 127 + siteName: 'Psyche Magazine', 128 + }, 129 + }, 130 + }; 131 + 132 + export const InLibrary: Story = { 133 + args: { 134 + id: 'card-txt-library', 135 + urlIsInLibrary: true, 136 + urlLibraryCount: 42, 137 + text: '<p>This is a really solid breakdown of how to organize a small apartment. The bit about <strong>vertical storage</strong> in the kitchen freed up so much counter space when I tried it at home.</p>', 138 + cardContent: { 139 + ...baseCardContent, 140 + url: 'https://example.com/small-space-living', 141 + title: 'Making the Most of a Small Kitchen', 142 + siteName: 'Apartment Therapy', 143 + }, 144 + }, 145 + }; 146 + 147 + export const Connected: Story = { 148 + args: { 149 + id: 'card-txt-connected', 150 + urlIsConnected: true, 151 + urlConnectionCount: 8, 152 + text: "<p>I like how this frames budgeting not as restriction but as <em>clarity</em>. Knowing where your money goes each month isn't about cutting fun — it's about making sure the fun stuff is actually what you'd choose on purpose.</p>", 153 + cardContent: { 154 + ...baseCardContent, 155 + url: 'https://example.com/budgeting-mindset', 156 + title: 'A Healthier Way to Think About Money', 157 + siteName: 'The Financial Diet', 158 + }, 159 + }, 160 + }; 161 + 162 + export const WithCollection: Story = { 163 + args: { 164 + id: 'card-txt-collection', 165 + currentCollection: baseCollection, 166 + cardAuthor: cardAuthor, 167 + authorHandle: cardAuthor.handle, 168 + text: "<p>Great advice in here about running a book club that people actually show up to. The key is keeping it casual — pick shorter books, don't guilt people for not finishing, and always have <strong>snacks</strong>.</p>", 169 + cardContent: { 170 + ...baseCardContent, 171 + url: 'https://example.com/book-club-tips', 172 + title: 'How to Run a Book Club That Lasts', 173 + siteName: 'Literary Hub', 174 + }, 175 + }, 176 + }; 177 + 178 + export const ShortText: Story = { 179 + args: { 180 + id: 'card-txt-short', 181 + text: '<p>The photos in this one are gorgeous.</p>', 182 + cardContent: { 183 + ...baseCardContent, 184 + url: 'https://example.com/travel-photos', 185 + title: 'A Weekend in the Cotswolds', 186 + siteName: 'Condé Nast Traveller', 187 + }, 188 + }, 189 + };
+62
src/webapp/features/cards/components/textCard/TextCard.tsx
··· 1 + 'use client'; 2 + 3 + import type { UrlCard, Collection, User } from '@/api-client'; 4 + import { Card, Spoiler, Stack, Typography } from '@mantine/core'; 5 + import UrlCardActions from '../urlCardActions/UrlCardActions'; 6 + import { CardSaveAnalyticsContext } from '@/features/analytics/types'; 7 + 8 + interface Props { 9 + id: string; 10 + text: string; 11 + cardContent: UrlCard['cardContent']; 12 + currentCollection?: Collection; 13 + urlLibraryCount: number; 14 + urlIsInLibrary?: boolean; 15 + urlConnectionCount: number; 16 + urlIsConnected?: boolean; 17 + authorHandle?: string; 18 + cardAuthor?: User; 19 + viaCardId?: string; 20 + analyticsContext?: CardSaveAnalyticsContext; 21 + onClick?: (e: React.MouseEvent<HTMLElement>) => void; 22 + } 23 + 24 + export default function TextCard(props: Props) { 25 + return ( 26 + <Card 27 + component="article" 28 + radius="lg" 29 + p="sm" 30 + flex={1} 31 + h="100%" 32 + withBorder 33 + style={{ cursor: 'pointer' }} 34 + onClick={props.onClick} 35 + > 36 + <Stack gap="xs" justify="space-between" flex={1}> 37 + <Spoiler showLabel={'Read more'} hideLabel={'See less'} maxHeight={200}> 38 + <Typography> 39 + <div dangerouslySetInnerHTML={{ __html: props.text }} /> 40 + </Typography> 41 + </Spoiler> 42 + 43 + <Stack gap="xs" pt={0}> 44 + <UrlCardActions 45 + cardAuthor={props.cardAuthor} 46 + cardContent={props.cardContent} 47 + cardCount={props.urlLibraryCount} 48 + id={props.id} 49 + authorHandle={props.authorHandle} 50 + currentCollection={props.currentCollection} 51 + urlLibraryCount={props.urlLibraryCount} 52 + urlIsInLibrary={props.urlIsInLibrary ?? false} 53 + urlConnectionCount={props.urlConnectionCount} 54 + urlIsConnected={props.urlIsConnected} 55 + viaCardId={props.viaCardId} 56 + analyticsContext={props.analyticsContext} 57 + /> 58 + </Stack> 59 + </Stack> 60 + </Card> 61 + ); 62 + }
+1 -1
src/webapp/next-env.d.ts
··· 1 1 /// <reference types="next" /> 2 2 /// <reference types="next/image-types/global" /> 3 - import "./.next/dev/types/routes.d.ts"; 3 + import "./.next/types/routes.d.ts"; 4 4 5 5 // NOTE: This file should not be edited 6 6 // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.