This repository has no description
0

Configure Feed

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

chore: subscribe button

+160
+100
src/webapp/features/follows/components/subscribeButton/SubscribeButton.stories.tsx
··· 1 + import type { Meta, StoryObj } from '@storybook/nextjs-vite'; 2 + import { useState } from 'react'; 3 + import SubscribeButton, { type SubscriptionPrefs } from './SubscribeButton'; 4 + 5 + const meta: Meta<typeof SubscribeButton> = { 6 + title: 'Features/Follows/SubscribeButton', 7 + component: SubscribeButton, 8 + }; 9 + 10 + export default meta; 11 + 12 + type Story = StoryObj<typeof SubscribeButton>; 13 + 14 + // ─── User ──────────────────────────────────────────────────────────────────── 15 + 16 + /** Following a user but no subscriptions on. Bell is outlined. */ 17 + export const UserDefault: Story = { 18 + args: { 19 + targetId: 'did:plc:user123', 20 + targetType: 'USER', 21 + prefs: { cards: false, connections: false }, 22 + }, 23 + }; 24 + 25 + /** Subscribed to cards only. */ 26 + export const UserCardsOnly: Story = { 27 + args: { 28 + targetId: 'did:plc:user123', 29 + targetType: 'USER', 30 + prefs: { cards: true, connections: false }, 31 + }, 32 + }; 33 + 34 + /** Subscribed to connections only. */ 35 + export const UserConnectionsOnly: Story = { 36 + args: { 37 + targetId: 'did:plc:user123', 38 + targetType: 'USER', 39 + prefs: { cards: false, connections: true }, 40 + }, 41 + }; 42 + 43 + /** Subscribed to everything. Bell is filled. */ 44 + export const UserAllOn: Story = { 45 + args: { 46 + targetId: 'did:plc:user123', 47 + targetType: 'USER', 48 + prefs: { cards: true, connections: true }, 49 + }, 50 + }; 51 + 52 + // ─── Collection ────────────────────────────────────────────────────────────── 53 + 54 + /** Following a collection but no subscriptions on. */ 55 + export const CollectionDefault: Story = { 56 + args: { 57 + targetId: 'col-abc-001', 58 + targetType: 'COLLECTION', 59 + prefs: { cards: false, connections: false }, 60 + }, 61 + }; 62 + 63 + /** Subscribed to cards only. */ 64 + export const CollectionCardsOnly: Story = { 65 + args: { 66 + targetId: 'col-abc-001', 67 + targetType: 'COLLECTION', 68 + prefs: { cards: true, connections: false }, 69 + }, 70 + }; 71 + 72 + /** Subscribed to everything. */ 73 + export const CollectionAllOn: Story = { 74 + args: { 75 + targetId: 'col-abc-001', 76 + targetType: 'COLLECTION', 77 + prefs: { cards: true, connections: true }, 78 + }, 79 + }; 80 + 81 + // ─── Interactive ───────────────────────────────────────────────────────────── 82 + 83 + /** Click the bell to open the menu and toggle subscriptions. */ 84 + export const Interactive: Story = { 85 + render: () => { 86 + const [prefs, setPrefs] = useState<SubscriptionPrefs>({ 87 + cards: false, 88 + connections: false, 89 + }); 90 + 91 + return ( 92 + <SubscribeButton 93 + targetId="did:plc:user123" 94 + targetType="USER" 95 + prefs={prefs} 96 + onPrefsChange={setPrefs} 97 + /> 98 + ); 99 + }, 100 + };
+60
src/webapp/features/follows/components/subscribeButton/SubscribeButton.tsx
··· 1 + 'use client'; 2 + 3 + import { ActionIcon, Menu } from '@mantine/core'; 4 + import { MdNotificationAdd, MdNotificationsActive } from 'react-icons/md'; 5 + import { IoMdCheckmark } from 'react-icons/io'; 6 + 7 + export interface SubscriptionPrefs { 8 + cards: boolean; 9 + connections: boolean; 10 + } 11 + 12 + interface Props { 13 + targetId: string; 14 + targetType: 'COLLECTION' | 'USER'; 15 + prefs?: SubscriptionPrefs; 16 + onPrefsChange?: (next: SubscriptionPrefs) => void; 17 + } 18 + 19 + const OPTIONS: { key: keyof SubscriptionPrefs; label: string }[] = [ 20 + { key: 'cards', label: 'Cards' }, 21 + { key: 'connections', label: 'Connections' }, 22 + ]; 23 + 24 + const DEFAULT_PREFS: SubscriptionPrefs = { cards: false, connections: false }; 25 + 26 + export default function SubscribeButton({ prefs, onPrefsChange }: Props) { 27 + const resolved = prefs ?? DEFAULT_PREFS; 28 + const isActive = resolved.cards || resolved.connections; 29 + 30 + function toggle(key: keyof SubscriptionPrefs) { 31 + onPrefsChange?.({ ...resolved, [key]: !resolved[key] }); 32 + } 33 + 34 + return ( 35 + <Menu shadow="sm" position="bottom-end" closeOnItemClick={false}> 36 + <Menu.Target> 37 + <ActionIcon 38 + variant={isActive ? 'light' : 'filled'} 39 + color={isActive ? 'gray' : 'dark'} 40 + radius="xl" 41 + aria-label="Subscribe" 42 + > 43 + {isActive ? <MdNotificationsActive size={16} /> : <MdNotificationAdd size={16} />} 44 + </ActionIcon> 45 + </Menu.Target> 46 + <Menu.Dropdown> 47 + <Menu.Label>Notify me about</Menu.Label> 48 + {OPTIONS.map((opt) => ( 49 + <Menu.Item 50 + key={opt.key} 51 + rightSection={resolved[opt.key] ? <IoMdCheckmark /> : null} 52 + onClick={() => toggle(opt.key)} 53 + > 54 + {opt.label} 55 + </Menu.Item> 56 + ))} 57 + </Menu.Dropdown> 58 + </Menu> 59 + ); 60 + }