@meith/theme-default 0.33.1 → 0.33.3
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/shared.tsx +72 -4
- package/src/slots/announcement.tsx +4 -4
- package/src/slots/auth-page.tsx +7 -5
- package/src/slots/board-index.tsx +7 -7
- package/src/slots/board-stats.tsx +10 -8
- package/src/slots/category-block.tsx +7 -6
- package/src/slots/discovery-view.tsx +36 -33
- package/src/slots/editor-toolbar.tsx +2 -2
- package/src/slots/footer.tsx +2 -2
- package/src/slots/forum-display.tsx +30 -31
- package/src/slots/forum-row.tsx +35 -12
- package/src/slots/header.tsx +42 -15
- package/src/slots/latest-posts.tsx +33 -21
- package/src/slots/latest-threads.tsx +16 -10
- package/src/slots/member-profile.tsx +35 -22
- package/src/slots/navigation.tsx +3 -3
- package/src/slots/pagination.tsx +2 -2
- package/src/slots/panel-nav.tsx +15 -13
- package/src/slots/panel-page.tsx +12 -9
- package/src/slots/panel-shell.tsx +34 -12
- package/src/slots/post-bit.tsx +61 -34
- package/src/slots/post-form.tsx +7 -7
- package/src/slots/subforum-list.tsx +41 -35
- package/src/slots/thread-row.tsx +15 -12
- package/src/slots/thread-view.tsx +4 -4
- package/src/slots/user-panel.tsx +13 -10
- package/src/slots/who-is-online.tsx +11 -5
- package/src/theme.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meith/theme-default",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.3",
|
|
4
4
|
"description": "Meith's default theme — every slot and every token, and the reference for what a complete theme fills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@meith/theme-kit": "^0.33.
|
|
24
|
-
"@meith/ui": "^0.33.
|
|
23
|
+
"@meith/theme-kit": "^0.33.3",
|
|
24
|
+
"@meith/ui": "^0.33.3"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": "^19.2.0"
|
package/src/shared.tsx
CHANGED
|
@@ -11,7 +11,7 @@ const PAGE_WIDTH = 'max-w-6xl'
|
|
|
11
11
|
|
|
12
12
|
export const PAGE = `mx-auto w-full ${PAGE_WIDTH} px-4 sm:px-6`
|
|
13
13
|
|
|
14
|
-
export const PAGE_BODY = `${PAGE} flex w-full flex-col gap-5 py-
|
|
14
|
+
export const PAGE_BODY = `${PAGE} flex w-full flex-col gap-5 py-5 sm:py-6`
|
|
15
15
|
|
|
16
16
|
export function pageAt(width: 'max-w-3xl' | 'max-w-4xl'): string {
|
|
17
17
|
return `mx-auto w-full ${width} px-4 sm:px-6`
|
|
@@ -24,6 +24,22 @@ export const PRIMARY_HEADER = 'border-b-primary/15 bg-primary/6'
|
|
|
24
24
|
|
|
25
25
|
export const MUTED_LINK = `text-muted-foreground ${LINK}`
|
|
26
26
|
|
|
27
|
+
export const HEADER_HEIGHT = 'h-14'
|
|
28
|
+
|
|
29
|
+
export const BELOW_HEADER = 'top-14'
|
|
30
|
+
|
|
31
|
+
export const BELOW_DESKTOP_HEADER = 'lg:top-[6.5rem]'
|
|
32
|
+
|
|
33
|
+
export const PAGE_TITLE =
|
|
34
|
+
'font-heading text-xl font-semibold tracking-tight text-balance sm:text-2xl'
|
|
35
|
+
|
|
36
|
+
export const SECTION_TITLE = 'font-heading text-lg font-semibold tracking-tight text-foreground'
|
|
37
|
+
|
|
38
|
+
export const EYEBROW = 'text-xs font-semibold tracking-wide text-muted-foreground uppercase'
|
|
39
|
+
|
|
40
|
+
export const PILL =
|
|
41
|
+
'inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground'
|
|
42
|
+
|
|
27
43
|
export function UserRef({ user, className }: { user: UserRefModel; className?: string }) {
|
|
28
44
|
const classes = cn(
|
|
29
45
|
'font-medium',
|
|
@@ -49,14 +65,48 @@ export function Stamp({ at, className }: { at: TimeModel; className?: string })
|
|
|
49
65
|
)
|
|
50
66
|
}
|
|
51
67
|
|
|
52
|
-
export function UnreadDot() {
|
|
53
|
-
return
|
|
68
|
+
export function UnreadDot({ className }: { className?: string }) {
|
|
69
|
+
return (
|
|
70
|
+
<span
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
className={cn('size-2 shrink-0 rounded-full bg-primary ring-4 ring-primary/15', className)}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
export function ReadSpacer() {
|
|
57
78
|
return <span aria-hidden="true" className="mt-1.5 size-2 shrink-0" />
|
|
58
79
|
}
|
|
59
80
|
|
|
81
|
+
export function Tile({
|
|
82
|
+
label,
|
|
83
|
+
unread,
|
|
84
|
+
className,
|
|
85
|
+
children,
|
|
86
|
+
}: {
|
|
87
|
+
label: string
|
|
88
|
+
unread?: boolean
|
|
89
|
+
className?: string
|
|
90
|
+
children?: React.ReactNode
|
|
91
|
+
}) {
|
|
92
|
+
const initial = (Array.from(label.trim())[0] ?? '?').toUpperCase()
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<span
|
|
96
|
+
aria-hidden="true"
|
|
97
|
+
className={cn(
|
|
98
|
+
'flex size-10 shrink-0 items-center justify-center rounded-lg text-base font-semibold select-none',
|
|
99
|
+
unread
|
|
100
|
+
? 'bg-primary text-primary-foreground shadow-sm'
|
|
101
|
+
: 'bg-primary/10 text-primary ring-1 ring-primary/15 ring-inset',
|
|
102
|
+
className,
|
|
103
|
+
)}
|
|
104
|
+
>
|
|
105
|
+
{children ?? initial}
|
|
106
|
+
</span>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
60
110
|
export function isEmptyRegion(node: React.ReactNode): boolean {
|
|
61
111
|
if (node === null || node === undefined || node === false || node === '') return true
|
|
62
112
|
return Array.isArray(node) && node.every((child) => isEmptyRegion(child))
|
|
@@ -104,7 +154,25 @@ export function Counts({ items, className }: { items: readonly CountItem[]; clas
|
|
|
104
154
|
<div key={item.label}>
|
|
105
155
|
<dt className="sr-only">{item.label}</dt>
|
|
106
156
|
<dd>
|
|
107
|
-
<span className={cn('font-semibold text-
|
|
157
|
+
<span className={cn('font-semibold text-foreground', NUMERIC)}>{item.value.label}</span>{' '}
|
|
158
|
+
{item.value.value === 1 ? item.one : item.many}
|
|
159
|
+
</dd>
|
|
160
|
+
</div>
|
|
161
|
+
))}
|
|
162
|
+
</dl>
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function Figures({ items, className }: { items: readonly CountItem[]; className?: string }) {
|
|
167
|
+
return (
|
|
168
|
+
<dl className={cn('grid grid-cols-2 gap-x-5 text-center', className)}>
|
|
169
|
+
{items.map((item) => (
|
|
170
|
+
<div key={item.label} className="flex flex-col">
|
|
171
|
+
<dt className="sr-only">{item.label}</dt>
|
|
172
|
+
<dd className={cn('text-sm font-semibold text-foreground', NUMERIC)}>
|
|
173
|
+
{item.value.label}
|
|
174
|
+
</dd>
|
|
175
|
+
<dd className="text-[0.6875rem] leading-4 text-muted-foreground">
|
|
108
176
|
{item.value.value === 1 ? item.one : item.many}
|
|
109
177
|
</dd>
|
|
110
178
|
</div>
|
|
@@ -15,14 +15,14 @@ export function Announcement({
|
|
|
15
15
|
const c = (key: string) => fromSlotCopy(copy, `default.announcement.${key}`)
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<Card as="article" className="border-l-4 border-l-primary">
|
|
19
|
-
<CardContent className="flex flex-col gap-2 p-
|
|
20
|
-
<h2 className="text-lg font-semibold tracking-tight text-balance">{title}</h2>
|
|
18
|
+
<Card as="article" className="rounded-xl border-l-4 border-l-primary">
|
|
19
|
+
<CardContent className="flex flex-col gap-2 p-5">
|
|
20
|
+
<h2 className="font-heading text-lg font-semibold tracking-tight text-balance">{title}</h2>
|
|
21
21
|
|
|
22
22
|
<div className="prose-md text-sm" dangerouslySetInnerHTML={{ __html: bodyHtml }} />
|
|
23
23
|
</CardContent>
|
|
24
24
|
|
|
25
|
-
<CardFooter>
|
|
25
|
+
<CardFooter className="px-5">
|
|
26
26
|
<span>
|
|
27
27
|
{postedBy === null ? (
|
|
28
28
|
c('posted')
|
package/src/slots/auth-page.tsx
CHANGED
|
@@ -7,11 +7,13 @@ export function AuthPage({ title, alert, links, regions }: AuthPageModel) {
|
|
|
7
7
|
<main
|
|
8
8
|
id="board-content"
|
|
9
9
|
tabIndex={-1}
|
|
10
|
-
className="flex flex-1 flex-col items-center justify-center px-6 py-
|
|
10
|
+
className="flex flex-1 flex-col items-center justify-center px-4 py-10 sm:px-6 sm:py-14"
|
|
11
11
|
>
|
|
12
|
-
<div className="flex w-full max-w-sm flex-col gap-6 rounded-
|
|
12
|
+
<div className="flex w-full max-w-sm flex-col gap-6 rounded-2xl border border-border bg-card p-6 text-card-foreground shadow-elevation sm:p-8">
|
|
13
13
|
<div className="flex flex-col gap-1">
|
|
14
|
-
<h1 className="font-heading text-2xl font-semibold text-foreground">
|
|
14
|
+
<h1 className="font-heading text-2xl font-semibold tracking-tight text-foreground">
|
|
15
|
+
{title}
|
|
16
|
+
</h1>
|
|
15
17
|
{regions.lede !== undefined && (
|
|
16
18
|
<p className="text-sm text-muted-foreground">{regions.lede}</p>
|
|
17
19
|
)}
|
|
@@ -20,7 +22,7 @@ export function AuthPage({ title, alert, links, regions }: AuthPageModel) {
|
|
|
20
22
|
{alert !== null && (
|
|
21
23
|
<p
|
|
22
24
|
role="alert"
|
|
23
|
-
className="rounded-
|
|
25
|
+
className="rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm"
|
|
24
26
|
>
|
|
25
27
|
{alert}
|
|
26
28
|
</p>
|
|
@@ -33,7 +35,7 @@ export function AuthPage({ title, alert, links, regions }: AuthPageModel) {
|
|
|
33
35
|
{regions.form}
|
|
34
36
|
|
|
35
37
|
{links.length > 0 && (
|
|
36
|
-
<div className="flex flex-col gap-1 text-sm text-muted-foreground">
|
|
38
|
+
<div className="flex flex-col gap-1.5 border-t border-border pt-5 text-sm text-muted-foreground">
|
|
37
39
|
{links.map((link) => (
|
|
38
40
|
<span key={link.href}>
|
|
39
41
|
{link.lead === null ? null : `${link.lead} `}
|
|
@@ -22,8 +22,8 @@ export function BoardIndex({
|
|
|
22
22
|
)}
|
|
23
23
|
|
|
24
24
|
{markAllReadAction !== null && (
|
|
25
|
-
<form action={markAllReadAction} method="post" className="-mb-
|
|
26
|
-
<button type="submit" className={buttonVariants({ variant: '
|
|
25
|
+
<form action={markAllReadAction} method="post" className="-mb-3 self-end">
|
|
26
|
+
<button type="submit" className={buttonVariants({ variant: 'ghost', size: 'sm' })}>
|
|
27
27
|
{c('markAllRead')}
|
|
28
28
|
</button>
|
|
29
29
|
</form>
|
|
@@ -32,14 +32,14 @@ export function BoardIndex({
|
|
|
32
32
|
<div
|
|
33
33
|
className={
|
|
34
34
|
rail
|
|
35
|
-
? 'grid gap-
|
|
36
|
-
: 'flex min-w-0 flex-col gap-
|
|
35
|
+
? 'grid gap-6 lg:grid-cols-[minmax(0,1fr)_19rem] lg:items-start'
|
|
36
|
+
: 'flex min-w-0 flex-col gap-5'
|
|
37
37
|
}
|
|
38
38
|
>
|
|
39
|
-
<div className="flex min-w-0 flex-col gap-
|
|
39
|
+
<div className="flex min-w-0 flex-col gap-5">{regions.categories}</div>
|
|
40
40
|
|
|
41
41
|
{rail && (
|
|
42
|
-
<aside aria-label={c('boardActivity')} className="flex min-w-0 flex-col gap-
|
|
42
|
+
<aside aria-label={c('boardActivity')} className="flex min-w-0 flex-col gap-5">
|
|
43
43
|
{latest}
|
|
44
44
|
{regions.stats}
|
|
45
45
|
</aside>
|
|
@@ -47,7 +47,7 @@ export function BoardIndex({
|
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
{footer && (
|
|
50
|
-
<div className="flex flex-col gap-1.5
|
|
50
|
+
<div className="flex flex-col gap-1.5 rounded-xl border border-border bg-card px-4 py-3 text-xs text-muted-foreground shadow-elevation">
|
|
51
51
|
{regions.online}
|
|
52
52
|
</div>
|
|
53
53
|
)}
|
|
@@ -2,7 +2,7 @@ import type { BoardStatsModel, SlotCopy } from '@meith/theme-kit'
|
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
3
|
import { Card, CardContent, CardHeader, CardTitle } from '@meith/ui'
|
|
4
4
|
|
|
5
|
-
import { NUMERIC,
|
|
5
|
+
import { NUMERIC, Stamp, UserRef } from '../shared'
|
|
6
6
|
|
|
7
7
|
export function BoardStats({
|
|
8
8
|
threadCount,
|
|
@@ -15,14 +15,14 @@ export function BoardStats({
|
|
|
15
15
|
const c = (key: string) => fromSlotCopy(copy, `default.boardStats.${key}`)
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
|
-
<Card aria-labelledby="board-stats-heading">
|
|
19
|
-
<CardHeader className=
|
|
20
|
-
<CardTitle id="board-stats-heading" className="text-
|
|
18
|
+
<Card aria-labelledby="board-stats-heading" className="rounded-xl">
|
|
19
|
+
<CardHeader className="bg-card">
|
|
20
|
+
<CardTitle id="board-stats-heading" className="text-sm">
|
|
21
21
|
{c('heading')}
|
|
22
22
|
</CardTitle>
|
|
23
23
|
</CardHeader>
|
|
24
24
|
|
|
25
|
-
<CardContent className="flex flex-col gap-3">
|
|
25
|
+
<CardContent className="flex flex-col gap-3 px-5 py-4">
|
|
26
26
|
{computedAt === null ? (
|
|
27
27
|
<p className="text-xs text-muted-foreground">{c('notComputed')}</p>
|
|
28
28
|
) : (
|
|
@@ -33,11 +33,13 @@ export function BoardStats({
|
|
|
33
33
|
{ label: c('posts'), value: postCount },
|
|
34
34
|
{ label: c('members'), value: memberCount },
|
|
35
35
|
].map((figure) => (
|
|
36
|
-
<div key={figure.label}>
|
|
37
|
-
<dd className={`text-
|
|
36
|
+
<div key={figure.label} className="rounded-lg bg-muted/60 px-2 py-2.5">
|
|
37
|
+
<dd className={`text-lg font-semibold text-foreground ${NUMERIC}`}>
|
|
38
38
|
{figure.value.label}
|
|
39
39
|
</dd>
|
|
40
|
-
<dt className="text-
|
|
40
|
+
<dt className="text-[0.6875rem] text-muted-foreground uppercase">
|
|
41
|
+
{figure.label}
|
|
42
|
+
</dt>
|
|
41
43
|
</div>
|
|
42
44
|
))}
|
|
43
45
|
</dl>
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import type { CategoryBlockModel } from '@meith/theme-kit'
|
|
2
2
|
import { Card, CardDescription, CardHeader, CardRows, CardTitle } from '@meith/ui'
|
|
3
3
|
|
|
4
|
-
import { LINK
|
|
4
|
+
import { LINK } from '../shared'
|
|
5
5
|
|
|
6
6
|
export function CategoryBlock({ category, children }: CategoryBlockModel) {
|
|
7
7
|
const headingId = `category-${category.id}`
|
|
8
8
|
|
|
9
9
|
return (
|
|
10
|
-
<Card aria-labelledby={headingId}>
|
|
11
|
-
<CardHeader className=
|
|
12
|
-
<CardTitle id={headingId} className="text-
|
|
13
|
-
<
|
|
10
|
+
<Card aria-labelledby={headingId} className="rounded-xl">
|
|
11
|
+
<CardHeader className="flex-col items-start gap-0 bg-card px-5 py-3">
|
|
12
|
+
<CardTitle id={headingId} className="flex items-center gap-2.5 text-base">
|
|
13
|
+
<span aria-hidden="true" className="h-4 w-1 rounded-full bg-primary" />
|
|
14
|
+
<a href={category.href} className={`${LINK} text-foreground`}>
|
|
14
15
|
{category.title}
|
|
15
16
|
</a>
|
|
16
17
|
</CardTitle>
|
|
17
18
|
{category.description !== null && (
|
|
18
|
-
<CardDescription className="text-xs">{category.description}</CardDescription>
|
|
19
|
+
<CardDescription className="pl-3.5 text-xs">{category.description}</CardDescription>
|
|
19
20
|
)}
|
|
20
21
|
</CardHeader>
|
|
21
22
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { DiscoveryViewModel, SlotCopy, TabModel } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import { Card, CardRows, cn, Empty, EmptyDescription } from '@meith/ui'
|
|
3
|
+
import { Avatar, buttonVariants, Card, CardRows, cn, Empty, EmptyDescription } from '@meith/ui'
|
|
4
4
|
|
|
5
|
-
import { LINK, NUMERIC, pageAt, Stamp } from '../shared'
|
|
5
|
+
import { LINK, NUMERIC, PAGE_TITLE, PILL, pageAt, Stamp } from '../shared'
|
|
6
6
|
|
|
7
7
|
const TAB =
|
|
8
8
|
'inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-sm whitespace-nowrap transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
@@ -52,62 +52,65 @@ export function DiscoveryView({
|
|
|
52
52
|
<main
|
|
53
53
|
id="board-content"
|
|
54
54
|
tabIndex={-1}
|
|
55
|
-
className={`${pageAt('max-w-4xl')} flex flex-1 flex-col gap-6 py-8`}
|
|
55
|
+
className={`${pageAt('max-w-4xl')} flex flex-1 flex-col gap-6 py-6 sm:py-8`}
|
|
56
56
|
>
|
|
57
57
|
<div className="flex flex-col gap-1">
|
|
58
|
-
<h1 className=
|
|
58
|
+
<h1 className={PAGE_TITLE}>{title}</h1>
|
|
59
59
|
<p className="text-sm text-muted-foreground">{blurb}</p>
|
|
60
60
|
</div>
|
|
61
61
|
|
|
62
62
|
<Tabs label={tabsLabel} tabs={tabs} />
|
|
63
63
|
|
|
64
64
|
{refusal !== null ? (
|
|
65
|
-
<p role="alert" className="rounded-
|
|
65
|
+
<p role="alert" className="rounded-lg border border-border bg-card px-4 py-3 text-sm">
|
|
66
66
|
{refusal.message}{' '}
|
|
67
67
|
<a href={refusal.signInHref} className={`font-medium text-foreground ${LINK}`}>
|
|
68
68
|
{refusal.signInLabel}
|
|
69
69
|
</a>
|
|
70
70
|
</p>
|
|
71
71
|
) : rows.length === 0 ? (
|
|
72
|
-
<Card>
|
|
72
|
+
<Card className="rounded-xl">
|
|
73
73
|
<Empty>
|
|
74
74
|
<EmptyDescription>{emptyMessage}</EmptyDescription>
|
|
75
75
|
</Empty>
|
|
76
76
|
</Card>
|
|
77
77
|
) : (
|
|
78
|
-
<Card>
|
|
78
|
+
<Card className="rounded-xl">
|
|
79
79
|
<CardRows>
|
|
80
80
|
{rows.map((row) => (
|
|
81
81
|
<li
|
|
82
82
|
key={row.threadId}
|
|
83
|
-
className="flex
|
|
83
|
+
className="flex gap-3.5 px-4 py-3.5 transition-colors hover:bg-muted/50 sm:items-center sm:px-5"
|
|
84
84
|
>
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
<Avatar src={null} name={row.authorUsername} size={36} className="rounded-full" />
|
|
86
|
+
|
|
87
|
+
<div className="flex min-w-0 flex-1 flex-col gap-1 sm:flex-row sm:items-center sm:justify-between sm:gap-6">
|
|
88
|
+
<div className="flex min-w-0 flex-col gap-0.5">
|
|
89
|
+
<a
|
|
90
|
+
href={row.href}
|
|
91
|
+
className={`truncate text-[0.9375rem] font-medium text-foreground ${LINK}`}
|
|
92
|
+
>
|
|
93
|
+
{row.title}
|
|
94
|
+
</a>
|
|
95
|
+
<p className="truncate text-xs text-muted-foreground">
|
|
96
|
+
{c('startedBy')} {row.authorUsername} {c('in')}{' '}
|
|
97
|
+
<a href={row.forum.href} className="font-medium hover:underline">
|
|
98
|
+
{row.forum.label}
|
|
99
|
+
</a>
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<p className="flex shrink-0 flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground sm:flex-col sm:items-end sm:gap-0.5">
|
|
104
|
+
<span className={`${PILL} ${NUMERIC}`}>
|
|
105
|
+
{row.replyCount.label}{' '}
|
|
106
|
+
{row.replyCount.value === 1 ? c('reply.one') : c('reply.other')}
|
|
107
|
+
</span>
|
|
108
|
+
<span>
|
|
109
|
+
{c('lastPost')} <Stamp at={row.lastPostAt} />
|
|
110
|
+
{row.lastPostUsername === null ? null : ` ${c('by')} ${row.lastPostUsername}`}
|
|
111
|
+
</span>
|
|
98
112
|
</p>
|
|
99
113
|
</div>
|
|
100
|
-
|
|
101
|
-
<p className="shrink-0 text-xs text-muted-foreground sm:text-right">
|
|
102
|
-
<span className={cn('font-semibold text-foreground', NUMERIC)}>
|
|
103
|
-
{row.replyCount.label}
|
|
104
|
-
</span>{' '}
|
|
105
|
-
{row.replyCount.value === 1 ? c('reply.one') : c('reply.other')}
|
|
106
|
-
<span className="block">
|
|
107
|
-
{c('lastPost')} <Stamp at={row.lastPostAt} />
|
|
108
|
-
{row.lastPostUsername === null ? null : ` ${c('by')} ${row.lastPostUsername}`}
|
|
109
|
-
</span>
|
|
110
|
-
</p>
|
|
111
114
|
</li>
|
|
112
115
|
))}
|
|
113
116
|
</CardRows>
|
|
@@ -117,7 +120,7 @@ export function DiscoveryView({
|
|
|
117
120
|
{nextHref !== null && (
|
|
118
121
|
<a
|
|
119
122
|
href={nextHref}
|
|
120
|
-
className=
|
|
123
|
+
className={cn(buttonVariants({ variant: 'secondary' }), 'w-fit rounded-full')}
|
|
121
124
|
>
|
|
122
125
|
{nextLabel} {c('nextArrow')}
|
|
123
126
|
</a>
|
|
@@ -26,7 +26,7 @@ const GLYPHS: Readonly<Record<EditorTag, string>> = {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const BUTTON =
|
|
29
|
-
'min-w-8 rounded px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-
|
|
29
|
+
'min-w-8 rounded-md px-2 py-1 text-xs font-medium text-muted-foreground hover:bg-card hover:text-foreground hover:shadow-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
30
30
|
|
|
31
31
|
function runButton(textareaId: string, button: EditorToolbarButtonModel): void {
|
|
32
32
|
const field = document.getElementById(textareaId)
|
|
@@ -54,7 +54,7 @@ export function EditorToolbar({
|
|
|
54
54
|
<div
|
|
55
55
|
role="group"
|
|
56
56
|
aria-label={groupLabel}
|
|
57
|
-
className="flex flex-wrap gap-0.5 border-b border-border bg-
|
|
57
|
+
className="flex flex-wrap gap-0.5 border-b border-border bg-surface/60 p-2"
|
|
58
58
|
>
|
|
59
59
|
{buttons.map((button, index) => (
|
|
60
60
|
<button
|
package/src/slots/footer.tsx
CHANGED
|
@@ -23,9 +23,9 @@ export function Footer({
|
|
|
23
23
|
</div>
|
|
24
24
|
)}
|
|
25
25
|
<div
|
|
26
|
-
className={`${PAGE} flex flex-col gap-3 py-
|
|
26
|
+
className={`${PAGE} flex flex-col gap-3 py-6 text-xs text-muted-foreground sm:flex-row sm:items-center sm:justify-between sm:gap-6`}
|
|
27
27
|
>
|
|
28
|
-
<span className="font-semibold text-
|
|
28
|
+
<span className="font-semibold text-foreground">{boardTitle}</span>
|
|
29
29
|
|
|
30
30
|
{links.length > 0 && (
|
|
31
31
|
<nav aria-label={c('nav')} className="flex flex-wrap gap-x-4 gap-y-1">
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
EmptyTitle,
|
|
12
12
|
} from '@meith/ui'
|
|
13
13
|
|
|
14
|
-
import { Counts, isEmptyRegion, PAGE_BODY } from '../shared'
|
|
14
|
+
import { Counts, isEmptyRegion, PAGE_BODY, PAGE_TITLE } from '../shared'
|
|
15
15
|
|
|
16
16
|
export function ForumDisplay({
|
|
17
17
|
forum,
|
|
@@ -24,37 +24,36 @@ export function ForumDisplay({
|
|
|
24
24
|
|
|
25
25
|
return (
|
|
26
26
|
<div className={PAGE_BODY}>
|
|
27
|
-
<div className="flex flex-col gap-3 sm:flex-row sm:items-
|
|
27
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-6">
|
|
28
28
|
<div className="min-w-0">
|
|
29
|
-
<h1 className=
|
|
30
|
-
|
|
31
|
-
<p className="
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
)}
|
|
29
|
+
<h1 className={PAGE_TITLE}>{forum.title}</h1>
|
|
30
|
+
<div className="mt-1 flex flex-wrap items-baseline gap-x-3 gap-y-1 text-sm text-muted-foreground">
|
|
31
|
+
{forum.description !== null && <p className="max-w-prose">{forum.description}</p>}
|
|
32
|
+
{forum.type !== 'link' && (
|
|
33
|
+
<Counts
|
|
34
|
+
items={[
|
|
35
|
+
{
|
|
36
|
+
label: c('threadsLabel'),
|
|
37
|
+
value: forum.threadCount,
|
|
38
|
+
one: c('thread.one'),
|
|
39
|
+
many: c('thread.other'),
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
label: c('postsLabel'),
|
|
43
|
+
value: forum.postCount,
|
|
44
|
+
one: c('post.one'),
|
|
45
|
+
many: c('post.other'),
|
|
46
|
+
},
|
|
47
|
+
]}
|
|
48
|
+
/>
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
52
51
|
</div>
|
|
53
52
|
|
|
54
53
|
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
|
55
54
|
{markReadAction !== null && (
|
|
56
55
|
<form action={markReadAction} method="post">
|
|
57
|
-
<button type="submit" className={buttonVariants({ variant: '
|
|
56
|
+
<button type="submit" className={buttonVariants({ variant: 'ghost' })}>
|
|
58
57
|
{c('markRead')}
|
|
59
58
|
</button>
|
|
60
59
|
</form>
|
|
@@ -70,17 +69,17 @@ export function ForumDisplay({
|
|
|
70
69
|
</div>
|
|
71
70
|
</div>
|
|
72
71
|
|
|
73
|
-
{regions.tools !== undefined && (
|
|
74
|
-
<div className="flex flex-col gap-3 empty:hidden">{regions.tools}</div>
|
|
75
|
-
)}
|
|
76
|
-
|
|
77
72
|
{regions.announcements !== undefined && (
|
|
78
73
|
<div className="flex flex-col gap-3">{regions.announcements}</div>
|
|
79
74
|
)}
|
|
80
75
|
|
|
81
76
|
{regions.subforums}
|
|
82
77
|
|
|
83
|
-
|
|
78
|
+
{regions.tools !== undefined && (
|
|
79
|
+
<div className="-mb-2 flex flex-col gap-3 empty:hidden">{regions.tools}</div>
|
|
80
|
+
)}
|
|
81
|
+
|
|
82
|
+
<Card className="rounded-xl">
|
|
84
83
|
{isEmptyRegion(regions.threads) ? (
|
|
85
84
|
<Empty>
|
|
86
85
|
<EmptyTitle>{c('noThreadsYet')}</EmptyTitle>
|
package/src/slots/forum-row.tsx
CHANGED
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import type { ForumRowSlotModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { Figures, LINK, Stamp, Tile, UserRef } from '../shared'
|
|
5
|
+
|
|
6
|
+
function LinkGlyph() {
|
|
7
|
+
return (
|
|
8
|
+
<svg
|
|
9
|
+
aria-hidden="true"
|
|
10
|
+
viewBox="0 0 16 16"
|
|
11
|
+
className="size-4"
|
|
12
|
+
fill="none"
|
|
13
|
+
stroke="currentColor"
|
|
14
|
+
strokeWidth="1.5"
|
|
15
|
+
strokeLinecap="round"
|
|
16
|
+
strokeLinejoin="round"
|
|
17
|
+
>
|
|
18
|
+
<path d="M6.5 3.5h6v6M12.5 3.5 4 12" />
|
|
19
|
+
</svg>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
5
22
|
|
|
6
23
|
export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }) {
|
|
7
24
|
const isLink = forum.type === 'link'
|
|
@@ -12,18 +29,20 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
12
29
|
<li
|
|
13
30
|
data-unread={forum.isUnread ? '' : undefined}
|
|
14
31
|
className={
|
|
15
|
-
'grid grid-cols-[auto_minmax(0,1fr)] gap-x-
|
|
16
|
-
(isLink ? '' : ' md:grid-cols-[auto_minmax(0,1fr)
|
|
32
|
+
'grid grid-cols-[auto_minmax(0,1fr)] gap-x-3.5 gap-y-2 px-4 py-3.5 transition-colors hover:bg-muted/50 sm:px-5' +
|
|
33
|
+
(isLink ? '' : ' md:grid-cols-[auto_minmax(0,1fr)_9rem_16rem] md:items-center md:gap-x-5')
|
|
17
34
|
}
|
|
18
35
|
>
|
|
19
|
-
{forum.isUnread
|
|
36
|
+
<Tile label={forum.title} unread={forum.isUnread} className="mt-0.5 md:mt-0">
|
|
37
|
+
{isLink ? <LinkGlyph /> : undefined}
|
|
38
|
+
</Tile>
|
|
20
39
|
|
|
21
40
|
<div className="min-w-0">
|
|
22
41
|
<a
|
|
23
42
|
href={forum.href}
|
|
24
43
|
className={
|
|
25
44
|
(forum.isUnread ? 'font-semibold text-foreground' : 'font-medium text-foreground') +
|
|
26
|
-
` ${LINK}`
|
|
45
|
+
` text-[0.9375rem] ${LINK}`
|
|
27
46
|
}
|
|
28
47
|
>
|
|
29
48
|
{forum.title}
|
|
@@ -35,10 +54,14 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
35
54
|
)}
|
|
36
55
|
|
|
37
56
|
{forum.subforums.length > 0 && (
|
|
38
|
-
<p className="mt-
|
|
39
|
-
<span className="
|
|
57
|
+
<p className="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
|
|
58
|
+
<span className="sr-only">{c('subforums')}</span>
|
|
40
59
|
{forum.subforums.map((sub) => (
|
|
41
|
-
<a
|
|
60
|
+
<a
|
|
61
|
+
key={sub.href}
|
|
62
|
+
href={sub.href}
|
|
63
|
+
className="rounded-md bg-muted px-2 py-0.5 font-medium text-muted-foreground transition-colors hover:bg-primary/10 hover:text-primary"
|
|
64
|
+
>
|
|
42
65
|
{sub.label}
|
|
43
66
|
</a>
|
|
44
67
|
))}
|
|
@@ -47,9 +70,9 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
47
70
|
</div>
|
|
48
71
|
|
|
49
72
|
{!isLink && (
|
|
50
|
-
<div className="col-start-2 flex min-w-0 flex-wrap items-
|
|
51
|
-
<
|
|
52
|
-
className="md:col-start-3 md:justify-end"
|
|
73
|
+
<div className="col-start-2 flex min-w-0 flex-wrap items-center gap-x-5 gap-y-1 text-xs text-muted-foreground md:contents">
|
|
74
|
+
<Figures
|
|
75
|
+
className="md:col-start-3 md:justify-self-end"
|
|
53
76
|
items={[
|
|
54
77
|
{
|
|
55
78
|
label: c('threadsLabel'),
|
|
@@ -66,7 +89,7 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
66
89
|
]}
|
|
67
90
|
/>
|
|
68
91
|
|
|
69
|
-
<div className="flex min-w-0 max-w-full flex-wrap gap-x-1 md:col-start-4 md:block">
|
|
92
|
+
<div className="flex min-w-0 max-w-full flex-wrap gap-x-1 md:col-start-4 md:block md:border-l md:border-border md:pl-5">
|
|
70
93
|
{forum.lastPost === null ? (
|
|
71
94
|
<span className="text-forum-read">{c('noPostsYet')}</span>
|
|
72
95
|
) : (
|