@meith/theme-raidframe 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 +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 +43 -10
- 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
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
|
{' / '}
|
package/src/slots/panel-nav.tsx
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import type { PanelNavItemModel, PanelNavModel } from '@meith/theme-kit'
|
|
1
|
+
import type { PanelNavItemModel, PanelNavModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { MICRO, NUMERIC, RULE } from '../shared'
|
|
4
5
|
|
|
5
|
-
const ITEM =
|
|
6
|
-
'flex items-baseline gap-2 border-l-2 px-2.5 py-1.5 text-[0.8125rem] tracking-[0.02em]'
|
|
6
|
+
const ITEM = 'flex items-baseline gap-2 border-l-2 px-2.5 py-1.5 text-[0.8125rem] tracking-[0.02em]'
|
|
7
7
|
|
|
8
8
|
const HERE = 'border-l-primary bg-secondary font-semibold text-foreground'
|
|
9
9
|
const OPEN = 'border-l-primary/40 font-semibold text-foreground'
|
|
10
|
-
const ELSEWHERE =
|
|
10
|
+
const ELSEWHERE =
|
|
11
|
+
'border-l-transparent text-muted-foreground hover:border-l-border hover:text-primary'
|
|
12
|
+
|
|
13
|
+
function Count({ count, copy }: { count: number; copy: SlotCopy }) {
|
|
14
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.panelNav.${key}`)
|
|
11
15
|
|
|
12
|
-
function Count({ count }: { count: number }) {
|
|
13
16
|
return (
|
|
14
17
|
<>
|
|
15
18
|
<span
|
|
@@ -18,16 +21,26 @@ function Count({ count }: { count: number }) {
|
|
|
18
21
|
>
|
|
19
22
|
{count > 99 ? '99+' : count}
|
|
20
23
|
</span>
|
|
21
|
-
<span className="sr-only">
|
|
24
|
+
<span className="sr-only">
|
|
25
|
+
({count} {c('waiting')})
|
|
26
|
+
</span>
|
|
22
27
|
</>
|
|
23
28
|
)
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
function Item({
|
|
31
|
+
function Item({
|
|
32
|
+
item,
|
|
33
|
+
className,
|
|
34
|
+
copy,
|
|
35
|
+
}: {
|
|
36
|
+
item: PanelNavItemModel
|
|
37
|
+
className: string
|
|
38
|
+
copy: SlotCopy
|
|
39
|
+
}) {
|
|
27
40
|
const body = (
|
|
28
41
|
<>
|
|
29
42
|
<span className="min-w-0 flex-1">{item.title}</span>
|
|
30
|
-
{item.count !== null && <Count count={item.count} />}
|
|
43
|
+
{item.count !== null && <Count count={item.count} copy={copy} />}
|
|
31
44
|
</>
|
|
32
45
|
)
|
|
33
46
|
|
|
@@ -52,7 +65,11 @@ function Item({ item, className }: { item: PanelNavItemModel; className: string
|
|
|
52
65
|
)
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
function Sections({
|
|
68
|
+
function Sections({
|
|
69
|
+
label,
|
|
70
|
+
sections,
|
|
71
|
+
copy,
|
|
72
|
+
}: Pick<PanelNavModel, 'label' | 'sections'> & { copy: SlotCopy }) {
|
|
56
73
|
return (
|
|
57
74
|
<nav aria-label={label}>
|
|
58
75
|
<ul className="flex flex-col">
|
|
@@ -64,13 +81,18 @@ function Sections({ label, sections }: Pick<PanelNavModel, 'label' | 'sections'>
|
|
|
64
81
|
<Item
|
|
65
82
|
item={section}
|
|
66
83
|
className={section.current === 'here' ? HERE : section.isOpen ? OPEN : ELSEWHERE}
|
|
84
|
+
copy={copy}
|
|
67
85
|
/>
|
|
68
86
|
|
|
69
87
|
{section.isOpen && section.children.length > 0 && (
|
|
70
88
|
<ul className="my-0.5 ml-3 flex flex-col border-l border-border pl-1">
|
|
71
89
|
{section.children.map((child) => (
|
|
72
90
|
<li key={child.href}>
|
|
73
|
-
<Item
|
|
91
|
+
<Item
|
|
92
|
+
item={child}
|
|
93
|
+
className={child.current === 'here' ? HERE : ELSEWHERE}
|
|
94
|
+
copy={copy}
|
|
95
|
+
/>
|
|
74
96
|
</li>
|
|
75
97
|
))}
|
|
76
98
|
</ul>
|
|
@@ -82,7 +104,12 @@ function Sections({ label, sections }: Pick<PanelNavModel, 'label' | 'sections'>
|
|
|
82
104
|
)
|
|
83
105
|
}
|
|
84
106
|
|
|
85
|
-
export function PanelNav({
|
|
107
|
+
export function PanelNav({
|
|
108
|
+
label,
|
|
109
|
+
sections,
|
|
110
|
+
currentTitle,
|
|
111
|
+
copy,
|
|
112
|
+
}: PanelNavModel & { copy: SlotCopy }) {
|
|
86
113
|
return (
|
|
87
114
|
<>
|
|
88
115
|
<details className="border border-border bg-card lg:hidden">
|
|
@@ -92,7 +119,7 @@ export function PanelNav({ label, sections, currentTitle }: PanelNavModel) {
|
|
|
92
119
|
</summary>
|
|
93
120
|
<div className={RULE} aria-hidden="true" />
|
|
94
121
|
<div className="p-2">
|
|
95
|
-
<Sections label={label} sections={sections} />
|
|
122
|
+
<Sections label={label} sections={sections} copy={copy} />
|
|
96
123
|
</div>
|
|
97
124
|
</details>
|
|
98
125
|
|
|
@@ -102,7 +129,7 @@ export function PanelNav({ label, sections, currentTitle }: PanelNavModel) {
|
|
|
102
129
|
</p>
|
|
103
130
|
<div className={RULE} aria-hidden="true" />
|
|
104
131
|
<div className="p-2">
|
|
105
|
-
<Sections label={label} sections={sections} />
|
|
132
|
+
<Sections label={label} sections={sections} copy={copy} />
|
|
106
133
|
</div>
|
|
107
134
|
</div>
|
|
108
135
|
</>
|
package/src/slots/panel-page.tsx
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import type { PanelKind, PanelPageModel, PanelSectionModel } from '@meith/theme-kit'
|
|
1
|
+
import type { PanelKind, PanelPageModel, PanelSectionModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { HEADING, MICRO, RULE } from '../shared'
|
|
4
5
|
|
|
5
|
-
const LABEL: Record<PanelKind, string> = {
|
|
6
|
-
usercp: 'your control panel',
|
|
7
|
-
modcp: 'moderator control panel',
|
|
8
|
-
admincp: 'control panel',
|
|
9
|
-
}
|
|
10
|
-
|
|
11
6
|
export function PanelPage({
|
|
12
7
|
panel,
|
|
13
8
|
title,
|
|
@@ -17,16 +12,22 @@ export function PanelPage({
|
|
|
17
12
|
gap,
|
|
18
13
|
regions,
|
|
19
14
|
children,
|
|
20
|
-
|
|
15
|
+
copy,
|
|
16
|
+
}: PanelPageModel & { copy: SlotCopy }) {
|
|
17
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.panelPage.${key}`)
|
|
18
|
+
const LABEL: Record<PanelKind, string> = {
|
|
19
|
+
usercp: c('panelLabel.usercp'),
|
|
20
|
+
modcp: c('panelLabel.modcp'),
|
|
21
|
+
admincp: c('panelLabel.admincp'),
|
|
22
|
+
}
|
|
23
|
+
|
|
21
24
|
return (
|
|
22
25
|
<main
|
|
23
26
|
id="board-content"
|
|
24
27
|
tabIndex={-1}
|
|
25
28
|
className={`flex w-full flex-col p-4 ${
|
|
26
29
|
frame === 'standalone' ? 'mx-auto flex-1' : ''
|
|
27
|
-
} ${width === 'wide' ? 'max-w-none' : 'max-w-4xl'} ${
|
|
28
|
-
gap === 'loose' ? 'gap-6' : 'gap-4'
|
|
29
|
-
}`}
|
|
30
|
+
} ${width === 'wide' ? 'max-w-none' : 'max-w-4xl'} ${gap === 'loose' ? 'gap-6' : 'gap-4'}`}
|
|
30
31
|
>
|
|
31
32
|
<div className="border border-border bg-card shadow-elevation">
|
|
32
33
|
<div className="flex flex-wrap items-start justify-between gap-x-6 gap-y-2 px-4 py-3">
|
|
@@ -1,23 +1,30 @@
|
|
|
1
|
-
import type { PostActionsSlotModel } from '@meith/theme-kit'
|
|
1
|
+
import type { PostActionsSlotModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
const ACTION =
|
|
4
5
|
'font-mono text-[0.625rem] font-semibold uppercase tracking-[0.14em] text-muted-foreground hover:text-primary'
|
|
5
6
|
|
|
6
|
-
export function PostActions({
|
|
7
|
+
export function PostActions({
|
|
8
|
+
actions,
|
|
9
|
+
children,
|
|
10
|
+
copy,
|
|
11
|
+
}: PostActionsSlotModel & { copy: SlotCopy }) {
|
|
12
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.postActions.${key}`)
|
|
13
|
+
|
|
7
14
|
const items = [
|
|
8
|
-
{ href: actions.quoteHref, label: 'quote' },
|
|
9
|
-
{ href: actions.editHref, label: 'edit' },
|
|
10
|
-
{ href: actions.restoreHref, label: 'restore' },
|
|
11
|
-
{ href: actions.rateHref, label: 'rate' },
|
|
12
|
-
{ href: actions.reportHref, label: 'report' },
|
|
13
|
-
{ href: actions.warnHref, label: 'warn' },
|
|
14
|
-
{ href: actions.moderateHref, label: 'moderate' },
|
|
15
|
+
{ href: actions.quoteHref, label: c('quote') },
|
|
16
|
+
{ href: actions.editHref, label: c('edit') },
|
|
17
|
+
{ href: actions.restoreHref, label: c('restore') },
|
|
18
|
+
{ href: actions.rateHref, label: c('rate') },
|
|
19
|
+
{ href: actions.reportHref, label: c('report') },
|
|
20
|
+
{ href: actions.warnHref, label: c('warn') },
|
|
21
|
+
{ href: actions.moderateHref, label: c('moderate') },
|
|
15
22
|
].filter((item): item is { href: string; label: string } => item.href !== null)
|
|
16
23
|
|
|
17
24
|
if (items.length === 0 && children === undefined) return null
|
|
18
25
|
|
|
19
26
|
return (
|
|
20
|
-
<nav aria-label=
|
|
27
|
+
<nav aria-label={c('ariaLabel')} className="flex flex-wrap items-center gap-x-4 gap-y-1">
|
|
21
28
|
{items.map((item) => (
|
|
22
29
|
<a key={item.label} href={item.href} className={ACTION}>
|
|
23
30
|
{item.label}
|
package/src/slots/post-bit.tsx
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { ReactNode } from 'react'
|
|
2
2
|
|
|
3
|
-
import type { PostBitSlotModel } from '@meith/theme-kit'
|
|
3
|
+
import type { PostBitSlotModel, SlotCopy } from '@meith/theme-kit'
|
|
4
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
4
5
|
|
|
5
6
|
import { HEADING, MICRO, NUMERIC, Stamp, UserRef } from '../shared'
|
|
6
7
|
|
|
7
|
-
function GroupBadge({
|
|
8
|
+
function GroupBadge({
|
|
9
|
+
badge,
|
|
10
|
+
}: {
|
|
11
|
+
badge: NonNullable<PostBitSlotModel['post']['author']['badge']>
|
|
12
|
+
}) {
|
|
8
13
|
const image = (
|
|
9
14
|
<img
|
|
10
15
|
src={badge.src}
|
|
@@ -26,18 +31,24 @@ function GroupBadge({ badge }: { badge: NonNullable<PostBitSlotModel['post']['au
|
|
|
26
31
|
)
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
function StatusBanner({
|
|
34
|
+
function StatusBanner({
|
|
35
|
+
visibility,
|
|
36
|
+
copy,
|
|
37
|
+
}: {
|
|
38
|
+
visibility: PostBitSlotModel['post']['visibility']
|
|
39
|
+
copy: SlotCopy
|
|
40
|
+
}) {
|
|
30
41
|
if (visibility === 'visible') return null
|
|
31
42
|
|
|
43
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.postBit.${key}`)
|
|
44
|
+
|
|
32
45
|
return (
|
|
33
46
|
<p
|
|
34
47
|
className={`${MICRO} border-b border-border px-3 py-1.5 ${
|
|
35
48
|
visibility === 'deleted' ? 'bg-thread-deleted/10' : 'bg-post-unapproved'
|
|
36
49
|
}`}
|
|
37
50
|
>
|
|
38
|
-
{visibility === 'deleted'
|
|
39
|
-
? 'deleted — visible to moderators only'
|
|
40
|
-
: 'awaiting approval — visible to moderators only'}
|
|
51
|
+
{visibility === 'deleted' ? c('deletedNotice') : c('awaitingApprovalNotice')}
|
|
41
52
|
</p>
|
|
42
53
|
)
|
|
43
54
|
}
|
|
@@ -51,7 +62,9 @@ function StatLine({ label, children }: { label: string; children: ReactNode }) {
|
|
|
51
62
|
)
|
|
52
63
|
}
|
|
53
64
|
|
|
54
|
-
export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
65
|
+
export function PostBit({ post, select, regions, copy }: PostBitSlotModel & { copy: SlotCopy }) {
|
|
66
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.postBit.${key}`)
|
|
67
|
+
|
|
55
68
|
return (
|
|
56
69
|
<article
|
|
57
70
|
id={`post-${post.number}`}
|
|
@@ -67,7 +80,7 @@ export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
|
67
80
|
className="pointer-events-none absolute -right-px -bottom-px size-2 border-r-2 border-b-2 border-primary/70"
|
|
68
81
|
/>
|
|
69
82
|
|
|
70
|
-
<StatusBanner visibility={post.visibility} />
|
|
83
|
+
<StatusBanner visibility={post.visibility} copy={copy} />
|
|
71
84
|
|
|
72
85
|
<div className="grid grid-cols-1 sm:grid-cols-[12rem_1fr]">
|
|
73
86
|
<div className="border-b border-border bg-surface px-3 py-3 sm:border-r sm:border-b-0">
|
|
@@ -115,11 +128,7 @@ export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
|
115
128
|
aria-hidden="true"
|
|
116
129
|
className="size-1.5 bg-moderation-approved shadow-[0_0_6px_var(--color-moderation-approved)]"
|
|
117
130
|
/>
|
|
118
|
-
<span
|
|
119
|
-
className={`${MICRO} text-moderation-approved`}
|
|
120
|
-
>
|
|
121
|
-
online
|
|
122
|
-
</span>
|
|
131
|
+
<span className={`${MICRO} text-moderation-approved`}>{c('online')}</span>
|
|
123
132
|
</p>
|
|
124
133
|
)}
|
|
125
134
|
</div>
|
|
@@ -134,12 +143,12 @@ export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
|
134
143
|
)}
|
|
135
144
|
|
|
136
145
|
<dl className="mt-2.5">
|
|
137
|
-
<StatLine label=
|
|
146
|
+
<StatLine label={c('posts')}>{post.author.postCount.label}</StatLine>
|
|
138
147
|
{post.author.reputation != null && (
|
|
139
|
-
<StatLine label=
|
|
148
|
+
<StatLine label={c('rep')}>{post.author.reputation.label}</StatLine>
|
|
140
149
|
)}
|
|
141
150
|
{post.author.joinedAt !== null && (
|
|
142
|
-
<StatLine label=
|
|
151
|
+
<StatLine label={c('joined')}>
|
|
143
152
|
<Stamp at={post.author.joinedAt} />
|
|
144
153
|
</StatLine>
|
|
145
154
|
)}
|
|
@@ -164,9 +173,11 @@ export function PostBit({ post, select, regions }: PostBitSlotModel) {
|
|
|
164
173
|
|
|
165
174
|
{post.ignored !== null ? (
|
|
166
175
|
<p className="px-3 py-3 text-sm text-muted-foreground">
|
|
167
|
-
|
|
176
|
+
{c('ignoredPrefix')}
|
|
177
|
+
{post.ignored.authorUsername}
|
|
178
|
+
{c('ignoredSuffix')}{' '}
|
|
168
179
|
<a href={post.ignored.revealHref} className="text-primary hover:underline">
|
|
169
|
-
|
|
180
|
+
{c('showIt')}
|
|
170
181
|
</a>
|
|
171
182
|
</p>
|
|
172
183
|
) : (
|
package/src/slots/post-form.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { PostFormModel } from '@meith/theme-kit'
|
|
1
|
+
import type { PostFormModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { BUTTON, HEADING, MICRO, RULE } from '../shared'
|
|
4
5
|
|
|
@@ -9,7 +10,10 @@ export function PostForm({
|
|
|
9
10
|
cancelLabel,
|
|
10
11
|
errorMessage,
|
|
11
12
|
regions,
|
|
12
|
-
|
|
13
|
+
copy,
|
|
14
|
+
}: PostFormModel & { copy: SlotCopy }) {
|
|
15
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.postForm.${key}`)
|
|
16
|
+
|
|
13
17
|
return (
|
|
14
18
|
<div className="mx-auto flex w-full max-w-4xl flex-col gap-4 p-4">
|
|
15
19
|
{errorMessage !== null && (
|
|
@@ -17,7 +21,7 @@ export function PostForm({
|
|
|
17
21
|
role="alert"
|
|
18
22
|
className="border border-l-[3px] border-border border-l-destructive bg-card px-3 py-2 text-sm"
|
|
19
23
|
>
|
|
20
|
-
<span className={`${MICRO} mr-2 text-destructive`}>
|
|
24
|
+
<span className={`${MICRO} mr-2 text-destructive`}>{c('cannotPost')}</span>
|
|
21
25
|
{errorMessage}
|
|
22
26
|
</p>
|
|
23
27
|
)}
|
|
@@ -25,7 +29,13 @@ export function PostForm({
|
|
|
25
29
|
<section className="border border-border bg-card shadow-elevation">
|
|
26
30
|
<div className="flex flex-wrap items-center justify-between gap-3 px-4 py-3">
|
|
27
31
|
<div>
|
|
28
|
-
<p className={MICRO}>
|
|
32
|
+
<p className={MICRO}>
|
|
33
|
+
{mode === 'edit'
|
|
34
|
+
? c('mode.edit')
|
|
35
|
+
: mode === 'reply'
|
|
36
|
+
? c('mode.reply')
|
|
37
|
+
: c('mode.newThread')}
|
|
38
|
+
</p>
|
|
29
39
|
<h1 className={`${HEADING} text-lg text-foreground`}>{heading}</h1>
|
|
30
40
|
</div>
|
|
31
41
|
<a href={cancelHref} className={BUTTON}>
|
|
@@ -1,23 +1,31 @@
|
|
|
1
|
-
import type { RedirectNoticeModel } from '@meith/theme-kit'
|
|
1
|
+
import type { RedirectNoticeModel, 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 RedirectNotice({
|
|
6
|
+
export function RedirectNotice({
|
|
7
|
+
message,
|
|
8
|
+
targetHref,
|
|
9
|
+
delaySeconds,
|
|
10
|
+
copy,
|
|
11
|
+
}: RedirectNoticeModel & { copy: SlotCopy }) {
|
|
12
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.redirectNotice.${key}`)
|
|
13
|
+
|
|
6
14
|
return (
|
|
7
15
|
<Frame className="w-full max-w-lg">
|
|
8
16
|
<div className="px-5 py-5">
|
|
9
|
-
<p className={`${MICRO} text-primary`}>
|
|
10
|
-
<h1 className={`${HEADING} mt-1 text-2xl text-foreground`}>
|
|
17
|
+
<p className={`${MICRO} text-primary`}>{c('kicker')}</p>
|
|
18
|
+
<h1 className={`${HEADING} mt-1 text-2xl text-foreground`}>{c('title')}</h1>
|
|
11
19
|
<p className="mt-2 text-sm text-muted-foreground">{message}</p>
|
|
12
20
|
|
|
13
21
|
<div className="mt-5 flex flex-wrap items-center gap-3">
|
|
14
22
|
<a href={targetHref} className={BUTTON_PRIMARY}>
|
|
15
|
-
|
|
23
|
+
{c('continueNow')}
|
|
16
24
|
</a>
|
|
17
25
|
<span className={`${MICRO} normal-case`}>
|
|
18
|
-
<span className="uppercase">
|
|
19
|
-
<span className={NUMERIC}>{delaySeconds}</span>
|
|
20
|
-
{delaySeconds === 1 ? '
|
|
26
|
+
<span className="uppercase">{c('continuingIn')}</span>{' '}
|
|
27
|
+
<span className={NUMERIC}>{delaySeconds}</span>{' '}
|
|
28
|
+
{delaySeconds === 1 ? c('second.one') : c('second.other')}
|
|
21
29
|
</span>
|
|
22
30
|
</div>
|
|
23
31
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { OptionModel, SearchAdvancedModel, SearchFormModel } from '@meith/theme-kit'
|
|
1
|
+
import type { OptionModel, SearchAdvancedModel, SearchFormModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { BUTTON_PRIMARY, Frame, MICRO, PanelHead } from '../shared'
|
|
4
5
|
|
|
@@ -98,7 +99,10 @@ export function SearchForm({
|
|
|
98
99
|
hint,
|
|
99
100
|
errorMessage,
|
|
100
101
|
advanced,
|
|
101
|
-
|
|
102
|
+
copy,
|
|
103
|
+
}: SearchFormModel & { copy: SlotCopy }) {
|
|
104
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.searchForm.${key}`)
|
|
105
|
+
|
|
102
106
|
const described = [
|
|
103
107
|
hint === null ? null : 'search-hint',
|
|
104
108
|
errorMessage === null ? null : 'search-error',
|
|
@@ -108,12 +112,12 @@ export function SearchForm({
|
|
|
108
112
|
|
|
109
113
|
return (
|
|
110
114
|
<Frame aria-labelledby="search-heading">
|
|
111
|
-
<PanelHead id="search-heading" title=
|
|
115
|
+
<PanelHead id="search-heading" title={c('heading')} />
|
|
112
116
|
|
|
113
117
|
<form method="get" action={action} className="flex flex-col gap-4 px-4 py-4">
|
|
114
118
|
<div>
|
|
115
119
|
<label htmlFor="search-query" className={`${MICRO} mb-1 block`}>
|
|
116
|
-
|
|
120
|
+
{c('searchForLabel')}
|
|
117
121
|
</label>
|
|
118
122
|
<input
|
|
119
123
|
id="search-query"
|
|
@@ -122,7 +126,7 @@ export function SearchForm({
|
|
|
122
126
|
defaultValue={query}
|
|
123
127
|
maxLength={maxQueryLength}
|
|
124
128
|
autoComplete="off"
|
|
125
|
-
placeholder=
|
|
129
|
+
placeholder={c('placeholder')}
|
|
126
130
|
aria-invalid={errorMessage === null ? undefined : true}
|
|
127
131
|
{...(described === '' ? {} : { 'aria-describedby': described })}
|
|
128
132
|
className={CONTROL}
|
|
@@ -142,15 +146,15 @@ export function SearchForm({
|
|
|
142
146
|
</div>
|
|
143
147
|
|
|
144
148
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
145
|
-
<Choice id="search-forum" label=
|
|
146
|
-
<Choice id="search-sort" label=
|
|
149
|
+
<Choice id="search-forum" label={c('inLabel')} name={fields.forum} options={forums} />
|
|
150
|
+
<Choice id="search-sort" label={c('sortByLabel')} name={fields.sort} options={sorts} />
|
|
147
151
|
</div>
|
|
148
152
|
|
|
149
153
|
{advanced !== undefined && <Advanced {...advanced} />}
|
|
150
154
|
|
|
151
155
|
<div>
|
|
152
156
|
<button type="submit" className={BUTTON_PRIMARY}>
|
|
153
|
-
|
|
157
|
+
{c('submit')}
|
|
154
158
|
</button>
|
|
155
159
|
</div>
|
|
156
160
|
</form>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { OptionModel, SearchRefineModel, SearchResultsModel } from '@meith/theme-kit'
|
|
1
|
+
import type { OptionModel, SearchRefineModel, SearchResultsModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { BUTTON_PRIMARY, Frame, HEADING, MICRO, RULE, Stamp } from '../shared'
|
|
4
5
|
|
|
@@ -15,7 +16,10 @@ export function SearchResults({
|
|
|
15
16
|
within,
|
|
16
17
|
refine,
|
|
17
18
|
regions,
|
|
18
|
-
|
|
19
|
+
copy,
|
|
20
|
+
}: SearchResultsModel & { copy: SlotCopy }) {
|
|
21
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.searchResults.${key}`)
|
|
22
|
+
|
|
19
23
|
return (
|
|
20
24
|
<main
|
|
21
25
|
id="board-content"
|
|
@@ -24,34 +28,37 @@ export function SearchResults({
|
|
|
24
28
|
>
|
|
25
29
|
<div className="border border-border bg-card shadow-elevation">
|
|
26
30
|
<div className="px-4 py-3">
|
|
27
|
-
<p className={MICRO}>
|
|
31
|
+
<p className={MICRO}>{c('kicker')}</p>
|
|
28
32
|
<h1 className={`${HEADING} text-xl text-foreground`}>
|
|
29
|
-
|
|
33
|
+
{c('resultsForPrefix')}
|
|
34
|
+
{terms}
|
|
35
|
+
{c('resultsForSuffix')}
|
|
30
36
|
</h1>
|
|
31
37
|
<p className={`${MICRO} mt-1 normal-case`}>
|
|
32
|
-
<span className="uppercase">run</span> <Stamp at={searchedAt} />
|
|
38
|
+
<span className="uppercase">{c('run')}</span> <Stamp at={searchedAt} />
|
|
33
39
|
<span className="text-border">{' | '}</span>
|
|
34
|
-
<span>
|
|
40
|
+
<span>{c('accessNotice')}</span>
|
|
35
41
|
</p>
|
|
36
42
|
</div>
|
|
37
43
|
<div className={RULE} aria-hidden="true" />
|
|
38
44
|
</div>
|
|
39
45
|
|
|
40
|
-
{refine !== undefined && <Refine {...refine} />}
|
|
46
|
+
{refine !== undefined && <Refine {...refine} copy={copy} />}
|
|
41
47
|
|
|
42
48
|
<Frame>
|
|
43
49
|
{hits.length === 0 ? (
|
|
44
50
|
<div className="px-4 py-6 text-center">
|
|
45
|
-
<p className={`${HEADING} text-sm text-foreground`}>
|
|
46
|
-
<p className="mt-1 text-sm text-muted-foreground">
|
|
47
|
-
Or nothing you can see does. Try fewer words, or a different spelling.
|
|
48
|
-
</p>
|
|
51
|
+
<p className={`${HEADING} text-sm text-foreground`}>{c('nothingMatched')}</p>
|
|
52
|
+
<p className="mt-1 text-sm text-muted-foreground">{c('nothingMatchedHint')}</p>
|
|
49
53
|
</div>
|
|
50
54
|
) : (
|
|
51
55
|
<ul className="divide-y divide-border">
|
|
52
56
|
{hits.map((hit) => (
|
|
53
57
|
<li key={hit.postId} className="flex flex-col gap-1 px-4 py-3 hover:bg-secondary/50">
|
|
54
|
-
<a
|
|
58
|
+
<a
|
|
59
|
+
href={hit.href}
|
|
60
|
+
className="text-sm font-medium text-foreground hover:text-primary"
|
|
61
|
+
>
|
|
55
62
|
{hit.threadTitle}
|
|
56
63
|
</a>
|
|
57
64
|
<p
|
|
@@ -120,7 +127,7 @@ export function SearchResults({
|
|
|
120
127
|
|
|
121
128
|
<p>
|
|
122
129
|
<a href={newSearchHref} className={`${MICRO} hover:text-primary`}>
|
|
123
|
-
|
|
130
|
+
{c('startNewSearch')}
|
|
124
131
|
</a>
|
|
125
132
|
</p>
|
|
126
133
|
</main>
|
|
@@ -138,7 +145,10 @@ function Refine({
|
|
|
138
145
|
submitLabel,
|
|
139
146
|
applied,
|
|
140
147
|
clearHref,
|
|
141
|
-
|
|
148
|
+
copy,
|
|
149
|
+
}: SearchRefineModel & { copy: SlotCopy }) {
|
|
150
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.searchResults.${key}`)
|
|
151
|
+
|
|
142
152
|
return (
|
|
143
153
|
<section
|
|
144
154
|
aria-label={label}
|
|
@@ -172,7 +182,7 @@ function Refine({
|
|
|
172
182
|
>
|
|
173
183
|
{chip.label}
|
|
174
184
|
<span aria-hidden="true">×</span>
|
|
175
|
-
<span className="sr-only"
|
|
185
|
+
<span className="sr-only">{c('removeFilter')}</span>
|
|
176
186
|
</a>
|
|
177
187
|
))}
|
|
178
188
|
</div>
|
|
@@ -192,7 +202,7 @@ function Refine({
|
|
|
192
202
|
|
|
193
203
|
{clearHref !== null && (
|
|
194
204
|
<a href={clearHref} className={`${MICRO} hover:text-primary`}>
|
|
195
|
-
|
|
205
|
+
{c('clearFilters')}
|
|
196
206
|
</a>
|
|
197
207
|
)}
|
|
198
208
|
</form>
|
package/src/slots/shell.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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
4
|
const APP_REGIONS = `
|
|
4
5
|
[data-theme-raidframe] .font-heading:not([class*="tracking-"]) {
|
|
@@ -7,7 +8,9 @@ const APP_REGIONS = `
|
|
|
7
8
|
}
|
|
8
9
|
`
|
|
9
10
|
|
|
10
|
-
export function Shell({ viewer, children }: ShellModel) {
|
|
11
|
+
export function Shell({ viewer, children, copy }: ShellModel & { copy: SlotCopy }) {
|
|
12
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.shell.${key}`)
|
|
13
|
+
|
|
11
14
|
return (
|
|
12
15
|
<div
|
|
13
16
|
className="relative min-h-dvh bg-background text-foreground"
|
|
@@ -29,7 +32,7 @@ export function Shell({ viewer, children }: ShellModel) {
|
|
|
29
32
|
href="#board-content"
|
|
30
33
|
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:border focus:border-primary focus:bg-card focus:px-3 focus:py-2 focus:font-mono focus:text-xs focus:uppercase"
|
|
31
34
|
>
|
|
32
|
-
|
|
35
|
+
{c('skipToContent')}
|
|
33
36
|
</a>
|
|
34
37
|
|
|
35
38
|
<div className="relative mx-auto flex min-h-dvh w-full max-w-6xl flex-col border-x border-border bg-background/80">
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import type { SubforumListModel } from '@meith/theme-kit'
|
|
1
|
+
import type { SlotCopy, SubforumListModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
4
|
import { MICRO, NUMERIC } from '../shared'
|
|
4
5
|
|
|
5
|
-
export function SubforumList({ forums }: SubforumListModel) {
|
|
6
|
+
export function SubforumList({ forums, copy }: SubforumListModel & { copy: SlotCopy }) {
|
|
6
7
|
if (forums.length === 0) return null
|
|
7
8
|
|
|
9
|
+
const c = (key: string) => fromSlotCopy(copy, `raidframe.subforumList.${key}`)
|
|
10
|
+
|
|
8
11
|
return (
|
|
9
12
|
<nav
|
|
10
|
-
aria-label=
|
|
13
|
+
aria-label={c('ariaLabel')}
|
|
11
14
|
className="flex flex-wrap items-center gap-2 border border-border bg-card px-3 py-2"
|
|
12
15
|
>
|
|
13
|
-
<span className={MICRO}>
|
|
16
|
+
<span className={MICRO}>{c('label')}</span>
|
|
14
17
|
{forums.map((forum) => (
|
|
15
18
|
<a
|
|
16
19
|
key={forum.href}
|
|
@@ -18,7 +21,7 @@ export function SubforumList({ forums }: SubforumListModel) {
|
|
|
18
21
|
className="inline-flex items-center gap-1.5 border border-border bg-surface px-2 py-0.5 font-mono text-[0.625rem] tracking-[0.1em] text-foreground uppercase hover:border-primary/60 hover:text-primary"
|
|
19
22
|
>
|
|
20
23
|
{forum.title}
|
|
21
|
-
<span className={`${NUMERIC} text-muted-foreground`}>{forum.threadCount}</span>
|
|
24
|
+
<span className={`${NUMERIC} text-muted-foreground`}>{forum.threadCount.label}</span>
|
|
22
25
|
</a>
|
|
23
26
|
))}
|
|
24
27
|
</nav>
|