@meith/theme-default 0.6.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 +3 -3
- package/src/copy.ts +316 -0
- package/src/index.ts +6 -6
- package/src/messages/en.json +189 -0
- package/src/messages/index.ts +5 -0
- package/src/panel-icons.tsx +1 -2
- package/src/shared.tsx +4 -6
- package/src/slots/announcement.tsx +15 -9
- package/src/slots/auth-page.tsx +3 -1
- package/src/slots/board-index.tsx +11 -4
- package/src/slots/board-stats.tsx +14 -12
- package/src/slots/category-block.tsx +1 -1
- package/src/slots/discovery-view.tsx +16 -15
- package/src/slots/error-notice.tsx +17 -9
- package/src/slots/footer.tsx +15 -4
- package/src/slots/forum-display.tsx +25 -20
- package/src/slots/forum-jump.tsx +1 -1
- package/src/slots/forum-row.tsx +16 -15
- package/src/slots/header.tsx +13 -3
- package/src/slots/latest-posts.tsx +21 -12
- package/src/slots/latest-threads.tsx +27 -16
- package/src/slots/member-profile.tsx +19 -15
- package/src/slots/navigation.tsx +7 -4
- package/src/slots/notice.tsx +12 -9
- package/src/slots/pagination.tsx +16 -10
- package/src/slots/panel-nav.tsx +37 -16
- package/src/slots/panel-page.tsx +15 -3
- package/src/slots/panel-shell.tsx +1 -7
- package/src/slots/post-actions.tsx +18 -11
- package/src/slots/post-bit.tsx +42 -20
- package/src/slots/post-form.tsx +7 -3
- package/src/slots/redirect-notice.tsx +16 -7
- package/src/slots/search-form.tsx +13 -9
- package/src/slots/search-results.tsx +27 -26
- package/src/slots/shell.tsx +4 -3
- package/src/slots/subforum-list.tsx +12 -9
- package/src/slots/thread-row.tsx +19 -18
- package/src/slots/thread-view.tsx +23 -13
- package/src/slots/user-panel.tsx +19 -14
- package/src/slots/who-is-online.tsx +35 -19
- package/src/theme.ts +90 -19
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import type { RedirectNoticeModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
+
import { buttonVariants, Card, CardContent } from '@meith/ui'
|
|
4
|
+
|
|
5
|
+
export function RedirectNotice({
|
|
6
|
+
message,
|
|
7
|
+
targetHref,
|
|
8
|
+
delaySeconds,
|
|
9
|
+
copy,
|
|
10
|
+
}: RedirectNoticeModel & { copy: SlotCopy }) {
|
|
11
|
+
const c = (key: string) => fromSlotCopy(copy, `default.redirectNotice.${key}`)
|
|
3
12
|
|
|
4
|
-
export function RedirectNotice({ message, targetHref, delaySeconds }: RedirectNoticeModel) {
|
|
5
13
|
return (
|
|
6
14
|
<Card className="w-full max-w-lg">
|
|
7
15
|
<CardContent className="p-6">
|
|
8
16
|
<p className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
9
|
-
|
|
17
|
+
{c('redirecting')}
|
|
10
18
|
</p>
|
|
11
|
-
<h1 className="mt-1 text-2xl font-semibold tracking-tight">
|
|
19
|
+
<h1 className="mt-1 text-2xl font-semibold tracking-tight">{c('pleaseWait')}</h1>
|
|
12
20
|
<p className="mt-2 text-sm text-muted-foreground">{message}</p>
|
|
13
21
|
|
|
14
22
|
<div className="mt-5 flex flex-wrap items-center gap-3">
|
|
15
23
|
<a href={targetHref} className={buttonVariants({ variant: 'primary' })}>
|
|
16
|
-
|
|
24
|
+
{c('continueNow')}
|
|
17
25
|
</a>
|
|
18
26
|
<span className="text-xs text-muted-foreground">
|
|
19
|
-
|
|
27
|
+
{c('continuingOnItsOwnIn')} {delaySeconds}{' '}
|
|
28
|
+
{delaySeconds === 1 ? c('second.one') : c('second.other')}.
|
|
20
29
|
</span>
|
|
21
30
|
</div>
|
|
22
31
|
</CardContent>
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
import type { OptionModel, SearchAdvancedModel, SearchFormModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import {
|
|
2
4
|
Alert,
|
|
3
5
|
AlertDescription,
|
|
4
6
|
AlertTitle,
|
|
7
|
+
buttonVariants,
|
|
5
8
|
Card,
|
|
6
9
|
CardContent,
|
|
7
10
|
Disclosure,
|
|
8
11
|
Field,
|
|
9
12
|
Input,
|
|
10
13
|
NativeSelect,
|
|
11
|
-
buttonVariants,
|
|
12
14
|
} from '@meith/ui'
|
|
13
|
-
import type { OptionModel, SearchAdvancedModel, SearchFormModel } from '@meith/theme-kit'
|
|
14
15
|
|
|
15
16
|
export function SearchForm({
|
|
16
17
|
action,
|
|
@@ -22,14 +23,17 @@ export function SearchForm({
|
|
|
22
23
|
hint,
|
|
23
24
|
errorMessage,
|
|
24
25
|
advanced,
|
|
25
|
-
|
|
26
|
+
copy,
|
|
27
|
+
}: SearchFormModel & { copy: SlotCopy }) {
|
|
28
|
+
const c = (key: string) => fromSlotCopy(copy, `default.searchForm.${key}`)
|
|
29
|
+
|
|
26
30
|
return (
|
|
27
31
|
<Card>
|
|
28
32
|
<CardContent className="p-4 sm:p-5">
|
|
29
33
|
<form method="get" action={action} className="flex flex-col gap-4">
|
|
30
34
|
<Field
|
|
31
35
|
name={fields.query}
|
|
32
|
-
label=
|
|
36
|
+
label={c('searchFor')}
|
|
33
37
|
error={errorMessage}
|
|
34
38
|
{...(hint === null ? {} : { description: hint })}
|
|
35
39
|
>
|
|
@@ -40,21 +44,21 @@ export function SearchForm({
|
|
|
40
44
|
maxLength={maxQueryLength}
|
|
41
45
|
type="search"
|
|
42
46
|
autoComplete="off"
|
|
43
|
-
placeholder=
|
|
47
|
+
placeholder={c('placeholder')}
|
|
44
48
|
/>
|
|
45
49
|
)}
|
|
46
50
|
</Field>
|
|
47
51
|
|
|
48
52
|
<div className="grid gap-4 sm:grid-cols-2">
|
|
49
|
-
<Choice label=
|
|
50
|
-
<Choice label=
|
|
53
|
+
<Choice label={c('in')} name={fields.forum} options={forums} />
|
|
54
|
+
<Choice label={c('sortBy')} name={fields.sort} options={sorts} />
|
|
51
55
|
</div>
|
|
52
56
|
|
|
53
57
|
{advanced !== undefined && <Advanced {...advanced} />}
|
|
54
58
|
|
|
55
59
|
<div>
|
|
56
60
|
<button type="submit" className={buttonVariants({ variant: 'primary' })}>
|
|
57
|
-
|
|
61
|
+
{c('search')}
|
|
58
62
|
</button>
|
|
59
63
|
</div>
|
|
60
64
|
</form>
|
|
@@ -62,7 +66,7 @@ export function SearchForm({
|
|
|
62
66
|
{errorMessage !== null && (
|
|
63
67
|
<Alert tone="error" className="mt-4">
|
|
64
68
|
<AlertDescription>
|
|
65
|
-
<AlertTitle>
|
|
69
|
+
<AlertTitle>{c('noResults')}</AlertTitle> {errorMessage}
|
|
66
70
|
</AlertDescription>
|
|
67
71
|
</Alert>
|
|
68
72
|
)}
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
+
import type { OptionModel, SearchRefineModel, SearchResultsModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import {
|
|
4
|
+
buttonVariants,
|
|
2
5
|
Card,
|
|
3
6
|
CardContent,
|
|
4
7
|
CardFooter,
|
|
5
8
|
CardRows,
|
|
9
|
+
cn,
|
|
6
10
|
Empty,
|
|
7
11
|
EmptyDescription,
|
|
8
12
|
EmptyTitle,
|
|
9
13
|
Field,
|
|
10
14
|
Input,
|
|
11
15
|
NativeSelect,
|
|
12
|
-
buttonVariants,
|
|
13
|
-
cn,
|
|
14
16
|
} from '@meith/ui'
|
|
15
|
-
import type { OptionModel, SearchRefineModel, SearchResultsModel } from '@meith/theme-kit'
|
|
16
17
|
|
|
17
|
-
import { LINK,
|
|
18
|
+
import { LINK, pageAt, Stamp } from '../shared'
|
|
18
19
|
|
|
19
20
|
export function SearchResults({
|
|
20
21
|
terms,
|
|
@@ -26,7 +27,10 @@ export function SearchResults({
|
|
|
26
27
|
within,
|
|
27
28
|
refine,
|
|
28
29
|
regions,
|
|
29
|
-
|
|
30
|
+
copy,
|
|
31
|
+
}: SearchResultsModel & { copy: SlotCopy }) {
|
|
32
|
+
const c = (key: string) => fromSlotCopy(copy, `default.searchResults.${key}`)
|
|
33
|
+
|
|
30
34
|
return (
|
|
31
35
|
<main
|
|
32
36
|
id="board-content"
|
|
@@ -35,23 +39,22 @@ export function SearchResults({
|
|
|
35
39
|
>
|
|
36
40
|
<div className="flex flex-col gap-1">
|
|
37
41
|
<h1 className="font-heading text-2xl font-semibold">
|
|
38
|
-
|
|
42
|
+
{c('resultsFor')} {c('openQuote')}
|
|
43
|
+
{terms}
|
|
44
|
+
{c('closeQuote')}
|
|
39
45
|
</h1>
|
|
40
46
|
<p className="text-sm text-muted-foreground">
|
|
41
|
-
|
|
42
|
-
every time this page is opened, so they can change.
|
|
47
|
+
{c('searched')} <Stamp at={searchedAt} />. {c('searchedNote')}
|
|
43
48
|
</p>
|
|
44
49
|
</div>
|
|
45
50
|
|
|
46
|
-
{refine !== undefined && <Refine {...refine} />}
|
|
51
|
+
{refine !== undefined && <Refine {...refine} copy={copy} />}
|
|
47
52
|
|
|
48
53
|
<Card>
|
|
49
54
|
{hits.length === 0 ? (
|
|
50
55
|
<Empty>
|
|
51
|
-
<EmptyTitle>
|
|
52
|
-
<EmptyDescription>
|
|
53
|
-
Or nothing you can see does. Try fewer words, or a different spelling.
|
|
54
|
-
</EmptyDescription>
|
|
56
|
+
<EmptyTitle>{c('nothingMatched')}</EmptyTitle>
|
|
57
|
+
<EmptyDescription>{c('tryFewerWords')}</EmptyDescription>
|
|
55
58
|
</Empty>
|
|
56
59
|
) : (
|
|
57
60
|
<CardRows>
|
|
@@ -65,7 +68,7 @@ export function SearchResults({
|
|
|
65
68
|
dangerouslySetInnerHTML={{ __html: hit.excerptHtml }}
|
|
66
69
|
/>
|
|
67
70
|
<p className="text-xs text-muted-foreground">
|
|
68
|
-
{hit.authorUsername}
|
|
71
|
+
{hit.authorUsername} {c('dot')} <Stamp at={hit.postedAt} />
|
|
69
72
|
</p>
|
|
70
73
|
</li>
|
|
71
74
|
))}
|
|
@@ -78,7 +81,7 @@ export function SearchResults({
|
|
|
78
81
|
nextHref !== null && (
|
|
79
82
|
<CardFooter>
|
|
80
83
|
<a href={nextHref} className={`font-medium text-foreground ${LINK}`}>
|
|
81
|
-
{nextLabel}
|
|
84
|
+
{nextLabel} {c('nextArrow')}
|
|
82
85
|
</a>
|
|
83
86
|
</CardFooter>
|
|
84
87
|
)
|
|
@@ -99,12 +102,7 @@ export function SearchResults({
|
|
|
99
102
|
|
|
100
103
|
<Field name={within.field} label={within.label} description={within.hint}>
|
|
101
104
|
{(control) => (
|
|
102
|
-
<Input
|
|
103
|
-
{...control}
|
|
104
|
-
defaultValue={within.value}
|
|
105
|
-
type="search"
|
|
106
|
-
autoComplete="off"
|
|
107
|
-
/>
|
|
105
|
+
<Input {...control} defaultValue={within.value} type="search" autoComplete="off" />
|
|
108
106
|
)}
|
|
109
107
|
</Field>
|
|
110
108
|
|
|
@@ -118,7 +116,7 @@ export function SearchResults({
|
|
|
118
116
|
</Card>
|
|
119
117
|
|
|
120
118
|
<a href={newSearchHref} className={`text-sm font-medium text-foreground ${LINK}`}>
|
|
121
|
-
|
|
119
|
+
{c('startNewSearch')}
|
|
122
120
|
</a>
|
|
123
121
|
</main>
|
|
124
122
|
)
|
|
@@ -135,7 +133,10 @@ function Refine({
|
|
|
135
133
|
submitLabel,
|
|
136
134
|
applied,
|
|
137
135
|
clearHref,
|
|
138
|
-
|
|
136
|
+
copy,
|
|
137
|
+
}: SearchRefineModel & { copy: SlotCopy }) {
|
|
138
|
+
const c = (key: string) => fromSlotCopy(copy, `default.searchResults.${key}`)
|
|
139
|
+
|
|
139
140
|
return (
|
|
140
141
|
<section
|
|
141
142
|
aria-label={label}
|
|
@@ -171,8 +172,8 @@ function Refine({
|
|
|
171
172
|
className="inline-flex items-center gap-1.5 rounded-full border border-primary/40 bg-primary/10 px-2.5 py-1 text-xs font-medium text-foreground hover:bg-primary/20"
|
|
172
173
|
>
|
|
173
174
|
{chip.label}
|
|
174
|
-
<span aria-hidden="true"
|
|
175
|
-
<span className="sr-only"
|
|
175
|
+
<span aria-hidden="true">{c('removeFilterX')}</span>
|
|
176
|
+
<span className="sr-only">{c('removeFilter')}</span>
|
|
176
177
|
</a>
|
|
177
178
|
))}
|
|
178
179
|
</div>
|
|
@@ -200,7 +201,7 @@ function Refine({
|
|
|
200
201
|
|
|
201
202
|
{clearHref !== null && (
|
|
202
203
|
<a href={clearHref} className={`text-xs font-medium text-foreground ${LINK}`}>
|
|
203
|
-
|
|
204
|
+
{c('clearFilters')}
|
|
204
205
|
</a>
|
|
205
206
|
)}
|
|
206
207
|
</form>
|
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:left-4 focus-visible:top-4 focus-visible:z-50 focus-visible:inline-flex focus-visible:h-9 focus-visible:items-center focus-visible:rounded-md focus-visible:border focus-visible:border-border focus-visible:bg-card focus-visible:px-3 focus-visible:text-sm focus-visible:font-medium focus-visible:text-foreground focus-visible:shadow-lg"
|
|
12
13
|
>
|
|
13
|
-
|
|
14
|
+
{fromSlotCopy(copy, 'default.shell.skipToContent')}
|
|
14
15
|
</a>
|
|
15
16
|
{children}
|
|
16
17
|
</div>
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
+
import type { SlotCopy, SubforumListModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Card, CardContent, CardHeader, CardTitle } from '@meith/ui'
|
|
2
|
-
import type { SubforumListModel } from '@meith/theme-kit'
|
|
3
4
|
|
|
4
5
|
import { Counts, LINK } 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, `default.subforumList.${key}`)
|
|
11
|
+
|
|
9
12
|
return (
|
|
10
13
|
<Card aria-labelledby="subforums-heading">
|
|
11
14
|
<CardHeader>
|
|
12
15
|
<CardTitle id="subforums-heading" className="text-sm">
|
|
13
|
-
|
|
16
|
+
{c('heading')}
|
|
14
17
|
</CardTitle>
|
|
15
18
|
</CardHeader>
|
|
16
19
|
|
|
@@ -25,16 +28,16 @@ export function SubforumList({ forums }: SubforumListModel) {
|
|
|
25
28
|
<Counts
|
|
26
29
|
items={[
|
|
27
30
|
{
|
|
28
|
-
label: '
|
|
31
|
+
label: c('threadsLabel'),
|
|
29
32
|
value: forum.threadCount,
|
|
30
|
-
one: 'thread',
|
|
31
|
-
many: '
|
|
33
|
+
one: c('thread.one'),
|
|
34
|
+
many: c('thread.other'),
|
|
32
35
|
},
|
|
33
36
|
{
|
|
34
|
-
label: '
|
|
37
|
+
label: c('postsLabel'),
|
|
35
38
|
value: forum.postCount,
|
|
36
|
-
one: 'post',
|
|
37
|
-
many: '
|
|
39
|
+
one: c('post.one'),
|
|
40
|
+
many: c('post.other'),
|
|
38
41
|
},
|
|
39
42
|
]}
|
|
40
43
|
/>
|
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 { Counts, LINK, Prefix, ReadSpacer, Stamp, UnreadDot, 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, `default.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}
|
|
@@ -42,11 +45,11 @@ export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
|
|
|
42
45
|
>
|
|
43
46
|
{thread.title}
|
|
44
47
|
</a>
|
|
45
|
-
{thread.isUnread && <span className="sr-only"> (
|
|
48
|
+
{thread.isUnread && <span className="sr-only"> {c('newPosts')}</span>}
|
|
46
49
|
</div>
|
|
47
50
|
|
|
48
51
|
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
49
|
-
|
|
52
|
+
{c('startedBy')} <UserRef user={thread.author} className="font-normal" />
|
|
50
53
|
</p>
|
|
51
54
|
</div>
|
|
52
55
|
|
|
@@ -54,32 +57,30 @@ export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
|
|
|
54
57
|
className="col-start-2 md:col-start-3 md:justify-end"
|
|
55
58
|
items={[
|
|
56
59
|
{
|
|
57
|
-
label: '
|
|
60
|
+
label: c('repliesLabel'),
|
|
58
61
|
value: thread.replyCount,
|
|
59
|
-
one: 'reply',
|
|
60
|
-
many: '
|
|
62
|
+
one: c('reply.one'),
|
|
63
|
+
many: c('reply.other'),
|
|
61
64
|
},
|
|
62
65
|
{
|
|
63
|
-
label: '
|
|
66
|
+
label: c('viewsLabel'),
|
|
64
67
|
value: thread.viewCount,
|
|
65
|
-
one: 'view',
|
|
66
|
-
many: '
|
|
68
|
+
one: c('view.one'),
|
|
69
|
+
many: c('view.other'),
|
|
67
70
|
},
|
|
68
71
|
]}
|
|
69
72
|
/>
|
|
70
73
|
|
|
71
74
|
<div className="col-start-2 min-w-0 text-xs text-muted-foreground md:col-start-4">
|
|
72
75
|
{thread.lastPost === null ? (
|
|
73
|
-
<span className="text-thread-moved">
|
|
76
|
+
<span className="text-thread-moved">{c('noRepliesYet')}</span>
|
|
74
77
|
) : (
|
|
75
78
|
<>
|
|
76
79
|
<a href={thread.lastPost.href} className={`block font-medium text-foreground ${LINK}`}>
|
|
77
|
-
|
|
80
|
+
{c('latestReply')}
|
|
78
81
|
</a>
|
|
79
82
|
<span className="mt-0.5 block truncate">
|
|
80
|
-
{'by '}
|
|
81
|
-
<UserRef user={thread.lastPost.author} className="font-normal" />
|
|
82
|
-
{' · '}
|
|
83
|
+
{c('by')} <UserRef user={thread.lastPost.author} className="font-normal" /> {c('dot')}{' '}
|
|
83
84
|
<Stamp at={thread.lastPost.at} />
|
|
84
85
|
</span>
|
|
85
86
|
</>
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import type { SlotCopy, ThreadViewModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Badge, buttonVariants } from '@meith/ui'
|
|
2
|
-
import type { ThreadViewModel } from '@meith/theme-kit'
|
|
3
4
|
|
|
4
5
|
import { Counts, MUTED_LINK, PAGE_BODY, 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, `default.threadView.${key}`)
|
|
16
|
+
|
|
7
17
|
return (
|
|
8
18
|
<div className={PAGE_BODY}>
|
|
9
19
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
|
|
@@ -18,23 +28,23 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
|
|
|
18
28
|
|
|
19
29
|
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
20
30
|
{thread.prefix !== null && <Prefix prefix={thread.prefix} />}
|
|
21
|
-
{thread.isSticky && <Badge tone="pinned">
|
|
22
|
-
{thread.isLocked && <Badge tone="locked">
|
|
23
|
-
{thread.isMoved && <Badge tone="moved">
|
|
31
|
+
{thread.isSticky && <Badge tone="pinned">{c('pinned')}</Badge>}
|
|
32
|
+
{thread.isLocked && <Badge tone="locked">{c('locked')}</Badge>}
|
|
33
|
+
{thread.isMoved && <Badge tone="moved">{c('moved')}</Badge>}
|
|
24
34
|
|
|
25
35
|
<Counts
|
|
26
36
|
items={[
|
|
27
37
|
{
|
|
28
|
-
label: '
|
|
38
|
+
label: c('repliesLabel'),
|
|
29
39
|
value: thread.replyCount,
|
|
30
|
-
one: 'reply',
|
|
31
|
-
many: '
|
|
40
|
+
one: c('reply.one'),
|
|
41
|
+
many: c('reply.other'),
|
|
32
42
|
},
|
|
33
43
|
{
|
|
34
|
-
label: '
|
|
44
|
+
label: c('viewsLabel'),
|
|
35
45
|
value: thread.viewCount,
|
|
36
|
-
one: 'view',
|
|
37
|
-
many: '
|
|
46
|
+
one: c('view.one'),
|
|
47
|
+
many: c('view.other'),
|
|
38
48
|
},
|
|
39
49
|
]}
|
|
40
50
|
/>
|
|
@@ -45,13 +55,13 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
|
|
|
45
55
|
{markReadAction !== null && (
|
|
46
56
|
<form action={markReadAction} method="post">
|
|
47
57
|
<button type="submit" className={buttonVariants({ variant: 'ghost' })}>
|
|
48
|
-
|
|
58
|
+
{c('markRead')}
|
|
49
59
|
</button>
|
|
50
60
|
</form>
|
|
51
61
|
)}
|
|
52
62
|
{replyHref !== null && (
|
|
53
63
|
<a href={replyHref} className={buttonVariants({ variant: 'primary' })}>
|
|
54
|
-
|
|
64
|
+
{c('replyAction')}
|
|
55
65
|
</a>
|
|
56
66
|
)}
|
|
57
67
|
</div>
|
package/src/slots/user-panel.tsx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import type { SlotCopy, UserPanelModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Avatar, Badge, buttonVariants } from '@meith/ui'
|
|
2
4
|
import { Menu } from '@meith/ui/menu'
|
|
3
|
-
import type { UserPanelModel } from '@meith/theme-kit'
|
|
4
5
|
|
|
5
6
|
import { MUTED_LINK } from '../shared'
|
|
6
7
|
|
|
7
|
-
const COUNT_LINK =
|
|
8
|
+
const COUNT_LINK =
|
|
9
|
+
'rounded-full outline-offset-2 focus-visible:outline-2 focus-visible:outline-ring'
|
|
8
10
|
|
|
9
11
|
export function UserPanel({
|
|
10
12
|
viewer,
|
|
@@ -14,7 +16,10 @@ export function UserPanel({
|
|
|
14
16
|
notificationsHref,
|
|
15
17
|
messagesHref,
|
|
16
18
|
children,
|
|
17
|
-
|
|
19
|
+
copy,
|
|
20
|
+
}: UserPanelModel & { copy: SlotCopy }) {
|
|
21
|
+
const c = (key: string) => fromSlotCopy(copy, `default.userPanel.${key}`)
|
|
22
|
+
|
|
18
23
|
if (viewer.isGuest) {
|
|
19
24
|
return (
|
|
20
25
|
<div className="flex items-center gap-2">
|
|
@@ -34,7 +39,7 @@ export function UserPanel({
|
|
|
34
39
|
)
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
const name = viewer.username ?? '
|
|
42
|
+
const name = viewer.username ?? c('signedIn')
|
|
38
43
|
|
|
39
44
|
return (
|
|
40
45
|
<div className="flex min-w-0 flex-col items-start gap-1 sm:items-end">
|
|
@@ -46,28 +51,28 @@ export function UserPanel({
|
|
|
46
51
|
/>
|
|
47
52
|
|
|
48
53
|
<div className="flex min-w-0 items-center gap-2">
|
|
49
|
-
{unreadNotifications > 0 && (
|
|
54
|
+
{unreadNotifications.value > 0 && (
|
|
50
55
|
<a href={notificationsHref} className={COUNT_LINK}>
|
|
51
56
|
<Badge tone="solid">
|
|
52
|
-
{unreadNotifications}
|
|
53
|
-
<span className="sr-only">
|
|
54
|
-
<span aria-hidden="true"> new</span>
|
|
57
|
+
{unreadNotifications.label}
|
|
58
|
+
<span className="sr-only"> {c('unreadNotifications')}</span>
|
|
59
|
+
<span aria-hidden="true"> {c('new')}</span>
|
|
55
60
|
</Badge>
|
|
56
61
|
</a>
|
|
57
62
|
)}
|
|
58
|
-
{unreadMessages > 0 && (
|
|
63
|
+
{unreadMessages.value > 0 && (
|
|
59
64
|
<a href={messagesHref} className={COUNT_LINK}>
|
|
60
65
|
<Badge tone="outline">
|
|
61
|
-
{unreadMessages}
|
|
62
|
-
<span className="sr-only">
|
|
63
|
-
<span aria-hidden="true"> unread</span>
|
|
66
|
+
{unreadMessages.label}
|
|
67
|
+
<span className="sr-only"> {c('unreadMessages')}</span>
|
|
68
|
+
<span aria-hidden="true"> {c('unread')}</span>
|
|
64
69
|
</Badge>
|
|
65
70
|
</a>
|
|
66
71
|
)}
|
|
67
72
|
|
|
68
73
|
<span data-account="menu" className="flex min-w-0 items-center">
|
|
69
74
|
<Menu
|
|
70
|
-
label=
|
|
75
|
+
label={c('yourAccount')}
|
|
71
76
|
items={links}
|
|
72
77
|
trigger={
|
|
73
78
|
<>
|
|
@@ -96,7 +101,7 @@ export function UserPanel({
|
|
|
96
101
|
<nav
|
|
97
102
|
data-account="plain"
|
|
98
103
|
style={{ display: 'none' }}
|
|
99
|
-
aria-label=
|
|
104
|
+
aria-label={c('yourAccount')}
|
|
100
105
|
className="flex-wrap items-center gap-x-3 gap-y-1 text-xs sm:justify-end"
|
|
101
106
|
>
|
|
102
107
|
<span className="font-medium text-foreground">{name}</span>
|