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