@meith/theme-default 0.32.0 → 0.33.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/shared.tsx +12 -6
- package/src/slots/announcement.tsx +2 -2
- package/src/slots/auth-page.tsx +1 -1
- package/src/slots/board-index.tsx +2 -2
- package/src/slots/board-stats.tsx +1 -1
- package/src/slots/category-block.tsx +3 -3
- package/src/slots/discovery-view.tsx +2 -2
- package/src/slots/footer.tsx +3 -3
- package/src/slots/forum-display.tsx +7 -3
- package/src/slots/forum-row.tsx +7 -7
- package/src/slots/header.tsx +65 -45
- package/src/slots/latest-threads.tsx +1 -1
- package/src/slots/member-profile.tsx +1 -3
- package/src/slots/navigation.tsx +10 -9
- package/src/slots/panel-nav.tsx +1 -1
- package/src/slots/panel-shell.tsx +1 -1
- package/src/slots/post-actions.tsx +8 -2
- package/src/slots/post-bit.tsx +55 -40
- package/src/slots/thread-row.tsx +37 -32
- package/src/slots/thread-view.tsx +9 -13
- 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.
|
|
3
|
+
"version": "0.33.0",
|
|
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.
|
|
24
|
-
"@meith/ui": "^0.
|
|
23
|
+
"@meith/theme-kit": "^0.33.0",
|
|
24
|
+
"@meith/ui": "^0.33.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": "^19.2.0"
|
package/src/shared.tsx
CHANGED
|
@@ -17,12 +17,18 @@ export function pageAt(width: 'max-w-3xl' | 'max-w-4xl'): string {
|
|
|
17
17
|
return `mx-auto w-full ${width} px-4 sm:px-6`
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export const LINK =
|
|
20
|
+
export const LINK =
|
|
21
|
+
'transition-colors hover:text-primary hover:underline underline-offset-2 decoration-1 decoration-primary'
|
|
21
22
|
|
|
22
|
-
export const MUTED_LINK = `text-muted-foreground
|
|
23
|
+
export const MUTED_LINK = `text-muted-foreground ${LINK}`
|
|
23
24
|
|
|
24
25
|
export function UserRef({ user, className }: { user: UserRefModel; className?: string }) {
|
|
25
|
-
const classes = cn(
|
|
26
|
+
const classes = cn(
|
|
27
|
+
'font-medium',
|
|
28
|
+
user.nameClass == null && 'text-primary',
|
|
29
|
+
className,
|
|
30
|
+
user.nameClass,
|
|
31
|
+
)
|
|
26
32
|
if (user.profileHref === null) {
|
|
27
33
|
return <span className={classes}>{user.username}</span>
|
|
28
34
|
}
|
|
@@ -50,8 +56,8 @@ export function ReadSpacer() {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
export function isEmptyRegion(node: React.ReactNode): boolean {
|
|
53
|
-
if (node === null || node === undefined || node === false) return true
|
|
54
|
-
return Array.isArray(node) && node.
|
|
59
|
+
if (node === null || node === undefined || node === false || node === '') return true
|
|
60
|
+
return Array.isArray(node) && node.every((child) => isEmptyRegion(child))
|
|
55
61
|
}
|
|
56
62
|
|
|
57
63
|
export const NUMERIC = 'tabular-nums'
|
|
@@ -96,7 +102,7 @@ export function Counts({ items, className }: { items: readonly CountItem[]; clas
|
|
|
96
102
|
<div key={item.label}>
|
|
97
103
|
<dt className="sr-only">{item.label}</dt>
|
|
98
104
|
<dd>
|
|
99
|
-
<span className={cn('font-
|
|
105
|
+
<span className={cn('font-semibold text-primary', NUMERIC)}>{item.value.label}</span>{' '}
|
|
100
106
|
{item.value.value === 1 ? item.one : item.many}
|
|
101
107
|
</dd>
|
|
102
108
|
</div>
|
|
@@ -15,7 +15,7 @@ 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-
|
|
18
|
+
<Card as="article" className="border-l-4 border-l-primary">
|
|
19
19
|
<CardContent className="flex flex-col gap-2 p-4">
|
|
20
20
|
<h2 className="text-lg font-semibold tracking-tight text-balance">{title}</h2>
|
|
21
21
|
|
|
@@ -28,7 +28,7 @@ export function Announcement({
|
|
|
28
28
|
c('posted')
|
|
29
29
|
) : (
|
|
30
30
|
<>
|
|
31
|
-
{c('postedBy')} <UserRef user={postedBy}
|
|
31
|
+
{c('postedBy')} <UserRef user={postedBy} />
|
|
32
32
|
</>
|
|
33
33
|
)}{' '}
|
|
34
34
|
<Stamp at={postedAt} />
|
package/src/slots/auth-page.tsx
CHANGED
|
@@ -9,7 +9,7 @@ export function AuthPage({ title, alert, links, regions }: AuthPageModel) {
|
|
|
9
9
|
tabIndex={-1}
|
|
10
10
|
className="flex flex-1 flex-col items-center justify-center px-6 py-12"
|
|
11
11
|
>
|
|
12
|
-
<div className="flex w-full max-w-sm flex-col gap-6 rounded-lg border border-border bg-card p-6 text-card-foreground shadow-
|
|
12
|
+
<div className="flex w-full max-w-sm flex-col gap-6 rounded-lg border border-border bg-card p-6 text-card-foreground shadow-elevation">
|
|
13
13
|
<div className="flex flex-col gap-1">
|
|
14
14
|
<h1 className="font-heading text-2xl font-semibold text-foreground">{title}</h1>
|
|
15
15
|
{regions.lede !== undefined && (
|
|
@@ -21,8 +21,8 @@ export function BoardIndex({
|
|
|
21
21
|
)}
|
|
22
22
|
|
|
23
23
|
{markAllReadAction !== null && (
|
|
24
|
-
<form action={markAllReadAction} method="post" className="self-end">
|
|
25
|
-
<button type="submit" className={buttonVariants({ variant: '
|
|
24
|
+
<form action={markAllReadAction} method="post" className="-mb-2 self-end">
|
|
25
|
+
<button type="submit" className={buttonVariants({ variant: 'outline', size: 'sm' })}>
|
|
26
26
|
{c('markAllRead')}
|
|
27
27
|
</button>
|
|
28
28
|
</form>
|
|
@@ -8,9 +8,9 @@ export function CategoryBlock({ category, children }: CategoryBlockModel) {
|
|
|
8
8
|
|
|
9
9
|
return (
|
|
10
10
|
<Card aria-labelledby={headingId}>
|
|
11
|
-
<CardHeader className="flex-col items-start gap-0.5">
|
|
12
|
-
<CardTitle id={headingId}>
|
|
13
|
-
<a href={category.href} className={LINK}>
|
|
11
|
+
<CardHeader className="flex-col items-start gap-0.5 border-b-primary/15 bg-primary/6">
|
|
12
|
+
<CardTitle id={headingId} className="text-primary">
|
|
13
|
+
<a href={category.href} className={`${LINK} hover:text-primary-hover`}>
|
|
14
14
|
{category.title}
|
|
15
15
|
</a>
|
|
16
16
|
</CardTitle>
|
|
@@ -21,7 +21,7 @@ function Tabs({ label, tabs }: { label: string; tabs: readonly TabModel[] }) {
|
|
|
21
21
|
className={cn(
|
|
22
22
|
TAB,
|
|
23
23
|
tab.isCurrent
|
|
24
|
-
? 'bg-card font-semibold text-
|
|
24
|
+
? 'bg-card font-semibold text-primary shadow-sm'
|
|
25
25
|
: 'font-medium text-muted-foreground hover:text-foreground',
|
|
26
26
|
)}
|
|
27
27
|
>
|
|
@@ -80,7 +80,7 @@ export function DiscoveryView({
|
|
|
80
80
|
{rows.map((row) => (
|
|
81
81
|
<li
|
|
82
82
|
key={row.threadId}
|
|
83
|
-
className="flex flex-col gap-1.5 px-4 py-3.5 transition-colors hover:bg-
|
|
83
|
+
className="flex flex-col gap-1.5 px-4 py-3.5 transition-colors hover:bg-muted/60 sm:flex-row sm:items-center sm:justify-between sm:gap-6"
|
|
84
84
|
>
|
|
85
85
|
<div className="flex min-w-0 flex-col gap-1">
|
|
86
86
|
<a
|
package/src/slots/footer.tsx
CHANGED
|
@@ -17,15 +17,15 @@ export function Footer({
|
|
|
17
17
|
<footer className="mt-auto border-t border-border bg-card">
|
|
18
18
|
{regions?.controls && (
|
|
19
19
|
<div
|
|
20
|
-
className={`${PAGE} flex flex-wrap items-center justify-end gap-x-6 gap-y-
|
|
20
|
+
className={`${PAGE} flex flex-wrap items-center justify-end gap-x-6 gap-y-3 border-b border-border py-3`}
|
|
21
21
|
>
|
|
22
22
|
{regions.controls}
|
|
23
23
|
</div>
|
|
24
24
|
)}
|
|
25
25
|
<div
|
|
26
|
-
className={`${PAGE} flex flex-col gap-
|
|
26
|
+
className={`${PAGE} flex flex-col gap-3 py-5 text-xs text-muted-foreground sm:flex-row sm:items-center sm:justify-between sm:gap-6`}
|
|
27
27
|
>
|
|
28
|
-
<span className="font-
|
|
28
|
+
<span className="font-semibold text-primary">{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">
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
buttonVariants,
|
|
5
5
|
Card,
|
|
6
6
|
CardRows,
|
|
7
|
+
cn,
|
|
7
8
|
Empty,
|
|
8
9
|
EmptyAction,
|
|
9
10
|
EmptyDescription,
|
|
@@ -50,16 +51,19 @@ export function ForumDisplay({
|
|
|
50
51
|
)}
|
|
51
52
|
</div>
|
|
52
53
|
|
|
53
|
-
<div className="flex shrink-0 items-center gap-2">
|
|
54
|
+
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
|
54
55
|
{markReadAction !== null && (
|
|
55
56
|
<form action={markReadAction} method="post">
|
|
56
|
-
<button type="submit" className={buttonVariants({ variant: '
|
|
57
|
+
<button type="submit" className={buttonVariants({ variant: 'outline' })}>
|
|
57
58
|
{c('markRead')}
|
|
58
59
|
</button>
|
|
59
60
|
</form>
|
|
60
61
|
)}
|
|
61
62
|
{newThreadHref !== null && (
|
|
62
|
-
<a
|
|
63
|
+
<a
|
|
64
|
+
href={newThreadHref}
|
|
65
|
+
className={cn(buttonVariants({ variant: 'primary' }), 'ms-auto sm:ms-0')}
|
|
66
|
+
>
|
|
63
67
|
{c('newThread')}
|
|
64
68
|
</a>
|
|
65
69
|
)}
|
package/src/slots/forum-row.tsx
CHANGED
|
@@ -12,7 +12,7 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
12
12
|
<li
|
|
13
13
|
data-unread={forum.isUnread ? '' : undefined}
|
|
14
14
|
className={
|
|
15
|
-
'grid grid-cols-[auto_minmax(0,1fr)] gap-x-2.5 gap-y-
|
|
15
|
+
'grid grid-cols-[auto_minmax(0,1fr)] gap-x-2.5 gap-y-1.5 px-4 py-3 transition-colors hover:bg-muted/60' +
|
|
16
16
|
(isLink ? '' : ' md:grid-cols-[auto_minmax(0,1fr)_9rem_15rem] md:items-center md:gap-x-4')
|
|
17
17
|
}
|
|
18
18
|
>
|
|
@@ -47,9 +47,9 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
47
47
|
</div>
|
|
48
48
|
|
|
49
49
|
{!isLink && (
|
|
50
|
-
|
|
50
|
+
<div className="col-start-2 flex min-w-0 flex-wrap items-baseline gap-x-4 gap-y-1 text-xs text-muted-foreground md:contents">
|
|
51
51
|
<Counts
|
|
52
|
-
className="
|
|
52
|
+
className="md:col-start-3 md:justify-end"
|
|
53
53
|
items={[
|
|
54
54
|
{
|
|
55
55
|
label: c('threadsLabel'),
|
|
@@ -66,25 +66,25 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
66
66
|
]}
|
|
67
67
|
/>
|
|
68
68
|
|
|
69
|
-
<div className="
|
|
69
|
+
<div className="flex min-w-0 max-w-full flex-wrap gap-x-1 md:col-start-4 md:block">
|
|
70
70
|
{forum.lastPost === null ? (
|
|
71
71
|
<span className="text-forum-read">{c('noPostsYet')}</span>
|
|
72
72
|
) : (
|
|
73
73
|
<>
|
|
74
74
|
<a
|
|
75
75
|
href={forum.lastPost.href}
|
|
76
|
-
className={`
|
|
76
|
+
className={`max-w-full truncate font-medium text-foreground md:block ${LINK}`}
|
|
77
77
|
>
|
|
78
78
|
{forum.lastPost.threadTitle}
|
|
79
79
|
</a>
|
|
80
|
-
<span className="mt-0.5 block truncate">
|
|
80
|
+
<span className="md:mt-0.5 md:block md:truncate">
|
|
81
81
|
{c('by')} <UserRef user={forum.lastPost.author} className="font-normal" />{' '}
|
|
82
82
|
{c('dot')} <Stamp at={forum.lastPost.at} />
|
|
83
83
|
</span>
|
|
84
84
|
</>
|
|
85
85
|
)}
|
|
86
86
|
</div>
|
|
87
|
-
|
|
87
|
+
</div>
|
|
88
88
|
)}
|
|
89
89
|
</li>
|
|
90
90
|
)
|
package/src/slots/header.tsx
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import type { HeaderModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy, linkTarget } from '@meith/theme-kit'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { PAGE } from '../shared'
|
|
5
|
+
|
|
6
|
+
const DESKTOP_LINK =
|
|
7
|
+
'inline-flex h-10 items-center rounded-md px-3 font-medium text-muted-foreground transition-colors pointer-coarse:h-11 hover:bg-primary/8 hover:text-primary'
|
|
8
|
+
|
|
9
|
+
const MOBILE_LINK =
|
|
10
|
+
'flex min-h-11 items-center rounded-md px-3 text-sm font-medium text-muted-foreground hover:bg-primary/8 hover:text-primary'
|
|
11
|
+
|
|
12
|
+
const SUMMARY =
|
|
13
|
+
'cursor-default list-none select-none [&::-webkit-details-marker]:hidden [&::marker]:content-none'
|
|
5
14
|
|
|
6
15
|
function BoardMark({ boardTitle, logo }: Pick<HeaderModel, 'boardTitle' | 'logo'>) {
|
|
7
16
|
if (logo === undefined) return <>{boardTitle}</>
|
|
@@ -29,13 +38,13 @@ function Submenu({ items }: { items: HeaderModel['navigation'][number]['submenu'
|
|
|
29
38
|
if (items === undefined || items.length === 0) return null
|
|
30
39
|
|
|
31
40
|
return (
|
|
32
|
-
<ul className="invisible absolute top-full left-0 z-30 min-w-
|
|
41
|
+
<ul className="invisible absolute top-full left-0 z-30 min-w-48 rounded-md border border-border bg-card p-1 opacity-0 shadow-elevation transition-opacity group-focus-within:visible group-focus-within:opacity-100 group-hover:visible group-hover:opacity-100">
|
|
33
42
|
{items.map((child) => (
|
|
34
43
|
<li key={child.href}>
|
|
35
44
|
<a
|
|
36
45
|
href={child.href}
|
|
37
46
|
{...linkTarget(child)}
|
|
38
|
-
className="flex items-center px-
|
|
47
|
+
className="flex items-center rounded-sm px-2.5 py-1.5 text-muted-foreground pointer-coarse:min-h-11 hover:bg-primary/8 hover:text-primary"
|
|
39
48
|
>
|
|
40
49
|
{child.label}
|
|
41
50
|
</a>
|
|
@@ -62,15 +71,28 @@ function Chevron({ className }: { className: string }) {
|
|
|
62
71
|
)
|
|
63
72
|
}
|
|
64
73
|
|
|
74
|
+
function MenuGlyph() {
|
|
75
|
+
return (
|
|
76
|
+
<svg
|
|
77
|
+
aria-hidden="true"
|
|
78
|
+
viewBox="0 0 20 20"
|
|
79
|
+
className="size-5"
|
|
80
|
+
fill="none"
|
|
81
|
+
stroke="currentColor"
|
|
82
|
+
strokeWidth="1.75"
|
|
83
|
+
strokeLinecap="round"
|
|
84
|
+
>
|
|
85
|
+
<path className="group-open/menu:hidden" d="M3 5.5h14M3 10h14M3 14.5h14" />
|
|
86
|
+
<path className="hidden group-open/menu:block" d="m5 5 10 10M15 5 5 15" />
|
|
87
|
+
</svg>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
65
91
|
function MobileNavItem({ item }: { item: HeaderModel['navigation'][number] }) {
|
|
66
92
|
if (item.submenu === undefined || item.submenu.length === 0) {
|
|
67
93
|
return (
|
|
68
94
|
<li>
|
|
69
|
-
<a
|
|
70
|
-
href={item.href}
|
|
71
|
-
{...linkTarget(item)}
|
|
72
|
-
className="flex min-h-11 items-center rounded-md px-2.5 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
73
|
-
>
|
|
95
|
+
<a href={item.href} {...linkTarget(item)} className={MOBILE_LINK}>
|
|
74
96
|
{item.label}
|
|
75
97
|
</a>
|
|
76
98
|
</li>
|
|
@@ -80,7 +102,9 @@ function MobileNavItem({ item }: { item: HeaderModel['navigation'][number] }) {
|
|
|
80
102
|
return (
|
|
81
103
|
<li>
|
|
82
104
|
<details data-nav-disclosure name="default-header-submenu" className="group/submenu">
|
|
83
|
-
<summary
|
|
105
|
+
<summary
|
|
106
|
+
className={`${SUMMARY} flex min-h-11 items-center justify-between gap-2 rounded-md pl-3 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground`}
|
|
107
|
+
>
|
|
84
108
|
<a
|
|
85
109
|
href={item.href}
|
|
86
110
|
{...linkTarget(item)}
|
|
@@ -92,14 +116,10 @@ function MobileNavItem({ item }: { item: HeaderModel['navigation'][number] }) {
|
|
|
92
116
|
<Chevron className="size-3 text-muted-foreground transition-transform group-open/submenu:rotate-90" />
|
|
93
117
|
</span>
|
|
94
118
|
</summary>
|
|
95
|
-
<ul className="ml-
|
|
119
|
+
<ul className="mb-1 ml-3 flex flex-col gap-0.5 border-l border-border py-1 pl-2">
|
|
96
120
|
{item.submenu.map((child) => (
|
|
97
121
|
<li key={child.href}>
|
|
98
|
-
<a
|
|
99
|
-
href={child.href}
|
|
100
|
-
{...linkTarget(child)}
|
|
101
|
-
className="flex min-h-11 items-center rounded-md px-2.5 text-sm text-muted-foreground hover:bg-muted hover:text-foreground"
|
|
102
|
-
>
|
|
122
|
+
<a href={child.href} {...linkTarget(child)} className={MOBILE_LINK}>
|
|
103
123
|
{child.label}
|
|
104
124
|
</a>
|
|
105
125
|
</li>
|
|
@@ -120,58 +140,58 @@ export function Header({
|
|
|
120
140
|
}: HeaderModel & { copy: SlotCopy }) {
|
|
121
141
|
const c = (key: string) => fromSlotCopy(copy, `default.header.${key}`)
|
|
122
142
|
|
|
123
|
-
const
|
|
143
|
+
const hasNavigation = navigation.length > 0
|
|
124
144
|
|
|
125
145
|
return (
|
|
126
|
-
<header className="border-b border-border bg-card">
|
|
127
|
-
<div className={`${PAGE} flex flex-wrap items-center
|
|
146
|
+
<header className="relative border-t-4 border-b border-t-primary border-b-border bg-card">
|
|
147
|
+
<div className={`${PAGE} flex flex-wrap items-center gap-x-4 gap-y-3 py-3`}>
|
|
128
148
|
<a
|
|
129
149
|
href={homeHref}
|
|
130
|
-
className=
|
|
150
|
+
className="me-auto inline-flex min-h-9 min-w-0 items-center text-xl font-semibold tracking-tight text-primary transition-colors hover:text-primary-hover"
|
|
131
151
|
>
|
|
132
152
|
<BoardMark boardTitle={boardTitle} logo={logo} />
|
|
133
153
|
</a>
|
|
154
|
+
|
|
134
155
|
{children}
|
|
156
|
+
|
|
157
|
+
{hasNavigation && (
|
|
158
|
+
<nav aria-label={c('sections')} data-nav-view="mobile" className="lg:hidden">
|
|
159
|
+
<details data-nav-disclosure className="group/menu">
|
|
160
|
+
<summary
|
|
161
|
+
className={`${SUMMARY} flex size-11 items-center justify-center rounded-md border border-border text-foreground transition-colors hover:border-primary/40 hover:text-primary group-open/menu:border-primary/40 group-open/menu:bg-primary/8 group-open/menu:text-primary`}
|
|
162
|
+
>
|
|
163
|
+
<MenuGlyph />
|
|
164
|
+
<span className="sr-only">{c('sections')}</span>
|
|
165
|
+
</summary>
|
|
166
|
+
|
|
167
|
+
<div className="absolute inset-x-0 top-full z-30 max-h-[calc(100dvh-5rem)] overflow-y-auto border-b border-border bg-card shadow-elevation">
|
|
168
|
+
<ul className={`${PAGE} flex flex-col gap-0.5 py-2`}>
|
|
169
|
+
{navigation.map((item) => (
|
|
170
|
+
<MobileNavItem key={item.href} item={item} />
|
|
171
|
+
))}
|
|
172
|
+
</ul>
|
|
173
|
+
</div>
|
|
174
|
+
</details>
|
|
175
|
+
</nav>
|
|
176
|
+
)}
|
|
135
177
|
</div>
|
|
136
178
|
|
|
137
|
-
{
|
|
138
|
-
<nav aria-label={c('sections')} className="border-t border-border
|
|
179
|
+
{hasNavigation && (
|
|
180
|
+
<nav aria-label={c('sections')} className="hidden border-t border-border lg:block">
|
|
139
181
|
<div className={PAGE}>
|
|
140
182
|
<ul
|
|
141
183
|
data-nav-view="desktop"
|
|
142
|
-
className=
|
|
143
|
-
opensMenus
|
|
144
|
-
? 'flex-wrap'
|
|
145
|
-
: 'overflow-x-auto [mask-image:linear-gradient(to_right,black_calc(100%-1.5rem),transparent)] sm:[mask-image:none]'
|
|
146
|
-
}`}
|
|
184
|
+
className="-mx-3 flex flex-wrap items-center gap-0.5 py-1 text-sm"
|
|
147
185
|
>
|
|
148
186
|
{navigation.map((item) => (
|
|
149
187
|
<li key={item.href} className="group relative shrink-0">
|
|
150
|
-
<a
|
|
151
|
-
href={item.href}
|
|
152
|
-
{...linkTarget(item)}
|
|
153
|
-
className="inline-flex h-10 items-center rounded-t-md border-b-2 border-transparent px-2.5 font-medium text-muted-foreground transition-colors pointer-coarse:h-11 hover:border-border hover:text-foreground"
|
|
154
|
-
>
|
|
188
|
+
<a href={item.href} {...linkTarget(item)} className={DESKTOP_LINK}>
|
|
155
189
|
{item.label}
|
|
156
190
|
</a>
|
|
157
191
|
<Submenu items={item.submenu} />
|
|
158
192
|
</li>
|
|
159
193
|
))}
|
|
160
194
|
</ul>
|
|
161
|
-
|
|
162
|
-
<div data-nav-view="mobile" className="-mx-4 px-4 py-1 sm:-mx-6 sm:px-6 lg:hidden">
|
|
163
|
-
<details data-nav-disclosure className="group">
|
|
164
|
-
<summary className="flex min-h-11 cursor-default list-none items-center gap-2 text-sm font-medium text-foreground select-none [&::-webkit-details-marker]:hidden [&::marker]:content-none">
|
|
165
|
-
<Chevron className="size-3 shrink-0 transition-transform group-open:rotate-90" />
|
|
166
|
-
{c('sections')}
|
|
167
|
-
</summary>
|
|
168
|
-
<ul className="flex flex-col gap-0.5 pb-2">
|
|
169
|
-
{navigation.map((item) => (
|
|
170
|
-
<MobileNavItem key={item.href} item={item} />
|
|
171
|
-
))}
|
|
172
|
-
</ul>
|
|
173
|
-
</details>
|
|
174
|
-
</div>
|
|
175
195
|
</div>
|
|
176
196
|
</nav>
|
|
177
197
|
)}
|
|
@@ -46,7 +46,7 @@ export function LatestThreads({
|
|
|
46
46
|
{thread.title}
|
|
47
47
|
</a>
|
|
48
48
|
<span className={`shrink-0 text-xs text-muted-foreground ${NUMERIC}`}>
|
|
49
|
-
{thread.replyCount.label}{' '}
|
|
49
|
+
<span className="font-medium text-primary">{thread.replyCount.label}</span>{' '}
|
|
50
50
|
{thread.replyCount.value === 1 ? c('reply.one') : c('reply.other')}
|
|
51
51
|
</span>
|
|
52
52
|
</div>
|
|
@@ -64,9 +64,7 @@ export function MemberProfile({
|
|
|
64
64
|
|
|
65
65
|
<dl className="grid grid-cols-3 gap-3 border-t border-border px-5 py-4">
|
|
66
66
|
<div>
|
|
67
|
-
<dd className={`text-lg font-semibold text-
|
|
68
|
-
{postCount.label}
|
|
69
|
-
</dd>
|
|
67
|
+
<dd className={`text-lg font-semibold text-primary ${NUMERIC}`}>{postCount.label}</dd>
|
|
70
68
|
<dt className="text-xs text-muted-foreground">{c('postsLabel')}</dt>
|
|
71
69
|
</div>
|
|
72
70
|
<div>
|
package/src/slots/navigation.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { NavigationModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { PAGE } from '../shared'
|
|
5
5
|
|
|
6
6
|
export function Navigation({ items, copy }: NavigationModel & { copy: SlotCopy }) {
|
|
7
7
|
if (items.length === 0) return null
|
|
@@ -9,28 +9,29 @@ export function Navigation({ items, copy }: NavigationModel & { copy: SlotCopy }
|
|
|
9
9
|
const c = (key: string) => fromSlotCopy(copy, `default.navigation.${key}`)
|
|
10
10
|
|
|
11
11
|
return (
|
|
12
|
-
<nav aria-label={c('breadcrumb')} className=
|
|
13
|
-
<ol
|
|
14
|
-
className={`${PAGE} flex items-center gap-1.5 overflow-x-auto py-2 text-xs whitespace-nowrap text-muted-foreground`}
|
|
15
|
-
>
|
|
12
|
+
<nav aria-label={c('breadcrumb')} className={`${PAGE} pt-4`}>
|
|
13
|
+
<ol className="-mx-1.5 flex items-center overflow-x-auto py-0.5 text-xs whitespace-nowrap text-muted-foreground">
|
|
16
14
|
{items.map((item, index) => {
|
|
17
15
|
const isLast = index === items.length - 1
|
|
18
16
|
return (
|
|
19
|
-
<li key={item.href} className="flex shrink-0 items-center
|
|
17
|
+
<li key={item.href} className="flex shrink-0 items-center">
|
|
20
18
|
{index > 0 && (
|
|
21
|
-
<span aria-hidden="true" className="text-border select-none">
|
|
19
|
+
<span aria-hidden="true" className="px-0.5 text-border select-none">
|
|
22
20
|
{c('separator')}
|
|
23
21
|
</span>
|
|
24
22
|
)}
|
|
25
23
|
{isLast ? (
|
|
26
24
|
<span
|
|
27
25
|
aria-current="page"
|
|
28
|
-
className="max-w-[24ch] truncate font-medium text-foreground sm:max-w-none"
|
|
26
|
+
className="max-w-[24ch] truncate px-1.5 py-1 font-medium text-foreground sm:max-w-none"
|
|
29
27
|
>
|
|
30
28
|
{item.label}
|
|
31
29
|
</span>
|
|
32
30
|
) : (
|
|
33
|
-
<a
|
|
31
|
+
<a
|
|
32
|
+
href={item.href}
|
|
33
|
+
className="rounded-md px-1.5 py-1 transition-colors hover:bg-primary/8 hover:text-primary"
|
|
34
|
+
>
|
|
34
35
|
{item.label}
|
|
35
36
|
</a>
|
|
36
37
|
)}
|
package/src/slots/panel-nav.tsx
CHANGED
|
@@ -131,7 +131,7 @@ export function PanelNav({
|
|
|
131
131
|
</Disclosure>
|
|
132
132
|
</div>
|
|
133
133
|
|
|
134
|
-
<div className="hidden rounded-
|
|
134
|
+
<div className="hidden rounded-lg border border-border bg-card p-2 shadow-elevation lg:block">
|
|
135
135
|
<Sections label={label} sections={sections} copy={copy} />
|
|
136
136
|
</div>
|
|
137
137
|
</>
|
|
@@ -14,7 +14,7 @@ export function PanelShell({ panel, links, linksLabel, regions, children }: Pane
|
|
|
14
14
|
{links.length > 0 && (
|
|
15
15
|
<nav
|
|
16
16
|
aria-label={linksLabel}
|
|
17
|
-
className="hidden rounded-
|
|
17
|
+
className="hidden rounded-lg border border-border bg-card p-2 shadow-elevation lg:block"
|
|
18
18
|
>
|
|
19
19
|
<p className="px-2.5 pt-1 pb-1.5 text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
20
20
|
{linksLabel}
|
|
@@ -2,6 +2,8 @@ import type { PostActionsSlotModel, SlotCopy } from '@meith/theme-kit'
|
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
3
|
import { buttonVariants, Separator } from '@meith/ui'
|
|
4
4
|
|
|
5
|
+
import { isEmptyRegion } from '../shared'
|
|
6
|
+
|
|
5
7
|
interface Action {
|
|
6
8
|
readonly href: string
|
|
7
9
|
readonly label: string
|
|
@@ -28,7 +30,9 @@ export function PostActions({
|
|
|
28
30
|
actions.moderateHref === null ? null : { href: actions.moderateHref, label: c('moderate') },
|
|
29
31
|
].filter((action): action is Action => action !== null)
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
const extra = isEmptyRegion(children) ? null : children
|
|
34
|
+
|
|
35
|
+
if (reader.length === 0 && staff.length === 0 && extra === null) return null
|
|
32
36
|
|
|
33
37
|
return (
|
|
34
38
|
<nav aria-label={c('nav')} className="flex flex-wrap items-center gap-x-1 gap-y-1">
|
|
@@ -56,7 +60,9 @@ export function PostActions({
|
|
|
56
60
|
</a>
|
|
57
61
|
))}
|
|
58
62
|
|
|
59
|
-
{
|
|
63
|
+
{extra !== null && (
|
|
64
|
+
<span className="ms-auto flex flex-wrap items-center gap-x-1 gap-y-1">{extra}</span>
|
|
65
|
+
)}
|
|
60
66
|
</nav>
|
|
61
67
|
)
|
|
62
68
|
}
|
package/src/slots/post-bit.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PostBitSlotModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import { Alert, AlertDescription, AlertTitle, Avatar, Card } from '@meith/ui'
|
|
3
|
+
import { Alert, AlertDescription, AlertTitle, Avatar, Card, cn } from '@meith/ui'
|
|
4
4
|
|
|
5
|
-
import { groupTags, LINK, MUTED_LINK, NUMERIC, Stamp, UserRef } from '../shared'
|
|
5
|
+
import { groupTags, isEmptyRegion, LINK, MUTED_LINK, NUMERIC, Stamp, UserRef } from '../shared'
|
|
6
6
|
|
|
7
7
|
const VISIBILITY_TINT = {
|
|
8
8
|
visible: '',
|
|
@@ -10,6 +10,8 @@ const VISIBILITY_TINT = {
|
|
|
10
10
|
deleted: 'border-thread-deleted/50 bg-destructive/5',
|
|
11
11
|
} as const
|
|
12
12
|
|
|
13
|
+
const BODY_X = 'px-4 sm:px-5'
|
|
14
|
+
|
|
13
15
|
function StatusBanner({
|
|
14
16
|
visibility,
|
|
15
17
|
copy,
|
|
@@ -74,12 +76,12 @@ function AuthorBlock({
|
|
|
74
76
|
const c = (key: string) => fromSlotCopy(copy, `default.postBit.${key}`)
|
|
75
77
|
|
|
76
78
|
return (
|
|
77
|
-
<div className="flex gap-3 sm:flex-col sm:gap-2 sm:text-center">
|
|
79
|
+
<div className="flex items-center gap-3 sm:flex-col sm:items-stretch sm:gap-2 sm:text-center">
|
|
78
80
|
<Avatar src={author.avatarUrl} name={author.username} size={48} className="sm:self-center" />
|
|
79
81
|
|
|
80
82
|
<div className="min-w-0 flex-1 sm:flex-none">
|
|
81
83
|
<p className="truncate text-sm">
|
|
82
|
-
<UserRef user={author} className="font-semibold
|
|
84
|
+
<UserRef user={author} className="font-semibold" />
|
|
83
85
|
</p>
|
|
84
86
|
|
|
85
87
|
{author.badge != null && (
|
|
@@ -103,7 +105,9 @@ function AuthorBlock({
|
|
|
103
105
|
|
|
104
106
|
{badges}
|
|
105
107
|
|
|
106
|
-
<dl
|
|
108
|
+
<dl
|
|
109
|
+
className={`mt-1 flex flex-wrap gap-x-2 text-xs text-muted-foreground sm:mt-2 sm:flex-col sm:gap-x-0 sm:gap-y-0.5 ${NUMERIC}`}
|
|
110
|
+
>
|
|
107
111
|
<div className="flex gap-1 sm:justify-center">
|
|
108
112
|
<dt className="sr-only">{c('postsLabel')}</dt>
|
|
109
113
|
<dd>
|
|
@@ -130,9 +134,9 @@ function AuthorBlock({
|
|
|
130
134
|
</dl>
|
|
131
135
|
|
|
132
136
|
{author.fields.length > 0 && (
|
|
133
|
-
<dl className="mt-1
|
|
137
|
+
<dl className="mt-1 flex flex-wrap gap-x-3 gap-y-0.5 text-xs text-muted-foreground sm:mt-1.5 sm:flex-col sm:gap-x-0">
|
|
134
138
|
{author.fields.map((field) => (
|
|
135
|
-
<div key={field.label} className="flex
|
|
139
|
+
<div key={field.label} className="flex min-w-0 gap-1 sm:justify-center">
|
|
136
140
|
<dt className="font-medium">{field.label}:</dt>
|
|
137
141
|
<dd className="truncate">{field.value}</dd>
|
|
138
142
|
</div>
|
|
@@ -147,46 +151,57 @@ function AuthorBlock({
|
|
|
147
151
|
export function PostBit({ post, select, regions, copy }: PostBitSlotModel & { copy: SlotCopy }) {
|
|
148
152
|
const c = (key: string) => fromSlotCopy(copy, `default.postBit.${key}`)
|
|
149
153
|
|
|
154
|
+
const actions = regions.actions !== null && !isEmptyRegion(regions.actions)
|
|
155
|
+
const pluginFooter = !isEmptyRegion(regions.pluginFooter)
|
|
156
|
+
|
|
150
157
|
return (
|
|
151
158
|
<Card
|
|
152
159
|
as="article"
|
|
153
160
|
id={`post-${post.number}`}
|
|
154
161
|
data-post-id={post.id}
|
|
155
162
|
data-visibility={post.visibility}
|
|
156
|
-
className={
|
|
163
|
+
className={cn(
|
|
164
|
+
'target:border-primary/60 target:ring-2 target:ring-primary/20',
|
|
165
|
+
VISIBILITY_TINT[post.visibility],
|
|
166
|
+
)}
|
|
157
167
|
>
|
|
158
168
|
<StatusBanner visibility={post.visibility} copy={copy} />
|
|
159
169
|
|
|
160
|
-
<div className="grid grid-cols-
|
|
161
|
-
<div className="border-b border-border
|
|
170
|
+
<div className="grid grid-cols-[minmax(0,1fr)_auto] sm:grid-cols-[10.5rem_minmax(0,1fr)] sm:grid-rows-[auto_minmax(0,1fr)]">
|
|
171
|
+
<div className="col-start-1 row-start-1 min-w-0 border-b border-border py-3 pl-4 sm:row-span-2 sm:border-r sm:border-b-0 sm:bg-surface/60 sm:px-4 sm:py-4">
|
|
162
172
|
<AuthorBlock author={post.author} badges={regions.pluginBadges} copy={copy} />
|
|
163
173
|
</div>
|
|
164
174
|
|
|
165
|
-
<div
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
</
|
|
180
|
-
|
|
175
|
+
<div
|
|
176
|
+
className={`col-start-2 row-start-1 flex flex-col-reverse items-end justify-center gap-1.5 border-b border-border py-3 text-xs text-muted-foreground sm:flex-row sm:items-center sm:justify-between sm:py-1.5 ${BODY_X}`}
|
|
177
|
+
>
|
|
178
|
+
{select === null ? (
|
|
179
|
+
<span className="hidden sm:block" />
|
|
180
|
+
) : (
|
|
181
|
+
<label className="flex min-h-6 items-center gap-2">
|
|
182
|
+
<input
|
|
183
|
+
type="checkbox"
|
|
184
|
+
name={select.name}
|
|
185
|
+
value={select.value}
|
|
186
|
+
form={select.formId}
|
|
187
|
+
className="size-4 accent-primary"
|
|
188
|
+
/>
|
|
189
|
+
<span className="sr-only">{select.label}</span>
|
|
190
|
+
</label>
|
|
191
|
+
)}
|
|
181
192
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
193
|
+
<a
|
|
194
|
+
href={post.permalink}
|
|
195
|
+
className={`inline-flex min-h-8 items-center gap-2 ${MUTED_LINK} ${NUMERIC}`}
|
|
196
|
+
>
|
|
197
|
+
<Stamp at={post.postedAt} />
|
|
198
|
+
<span className="font-semibold text-primary">#{post.number}</span>
|
|
199
|
+
</a>
|
|
200
|
+
</div>
|
|
187
201
|
|
|
202
|
+
<div className="col-span-2 row-start-2 min-w-0 sm:col-span-1 sm:col-start-2">
|
|
188
203
|
{post.ignored !== null ? (
|
|
189
|
-
<div className=
|
|
204
|
+
<div className={`py-5 text-sm text-muted-foreground ${BODY_X}`}>
|
|
190
205
|
{c('ignoringPrefix')}{' '}
|
|
191
206
|
<span className="font-medium text-foreground">{post.ignored.authorUsername}</span>.{' '}
|
|
192
207
|
{c('hiddenNotice')}{' '}
|
|
@@ -195,9 +210,9 @@ export function PostBit({ post, select, regions, copy }: PostBitSlotModel & { co
|
|
|
195
210
|
</a>
|
|
196
211
|
</div>
|
|
197
212
|
) : (
|
|
198
|
-
<div className=
|
|
213
|
+
<div className={`py-4 sm:py-5 ${BODY_X}`}>
|
|
199
214
|
<div
|
|
200
|
-
className="prose-md text-sm"
|
|
215
|
+
className="prose-md text-[0.9375rem] sm:text-base"
|
|
201
216
|
dangerouslySetInnerHTML={{ __html: post.bodyHtml }}
|
|
202
217
|
/>
|
|
203
218
|
|
|
@@ -210,7 +225,7 @@ export function PostBit({ post, select, regions, copy }: PostBitSlotModel & { co
|
|
|
210
225
|
)}
|
|
211
226
|
|
|
212
227
|
{post.attachments.length > 0 && (
|
|
213
|
-
<div className=
|
|
228
|
+
<div className={`border-t border-border py-3 ${BODY_X}`}>
|
|
214
229
|
<h4 className="mb-2 text-xs font-semibold tracking-wide text-muted-foreground uppercase">
|
|
215
230
|
{post.attachments.length}{' '}
|
|
216
231
|
{post.attachments.length === 1 ? c('attachment.one') : c('attachment.other')}
|
|
@@ -246,19 +261,19 @@ export function PostBit({ post, select, regions, copy }: PostBitSlotModel & { co
|
|
|
246
261
|
|
|
247
262
|
{post.ignored === null && post.author.signatureHtml !== null && (
|
|
248
263
|
<div
|
|
249
|
-
className=
|
|
264
|
+
className={`prose-md border-t border-border py-2.5 text-xs text-muted-foreground ${BODY_X}`}
|
|
250
265
|
dangerouslySetInnerHTML={{ __html: post.author.signatureHtml }}
|
|
251
266
|
/>
|
|
252
267
|
)}
|
|
253
268
|
|
|
254
|
-
{
|
|
255
|
-
<div className=
|
|
269
|
+
{pluginFooter && (
|
|
270
|
+
<div className={`border-t border-border py-2 text-xs empty:hidden ${BODY_X}`}>
|
|
256
271
|
{regions.pluginFooter}
|
|
257
272
|
</div>
|
|
258
273
|
)}
|
|
259
274
|
|
|
260
|
-
{
|
|
261
|
-
<footer className=
|
|
275
|
+
{actions && (
|
|
276
|
+
<footer className={`border-t border-border py-1.5 empty:hidden ${BODY_X}`}>
|
|
262
277
|
{regions.actions}
|
|
263
278
|
</footer>
|
|
264
279
|
)}
|
package/src/slots/thread-row.tsx
CHANGED
|
@@ -19,7 +19,7 @@ export function ThreadRow({
|
|
|
19
19
|
<li
|
|
20
20
|
data-unread={thread.isUnread ? '' : undefined}
|
|
21
21
|
data-visibility={hidden ? thread.visibility : undefined}
|
|
22
|
-
className={`grid grid-cols-[auto_minmax(0,1fr)] gap-x-2.5 gap-y-
|
|
22
|
+
className={`grid grid-cols-[auto_minmax(0,1fr)] gap-x-2.5 gap-y-1.5 px-4 py-3 transition-colors hover:bg-muted/60 md:grid-cols-[auto_minmax(0,1fr)_9rem_14rem] md:items-center md:gap-x-4 ${hidden ? tint : ''}`}
|
|
23
23
|
>
|
|
24
24
|
<span className="flex items-start gap-2">
|
|
25
25
|
{select !== null && (
|
|
@@ -65,38 +65,43 @@ export function ThreadRow({
|
|
|
65
65
|
</p>
|
|
66
66
|
</div>
|
|
67
67
|
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
{
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
68
|
+
<div className="col-start-2 flex min-w-0 flex-wrap items-baseline gap-x-4 gap-y-1 text-xs text-muted-foreground md:contents">
|
|
69
|
+
<Counts
|
|
70
|
+
className="md:col-start-3 md:justify-end"
|
|
71
|
+
items={[
|
|
72
|
+
{
|
|
73
|
+
label: c('repliesLabel'),
|
|
74
|
+
value: thread.replyCount,
|
|
75
|
+
one: c('reply.one'),
|
|
76
|
+
many: c('reply.other'),
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
label: c('viewsLabel'),
|
|
80
|
+
value: thread.viewCount,
|
|
81
|
+
one: c('view.one'),
|
|
82
|
+
many: c('view.other'),
|
|
83
|
+
},
|
|
84
|
+
]}
|
|
85
|
+
/>
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
87
|
+
<div className="flex min-w-0 max-w-full flex-wrap gap-x-1 md:col-start-4 md:block">
|
|
88
|
+
{thread.lastPost === null ? (
|
|
89
|
+
<span className="text-thread-moved">{c('noRepliesYet')}</span>
|
|
90
|
+
) : (
|
|
91
|
+
<>
|
|
92
|
+
<a
|
|
93
|
+
href={thread.lastPost.href}
|
|
94
|
+
className={`font-medium text-foreground md:block ${LINK}`}
|
|
95
|
+
>
|
|
96
|
+
{c('latestReply')}
|
|
97
|
+
</a>
|
|
98
|
+
<span className="md:mt-0.5 md:block md:truncate">
|
|
99
|
+
{c('by')} <UserRef user={thread.lastPost.author} className="font-normal" />{' '}
|
|
100
|
+
{c('dot')} <Stamp at={thread.lastPost.at} />
|
|
101
|
+
</span>
|
|
102
|
+
</>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
100
105
|
</div>
|
|
101
106
|
</li>
|
|
102
107
|
)
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { SlotCopy, ThreadViewModel } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import { Badge, buttonVariants } from '@meith/ui'
|
|
3
|
+
import { Badge, buttonVariants, cn } from '@meith/ui'
|
|
4
4
|
|
|
5
|
-
import { Counts,
|
|
5
|
+
import { Counts, PAGE_BODY, Prefix } from '../shared'
|
|
6
6
|
|
|
7
7
|
export function ThreadView({
|
|
8
8
|
thread,
|
|
9
|
-
forum,
|
|
10
9
|
replyHref,
|
|
11
10
|
markReadAction,
|
|
12
11
|
watch,
|
|
@@ -19,13 +18,7 @@ export function ThreadView({
|
|
|
19
18
|
<div className={PAGE_BODY}>
|
|
20
19
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between sm:gap-6">
|
|
21
20
|
<div className="min-w-0">
|
|
22
|
-
<
|
|
23
|
-
{forum.label}
|
|
24
|
-
</a>
|
|
25
|
-
|
|
26
|
-
<h1 className="mt-1 text-2xl font-semibold tracking-tight text-balance sm:mt-0">
|
|
27
|
-
{thread.title}
|
|
28
|
-
</h1>
|
|
21
|
+
<h1 className="text-2xl font-semibold tracking-tight text-balance">{thread.title}</h1>
|
|
29
22
|
|
|
30
23
|
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
31
24
|
{thread.prefix !== null && <Prefix prefix={thread.prefix} />}
|
|
@@ -52,10 +45,10 @@ export function ThreadView({
|
|
|
52
45
|
</div>
|
|
53
46
|
</div>
|
|
54
47
|
|
|
55
|
-
<div className="flex shrink-0 items-center gap-2">
|
|
48
|
+
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
|
56
49
|
{watch != null && (
|
|
57
50
|
<form action={watch.action} method="post">
|
|
58
|
-
<button type="submit" className={buttonVariants({ variant: '
|
|
51
|
+
<button type="submit" className={buttonVariants({ variant: 'outline' })}>
|
|
59
52
|
{watch.subscribed ? (
|
|
60
53
|
<>
|
|
61
54
|
{c('watching')} <span aria-hidden="true">✓</span>
|
|
@@ -74,7 +67,10 @@ export function ThreadView({
|
|
|
74
67
|
</form>
|
|
75
68
|
)}
|
|
76
69
|
{replyHref !== null && (
|
|
77
|
-
<a
|
|
70
|
+
<a
|
|
71
|
+
href={replyHref}
|
|
72
|
+
className={cn(buttonVariants({ variant: 'primary' }), 'ms-auto sm:ms-0')}
|
|
73
|
+
>
|
|
78
74
|
{c('replyAction')}
|
|
79
75
|
</a>
|
|
80
76
|
)}
|