@meith/theme-clubhouse 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/copy.ts +229 -0
- package/src/index.ts +2 -2
- package/src/messages/en.json +126 -0
- package/src/messages/index.ts +5 -0
- package/src/shared.tsx +3 -3
- package/src/slots/announcement.tsx +15 -6
- package/src/slots/board-index.tsx +11 -4
- package/src/slots/board-stats.tsx +12 -10
- package/src/slots/category-block.tsx +9 -3
- package/src/slots/footer.tsx +15 -4
- package/src/slots/forum-display.tsx +24 -14
- package/src/slots/forum-row.tsx +10 -8
- package/src/slots/header.tsx +43 -5
- package/src/slots/latest-posts.tsx +10 -7
- package/src/slots/latest-threads.tsx +15 -8
- package/src/slots/member-profile.tsx +14 -12
- package/src/slots/navigation.tsx +6 -3
- package/src/slots/notice.tsx +13 -10
- package/src/slots/pagination.tsx +15 -9
- package/src/slots/post-actions.tsx +18 -11
- package/src/slots/post-bit.tsx +32 -17
- package/src/slots/shell.tsx +4 -3
- package/src/slots/subforum-list.tsx +7 -5
- package/src/slots/thread-row.tsx +15 -12
- package/src/slots/thread-view.tsx +18 -9
- package/src/slots/user-panel.tsx +17 -13
- package/src/slots/who-is-online.tsx +39 -20
- package/src/theme.ts +48 -0
package/src/slots/post-bit.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { PostBitSlotModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Alert, AlertDescription, AlertTitle, Avatar, Card } from '@meith/ui'
|
|
2
|
-
import type { PostBitSlotModel } from '@meith/theme-kit'
|
|
3
4
|
|
|
4
5
|
import { HEADING, LINK, MICRO, MICRO_BARE, MUTED_LINK, NUMERIC, Stamp, UserRef } from '../shared'
|
|
5
6
|
|
|
@@ -9,9 +10,17 @@ const VISIBILITY_TINT = {
|
|
|
9
10
|
deleted: 'border-thread-deleted/50 bg-destructive/5',
|
|
10
11
|
} as const
|
|
11
12
|
|
|
12
|
-
function StatusBanner({
|
|
13
|
+
function StatusBanner({
|
|
14
|
+
visibility,
|
|
15
|
+
copy,
|
|
16
|
+
}: {
|
|
17
|
+
visibility: PostBitSlotModel['post']['visibility']
|
|
18
|
+
copy: SlotCopy
|
|
19
|
+
}) {
|
|
13
20
|
if (visibility === 'visible') return null
|
|
14
21
|
|
|
22
|
+
const c = (key: string) => fromSlotCopy(copy, `clubhouse.postBit.${key}`)
|
|
23
|
+
|
|
15
24
|
return (
|
|
16
25
|
<Alert
|
|
17
26
|
tone={visibility === 'deleted' ? 'error' : 'warning'}
|
|
@@ -19,9 +28,9 @@ function StatusBanner({ visibility }: { visibility: PostBitSlotModel['post']['vi
|
|
|
19
28
|
>
|
|
20
29
|
<AlertDescription>
|
|
21
30
|
<AlertTitle>
|
|
22
|
-
{visibility === 'deleted' ? '
|
|
31
|
+
{visibility === 'deleted' ? c('deletedPost') : c('waitingApproval')}
|
|
23
32
|
</AlertTitle>{' '}
|
|
24
|
-
|
|
33
|
+
{c('staffOnly')}
|
|
25
34
|
</AlertDescription>
|
|
26
35
|
</Alert>
|
|
27
36
|
)
|
|
@@ -65,10 +74,14 @@ function StatLine({ label, children }: { label: string; children: React.ReactNod
|
|
|
65
74
|
function AuthorBlock({
|
|
66
75
|
author,
|
|
67
76
|
badges,
|
|
77
|
+
copy,
|
|
68
78
|
}: {
|
|
69
79
|
author: PostBitSlotModel['post']['author']
|
|
70
80
|
badges: React.ReactNode
|
|
81
|
+
copy: SlotCopy
|
|
71
82
|
}) {
|
|
83
|
+
const c = (key: string) => fromSlotCopy(copy, `clubhouse.postBit.${key}`)
|
|
84
|
+
|
|
72
85
|
return (
|
|
73
86
|
<>
|
|
74
87
|
<div aria-hidden="true" className="h-12 border-b-2 border-b-secondary bg-primary" />
|
|
@@ -90,7 +103,7 @@ function AuthorBlock({
|
|
|
90
103
|
{author.isOnline && (
|
|
91
104
|
<p className="mt-1 flex items-center gap-1.5 text-xs text-moderation-approved">
|
|
92
105
|
<span aria-hidden="true" className="size-1.5 rounded-full bg-moderation-approved" />
|
|
93
|
-
|
|
106
|
+
{c('online')}
|
|
94
107
|
</p>
|
|
95
108
|
)}
|
|
96
109
|
|
|
@@ -104,12 +117,12 @@ function AuthorBlock({
|
|
|
104
117
|
</div>
|
|
105
118
|
|
|
106
119
|
<dl className="mt-2.5 space-y-1 border-t border-border px-4 py-2.5">
|
|
107
|
-
<StatLine label=
|
|
120
|
+
<StatLine label={c('posts')}>{author.postCount.label}</StatLine>
|
|
108
121
|
{author.reputation != null && (
|
|
109
|
-
<StatLine label=
|
|
122
|
+
<StatLine label={c('rep')}>{author.reputation.label}</StatLine>
|
|
110
123
|
)}
|
|
111
124
|
{author.joinedAt !== null && (
|
|
112
|
-
<StatLine label=
|
|
125
|
+
<StatLine label={c('joined')}>
|
|
113
126
|
<Stamp at={author.joinedAt} />
|
|
114
127
|
</StatLine>
|
|
115
128
|
)}
|
|
@@ -123,7 +136,9 @@ function AuthorBlock({
|
|
|
123
136
|
)
|
|
124
137
|
}
|
|
125
138
|
|
|
126
|
-
export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
139
|
+
export function PostBit({ post, select, regions, copy }: PostBitSlotModel & { copy: SlotCopy }) {
|
|
140
|
+
const c = (key: string) => fromSlotCopy(copy, `clubhouse.postBit.${key}`)
|
|
141
|
+
|
|
127
142
|
return (
|
|
128
143
|
<Card
|
|
129
144
|
as="article"
|
|
@@ -132,11 +147,11 @@ export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
|
132
147
|
data-visibility={post.visibility}
|
|
133
148
|
className={`target:bg-post-highlight ${VISIBILITY_TINT[post.visibility]}`}
|
|
134
149
|
>
|
|
135
|
-
<StatusBanner visibility={post.visibility} />
|
|
150
|
+
<StatusBanner visibility={post.visibility} copy={copy} />
|
|
136
151
|
|
|
137
152
|
<div className="grid grid-cols-1 sm:grid-cols-[11rem_minmax(0,1fr)]">
|
|
138
153
|
<div className="border-b border-border bg-surface pb-1 sm:border-r sm:border-b-0">
|
|
139
|
-
<AuthorBlock author={post.author} badges={regions.pluginBadges} />
|
|
154
|
+
<AuthorBlock author={post.author} badges={regions.pluginBadges} copy={copy} />
|
|
140
155
|
</div>
|
|
141
156
|
|
|
142
157
|
<div className="min-w-0">
|
|
@@ -159,7 +174,7 @@ export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
|
159
174
|
<span
|
|
160
175
|
className={`${MICRO_BARE} rounded-sm bg-primary px-1.5 py-0.5 text-primary-foreground`}
|
|
161
176
|
>
|
|
162
|
-
|
|
177
|
+
{c('openingPost')}
|
|
163
178
|
</span>
|
|
164
179
|
)}
|
|
165
180
|
|
|
@@ -173,11 +188,11 @@ export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
|
173
188
|
|
|
174
189
|
{post.ignored !== null ? (
|
|
175
190
|
<div className="px-4 py-5 text-sm text-muted-foreground">
|
|
176
|
-
|
|
177
|
-
<span className="font-semibold text-foreground">{post.ignored.authorUsername}</span>.
|
|
178
|
-
|
|
191
|
+
{c('ignoring')}{' '}
|
|
192
|
+
<span className="font-semibold text-foreground">{post.ignored.authorUsername}</span>.{' '}
|
|
193
|
+
{c('postHidden')}{' '}
|
|
179
194
|
<a href={post.ignored.revealHref} className={`font-semibold text-foreground ${LINK}`}>
|
|
180
|
-
|
|
195
|
+
{c('showAnyway')}
|
|
181
196
|
</a>
|
|
182
197
|
</div>
|
|
183
198
|
) : (
|
|
@@ -199,7 +214,7 @@ export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
|
199
214
|
<div className="border-t border-border px-4 py-3">
|
|
200
215
|
<h4 className={`${MICRO} mb-2`}>
|
|
201
216
|
{post.attachments.length}{' '}
|
|
202
|
-
{post.attachments.length === 1 ? 'attachment' : '
|
|
217
|
+
{post.attachments.length === 1 ? c('attachment.one') : c('attachment.other')}
|
|
203
218
|
</h4>
|
|
204
219
|
<ul className="flex flex-wrap gap-3">
|
|
205
220
|
{post.attachments.map((file) => (
|
package/src/slots/shell.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { ShellModel } from '@meith/theme-kit'
|
|
1
|
+
import type { ShellModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
|
-
export function Shell({ viewer, children }: ShellModel) {
|
|
4
|
+
export function Shell({ viewer, children, copy }: ShellModel & { copy: SlotCopy }) {
|
|
4
5
|
return (
|
|
5
6
|
<div
|
|
6
7
|
className="flex min-h-dvh flex-col bg-background text-foreground"
|
|
@@ -10,7 +11,7 @@ export function Shell({ viewer, children }: ShellModel) {
|
|
|
10
11
|
href="#board-content"
|
|
11
12
|
className="sr-only focus-visible:not-sr-only focus-visible:absolute focus-visible:top-4 focus-visible:left-4 focus-visible:z-50 focus-visible:inline-flex focus-visible:h-9 focus-visible:items-center focus-visible:rounded-full focus-visible:border focus-visible:border-border focus-visible:bg-card focus-visible:px-4 focus-visible:text-sm focus-visible:font-semibold focus-visible:text-foreground focus-visible:shadow-elevation"
|
|
12
13
|
>
|
|
13
|
-
|
|
14
|
+
{fromSlotCopy(copy, 'clubhouse.shell.skipToContent')}
|
|
14
15
|
</a>
|
|
15
16
|
|
|
16
17
|
{children}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import type { SlotCopy, SubforumListModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Card, CardContent } from '@meith/ui'
|
|
2
|
-
import type { SubforumListModel } from '@meith/theme-kit'
|
|
3
4
|
|
|
4
5
|
import { HEADING, LINK, MICRO, NUMERIC, PanelHead } from '../shared'
|
|
5
6
|
|
|
6
|
-
export function SubforumList({ forums }: SubforumListModel) {
|
|
7
|
+
export function SubforumList({ forums, copy }: SubforumListModel & { copy: SlotCopy }) {
|
|
7
8
|
if (forums.length === 0) return null
|
|
8
9
|
|
|
10
|
+
const c = (key: string) => fromSlotCopy(copy, `clubhouse.subforumList.${key}`)
|
|
11
|
+
|
|
9
12
|
return (
|
|
10
13
|
<Card aria-labelledby="subforums-heading">
|
|
11
|
-
<PanelHead id="subforums-heading" title=
|
|
14
|
+
<PanelHead id="subforums-heading" title={c('title')} />
|
|
12
15
|
|
|
13
16
|
<CardContent className="px-4 py-3">
|
|
14
17
|
<ul className="flex flex-wrap gap-x-6 gap-y-2 text-sm">
|
|
@@ -19,8 +22,7 @@ export function SubforumList({ forums }: SubforumListModel) {
|
|
|
19
22
|
</a>
|
|
20
23
|
{forum.type !== 'link' && (
|
|
21
24
|
<p className={`${MICRO} ${NUMERIC} mt-0.5`}>
|
|
22
|
-
{forum.threadCount.
|
|
23
|
-
{forum.postCount.toLocaleString('en')} posts
|
|
25
|
+
{forum.threadCount.label} {c('threads')} · {forum.postCount.label} {c('posts')}
|
|
24
26
|
</p>
|
|
25
27
|
)}
|
|
26
28
|
</li>
|
package/src/slots/thread-row.tsx
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import type { SlotCopy, ThreadRowSlotModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Badge } from '@meith/ui'
|
|
2
|
-
import type { ThreadRowSlotModel } from '@meith/theme-kit'
|
|
3
4
|
|
|
4
5
|
import { HEADING, LINK, MICRO, Prefix, ReadMark, Stamp, TABLE_ROW, Tally, UserRef } from '../shared'
|
|
5
6
|
|
|
6
|
-
export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
|
|
7
|
+
export function ThreadRow({ thread, select, copy }: ThreadRowSlotModel & { copy: SlotCopy }) {
|
|
8
|
+
const c = (key: string) => fromSlotCopy(copy, `clubhouse.threadRow.${key}`)
|
|
9
|
+
|
|
7
10
|
return (
|
|
8
11
|
<li
|
|
9
12
|
data-unread={thread.isUnread ? '' : undefined}
|
|
@@ -29,9 +32,9 @@ export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
|
|
|
29
32
|
<div className="min-w-0">
|
|
30
33
|
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
|
31
34
|
{thread.prefix !== null && <Prefix prefix={thread.prefix} />}
|
|
32
|
-
{thread.isSticky && <Badge tone="pinned">
|
|
33
|
-
{thread.isLocked && <Badge tone="locked">
|
|
34
|
-
{thread.isMoved && <Badge tone="moved">
|
|
35
|
+
{thread.isSticky && <Badge tone="pinned">{c('pinned')}</Badge>}
|
|
36
|
+
{thread.isLocked && <Badge tone="locked">{c('locked')}</Badge>}
|
|
37
|
+
{thread.isMoved && <Badge tone="moved">{c('moved')}</Badge>}
|
|
35
38
|
|
|
36
39
|
<a
|
|
37
40
|
href={thread.href}
|
|
@@ -39,35 +42,35 @@ export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
|
|
|
39
42
|
>
|
|
40
43
|
{thread.title}
|
|
41
44
|
</a>
|
|
42
|
-
{thread.isUnread && <span className="sr-only"> (
|
|
45
|
+
{thread.isUnread && <span className="sr-only"> {c('newPosts')}</span>}
|
|
43
46
|
</div>
|
|
44
47
|
|
|
45
48
|
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
46
|
-
|
|
49
|
+
{c('startedBy')} <UserRef user={thread.author} className="font-medium" />
|
|
47
50
|
</p>
|
|
48
51
|
</div>
|
|
49
52
|
|
|
50
53
|
<span className="col-start-2 md:col-start-3 md:block md:text-right">
|
|
51
|
-
<Tally value={thread.replyCount} label=
|
|
54
|
+
<Tally value={thread.replyCount} label={c('replies')} />
|
|
52
55
|
<span className="ms-3 md:hidden">
|
|
53
|
-
<Tally value={thread.viewCount} label=
|
|
56
|
+
<Tally value={thread.viewCount} label={c('views')} />
|
|
54
57
|
</span>
|
|
55
58
|
</span>
|
|
56
59
|
|
|
57
60
|
<span className="hidden md:block md:text-right">
|
|
58
|
-
<Tally value={thread.viewCount} label=
|
|
61
|
+
<Tally value={thread.viewCount} label={c('views')} />
|
|
59
62
|
</span>
|
|
60
63
|
|
|
61
64
|
<div className="col-start-2 min-w-0 text-xs text-muted-foreground md:col-start-5">
|
|
62
65
|
{thread.lastPost === null ? (
|
|
63
|
-
<span className={MICRO}>
|
|
66
|
+
<span className={MICRO}>{c('noRepliesYet')}</span>
|
|
64
67
|
) : (
|
|
65
68
|
<>
|
|
66
69
|
<a
|
|
67
70
|
href={thread.lastPost.href}
|
|
68
71
|
className={`block font-semibold text-foreground ${LINK}`}
|
|
69
72
|
>
|
|
70
|
-
|
|
73
|
+
{c('latestReply')}
|
|
71
74
|
</a>
|
|
72
75
|
<span className="mt-0.5 block truncate">
|
|
73
76
|
<UserRef user={thread.lastPost.author} className="font-medium" />
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import type { SlotCopy, ThreadViewModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Badge, cn } from '@meith/ui'
|
|
2
|
-
import type { ThreadViewModel } from '@meith/theme-kit'
|
|
3
4
|
|
|
4
5
|
import { BUTTON, HEADING, MICRO, MUTED_LINK, NUMERIC, PAGE_BODY, PageHead, Prefix } from '../shared'
|
|
5
6
|
|
|
6
|
-
export function ThreadView({
|
|
7
|
+
export function ThreadView({
|
|
8
|
+
thread,
|
|
9
|
+
forum,
|
|
10
|
+
replyHref,
|
|
11
|
+
markReadAction,
|
|
12
|
+
regions,
|
|
13
|
+
copy,
|
|
14
|
+
}: ThreadViewModel & { copy: SlotCopy }) {
|
|
15
|
+
const c = (key: string) => fromSlotCopy(copy, `clubhouse.threadView.${key}`)
|
|
16
|
+
|
|
7
17
|
return (
|
|
8
18
|
<div className={PAGE_BODY}>
|
|
9
19
|
<PageHead
|
|
@@ -18,7 +28,7 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
|
|
|
18
28
|
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
19
29
|
)}
|
|
20
30
|
>
|
|
21
|
-
|
|
31
|
+
{c('markRead')}
|
|
22
32
|
</button>
|
|
23
33
|
</form>
|
|
24
34
|
)}
|
|
@@ -27,7 +37,7 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
|
|
|
27
37
|
href={replyHref}
|
|
28
38
|
className={cn(BUTTON, 'bg-primary text-primary-foreground hover:bg-primary-hover')}
|
|
29
39
|
>
|
|
30
|
-
|
|
40
|
+
{c('reply')}
|
|
31
41
|
</a>
|
|
32
42
|
)}
|
|
33
43
|
</>
|
|
@@ -41,13 +51,12 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
|
|
|
41
51
|
|
|
42
52
|
<div className="mt-1 flex flex-wrap items-center gap-x-2 gap-y-1">
|
|
43
53
|
{thread.prefix !== null && <Prefix prefix={thread.prefix} />}
|
|
44
|
-
{thread.isSticky && <Badge tone="pinned">
|
|
45
|
-
{thread.isLocked && <Badge tone="locked">
|
|
46
|
-
{thread.isMoved && <Badge tone="moved">
|
|
54
|
+
{thread.isSticky && <Badge tone="pinned">{c('pinned')}</Badge>}
|
|
55
|
+
{thread.isLocked && <Badge tone="locked">{c('lockedNoReplies')}</Badge>}
|
|
56
|
+
{thread.isMoved && <Badge tone="moved">{c('moved')}</Badge>}
|
|
47
57
|
|
|
48
58
|
<span className={`${MICRO} ${NUMERIC}`}>
|
|
49
|
-
{thread.replyCount.
|
|
50
|
-
{thread.viewCount.toLocaleString('en')} views
|
|
59
|
+
{thread.replyCount.label} {c('replies')} · {thread.viewCount.label} {c('views')}
|
|
51
60
|
</span>
|
|
52
61
|
</div>
|
|
53
62
|
</PageHead>
|
package/src/slots/user-panel.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { LinkModel, SlotCopy, UserPanelModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Avatar, cn } from '@meith/ui'
|
|
2
4
|
import { Menu } from '@meith/ui/menu'
|
|
3
|
-
import type { LinkModel, UserPanelModel } from '@meith/theme-kit'
|
|
4
5
|
|
|
5
6
|
import { BUTTON, MUTED_LINK } from '../shared'
|
|
6
7
|
|
|
@@ -36,7 +37,10 @@ export function UserPanel({
|
|
|
36
37
|
notificationsHref,
|
|
37
38
|
messagesHref,
|
|
38
39
|
children,
|
|
39
|
-
|
|
40
|
+
copy,
|
|
41
|
+
}: UserPanelModel & { copy: SlotCopy }) {
|
|
42
|
+
const c = (key: string) => fromSlotCopy(copy, `clubhouse.userPanel.${key}`)
|
|
43
|
+
|
|
40
44
|
if (viewer.isGuest) {
|
|
41
45
|
return (
|
|
42
46
|
<div className="flex items-center gap-2">
|
|
@@ -58,7 +62,7 @@ export function UserPanel({
|
|
|
58
62
|
)
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
const name = viewer.username ?? '
|
|
65
|
+
const name = viewer.username ?? c('signedIn')
|
|
62
66
|
|
|
63
67
|
return (
|
|
64
68
|
<div className="flex min-w-0 flex-col items-start gap-1 sm:items-end">
|
|
@@ -70,24 +74,24 @@ export function UserPanel({
|
|
|
70
74
|
/>
|
|
71
75
|
|
|
72
76
|
<div className="flex min-w-0 items-center gap-2">
|
|
73
|
-
{unreadNotifications > 0 && (
|
|
77
|
+
{unreadNotifications.value > 0 && (
|
|
74
78
|
<Count href={notificationsHref} className="bg-primary text-primary-foreground">
|
|
75
|
-
{unreadNotifications}
|
|
76
|
-
<span className="sr-only">
|
|
77
|
-
<span aria-hidden="true">new</span>
|
|
79
|
+
{unreadNotifications.label}
|
|
80
|
+
<span className="sr-only"> {c('unreadNotifications')}</span>
|
|
81
|
+
<span aria-hidden="true">{c('new')}</span>
|
|
78
82
|
</Count>
|
|
79
83
|
)}
|
|
80
|
-
{unreadMessages > 0 && (
|
|
84
|
+
{unreadMessages.value > 0 && (
|
|
81
85
|
<Count href={messagesHref} className="border border-border text-foreground">
|
|
82
|
-
{unreadMessages}
|
|
83
|
-
<span className="sr-only">
|
|
84
|
-
<span aria-hidden="true">unread</span>
|
|
86
|
+
{unreadMessages.label}
|
|
87
|
+
<span className="sr-only"> {c('unreadMessages')}</span>
|
|
88
|
+
<span aria-hidden="true">{c('unread')}</span>
|
|
85
89
|
</Count>
|
|
86
90
|
)}
|
|
87
91
|
|
|
88
92
|
<span data-account="menu" className="flex min-w-0 items-center">
|
|
89
93
|
<Menu
|
|
90
|
-
label=
|
|
94
|
+
label={c('yourAccount')}
|
|
91
95
|
items={links}
|
|
92
96
|
triggerClassName="rounded-full px-2 py-1"
|
|
93
97
|
trigger={
|
|
@@ -124,7 +128,7 @@ export function UserPanel({
|
|
|
124
128
|
<nav
|
|
125
129
|
data-account="plain"
|
|
126
130
|
style={{ display: 'none' }}
|
|
127
|
-
aria-label=
|
|
131
|
+
aria-label={c('yourAccount')}
|
|
128
132
|
className="flex-wrap items-center gap-x-3 gap-y-1 text-xs sm:justify-end"
|
|
129
133
|
>
|
|
130
134
|
<span className="font-semibold text-foreground">{name}</span>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { OnlineMemberModel, SlotCopy, WhoIsOnlineModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Badge } from '@meith/ui'
|
|
2
|
-
import type { OnlineMemberModel, WhoIsOnlineModel } from '@meith/theme-kit'
|
|
3
4
|
|
|
4
5
|
import { LINK, MICRO, NUMERIC, Stamp, UserRef } from '../shared'
|
|
5
6
|
|
|
@@ -8,39 +9,44 @@ const VISIBLE_NAMES = 12
|
|
|
8
9
|
export function WhoIsOnline({
|
|
9
10
|
guestCount,
|
|
10
11
|
members,
|
|
12
|
+
memberCount,
|
|
11
13
|
total,
|
|
12
14
|
recordCount,
|
|
13
15
|
recordAt,
|
|
14
16
|
fullListHref,
|
|
15
|
-
|
|
17
|
+
copy,
|
|
18
|
+
}: WhoIsOnlineModel & { copy: SlotCopy }) {
|
|
16
19
|
const shown = members.slice(0, VISIBLE_NAMES)
|
|
17
20
|
const rest = members.slice(VISIBLE_NAMES)
|
|
18
21
|
|
|
22
|
+
const c = (key: string) => fromSlotCopy(copy, `clubhouse.whoIsOnline.${key}`)
|
|
23
|
+
|
|
19
24
|
return (
|
|
20
25
|
<section
|
|
21
26
|
aria-labelledby="who-is-online-heading"
|
|
22
27
|
className="flex flex-col gap-1.5 text-xs text-muted-foreground"
|
|
23
28
|
>
|
|
24
29
|
<h2 id="who-is-online-heading" className={MICRO}>
|
|
25
|
-
|
|
30
|
+
{c('heading')}
|
|
26
31
|
</h2>
|
|
27
32
|
|
|
28
33
|
<p className={NUMERIC}>
|
|
29
|
-
<span className="font-semibold text-foreground">{total.
|
|
30
|
-
{
|
|
31
|
-
{guestCount.
|
|
34
|
+
<span className="font-semibold text-foreground">{total.label}</span> {c('online')}{' '}
|
|
35
|
+
{memberCount.label} {memberCount.value === 1 ? c('member.one') : c('member.other')},{' '}
|
|
36
|
+
{guestCount.label} {guestCount.value === 1 ? c('guest.one') : c('guest.other')}
|
|
32
37
|
</p>
|
|
33
38
|
|
|
34
|
-
{
|
|
35
|
-
<p>
|
|
36
|
-
{guestCount === 0
|
|
37
|
-
? 'Nobody is reading the board right now.'
|
|
38
|
-
: 'Only guests are reading the board right now.'}
|
|
39
|
-
</p>
|
|
39
|
+
{memberCount.value === 0 ? (
|
|
40
|
+
<p>{guestCount.value === 0 ? c('nobody') : c('onlyGuests')}</p>
|
|
40
41
|
) : (
|
|
41
42
|
<p className="min-w-0">
|
|
42
43
|
{shown.map((member, index) => (
|
|
43
|
-
<Name
|
|
44
|
+
<Name
|
|
45
|
+
key={member.userId ?? member.username}
|
|
46
|
+
member={member}
|
|
47
|
+
first={index === 0}
|
|
48
|
+
copy={copy}
|
|
49
|
+
/>
|
|
44
50
|
))}
|
|
45
51
|
|
|
46
52
|
{rest.length > 0 && (
|
|
@@ -48,12 +54,17 @@ export function WhoIsOnline({
|
|
|
48
54
|
<summary
|
|
49
55
|
className={`inline cursor-default list-none ${LINK} [&::-webkit-details-marker]:hidden [&::marker]:content-none`}
|
|
50
56
|
>
|
|
51
|
-
{'
|
|
52
|
-
<span className={NUMERIC}>{rest.length
|
|
57
|
+
{' '}
|
|
58
|
+
{c('and')} <span className={NUMERIC}>{rest.length}</span> {c('more')}
|
|
53
59
|
</summary>
|
|
54
60
|
{': '}
|
|
55
61
|
{rest.map((member, index) => (
|
|
56
|
-
<Name
|
|
62
|
+
<Name
|
|
63
|
+
key={member.userId ?? member.username}
|
|
64
|
+
member={member}
|
|
65
|
+
first={index === 0}
|
|
66
|
+
copy={copy}
|
|
67
|
+
/>
|
|
57
68
|
))}
|
|
58
69
|
</details>
|
|
59
70
|
)}
|
|
@@ -62,11 +73,11 @@ export function WhoIsOnline({
|
|
|
62
73
|
|
|
63
74
|
<p className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
|
64
75
|
<a href={fullListHref} className={`font-semibold text-foreground ${LINK}`}>
|
|
65
|
-
|
|
76
|
+
{c('seeEveryone')}
|
|
66
77
|
</a>
|
|
67
78
|
{recordAt !== null && (
|
|
68
79
|
<span className={`ms-auto ${NUMERIC}`}>
|
|
69
|
-
|
|
80
|
+
{c('record')} {recordCount.label} on <Stamp at={recordAt} />
|
|
70
81
|
</span>
|
|
71
82
|
)}
|
|
72
83
|
</p>
|
|
@@ -74,7 +85,15 @@ export function WhoIsOnline({
|
|
|
74
85
|
)
|
|
75
86
|
}
|
|
76
87
|
|
|
77
|
-
function Name({
|
|
88
|
+
function Name({
|
|
89
|
+
member,
|
|
90
|
+
first,
|
|
91
|
+
copy,
|
|
92
|
+
}: {
|
|
93
|
+
member: OnlineMemberModel
|
|
94
|
+
first: boolean
|
|
95
|
+
copy: SlotCopy
|
|
96
|
+
}) {
|
|
78
97
|
return (
|
|
79
98
|
<>
|
|
80
99
|
{!first && ', '}
|
|
@@ -82,7 +101,7 @@ function Name({ member, first }: { member: OnlineMemberModel; first: boolean })
|
|
|
82
101
|
{member.isInvisible && (
|
|
83
102
|
<>
|
|
84
103
|
{' '}
|
|
85
|
-
<Badge tone="neutral">
|
|
104
|
+
<Badge tone="neutral">{fromSlotCopy(copy, 'clubhouse.whoIsOnline.invisible')}</Badge>
|
|
86
105
|
</>
|
|
87
106
|
)}
|
|
88
107
|
</>
|
package/src/theme.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { defaultTheme } from '@meith/theme-default'
|
|
2
2
|
import { defineTheme } from '@meith/theme-kit'
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
announcementCopy,
|
|
6
|
+
boardIndexCopy,
|
|
7
|
+
boardStatsCopy,
|
|
8
|
+
categoryBlockCopy,
|
|
9
|
+
footerCopy,
|
|
10
|
+
forumDisplayCopy,
|
|
11
|
+
forumRowCopy,
|
|
12
|
+
headerCopy,
|
|
13
|
+
latestPostsCopy,
|
|
14
|
+
latestThreadsCopy,
|
|
15
|
+
memberProfileCopy,
|
|
16
|
+
navigationCopy,
|
|
17
|
+
noticeCopy,
|
|
18
|
+
paginationCopy,
|
|
19
|
+
postActionsCopy,
|
|
20
|
+
postBitCopy,
|
|
21
|
+
shellCopy,
|
|
22
|
+
subforumListCopy,
|
|
23
|
+
threadRowCopy,
|
|
24
|
+
threadViewCopy,
|
|
25
|
+
userPanelCopy,
|
|
26
|
+
whoIsOnlineCopy,
|
|
27
|
+
} from './copy'
|
|
4
28
|
import { Announcement } from './slots/announcement'
|
|
5
29
|
import { BoardIndex } from './slots/board-index'
|
|
6
30
|
import { BoardStats } from './slots/board-stats'
|
|
@@ -56,4 +80,28 @@ export const clubhouseTheme = defineTheme({
|
|
|
56
80
|
|
|
57
81
|
MemberProfile,
|
|
58
82
|
},
|
|
83
|
+
copy: {
|
|
84
|
+
Announcement: announcementCopy,
|
|
85
|
+
BoardIndex: boardIndexCopy,
|
|
86
|
+
BoardStats: boardStatsCopy,
|
|
87
|
+
CategoryBlock: categoryBlockCopy,
|
|
88
|
+
Footer: footerCopy,
|
|
89
|
+
ForumDisplay: forumDisplayCopy,
|
|
90
|
+
ForumRow: forumRowCopy,
|
|
91
|
+
Header: headerCopy,
|
|
92
|
+
LatestPosts: latestPostsCopy,
|
|
93
|
+
LatestThreads: latestThreadsCopy,
|
|
94
|
+
MemberProfile: memberProfileCopy,
|
|
95
|
+
Navigation: navigationCopy,
|
|
96
|
+
Notice: noticeCopy,
|
|
97
|
+
Pagination: paginationCopy,
|
|
98
|
+
PostActions: postActionsCopy,
|
|
99
|
+
PostBit: postBitCopy,
|
|
100
|
+
Shell: shellCopy,
|
|
101
|
+
SubforumList: subforumListCopy,
|
|
102
|
+
ThreadRow: threadRowCopy,
|
|
103
|
+
ThreadView: threadViewCopy,
|
|
104
|
+
UserPanel: userPanelCopy,
|
|
105
|
+
WhoIsOnline: whoIsOnlineCopy,
|
|
106
|
+
},
|
|
59
107
|
})
|