@meith/theme-raidframe 0.7.0 → 0.8.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 +286 -0
- package/src/index.ts +2 -2
- package/src/messages/en.json +144 -0
- package/src/messages/index.ts +5 -0
- package/src/shared.tsx +1 -2
- package/src/slots/announcement.tsx +16 -6
- package/src/slots/auth-page.tsx +12 -3
- package/src/slots/board-index.tsx +10 -3
- package/src/slots/board-stats.tsx +18 -12
- package/src/slots/category-block.tsx +13 -6
- package/src/slots/discovery-view.tsx +17 -8
- package/src/slots/error-notice.tsx +17 -5
- package/src/slots/footer.tsx +16 -4
- package/src/slots/forum-display.tsx +20 -11
- package/src/slots/forum-row.tsx +26 -10
- package/src/slots/header.tsx +13 -3
- package/src/slots/latest-posts.tsx +12 -6
- package/src/slots/latest-threads.tsx +18 -8
- package/src/slots/member-profile.tsx +14 -10
- package/src/slots/navigation.tsx +6 -3
- package/src/slots/notice.tsx +7 -4
- package/src/slots/pagination.tsx +10 -6
- package/src/slots/panel-nav.tsx +40 -13
- package/src/slots/panel-page.tsx +12 -11
- package/src/slots/post-actions.tsx +17 -10
- package/src/slots/post-bit.tsx +29 -18
- package/src/slots/post-form.tsx +14 -4
- package/src/slots/redirect-notice.tsx +16 -8
- package/src/slots/search-form.tsx +12 -8
- package/src/slots/search-results.tsx +26 -16
- package/src/slots/shell.tsx +6 -3
- package/src/slots/subforum-list.tsx +8 -5
- package/src/slots/thread-row.tsx +19 -11
- package/src/slots/thread-view.tsx +22 -8
- package/src/slots/user-panel.tsx +14 -14
- package/src/slots/who-is-online.tsx +19 -10
- package/src/theme.ts +66 -0
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import type { CategoryBlockModel } from '@meith/theme-kit'
|
|
1
|
+
import type { CategoryBlockModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { Frame, MICRO, PanelHead } from '../shared'
|
|
4
5
|
|
|
5
|
-
export function CategoryBlock({
|
|
6
|
+
export function CategoryBlock({
|
|
7
|
+
category,
|
|
8
|
+
children,
|
|
9
|
+
copy,
|
|
10
|
+
}: CategoryBlockModel & { copy: SlotCopy }) {
|
|
11
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.categoryBlock.${key}`)
|
|
12
|
+
|
|
6
13
|
return (
|
|
7
14
|
<Frame>
|
|
8
15
|
<PanelHead title={category.title} href={category.href} />
|
|
@@ -17,16 +24,16 @@ export function CategoryBlock({ category, children }: CategoryBlockModel) {
|
|
|
17
24
|
<thead>
|
|
18
25
|
<tr className={`${MICRO} text-left`}>
|
|
19
26
|
<th scope="col" className="px-3 py-1.5 font-semibold">
|
|
20
|
-
|
|
27
|
+
{c('forum')}
|
|
21
28
|
</th>
|
|
22
29
|
<th scope="col" className="w-16 px-2 py-1.5 text-right font-semibold">
|
|
23
|
-
|
|
30
|
+
{c('threads')}
|
|
24
31
|
</th>
|
|
25
32
|
<th scope="col" className="w-16 px-2 py-1.5 text-right font-semibold">
|
|
26
|
-
|
|
33
|
+
{c('posts')}
|
|
27
34
|
</th>
|
|
28
35
|
<th scope="col" className="hidden w-60 px-3 py-1.5 font-semibold sm:table-cell">
|
|
29
|
-
|
|
36
|
+
{c('lastPost')}
|
|
30
37
|
</th>
|
|
31
38
|
</tr>
|
|
32
39
|
</thead>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { DiscoveryViewModel, TabModel } from '@meith/theme-kit'
|
|
1
|
+
import type { DiscoveryViewModel, SlotCopy, TabModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { Frame, HEADING, MICRO, NUMERIC, RULE, Stamp } from '../shared'
|
|
4
5
|
|
|
@@ -6,7 +7,8 @@ const TAB =
|
|
|
6
7
|
'inline-flex items-center border px-3 py-1.5 font-mono text-[0.6875rem] font-semibold uppercase tracking-[0.14em] whitespace-nowrap'
|
|
7
8
|
|
|
8
9
|
const CURRENT = 'border-primary bg-primary text-primary-foreground'
|
|
9
|
-
const OTHER =
|
|
10
|
+
const OTHER =
|
|
11
|
+
'border-border bg-card text-muted-foreground hover:border-primary/60 hover:text-primary'
|
|
10
12
|
|
|
11
13
|
function Tabs({ label, tabs }: { label: string; tabs: readonly TabModel[] }) {
|
|
12
14
|
if (tabs.length === 0) return null
|
|
@@ -40,7 +42,10 @@ export function DiscoveryView({
|
|
|
40
42
|
nextLabel,
|
|
41
43
|
emptyMessage,
|
|
42
44
|
refusal,
|
|
43
|
-
|
|
45
|
+
copy,
|
|
46
|
+
}: DiscoveryViewModel & { copy: SlotCopy }) {
|
|
47
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.discoveryView.${key}`)
|
|
48
|
+
|
|
44
49
|
return (
|
|
45
50
|
<main
|
|
46
51
|
id="board-content"
|
|
@@ -49,7 +54,7 @@ export function DiscoveryView({
|
|
|
49
54
|
>
|
|
50
55
|
<div className="border border-border bg-card shadow-elevation">
|
|
51
56
|
<div className="px-4 py-3">
|
|
52
|
-
<p className={MICRO}>
|
|
57
|
+
<p className={MICRO}>{c('kicker')}</p>
|
|
53
58
|
<h1 className={`${HEADING} text-xl text-foreground`}>{title}</h1>
|
|
54
59
|
<p className="mt-1 text-xs text-muted-foreground">{blurb}</p>
|
|
55
60
|
</div>
|
|
@@ -92,16 +97,20 @@ export function DiscoveryView({
|
|
|
92
97
|
{row.forum.label}
|
|
93
98
|
</a>
|
|
94
99
|
<span className="text-border">{' | '}</span>
|
|
95
|
-
<span className="uppercase">
|
|
100
|
+
<span className="uppercase">{c('startedBy')}</span> {row.authorUsername}
|
|
96
101
|
</p>
|
|
97
102
|
</div>
|
|
98
103
|
|
|
99
104
|
<p className={`${MICRO} shrink-0 normal-case sm:text-right`}>
|
|
100
|
-
<span className={`${NUMERIC} text-foreground`}>{row.replyCount}</span>{' '}
|
|
101
|
-
<span className="uppercase">
|
|
105
|
+
<span className={`${NUMERIC} text-foreground`}>{row.replyCount.label}</span>{' '}
|
|
106
|
+
<span className="uppercase">
|
|
107
|
+
{row.replyCount.value === 1 ? c('reply.one') : c('reply.other')}
|
|
108
|
+
</span>
|
|
102
109
|
<span className="block">
|
|
103
110
|
<Stamp at={row.lastPostAt} />
|
|
104
|
-
{row.lastPostUsername === null
|
|
111
|
+
{row.lastPostUsername === null
|
|
112
|
+
? null
|
|
113
|
+
: `${c('lastPostByPrefix')}${row.lastPostUsername}`}
|
|
105
114
|
</span>
|
|
106
115
|
</p>
|
|
107
116
|
</li>
|
|
@@ -1,23 +1,35 @@
|
|
|
1
|
-
import type { ErrorNoticeModel } from '@meith/theme-kit'
|
|
1
|
+
import type { ErrorNoticeModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { BUTTON_PRIMARY, Frame, HEADING, MICRO, NUMERIC } from '../shared'
|
|
4
5
|
|
|
5
|
-
export function ErrorNotice({
|
|
6
|
+
export function ErrorNotice({
|
|
7
|
+
status,
|
|
8
|
+
title,
|
|
9
|
+
message,
|
|
10
|
+
homeHref,
|
|
11
|
+
requestId,
|
|
12
|
+
copy,
|
|
13
|
+
}: ErrorNoticeModel & { copy: SlotCopy }) {
|
|
14
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.errorNotice.${key}`)
|
|
15
|
+
|
|
6
16
|
return (
|
|
7
17
|
<Frame className="w-full max-w-lg">
|
|
8
18
|
<div className="px-5 py-5">
|
|
9
|
-
<p className={`${MICRO} ${NUMERIC} text-destructive`}>
|
|
19
|
+
<p className={`${MICRO} ${NUMERIC} text-destructive`}>
|
|
20
|
+
{c('errorLabel')} {status}
|
|
21
|
+
</p>
|
|
10
22
|
<h1 className={`${HEADING} mt-1 text-2xl text-foreground`}>{title}</h1>
|
|
11
23
|
<p className="mt-2 text-sm text-muted-foreground">{message}</p>
|
|
12
24
|
|
|
13
25
|
<a href={homeHref} className={`${BUTTON_PRIMARY} mt-5`}>
|
|
14
|
-
|
|
26
|
+
{c('forumHome')}
|
|
15
27
|
</a>
|
|
16
28
|
</div>
|
|
17
29
|
|
|
18
30
|
{requestId !== null && (
|
|
19
31
|
<p className={`${MICRO} border-t border-border px-5 py-2 normal-case`}>
|
|
20
|
-
<span className="uppercase">
|
|
32
|
+
<span className="uppercase">{c('requestId')}</span>{' '}
|
|
21
33
|
<code className={`${NUMERIC} text-foreground select-all`}>{requestId}</code>
|
|
22
34
|
</p>
|
|
23
35
|
)}
|
package/src/slots/footer.tsx
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
-
import type { FooterModel } from '@meith/theme-kit'
|
|
1
|
+
import type { FooterModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { RULE } from '../shared'
|
|
4
5
|
|
|
5
|
-
export function Footer({
|
|
6
|
+
export function Footer({
|
|
7
|
+
boardTitle,
|
|
8
|
+
links,
|
|
9
|
+
timezoneLabel,
|
|
10
|
+
poweredBy,
|
|
11
|
+
copy,
|
|
12
|
+
}: FooterModel & { copy: SlotCopy }) {
|
|
13
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.footer.${key}`)
|
|
14
|
+
|
|
6
15
|
return (
|
|
7
16
|
<footer className="mt-auto">
|
|
8
17
|
<div className={RULE} aria-hidden="true" />
|
|
@@ -12,7 +21,7 @@ export function Footer({ boardTitle, links, timezoneLabel, poweredBy }: FooterMo
|
|
|
12
21
|
{boardTitle}
|
|
13
22
|
</span>
|
|
14
23
|
|
|
15
|
-
<nav aria-label=
|
|
24
|
+
<nav aria-label={c('linksAriaLabel')} className="flex flex-wrap gap-x-4 gap-y-1">
|
|
16
25
|
{links.map((link) => (
|
|
17
26
|
<a key={link.href} href={link.href} className="hover:text-primary">
|
|
18
27
|
{link.label}
|
|
@@ -21,7 +30,10 @@ export function Footer({ boardTitle, links, timezoneLabel, poweredBy }: FooterMo
|
|
|
21
30
|
</nav>
|
|
22
31
|
|
|
23
32
|
<span className="flex flex-wrap gap-x-4 gap-y-1">
|
|
24
|
-
<span>
|
|
33
|
+
<span>
|
|
34
|
+
{c('allTimesPrefix')}
|
|
35
|
+
{timezoneLabel}
|
|
36
|
+
</span>
|
|
25
37
|
{poweredBy && (
|
|
26
38
|
<a href={poweredBy.href} className="hover:text-primary">
|
|
27
39
|
{poweredBy.label}
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import type { ForumDisplayModel } from '@meith/theme-kit'
|
|
1
|
+
import type { ForumDisplayModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { BUTTON, BUTTON_PRIMARY, Frame, HEADING, MICRO, NUMERIC, RULE } from '../shared'
|
|
4
5
|
|
|
5
|
-
export function ForumDisplay({
|
|
6
|
+
export function ForumDisplay({
|
|
7
|
+
forum,
|
|
8
|
+
newThreadHref,
|
|
9
|
+
markReadAction,
|
|
10
|
+
regions,
|
|
11
|
+
copy,
|
|
12
|
+
}: ForumDisplayModel & { copy: SlotCopy }) {
|
|
13
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.forumDisplay.${key}`)
|
|
14
|
+
|
|
6
15
|
return (
|
|
7
16
|
<div className="flex flex-col gap-4 p-4">
|
|
8
17
|
<div className="border border-border bg-card shadow-elevation">
|
|
9
18
|
<div className="flex flex-wrap items-center justify-between gap-3 px-4 py-3">
|
|
10
19
|
<div className="min-w-0">
|
|
11
|
-
<p className={MICRO}>
|
|
20
|
+
<p className={MICRO}>{c('kicker')}</p>
|
|
12
21
|
<h1 className={`${HEADING} text-xl text-foreground`}>{forum.title}</h1>
|
|
13
22
|
{forum.description !== null && (
|
|
14
23
|
<p className="mt-1 text-xs text-muted-foreground">{forum.description}</p>
|
|
@@ -17,13 +26,13 @@ export function ForumDisplay({ forum, newThreadHref, markReadAction, regions }:
|
|
|
17
26
|
|
|
18
27
|
<div className="flex flex-wrap items-center gap-3">
|
|
19
28
|
<span className={`${MICRO} ${NUMERIC} normal-case`}>
|
|
20
|
-
<span className="text-foreground">{forum.threadCount}</span> threads
|
|
29
|
+
<span className="text-foreground">{forum.threadCount.label}</span> {c('threads')}
|
|
21
30
|
<span className="text-border">{' | '}</span>
|
|
22
|
-
<span className="text-foreground">{forum.postCount}</span> posts
|
|
31
|
+
<span className="text-foreground">{forum.postCount.label}</span> {c('posts')}
|
|
23
32
|
</span>
|
|
24
33
|
{newThreadHref !== null && (
|
|
25
34
|
<a href={newThreadHref} className={BUTTON_PRIMARY}>
|
|
26
|
-
|
|
35
|
+
{c('newThread')}
|
|
27
36
|
</a>
|
|
28
37
|
)}
|
|
29
38
|
</div>
|
|
@@ -46,16 +55,16 @@ export function ForumDisplay({ forum, newThreadHref, markReadAction, regions }:
|
|
|
46
55
|
<thead>
|
|
47
56
|
<tr className={`${MICRO} border-b border-border bg-surface text-left`}>
|
|
48
57
|
<th scope="col" className="px-3 py-2 font-semibold">
|
|
49
|
-
|
|
58
|
+
{c('threadHeader')}
|
|
50
59
|
</th>
|
|
51
60
|
<th scope="col" className="w-16 px-2 py-2 text-right font-semibold">
|
|
52
|
-
|
|
61
|
+
{c('repliesHeader')}
|
|
53
62
|
</th>
|
|
54
63
|
<th scope="col" className="w-16 px-2 py-2 text-right font-semibold">
|
|
55
|
-
|
|
64
|
+
{c('viewsHeader')}
|
|
56
65
|
</th>
|
|
57
66
|
<th scope="col" className="hidden w-52 px-3 py-2 font-semibold sm:table-cell">
|
|
58
|
-
|
|
67
|
+
{c('lastPostHeader')}
|
|
59
68
|
</th>
|
|
60
69
|
</tr>
|
|
61
70
|
</thead>
|
|
@@ -72,7 +81,7 @@ export function ForumDisplay({ forum, newThreadHref, markReadAction, regions }:
|
|
|
72
81
|
{markReadAction !== null && (
|
|
73
82
|
<form action={markReadAction} method="post">
|
|
74
83
|
<button type="submit" className={BUTTON}>
|
|
75
|
-
|
|
84
|
+
{c('markRead')}
|
|
76
85
|
</button>
|
|
77
86
|
</form>
|
|
78
87
|
)}
|
package/src/slots/forum-row.tsx
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import type { ForumRowSlotModel } from '@meith/theme-kit'
|
|
1
|
+
import type { ForumRowSlotModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { HEADING, MICRO, NUMERIC, ReadPip, Stamp, UserRef } from '../shared'
|
|
4
5
|
|
|
5
|
-
export function ForumRow({ forum }: ForumRowSlotModel) {
|
|
6
|
+
export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }) {
|
|
6
7
|
const isLink = forum.type === 'link'
|
|
8
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.forumRow.${key}`)
|
|
7
9
|
|
|
8
10
|
return (
|
|
9
11
|
<tr className="border-t border-border align-top hover:bg-secondary/50">
|
|
@@ -11,11 +13,14 @@ export function ForumRow({ forum }: ForumRowSlotModel) {
|
|
|
11
13
|
<div className="flex items-baseline gap-2">
|
|
12
14
|
<ReadPip unread={forum.isUnread} className="mt-1.5" />
|
|
13
15
|
<div className="min-w-0">
|
|
14
|
-
<a
|
|
16
|
+
<a
|
|
17
|
+
href={forum.href}
|
|
18
|
+
className={`${HEADING} text-sm text-foreground hover:text-primary`}
|
|
19
|
+
>
|
|
15
20
|
{forum.title}
|
|
16
21
|
</a>
|
|
17
|
-
{forum.isUnread && <span className="sr-only">(
|
|
18
|
-
{isLink && <span className={`${MICRO} ml-2`}>
|
|
22
|
+
{forum.isUnread && <span className="sr-only">{c('newPosts')}</span>}
|
|
23
|
+
{isLink && <span className={`${MICRO} ml-2`}>{c('linkBadge')}</span>}
|
|
19
24
|
|
|
20
25
|
{forum.description !== null && (
|
|
21
26
|
<p className="mt-0.5 text-xs text-muted-foreground">{forum.description}</p>
|
|
@@ -39,23 +44,34 @@ export function ForumRow({ forum }: ForumRowSlotModel) {
|
|
|
39
44
|
</td>
|
|
40
45
|
|
|
41
46
|
<td className={`w-16 px-2 py-2.5 text-right text-xs ${NUMERIC} text-foreground`}>
|
|
42
|
-
{isLink ?
|
|
47
|
+
{isLink ? (
|
|
48
|
+
<span className="text-muted-foreground">{c('none')}</span>
|
|
49
|
+
) : (
|
|
50
|
+
forum.threadCount.label
|
|
51
|
+
)}
|
|
43
52
|
</td>
|
|
44
53
|
<td className={`w-16 px-2 py-2.5 text-right text-xs ${NUMERIC} text-foreground`}>
|
|
45
|
-
{isLink ?
|
|
54
|
+
{isLink ? (
|
|
55
|
+
<span className="text-muted-foreground">{c('none')}</span>
|
|
56
|
+
) : (
|
|
57
|
+
forum.postCount.label
|
|
58
|
+
)}
|
|
46
59
|
</td>
|
|
47
60
|
|
|
48
61
|
<td className="hidden w-60 px-3 py-2.5 text-xs text-muted-foreground sm:table-cell">
|
|
49
62
|
{isLink || forum.lastPost === null ? (
|
|
50
|
-
<span className={MICRO}>{isLink ? '' : '
|
|
63
|
+
<span className={MICRO}>{isLink ? '' : c('noPostsYet')}</span>
|
|
51
64
|
) : (
|
|
52
65
|
<>
|
|
53
|
-
<a
|
|
66
|
+
<a
|
|
67
|
+
href={forum.lastPost.href}
|
|
68
|
+
className="block truncate text-foreground hover:text-primary"
|
|
69
|
+
>
|
|
54
70
|
{forum.lastPost.threadTitle}
|
|
55
71
|
</a>
|
|
56
72
|
<span className={`${MICRO} mt-0.5 block normal-case`}>
|
|
57
73
|
<UserRef user={forum.lastPost.author} className="hover:text-primary" />
|
|
58
|
-
{'
|
|
74
|
+
{c('separator')}
|
|
59
75
|
<Stamp at={forum.lastPost.at} />
|
|
60
76
|
</span>
|
|
61
77
|
</>
|
package/src/slots/header.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { HeaderModel } from '@meith/theme-kit'
|
|
1
|
+
import type { HeaderModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { RULE } from '../shared'
|
|
4
5
|
|
|
@@ -30,7 +31,16 @@ function BoardMark({ boardTitle, logo }: Pick<HeaderModel, 'boardTitle' | 'logo'
|
|
|
30
31
|
)
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export function Header({
|
|
34
|
+
export function Header({
|
|
35
|
+
boardTitle,
|
|
36
|
+
homeHref,
|
|
37
|
+
navigation,
|
|
38
|
+
logo,
|
|
39
|
+
children,
|
|
40
|
+
copy,
|
|
41
|
+
}: HeaderModel & { copy: SlotCopy }) {
|
|
42
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.header.${key}`)
|
|
43
|
+
|
|
34
44
|
return (
|
|
35
45
|
<header>
|
|
36
46
|
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-3 border-b border-border bg-surface px-4 py-3">
|
|
@@ -48,7 +58,7 @@ export function Header({ boardTitle, homeHref, navigation, logo, children }: Hea
|
|
|
48
58
|
|
|
49
59
|
{navigation.length > 0 && (
|
|
50
60
|
<nav
|
|
51
|
-
aria-label=
|
|
61
|
+
aria-label={c('sectionsAriaLabel')}
|
|
52
62
|
className="flex flex-wrap border-b border-border bg-card/60"
|
|
53
63
|
>
|
|
54
64
|
{navigation.map((item) => (
|
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import type { LatestPostsModel } from '@meith/theme-kit'
|
|
1
|
+
import type { LatestPostsModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { Frame, MICRO, PanelHead, Stamp, UserRef } from '../shared'
|
|
4
5
|
|
|
5
|
-
export function LatestPosts({ posts, capturedAt }: LatestPostsModel) {
|
|
6
|
+
export function LatestPosts({ posts, capturedAt, copy }: LatestPostsModel & { copy: SlotCopy }) {
|
|
7
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.latestPosts.${key}`)
|
|
8
|
+
|
|
6
9
|
return (
|
|
7
10
|
<Frame aria-labelledby="latest-posts-heading">
|
|
8
|
-
<PanelHead id="latest-posts-heading" title=
|
|
11
|
+
<PanelHead id="latest-posts-heading" title={c('heading')} />
|
|
9
12
|
|
|
10
13
|
{posts.length === 0 ? (
|
|
11
|
-
<p className={`${MICRO} px-3 py-2.5`}>
|
|
14
|
+
<p className={`${MICRO} px-3 py-2.5`}>{c('empty')}</p>
|
|
12
15
|
) : (
|
|
13
16
|
<ul className="divide-y divide-border">
|
|
14
17
|
{posts.map((post) => (
|
|
15
18
|
<li key={post.href} className="px-3 py-2 hover:bg-secondary/50">
|
|
16
|
-
<a
|
|
19
|
+
<a
|
|
20
|
+
href={post.href}
|
|
21
|
+
className="block truncate text-xs text-foreground hover:text-primary"
|
|
22
|
+
>
|
|
17
23
|
{post.threadTitle}
|
|
18
24
|
</a>
|
|
19
25
|
<p className={`${MICRO} mt-0.5 truncate normal-case`}>
|
|
@@ -35,7 +41,7 @@ export function LatestPosts({ posts, capturedAt }: LatestPostsModel) {
|
|
|
35
41
|
)}
|
|
36
42
|
|
|
37
43
|
<p className={`${MICRO} border-t border-border px-3 py-1.5 normal-case`}>
|
|
38
|
-
<span className="uppercase">
|
|
44
|
+
<span className="uppercase">{c('asOf')}</span> <Stamp at={capturedAt} />
|
|
39
45
|
</p>
|
|
40
46
|
</Frame>
|
|
41
47
|
)
|
|
@@ -1,19 +1,29 @@
|
|
|
1
|
-
import type { LatestThreadsModel } from '@meith/theme-kit'
|
|
1
|
+
import type { LatestThreadsModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { Frame, MICRO, NUMERIC, PanelHead, Stamp, UserRef } from '../shared'
|
|
4
5
|
|
|
5
|
-
export function LatestThreads({
|
|
6
|
+
export function LatestThreads({
|
|
7
|
+
threads,
|
|
8
|
+
capturedAt,
|
|
9
|
+
copy,
|
|
10
|
+
}: LatestThreadsModel & { copy: SlotCopy }) {
|
|
11
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.latestThreads.${key}`)
|
|
12
|
+
|
|
6
13
|
return (
|
|
7
14
|
<Frame aria-labelledby="latest-threads-heading">
|
|
8
|
-
<PanelHead id="latest-threads-heading" title=
|
|
15
|
+
<PanelHead id="latest-threads-heading" title={c('heading')} />
|
|
9
16
|
|
|
10
17
|
{threads.length === 0 ? (
|
|
11
|
-
<p className={`${MICRO} px-3 py-2.5`}>
|
|
18
|
+
<p className={`${MICRO} px-3 py-2.5`}>{c('empty')}</p>
|
|
12
19
|
) : (
|
|
13
20
|
<ul className="divide-y divide-border">
|
|
14
21
|
{threads.map((thread) => (
|
|
15
22
|
<li key={thread.href} className="px-3 py-2 hover:bg-secondary/50">
|
|
16
|
-
<a
|
|
23
|
+
<a
|
|
24
|
+
href={thread.href}
|
|
25
|
+
className="block truncate text-xs text-foreground hover:text-primary"
|
|
26
|
+
>
|
|
17
27
|
{thread.title}
|
|
18
28
|
</a>
|
|
19
29
|
<p className={`${MICRO} mt-0.5 truncate normal-case`}>
|
|
@@ -26,8 +36,8 @@ export function LatestThreads({ threads, capturedAt }: LatestThreadsModel) {
|
|
|
26
36
|
<p className={`${MICRO} truncate normal-case`}>
|
|
27
37
|
<Stamp at={thread.startedAt} />
|
|
28
38
|
<span className="text-border">{' | '}</span>
|
|
29
|
-
<span className={NUMERIC}>{thread.replyCount}</span>
|
|
30
|
-
<span className="uppercase">
|
|
39
|
+
<span className={NUMERIC}>{thread.replyCount.label}</span>
|
|
40
|
+
<span className="uppercase">{c('replies')}</span>
|
|
31
41
|
</p>
|
|
32
42
|
</li>
|
|
33
43
|
))}
|
|
@@ -35,7 +45,7 @@ export function LatestThreads({ threads, capturedAt }: LatestThreadsModel) {
|
|
|
35
45
|
)}
|
|
36
46
|
|
|
37
47
|
<p className={`${MICRO} border-t border-border px-3 py-1.5 normal-case`}>
|
|
38
|
-
<span className="uppercase">
|
|
48
|
+
<span className="uppercase">{c('asOf')}</span> <Stamp at={capturedAt} />
|
|
39
49
|
</p>
|
|
40
50
|
</Frame>
|
|
41
51
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
import type { MemberProfileModel } from '@meith/theme-kit'
|
|
3
|
+
import type { MemberProfileModel, SlotCopy } from '@meith/theme-kit'
|
|
4
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
4
5
|
|
|
5
6
|
import { BUTTON, Frame, HEADING, MICRO, NUMERIC, PanelHead, RULE, Stamp } from '../shared'
|
|
6
7
|
|
|
@@ -24,7 +25,10 @@ export function MemberProfile({
|
|
|
24
25
|
fields,
|
|
25
26
|
actions,
|
|
26
27
|
regions,
|
|
27
|
-
|
|
28
|
+
copy,
|
|
29
|
+
}: MemberProfileModel & { copy: SlotCopy }) {
|
|
30
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.memberProfile.${key}`)
|
|
31
|
+
|
|
28
32
|
return (
|
|
29
33
|
<article className="flex flex-col gap-4 p-4">
|
|
30
34
|
<div className="border border-border bg-card shadow-elevation">
|
|
@@ -44,7 +48,7 @@ export function MemberProfile({
|
|
|
44
48
|
)}
|
|
45
49
|
|
|
46
50
|
<div className="min-w-0">
|
|
47
|
-
<p className={MICRO}>
|
|
51
|
+
<p className={MICRO}>{c('kicker')}</p>
|
|
48
52
|
<h1
|
|
49
53
|
className={[`${HEADING} text-2xl text-foreground`, user.nameClass]
|
|
50
54
|
.filter(Boolean)
|
|
@@ -59,14 +63,14 @@ export function MemberProfile({
|
|
|
59
63
|
</div>
|
|
60
64
|
|
|
61
65
|
<Frame aria-labelledby="member-stats-heading">
|
|
62
|
-
<PanelHead id="member-stats-heading" title=
|
|
66
|
+
<PanelHead id="member-stats-heading" title={c('statsHeading')} />
|
|
63
67
|
<dl className="grid grid-cols-2 gap-1.5 p-3 sm:grid-cols-3">
|
|
64
|
-
<Field label=
|
|
65
|
-
<Field label=
|
|
68
|
+
<Field label={c('posts')}>{postCount.label}</Field>
|
|
69
|
+
<Field label={c('joined')}>
|
|
66
70
|
<Stamp at={joinedAt} />
|
|
67
71
|
</Field>
|
|
68
|
-
<Field label=
|
|
69
|
-
{lastVisitAt === null ? 'never' : <Stamp at={lastVisitAt} />}
|
|
72
|
+
<Field label={c('lastVisit')}>
|
|
73
|
+
{lastVisitAt === null ? c('never') : <Stamp at={lastVisitAt} />}
|
|
70
74
|
</Field>
|
|
71
75
|
{fields.map((field) => (
|
|
72
76
|
<Field key={field.label} label={field.label}>
|
|
@@ -78,7 +82,7 @@ export function MemberProfile({
|
|
|
78
82
|
|
|
79
83
|
{signatureHtml !== null && (
|
|
80
84
|
<Frame aria-labelledby="member-signature-heading">
|
|
81
|
-
<PanelHead id="member-signature-heading" title=
|
|
85
|
+
<PanelHead id="member-signature-heading" title={c('signatureHeading')} />
|
|
82
86
|
<div
|
|
83
87
|
className="prose-md p-3 text-sm"
|
|
84
88
|
dangerouslySetInnerHTML={{ __html: signatureHtml }}
|
|
@@ -89,7 +93,7 @@ export function MemberProfile({
|
|
|
89
93
|
{regions?.plugins}
|
|
90
94
|
|
|
91
95
|
{actions.length > 0 && (
|
|
92
|
-
<nav aria-label=
|
|
96
|
+
<nav aria-label={c('actionsAriaLabel')} className="flex flex-wrap gap-2">
|
|
93
97
|
{actions.map((action) => (
|
|
94
98
|
<a key={action.href} href={action.href} className={BUTTON}>
|
|
95
99
|
{action.label}
|
package/src/slots/navigation.tsx
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import type { NavigationModel } from '@meith/theme-kit'
|
|
1
|
+
import type { NavigationModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
+
|
|
4
|
+
export function Navigation({ items, copy }: NavigationModel & { copy: SlotCopy }) {
|
|
5
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.navigation.${key}`)
|
|
2
6
|
|
|
3
|
-
export function Navigation({ items }: NavigationModel) {
|
|
4
7
|
return (
|
|
5
|
-
<nav aria-label=
|
|
8
|
+
<nav aria-label={c('ariaLabel')} className="border-b border-border bg-card/40 px-4 py-2">
|
|
6
9
|
<ol className="flex flex-wrap items-center gap-1.5 font-mono text-[0.6875rem] tracking-[0.1em] text-muted-foreground uppercase">
|
|
7
10
|
{items.map((item, index) => {
|
|
8
11
|
const isLast = index === items.length - 1
|
package/src/slots/notice.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { NoticeModel } from '@meith/theme-kit'
|
|
1
|
+
import type { NoticeModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
const TONE: Record<NoticeModel['kind'], string> = {
|
|
4
5
|
info: 'border-l-forum-unread text-forum-unread',
|
|
@@ -7,7 +8,9 @@ const TONE: Record<NoticeModel['kind'], string> = {
|
|
|
7
8
|
error: 'border-l-destructive text-destructive',
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export function Notice({ kind, message, dismissHref }: NoticeModel) {
|
|
11
|
+
export function Notice({ kind, message, dismissHref, copy }: NoticeModel & { copy: SlotCopy }) {
|
|
12
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.notice.${key}`)
|
|
13
|
+
|
|
11
14
|
return (
|
|
12
15
|
<div
|
|
13
16
|
role={kind === 'error' ? 'alert' : undefined}
|
|
@@ -15,7 +18,7 @@ export function Notice({ kind, message, dismissHref }: NoticeModel) {
|
|
|
15
18
|
>
|
|
16
19
|
<p className="text-foreground">
|
|
17
20
|
<span className="mr-2 font-mono text-[0.625rem] font-bold tracking-[0.18em] uppercase">
|
|
18
|
-
{kind}
|
|
21
|
+
{c(kind)}
|
|
19
22
|
</span>
|
|
20
23
|
{message}
|
|
21
24
|
</p>
|
|
@@ -24,7 +27,7 @@ export function Notice({ kind, message, dismissHref }: NoticeModel) {
|
|
|
24
27
|
href={dismissHref}
|
|
25
28
|
className="font-mono text-[0.625rem] font-semibold tracking-[0.14em] text-muted-foreground uppercase hover:text-primary"
|
|
26
29
|
>
|
|
27
|
-
dismiss
|
|
30
|
+
{c('dismiss')}
|
|
28
31
|
</a>
|
|
29
32
|
)}
|
|
30
33
|
</div>
|
package/src/slots/pagination.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { PaginationModel } from '@meith/theme-kit'
|
|
1
|
+
import type { PaginationModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { MICRO, NUMERIC } from '../shared'
|
|
4
5
|
|
|
@@ -12,14 +13,17 @@ export function Pagination({
|
|
|
12
13
|
pages,
|
|
13
14
|
previousHref,
|
|
14
15
|
nextHref,
|
|
15
|
-
|
|
16
|
+
copy,
|
|
17
|
+
}: PaginationModel & { copy: SlotCopy }) {
|
|
16
18
|
if (pageCount <= 1 && previousHref === null && nextHref === null) return null
|
|
17
19
|
|
|
20
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.pagination.${key}`)
|
|
21
|
+
|
|
18
22
|
return (
|
|
19
|
-
<nav aria-label=
|
|
23
|
+
<nav aria-label={c('ariaLabel')} className="flex flex-wrap items-center gap-1.5">
|
|
20
24
|
{previousHref !== null && (
|
|
21
25
|
<a href={previousHref} rel="prev" className={STEP}>
|
|
22
|
-
prev
|
|
26
|
+
{c('prev')}
|
|
23
27
|
</a>
|
|
24
28
|
)}
|
|
25
29
|
|
|
@@ -41,12 +45,12 @@ export function Pagination({
|
|
|
41
45
|
|
|
42
46
|
{nextHref !== null && (
|
|
43
47
|
<a href={nextHref} rel="next" className={STEP}>
|
|
44
|
-
next
|
|
48
|
+
{c('next')}
|
|
45
49
|
</a>
|
|
46
50
|
)}
|
|
47
51
|
|
|
48
52
|
<span className={`${MICRO} ml-2 normal-case`}>
|
|
49
|
-
<span className="uppercase">page</span> <span className={NUMERIC}>{page}</span>
|
|
53
|
+
<span className="uppercase">{c('page')}</span> <span className={NUMERIC}>{page}</span>
|
|
50
54
|
{pageCountIsExact && (
|
|
51
55
|
<>
|
|
52
56
|
{' / '}
|