@meith/theme-default 0.35.1 → 0.36.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 +23 -3
- package/src/index.ts +3 -0
- package/src/messages/en.json +11 -0
- package/src/shared.tsx +9 -9
- package/src/slots/auth-page.tsx +8 -9
- package/src/slots/board-index.tsx +20 -8
- package/src/slots/category-block.tsx +1 -1
- package/src/slots/discovery-view.tsx +24 -39
- package/src/slots/error-notice.tsx +2 -2
- package/src/slots/footer.tsx +1 -1
- package/src/slots/forum-display.tsx +12 -8
- package/src/slots/forum-jump.tsx +2 -1
- package/src/slots/forum-row.tsx +11 -9
- package/src/slots/header.tsx +5 -107
- package/src/slots/mobile-header-nav.tsx +80 -0
- package/src/slots/pagination.tsx +2 -2
- package/src/slots/panel-nav.tsx +97 -28
- package/src/slots/panel-page.tsx +8 -10
- package/src/slots/panel-shell.tsx +2 -2
- package/src/slots/post-bit.tsx +1 -1
- package/src/slots/post-form.tsx +3 -3
- package/src/slots/redirect-notice.tsx +2 -2
- package/src/slots/search-results.tsx +23 -40
- package/src/slots/thread-row.tsx +7 -7
- package/src/slots/thread-view.tsx +17 -9
- 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.36.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.36.0",
|
|
24
|
+
"@meith/ui": "^0.36.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": "^19.2.0"
|
package/src/copy.ts
CHANGED
|
@@ -29,7 +29,12 @@ export function announcementCopy(t: Translator): SlotCopy {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
export function boardIndexCopy(t: Translator): SlotCopy {
|
|
32
|
-
return copyFor(t, [
|
|
32
|
+
return copyFor(t, [
|
|
33
|
+
'default.boardIndex.boardActivity',
|
|
34
|
+
'default.boardIndex.markAllRead',
|
|
35
|
+
'default.boardIndex.heading',
|
|
36
|
+
'default.boardIndex.description',
|
|
37
|
+
])
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
export function boardStatsCopy(t: Translator): SlotCopy {
|
|
@@ -103,7 +108,13 @@ export function forumRowCopy(t: Translator): SlotCopy {
|
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
export function headerCopy(t: Translator): SlotCopy {
|
|
106
|
-
return copyFor(t, [
|
|
111
|
+
return copyFor(t, [
|
|
112
|
+
'default.header.sections',
|
|
113
|
+
'default.header.openMenu',
|
|
114
|
+
'default.header.closeMenu',
|
|
115
|
+
'default.header.overview',
|
|
116
|
+
'default.panelNav.open',
|
|
117
|
+
])
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
export function latestPostsCopy(t: Translator): SlotCopy {
|
|
@@ -168,7 +179,16 @@ export function paginationCopy(t: Translator): SlotCopy {
|
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
export function panelNavCopy(t: Translator): SlotCopy {
|
|
171
|
-
return copyFor(t, [
|
|
182
|
+
return copyFor(t, [
|
|
183
|
+
'default.panelNav.countCap',
|
|
184
|
+
'default.panelNav.waiting',
|
|
185
|
+
'default.panelNav.open',
|
|
186
|
+
'default.panelNav.close',
|
|
187
|
+
'default.panelNav.overview',
|
|
188
|
+
'default.panelNav.title.admincp',
|
|
189
|
+
'default.panelNav.title.usercp',
|
|
190
|
+
'default.panelNav.title.modcp',
|
|
191
|
+
])
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
export function panelPageCopy(t: Translator): SlotCopy {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
export { headerCopy as mobileHeaderNavCopy, panelNavCopy as mobilePanelNavCopy } from './copy'
|
|
1
2
|
export { defaultMessages } from './messages'
|
|
3
|
+
export { MobileHeaderNav } from './slots/mobile-header-nav'
|
|
4
|
+
export { MobilePanelNav } from './slots/panel-nav'
|
|
2
5
|
export { defaultTheme } from './theme'
|
|
3
6
|
export {
|
|
4
7
|
BROWSER_THEME_COLOR,
|
package/src/messages/en.json
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"default.announcement.posted": "Posted",
|
|
3
3
|
"default.announcement.postedBy": "Posted by",
|
|
4
4
|
"default.boardIndex.boardActivity": "Board activity",
|
|
5
|
+
"default.boardIndex.description": "Find your next conversation.",
|
|
6
|
+
"default.boardIndex.heading": "Community",
|
|
5
7
|
"default.boardIndex.markAllRead": "Mark all forums read",
|
|
6
8
|
"default.boardStats.counted": "Counted",
|
|
7
9
|
"default.boardStats.heading": "Board statistics",
|
|
@@ -46,6 +48,9 @@
|
|
|
46
48
|
"default.forumRow.thread.one": "thread",
|
|
47
49
|
"default.forumRow.thread.other": "threads",
|
|
48
50
|
"default.forumRow.threadsLabel": "Threads",
|
|
51
|
+
"default.header.closeMenu": "Close navigation",
|
|
52
|
+
"default.header.openMenu": "Open navigation",
|
|
53
|
+
"default.header.overview": "Overview",
|
|
49
54
|
"default.header.sections": "Board sections",
|
|
50
55
|
"default.latestPosts.asOf": "As of",
|
|
51
56
|
"default.latestPosts.dot": "·",
|
|
@@ -81,7 +86,13 @@
|
|
|
81
86
|
"default.pagination.of": "of",
|
|
82
87
|
"default.pagination.page": "Page",
|
|
83
88
|
"default.pagination.previous": "Previous",
|
|
89
|
+
"default.panelNav.close": "Close panel navigation",
|
|
84
90
|
"default.panelNav.countCap": "99+",
|
|
91
|
+
"default.panelNav.open": "Open panel navigation",
|
|
92
|
+
"default.panelNav.overview": "Overview",
|
|
93
|
+
"default.panelNav.title.admincp": "Administration",
|
|
94
|
+
"default.panelNav.title.modcp": "Moderation",
|
|
95
|
+
"default.panelNav.title.usercp": "Your account",
|
|
85
96
|
"default.panelNav.waiting": "waiting",
|
|
86
97
|
"default.panelPage.back": "←",
|
|
87
98
|
"default.postActions.edit": "Edit",
|
package/src/shared.tsx
CHANGED
|
@@ -7,11 +7,11 @@ import type {
|
|
|
7
7
|
} from '@meith/theme-kit'
|
|
8
8
|
import { Badge, cn } from '@meith/ui'
|
|
9
9
|
|
|
10
|
-
const PAGE_WIDTH = 'max-w-
|
|
10
|
+
const PAGE_WIDTH = 'max-w-7xl'
|
|
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-
|
|
14
|
+
export const PAGE_BODY = `${PAGE} flex w-full flex-col gap-6 py-6 sm:py-8`
|
|
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`
|
|
@@ -26,9 +26,6 @@ export const MUTED_LINK = `text-muted-foreground ${LINK}`
|
|
|
26
26
|
|
|
27
27
|
export const BELOW_HEADER = 'top-14'
|
|
28
28
|
|
|
29
|
-
export const PAGE_TITLE =
|
|
30
|
-
'font-heading text-xl font-semibold tracking-tight text-balance sm:text-2xl'
|
|
31
|
-
|
|
32
29
|
export const SECTION_TITLE = 'font-heading text-lg font-semibold tracking-tight text-foreground'
|
|
33
30
|
|
|
34
31
|
export const EYEBROW = 'text-xs font-semibold tracking-wide text-muted-foreground uppercase'
|
|
@@ -163,17 +160,20 @@ export function Figures({ items, className }: { items: readonly CountItem[]; cla
|
|
|
163
160
|
return (
|
|
164
161
|
<dl
|
|
165
162
|
className={cn(
|
|
166
|
-
'flex flex-wrap gap-x-3 gap-y-0.5 text-xs text-muted-foreground
|
|
163
|
+
'flex flex-wrap gap-x-3 gap-y-0.5 text-xs text-muted-foreground @3xl/card:grid @3xl/card:grid-cols-2 @3xl/card:gap-x-5 @3xl/card:text-center',
|
|
167
164
|
className,
|
|
168
165
|
)}
|
|
169
166
|
>
|
|
170
167
|
{items.map((item) => (
|
|
171
|
-
<div
|
|
168
|
+
<div
|
|
169
|
+
key={item.label}
|
|
170
|
+
className="flex items-baseline gap-x-1 @3xl/card:flex-col @3xl/card:gap-0"
|
|
171
|
+
>
|
|
172
172
|
<dt className="sr-only">{item.label}</dt>
|
|
173
|
-
<dd className={cn('font-semibold text-foreground
|
|
173
|
+
<dd className={cn('font-semibold text-foreground @3xl/card:text-sm', NUMERIC)}>
|
|
174
174
|
{item.value.label}
|
|
175
175
|
</dd>
|
|
176
|
-
<dd className="
|
|
176
|
+
<dd className="@3xl/card:text-[0.6875rem] @3xl/card:leading-4">
|
|
177
177
|
{item.value.value === 1 ? item.one : item.many}
|
|
178
178
|
</dd>
|
|
179
179
|
</div>
|
package/src/slots/auth-page.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AuthPageModel } from '@meith/theme-kit'
|
|
2
|
+
import { PageDescription, PageHeader, PageHeaderContent, PageTitle } from '@meith/ui'
|
|
2
3
|
|
|
3
4
|
import { MUTED_LINK } from '../shared'
|
|
4
5
|
|
|
@@ -9,15 +10,13 @@ export function AuthPage({ title, alert, links, regions }: AuthPageModel) {
|
|
|
9
10
|
tabIndex={-1}
|
|
10
11
|
className="flex flex-1 flex-col items-center justify-center px-4 py-10 sm:px-6 sm:py-14"
|
|
11
12
|
>
|
|
12
|
-
<div className="flex w-full max-w-
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
{title}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
)}
|
|
20
|
-
</div>
|
|
13
|
+
<div className="flex w-full max-w-md flex-col gap-6 rounded-2xl border border-border bg-card p-6 text-card-foreground shadow-elevation sm:p-10">
|
|
14
|
+
<PageHeader>
|
|
15
|
+
<PageHeaderContent>
|
|
16
|
+
<PageTitle className="sm:text-2xl">{title}</PageTitle>
|
|
17
|
+
{regions.lede !== undefined && <PageDescription>{regions.lede}</PageDescription>}
|
|
18
|
+
</PageHeaderContent>
|
|
19
|
+
</PageHeader>
|
|
21
20
|
|
|
22
21
|
{alert !== null && (
|
|
23
22
|
<p
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { BoardIndexModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
buttonVariants,
|
|
5
|
+
PageDescription,
|
|
6
|
+
PageHeader,
|
|
7
|
+
PageHeaderContent,
|
|
8
|
+
PageTitle,
|
|
9
|
+
} from '@meith/ui'
|
|
4
10
|
|
|
5
11
|
import { PAGE_BODY } from '../shared'
|
|
6
12
|
|
|
@@ -21,13 +27,19 @@ export function BoardIndex({
|
|
|
21
27
|
<div className="flex flex-col gap-3">{regions.announcements}</div>
|
|
22
28
|
)}
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
<PageHeader>
|
|
31
|
+
<PageHeaderContent>
|
|
32
|
+
<PageTitle>{c('heading')}</PageTitle>
|
|
33
|
+
<PageDescription>{c('description')}</PageDescription>
|
|
34
|
+
</PageHeaderContent>
|
|
35
|
+
{markAllReadAction !== null && (
|
|
36
|
+
<form action={markAllReadAction} method="post">
|
|
37
|
+
<button type="submit" className={buttonVariants({ variant: 'ghost', size: 'sm' })}>
|
|
38
|
+
{c('markAllRead')}
|
|
39
|
+
</button>
|
|
40
|
+
</form>
|
|
41
|
+
)}
|
|
42
|
+
</PageHeader>
|
|
31
43
|
|
|
32
44
|
<div
|
|
33
45
|
className={
|
|
@@ -8,7 +8,7 @@ export function CategoryBlock({ category, children }: CategoryBlockModel) {
|
|
|
8
8
|
|
|
9
9
|
return (
|
|
10
10
|
<Card aria-labelledby={headingId} className="rounded-xl">
|
|
11
|
-
<CardHeader className="flex-col items-start gap-
|
|
11
|
+
<CardHeader className="flex-col items-start gap-1 bg-surface/50 px-5 py-4">
|
|
12
12
|
<CardTitle id={headingId} className="flex items-center gap-2.5 text-base">
|
|
13
13
|
<span aria-hidden="true" className="h-4 w-1 rounded-full bg-primary" />
|
|
14
14
|
<a href={category.href} className={`${LINK} text-foreground`}>
|
|
@@ -1,38 +1,21 @@
|
|
|
1
|
-
import type { DiscoveryViewModel, SlotCopy
|
|
1
|
+
import type { DiscoveryViewModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Avatar,
|
|
5
|
+
buttonVariants,
|
|
6
|
+
Card,
|
|
7
|
+
CardRows,
|
|
8
|
+
cn,
|
|
9
|
+
Empty,
|
|
10
|
+
EmptyDescription,
|
|
11
|
+
NavTabs,
|
|
12
|
+
PageDescription,
|
|
13
|
+
PageHeader,
|
|
14
|
+
PageHeaderContent,
|
|
15
|
+
PageTitle,
|
|
16
|
+
} from '@meith/ui'
|
|
4
17
|
|
|
5
|
-
import { LINK, NUMERIC,
|
|
6
|
-
|
|
7
|
-
const TAB =
|
|
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'
|
|
9
|
-
|
|
10
|
-
function Tabs({ label, tabs }: { label: string; tabs: readonly TabModel[] }) {
|
|
11
|
-
if (tabs.length === 0) return null
|
|
12
|
-
|
|
13
|
-
return (
|
|
14
|
-
<nav aria-label={label} className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
15
|
-
<ul className="inline-flex max-w-full items-center gap-1 overflow-x-auto rounded-lg border border-border bg-surface p-1">
|
|
16
|
-
{tabs.map((tab) => (
|
|
17
|
-
<li key={tab.href} className="shrink-0">
|
|
18
|
-
<a
|
|
19
|
-
href={tab.href}
|
|
20
|
-
{...(tab.isCurrent ? { 'aria-current': 'page' as const } : {})}
|
|
21
|
-
className={cn(
|
|
22
|
-
TAB,
|
|
23
|
-
tab.isCurrent
|
|
24
|
-
? 'bg-card font-semibold text-primary shadow-sm'
|
|
25
|
-
: 'font-medium text-muted-foreground hover:text-foreground',
|
|
26
|
-
)}
|
|
27
|
-
>
|
|
28
|
-
{tab.label}
|
|
29
|
-
</a>
|
|
30
|
-
</li>
|
|
31
|
-
))}
|
|
32
|
-
</ul>
|
|
33
|
-
</nav>
|
|
34
|
-
)
|
|
35
|
-
}
|
|
18
|
+
import { LINK, NUMERIC, PILL, pageAt, Stamp } from '../shared'
|
|
36
19
|
|
|
37
20
|
export function DiscoveryView({
|
|
38
21
|
title,
|
|
@@ -54,12 +37,14 @@ export function DiscoveryView({
|
|
|
54
37
|
tabIndex={-1}
|
|
55
38
|
className={`${pageAt('max-w-4xl')} flex flex-1 flex-col gap-6 py-6 sm:py-8`}
|
|
56
39
|
>
|
|
57
|
-
<
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
40
|
+
<PageHeader>
|
|
41
|
+
<PageHeaderContent>
|
|
42
|
+
<PageTitle>{title}</PageTitle>
|
|
43
|
+
<PageDescription>{blurb}</PageDescription>
|
|
44
|
+
</PageHeaderContent>
|
|
45
|
+
</PageHeader>
|
|
61
46
|
|
|
62
|
-
<
|
|
47
|
+
<NavTabs label={tabsLabel} tabs={tabs} />
|
|
63
48
|
|
|
64
49
|
{refusal !== null ? (
|
|
65
50
|
<p role="alert" className="rounded-lg border border-border bg-card px-4 py-3 text-sm">
|
|
@@ -88,7 +73,7 @@ export function DiscoveryView({
|
|
|
88
73
|
<div className="flex min-w-0 flex-col gap-0.5">
|
|
89
74
|
<a
|
|
90
75
|
href={row.href}
|
|
91
|
-
className={`
|
|
76
|
+
className={`line-clamp-2 [overflow-wrap:anywhere] text-[0.9375rem] font-medium text-foreground ${LINK}`}
|
|
92
77
|
>
|
|
93
78
|
{row.title}
|
|
94
79
|
</a>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ErrorNoticeModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import { buttonVariants, Card, CardContent, CardFooter } from '@meith/ui'
|
|
3
|
+
import { buttonVariants, Card, CardContent, CardFooter, PageTitle } from '@meith/ui'
|
|
4
4
|
|
|
5
5
|
import { NUMERIC } from '../shared'
|
|
6
6
|
|
|
@@ -20,7 +20,7 @@ export function ErrorNotice({
|
|
|
20
20
|
<p className={`text-xs font-medium tracking-wide text-destructive uppercase ${NUMERIC}`}>
|
|
21
21
|
{c('error')} {status}
|
|
22
22
|
</p>
|
|
23
|
-
<
|
|
23
|
+
<PageTitle className="mt-1 sm:text-2xl">{title}</PageTitle>
|
|
24
24
|
<p className="mt-2 text-sm text-muted-foreground">{message}</p>
|
|
25
25
|
|
|
26
26
|
<a href={homeHref} className={`mt-5 ${buttonVariants({ variant: 'primary' })}`}>
|
package/src/slots/footer.tsx
CHANGED
|
@@ -17,7 +17,7 @@ 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-
|
|
20
|
+
className={`${PAGE} flex flex-wrap items-center justify-between gap-x-6 gap-y-3 border-b border-border py-3`}
|
|
21
21
|
>
|
|
22
22
|
{regions.controls}
|
|
23
23
|
</div>
|
|
@@ -9,9 +9,13 @@ import {
|
|
|
9
9
|
EmptyAction,
|
|
10
10
|
EmptyDescription,
|
|
11
11
|
EmptyTitle,
|
|
12
|
+
PageHeader,
|
|
13
|
+
PageHeaderActions,
|
|
14
|
+
PageHeaderContent,
|
|
15
|
+
PageTitle,
|
|
12
16
|
} from '@meith/ui'
|
|
13
17
|
|
|
14
|
-
import { Counts, isEmptyRegion, PAGE_BODY
|
|
18
|
+
import { Counts, isEmptyRegion, PAGE_BODY } from '../shared'
|
|
15
19
|
|
|
16
20
|
export function ForumDisplay({
|
|
17
21
|
forum,
|
|
@@ -24,9 +28,9 @@ export function ForumDisplay({
|
|
|
24
28
|
|
|
25
29
|
return (
|
|
26
30
|
<div className={PAGE_BODY}>
|
|
27
|
-
<
|
|
28
|
-
<
|
|
29
|
-
<
|
|
31
|
+
<PageHeader>
|
|
32
|
+
<PageHeaderContent>
|
|
33
|
+
<PageTitle>{forum.title}</PageTitle>
|
|
30
34
|
<div className="mt-1 flex flex-wrap items-baseline gap-x-3 gap-y-1 text-sm text-muted-foreground">
|
|
31
35
|
{forum.description !== null && <p className="max-w-prose">{forum.description}</p>}
|
|
32
36
|
{forum.type !== 'link' && (
|
|
@@ -48,9 +52,9 @@ export function ForumDisplay({
|
|
|
48
52
|
/>
|
|
49
53
|
)}
|
|
50
54
|
</div>
|
|
51
|
-
</
|
|
55
|
+
</PageHeaderContent>
|
|
52
56
|
|
|
53
|
-
<
|
|
57
|
+
<PageHeaderActions>
|
|
54
58
|
{markReadAction !== null && (
|
|
55
59
|
<form action={markReadAction} method="post">
|
|
56
60
|
<button type="submit" className={buttonVariants({ variant: 'ghost' })}>
|
|
@@ -66,8 +70,8 @@ export function ForumDisplay({
|
|
|
66
70
|
{c('newThread')}
|
|
67
71
|
</a>
|
|
68
72
|
)}
|
|
69
|
-
</
|
|
70
|
-
</
|
|
73
|
+
</PageHeaderActions>
|
|
74
|
+
</PageHeader>
|
|
71
75
|
|
|
72
76
|
{regions.announcements !== undefined && (
|
|
73
77
|
<div className="flex flex-col gap-3">{regions.announcements}</div>
|
package/src/slots/forum-jump.tsx
CHANGED
|
@@ -13,10 +13,11 @@ export function ForumJump({ action, field, forums, submitLabel, label }: ForumJu
|
|
|
13
13
|
</Label>
|
|
14
14
|
|
|
15
15
|
<NativeSelect
|
|
16
|
+
controlSize="sm"
|
|
16
17
|
id={id}
|
|
17
18
|
name={field}
|
|
18
19
|
defaultValue={forums.find((forum) => forum.isSelected)?.value ?? ''}
|
|
19
|
-
className="h-8 w-auto min-w-
|
|
20
|
+
className="h-8 w-auto min-w-0 max-w-full flex-1 sm:w-48 text-xs sm:flex-none"
|
|
20
21
|
>
|
|
21
22
|
{forums.map((forum) => (
|
|
22
23
|
<option
|
package/src/slots/forum-row.tsx
CHANGED
|
@@ -29,15 +29,17 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
29
29
|
<li
|
|
30
30
|
data-unread={forum.isUnread ? '' : undefined}
|
|
31
31
|
className={
|
|
32
|
-
'grid grid-cols-[auto_minmax(0,1fr)] gap-x-3.5 gap-y-2 px-4 py-
|
|
33
|
-
(isLink
|
|
32
|
+
'grid grid-cols-[auto_minmax(0,1fr)] gap-x-3.5 gap-y-2 px-4 py-4 transition-colors hover:bg-muted/50 sm:px-5' +
|
|
33
|
+
(isLink
|
|
34
|
+
? ''
|
|
35
|
+
: ' @3xl/card:grid-cols-[auto_minmax(0,1fr)_6rem_13rem] @3xl/card:items-center @3xl/card:gap-x-5')
|
|
34
36
|
}
|
|
35
37
|
>
|
|
36
|
-
<Tile label={forum.title} unread={forum.isUnread} className="mt-0.5
|
|
38
|
+
<Tile label={forum.title} unread={forum.isUnread} className="mt-0.5 @3xl/card:mt-0">
|
|
37
39
|
{isLink ? <LinkGlyph /> : undefined}
|
|
38
40
|
</Tile>
|
|
39
41
|
|
|
40
|
-
<div className="min-w-0">
|
|
42
|
+
<div className="min-w-0 [overflow-wrap:anywhere]">
|
|
41
43
|
<a
|
|
42
44
|
href={forum.href}
|
|
43
45
|
className={
|
|
@@ -70,9 +72,9 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
70
72
|
</div>
|
|
71
73
|
|
|
72
74
|
{!isLink && (
|
|
73
|
-
<div className="col-start-2 flex min-w-0 flex-col gap-y-1 text-xs text-muted-foreground
|
|
75
|
+
<div className="col-start-2 flex min-w-0 flex-col gap-y-1 text-xs text-muted-foreground @3xl/card:contents">
|
|
74
76
|
<Figures
|
|
75
|
-
className="
|
|
77
|
+
className="@3xl/card:col-start-3 @3xl/card:justify-self-end"
|
|
76
78
|
items={[
|
|
77
79
|
{
|
|
78
80
|
label: c('threadsLabel'),
|
|
@@ -89,18 +91,18 @@ export function ForumRow({ forum, copy }: ForumRowSlotModel & { copy: SlotCopy }
|
|
|
89
91
|
]}
|
|
90
92
|
/>
|
|
91
93
|
|
|
92
|
-
<div className="order-first flex min-w-0 max-w-full flex-wrap gap-x-1
|
|
94
|
+
<div className="order-first flex min-w-0 max-w-full flex-wrap gap-x-1 @3xl/card:order-none @3xl/card:col-start-4 @3xl/card:block @3xl/card:border-l @3xl/card:border-border @3xl/card:pl-5">
|
|
93
95
|
{forum.lastPost === null ? (
|
|
94
96
|
<span className="text-forum-read">{c('noPostsYet')}</span>
|
|
95
97
|
) : (
|
|
96
98
|
<>
|
|
97
99
|
<a
|
|
98
100
|
href={forum.lastPost.href}
|
|
99
|
-
className={`max-w-full truncate font-medium text-foreground
|
|
101
|
+
className={`max-w-full truncate font-medium text-foreground @3xl/card:block ${LINK}`}
|
|
100
102
|
>
|
|
101
103
|
{forum.lastPost.threadTitle}
|
|
102
104
|
</a>
|
|
103
|
-
<span className="
|
|
105
|
+
<span className="@3xl/card:mt-0.5 @3xl/card:block @3xl/card:truncate">
|
|
104
106
|
{c('by')} <UserRef user={forum.lastPost.author} className="font-normal" />{' '}
|
|
105
107
|
{c('dot')} <Stamp at={forum.lastPost.at} />
|
|
106
108
|
</span>
|
package/src/slots/header.tsx
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
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
|
+
import { MobileHeaderNav } from './mobile-header-nav'
|
|
5
6
|
|
|
6
7
|
const DESKTOP_LINK =
|
|
7
8
|
'inline-flex h-9 items-center rounded-full px-3 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground'
|
|
8
9
|
|
|
9
|
-
const MOBILE_LINK =
|
|
10
|
-
'flex min-h-11 items-center rounded-lg px-3 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
11
|
-
|
|
12
|
-
const SUMMARY =
|
|
13
|
-
'cursor-default list-none select-none [&::-webkit-details-marker]:hidden [&::marker]:content-none'
|
|
14
|
-
|
|
15
10
|
function BoardMark({ boardTitle, logo }: Pick<HeaderModel, 'boardTitle' | 'logo'>) {
|
|
16
11
|
if (logo === undefined) {
|
|
17
12
|
return (
|
|
@@ -50,7 +45,7 @@ function Submenu({ items }: { items: HeaderModel['navigation'][number]['submenu'
|
|
|
50
45
|
if (items === undefined || items.length === 0) return null
|
|
51
46
|
|
|
52
47
|
return (
|
|
53
|
-
<ul className="invisible absolute top-full left-0 z-30
|
|
48
|
+
<ul className="invisible absolute top-full left-0 z-30 min-w-52 rounded-xl border border-border bg-card p-1.5 opacity-0 shadow-elevation transition-opacity group-focus-within:visible group-focus-within:opacity-100 group-hover:visible group-hover:opacity-100">
|
|
54
49
|
{items.map((child) => (
|
|
55
50
|
<li key={child.href}>
|
|
56
51
|
<a
|
|
@@ -66,82 +61,6 @@ function Submenu({ items }: { items: HeaderModel['navigation'][number]['submenu'
|
|
|
66
61
|
)
|
|
67
62
|
}
|
|
68
63
|
|
|
69
|
-
function Chevron({ className }: { className: string }) {
|
|
70
|
-
return (
|
|
71
|
-
<svg
|
|
72
|
-
aria-hidden="true"
|
|
73
|
-
viewBox="0 0 12 12"
|
|
74
|
-
className={className}
|
|
75
|
-
fill="none"
|
|
76
|
-
stroke="currentColor"
|
|
77
|
-
strokeWidth="1.5"
|
|
78
|
-
strokeLinecap="round"
|
|
79
|
-
strokeLinejoin="round"
|
|
80
|
-
>
|
|
81
|
-
<path d="m4.5 3 3 3-3 3" />
|
|
82
|
-
</svg>
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function MenuGlyph() {
|
|
87
|
-
return (
|
|
88
|
-
<svg
|
|
89
|
-
aria-hidden="true"
|
|
90
|
-
viewBox="0 0 20 20"
|
|
91
|
-
className="size-5"
|
|
92
|
-
fill="none"
|
|
93
|
-
stroke="currentColor"
|
|
94
|
-
strokeWidth="1.75"
|
|
95
|
-
strokeLinecap="round"
|
|
96
|
-
>
|
|
97
|
-
<path className="group-open/menu:hidden" d="M3 5.5h14M3 10h14M3 14.5h14" />
|
|
98
|
-
<path className="hidden group-open/menu:block" d="m5 5 10 10M15 5 5 15" />
|
|
99
|
-
</svg>
|
|
100
|
-
)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function MobileNavItem({ item }: { item: HeaderModel['navigation'][number] }) {
|
|
104
|
-
if (item.submenu === undefined || item.submenu.length === 0) {
|
|
105
|
-
return (
|
|
106
|
-
<li>
|
|
107
|
-
<a href={item.href} {...linkTarget(item)} className={MOBILE_LINK}>
|
|
108
|
-
{item.label}
|
|
109
|
-
</a>
|
|
110
|
-
</li>
|
|
111
|
-
)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<li>
|
|
116
|
-
<details data-nav-disclosure name="default-header-submenu" className="group/submenu">
|
|
117
|
-
<summary
|
|
118
|
-
className={`${SUMMARY} flex min-h-11 items-center justify-between gap-2 rounded-lg pl-3 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground`}
|
|
119
|
-
>
|
|
120
|
-
<a
|
|
121
|
-
href={item.href}
|
|
122
|
-
{...linkTarget(item)}
|
|
123
|
-
className="min-w-0 flex-1 truncate hover:underline"
|
|
124
|
-
>
|
|
125
|
-
{item.label}
|
|
126
|
-
</a>
|
|
127
|
-
<span className="flex size-11 shrink-0 items-center justify-center">
|
|
128
|
-
<Chevron className="size-3 text-muted-foreground transition-transform group-open/submenu:rotate-90" />
|
|
129
|
-
</span>
|
|
130
|
-
</summary>
|
|
131
|
-
<ul className="mb-1 ml-3 flex flex-col gap-0.5 border-l border-border py-1 pl-2">
|
|
132
|
-
{item.submenu.map((child) => (
|
|
133
|
-
<li key={child.href}>
|
|
134
|
-
<a href={child.href} {...linkTarget(child)} className={MOBILE_LINK}>
|
|
135
|
-
{child.label}
|
|
136
|
-
</a>
|
|
137
|
-
</li>
|
|
138
|
-
))}
|
|
139
|
-
</ul>
|
|
140
|
-
</details>
|
|
141
|
-
</li>
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
64
|
export function Header({
|
|
146
65
|
boardTitle,
|
|
147
66
|
homeHref,
|
|
@@ -165,7 +84,7 @@ export function Header({
|
|
|
165
84
|
<div className={`${PAGE} flex min-h-14 items-center gap-x-3 sm:gap-x-4`}>
|
|
166
85
|
<a
|
|
167
86
|
href={homeHref}
|
|
168
|
-
className="flex min-w-0
|
|
87
|
+
className="flex min-w-0 items-center gap-2.5 text-lg font-semibold tracking-tight text-foreground transition-colors hover:text-primary"
|
|
169
88
|
>
|
|
170
89
|
<BoardMark boardTitle={boardTitle} logo={logo} />
|
|
171
90
|
</a>
|
|
@@ -201,28 +120,7 @@ export function Header({
|
|
|
201
120
|
|
|
202
121
|
<div className="ms-auto flex min-w-0 shrink-0 items-center gap-2 sm:gap-3">{children}</div>
|
|
203
122
|
|
|
204
|
-
{
|
|
205
|
-
<nav aria-label={c('sections')} data-nav-view="mobile" className="lg:hidden">
|
|
206
|
-
<details data-nav-disclosure className="group/menu">
|
|
207
|
-
<summary
|
|
208
|
-
className={`${SUMMARY} flex size-10 items-center justify-center rounded-lg border border-border bg-card 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`}
|
|
209
|
-
>
|
|
210
|
-
<MenuGlyph />
|
|
211
|
-
<span className="sr-only">{c('sections')}</span>
|
|
212
|
-
</summary>
|
|
213
|
-
|
|
214
|
-
<div
|
|
215
|
-
className={`absolute inset-x-0 ${BELOW_HEADER} z-30 max-h-[calc(100dvh-3.5rem)] overflow-y-auto border-b border-border bg-card shadow-elevation`}
|
|
216
|
-
>
|
|
217
|
-
<ul className={`${PAGE} flex flex-col gap-0.5 py-2`}>
|
|
218
|
-
{navigation.map((item) => (
|
|
219
|
-
<MobileNavItem key={item.href} item={item} />
|
|
220
|
-
))}
|
|
221
|
-
</ul>
|
|
222
|
-
</div>
|
|
223
|
-
</details>
|
|
224
|
-
</nav>
|
|
225
|
-
)}
|
|
123
|
+
<MobileHeaderNav boardTitle={boardTitle} navigation={navigation} copy={copy} />
|
|
226
124
|
</div>
|
|
227
125
|
</header>
|
|
228
126
|
)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { HeaderModel, SlotCopy } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy, linkTarget } from '@meith/theme-kit'
|
|
3
|
+
import { NavigationDrawer, NavigationDrawerTrigger } from '@meith/ui'
|
|
4
|
+
|
|
5
|
+
const LINK =
|
|
6
|
+
'flex min-h-11 items-center rounded-lg px-3 py-2 text-sm font-medium text-foreground hover:bg-muted'
|
|
7
|
+
|
|
8
|
+
export function MobileHeaderNav({
|
|
9
|
+
boardTitle,
|
|
10
|
+
navigation,
|
|
11
|
+
copy,
|
|
12
|
+
}: Pick<HeaderModel, 'boardTitle' | 'navigation'> & { copy: SlotCopy }) {
|
|
13
|
+
const c = (key: string) => fromSlotCopy(copy, `default.header.${key}`)
|
|
14
|
+
return (
|
|
15
|
+
<nav data-nav-view="mobile" aria-label={c('sections')} className="lg:hidden">
|
|
16
|
+
{(['usercp', 'modcp'] as const).map((panel) => (
|
|
17
|
+
<NavigationDrawerTrigger
|
|
18
|
+
key={panel}
|
|
19
|
+
target={`panel-${panel}-navigation`}
|
|
20
|
+
data-panel-nav-trigger={panel}
|
|
21
|
+
label={fromSlotCopy(copy, 'default.panelNav.open')}
|
|
22
|
+
/>
|
|
23
|
+
))}
|
|
24
|
+
{navigation.length > 0 && (
|
|
25
|
+
<div data-main-navigation>
|
|
26
|
+
<NavigationDrawer
|
|
27
|
+
id="board-mobile-navigation"
|
|
28
|
+
title={boardTitle}
|
|
29
|
+
openLabel={c('openMenu')}
|
|
30
|
+
closeLabel={c('closeMenu')}
|
|
31
|
+
>
|
|
32
|
+
<ul className="flex flex-col gap-1">
|
|
33
|
+
{navigation.map((item) => (
|
|
34
|
+
<li key={item.href}>
|
|
35
|
+
{item.submenu !== undefined && item.submenu.length > 0 ? (
|
|
36
|
+
<details className="group/section">
|
|
37
|
+
<summary
|
|
38
|
+
className={`${LINK} cursor-pointer list-none justify-between gap-3 select-none [&::-webkit-details-marker]:hidden [&::marker]:content-none`}
|
|
39
|
+
>
|
|
40
|
+
<span className="min-w-0 break-words">{item.label}</span>
|
|
41
|
+
<svg
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
viewBox="0 0 16 16"
|
|
44
|
+
fill="none"
|
|
45
|
+
stroke="currentColor"
|
|
46
|
+
strokeWidth="1.5"
|
|
47
|
+
className="size-4 shrink-0 text-muted-foreground transition-transform group-open/section:rotate-90"
|
|
48
|
+
>
|
|
49
|
+
<path d="m6 4 4 4-4 4" />
|
|
50
|
+
</svg>
|
|
51
|
+
</summary>
|
|
52
|
+
<ul className="my-1 ms-3 flex flex-col gap-1 border-s border-border ps-2">
|
|
53
|
+
<li>
|
|
54
|
+
<a href={item.href} {...linkTarget(item)} className={LINK}>
|
|
55
|
+
{c('overview')}
|
|
56
|
+
</a>
|
|
57
|
+
</li>
|
|
58
|
+
{item.submenu.map((child) => (
|
|
59
|
+
<li key={child.href}>
|
|
60
|
+
<a href={child.href} {...linkTarget(child)} className={LINK}>
|
|
61
|
+
{child.label}
|
|
62
|
+
</a>
|
|
63
|
+
</li>
|
|
64
|
+
))}
|
|
65
|
+
</ul>
|
|
66
|
+
</details>
|
|
67
|
+
) : (
|
|
68
|
+
<a href={item.href} {...linkTarget(item)} className={LINK}>
|
|
69
|
+
{item.label}
|
|
70
|
+
</a>
|
|
71
|
+
)}
|
|
72
|
+
</li>
|
|
73
|
+
))}
|
|
74
|
+
</ul>
|
|
75
|
+
</NavigationDrawer>
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
78
|
+
</nav>
|
|
79
|
+
)
|
|
80
|
+
}
|
package/src/slots/pagination.tsx
CHANGED
|
@@ -17,7 +17,7 @@ export function Pagination({
|
|
|
17
17
|
|
|
18
18
|
const c = (key: string) => fromSlotCopy(copy, `default.pagination.${key}`)
|
|
19
19
|
|
|
20
|
-
const step = cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'min-w-20
|
|
20
|
+
const step = cn(buttonVariants({ variant: 'outline', size: 'sm' }), 'min-w-20')
|
|
21
21
|
const stepDisabled = cn(step, 'pointer-events-none opacity-40')
|
|
22
22
|
|
|
23
23
|
return (
|
|
@@ -53,7 +53,7 @@ export function Pagination({
|
|
|
53
53
|
variant: entry.isCurrent ? 'primary' : 'ghost',
|
|
54
54
|
size: 'sm',
|
|
55
55
|
}),
|
|
56
|
-
'min-w-8
|
|
56
|
+
'min-w-8 px-2 pointer-coarse:min-w-11',
|
|
57
57
|
NUMERIC,
|
|
58
58
|
entry.isCurrent ? '' : 'hidden sm:inline-flex',
|
|
59
59
|
)}
|
package/src/slots/panel-nav.tsx
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import type { PanelNavItemModel, PanelNavModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import { cn,
|
|
3
|
+
import { cn, NavigationDrawer } from '@meith/ui'
|
|
4
4
|
|
|
5
5
|
import { PanelIcon } from '../panel-icons'
|
|
6
|
-
import { BELOW_HEADER } from '../shared'
|
|
7
6
|
|
|
8
7
|
const ITEM =
|
|
9
|
-
'group/item relative flex items-center gap-3 rounded-lg px-3 py-2 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
8
|
+
'group/item relative flex items-center gap-3 rounded-lg px-3 py-2 pointer-coarse:min-h-11 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
10
9
|
|
|
11
10
|
const HERE = 'bg-primary/10 font-semibold text-primary [&_svg]:text-primary'
|
|
12
11
|
const OPEN = 'font-semibold text-foreground hover:bg-muted'
|
|
@@ -30,26 +29,30 @@ function Count({ count, copy }: { count: number; copy: SlotCopy }) {
|
|
|
30
29
|
)
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
className,
|
|
36
|
-
copy,
|
|
37
|
-
}: {
|
|
38
|
-
item: PanelNavItemModel
|
|
39
|
-
className: string
|
|
40
|
-
copy: SlotCopy
|
|
41
|
-
}) {
|
|
42
|
-
const body = (
|
|
32
|
+
function ItemContent({ item, copy }: { item: PanelNavItemModel; copy: SlotCopy }) {
|
|
33
|
+
return (
|
|
43
34
|
<>
|
|
44
35
|
{item.icon !== null && (
|
|
45
36
|
<span className="text-muted-foreground/70 transition-colors group-hover/item:text-foreground">
|
|
46
37
|
<PanelIcon icon={item.icon} />
|
|
47
38
|
</span>
|
|
48
39
|
)}
|
|
49
|
-
<span className="min-w-0 flex-1
|
|
40
|
+
<span className="min-w-0 flex-1 text-start [overflow-wrap:anywhere]">{item.title}</span>
|
|
50
41
|
{item.count !== null && <Count count={item.count} copy={copy} />}
|
|
51
42
|
</>
|
|
52
43
|
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function Item({
|
|
47
|
+
item,
|
|
48
|
+
className,
|
|
49
|
+
copy,
|
|
50
|
+
}: {
|
|
51
|
+
item: PanelNavItemModel
|
|
52
|
+
className: string
|
|
53
|
+
copy: SlotCopy
|
|
54
|
+
}) {
|
|
55
|
+
const body = <ItemContent item={item} copy={copy} />
|
|
53
56
|
|
|
54
57
|
if (item.isRecord) {
|
|
55
58
|
return (
|
|
@@ -113,28 +116,94 @@ function Sections({
|
|
|
113
116
|
)
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
export function
|
|
119
|
+
export function MobilePanelNav({
|
|
120
|
+
panel,
|
|
117
121
|
label,
|
|
118
122
|
sections,
|
|
119
|
-
currentTitle,
|
|
120
123
|
copy,
|
|
121
124
|
}: PanelNavModel & { copy: SlotCopy }) {
|
|
125
|
+
const c = (key: string) => fromSlotCopy(copy, `default.panelNav.${key}`)
|
|
126
|
+
|
|
122
127
|
return (
|
|
123
|
-
|
|
124
|
-
<
|
|
125
|
-
|
|
128
|
+
<div className="lg:hidden">
|
|
129
|
+
<NavigationDrawer
|
|
130
|
+
showTrigger={false}
|
|
131
|
+
id={`panel-${panel}-navigation`}
|
|
132
|
+
title={c(`title.${panel}`)}
|
|
133
|
+
openLabel={c('open')}
|
|
134
|
+
closeLabel={c('close')}
|
|
126
135
|
>
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
136
|
+
<nav aria-label={label} className="text-sm">
|
|
137
|
+
<ul className="flex flex-col gap-1">
|
|
138
|
+
{sections.map((section) => (
|
|
139
|
+
<li
|
|
140
|
+
key={section.href}
|
|
141
|
+
className={cn(section.isOverview && 'mb-2 border-b border-border pb-2')}
|
|
142
|
+
>
|
|
143
|
+
{section.children.length > 0 ? (
|
|
144
|
+
<details open={section.isOpen} className="group/section">
|
|
145
|
+
<summary
|
|
146
|
+
className={cn(
|
|
147
|
+
ITEM,
|
|
148
|
+
'min-h-11 cursor-pointer list-none [&::-webkit-details-marker]:hidden',
|
|
149
|
+
section.isOpen ? OPEN : ELSEWHERE,
|
|
150
|
+
)}
|
|
151
|
+
>
|
|
152
|
+
<ItemContent item={section} copy={copy} />
|
|
153
|
+
<svg
|
|
154
|
+
aria-hidden="true"
|
|
155
|
+
viewBox="0 0 16 16"
|
|
156
|
+
className="size-4 shrink-0 transition-transform group-open/section:rotate-90"
|
|
157
|
+
fill="none"
|
|
158
|
+
stroke="currentColor"
|
|
159
|
+
strokeWidth="1.5"
|
|
160
|
+
strokeLinecap="round"
|
|
161
|
+
strokeLinejoin="round"
|
|
162
|
+
>
|
|
163
|
+
<path d="m6 4 4 4-4 4" />
|
|
164
|
+
</svg>
|
|
165
|
+
</summary>
|
|
166
|
+
<ul className="my-1 ms-5 flex flex-col gap-1 border-s-2 border-border ps-2">
|
|
167
|
+
<li>
|
|
168
|
+
<Item
|
|
169
|
+
item={{ ...section, title: c('overview'), icon: null, count: null }}
|
|
170
|
+
className={cn('min-h-11', section.current === 'here' ? HERE : ELSEWHERE)}
|
|
171
|
+
copy={copy}
|
|
172
|
+
/>
|
|
173
|
+
</li>
|
|
174
|
+
{section.children.map((child) => (
|
|
175
|
+
<li key={child.href}>
|
|
176
|
+
<Item
|
|
177
|
+
item={child}
|
|
178
|
+
className={cn('min-h-11', child.current === 'here' ? HERE : ELSEWHERE)}
|
|
179
|
+
copy={copy}
|
|
180
|
+
/>
|
|
181
|
+
</li>
|
|
182
|
+
))}
|
|
183
|
+
</ul>
|
|
184
|
+
</details>
|
|
185
|
+
) : (
|
|
186
|
+
<Item
|
|
187
|
+
item={section}
|
|
188
|
+
className={cn('min-h-11', section.current === 'here' ? HERE : ELSEWHERE)}
|
|
189
|
+
copy={copy}
|
|
190
|
+
/>
|
|
191
|
+
)}
|
|
192
|
+
</li>
|
|
193
|
+
))}
|
|
194
|
+
</ul>
|
|
195
|
+
</nav>
|
|
196
|
+
</NavigationDrawer>
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
135
200
|
|
|
201
|
+
export function PanelNav(props: PanelNavModel & { copy: SlotCopy }) {
|
|
202
|
+
return (
|
|
203
|
+
<>
|
|
204
|
+
<MobilePanelNav {...props} />
|
|
136
205
|
<div className="hidden rounded-xl border border-border bg-card p-2 shadow-elevation lg:block">
|
|
137
|
-
<Sections label={label} sections={sections} copy={copy} />
|
|
206
|
+
<Sections label={props.label} sections={props.sections} copy={props.copy} />
|
|
138
207
|
</div>
|
|
139
208
|
</>
|
|
140
209
|
)
|
package/src/slots/panel-page.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PanelPageModel, PanelSectionModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import { cn } from '@meith/ui'
|
|
3
|
+
import { cn, PageHeader, PageHeaderActions, PageHeaderContent, PageTitle } from '@meith/ui'
|
|
4
4
|
|
|
5
|
-
import { MUTED_LINK, PAGE,
|
|
5
|
+
import { MUTED_LINK, PAGE, pageAt, SECTION_TITLE } from '../shared'
|
|
6
6
|
|
|
7
7
|
export function PanelPage({
|
|
8
8
|
title,
|
|
@@ -28,8 +28,8 @@ export function PanelPage({
|
|
|
28
28
|
gap === 'loose' ? 'gap-8' : 'gap-6',
|
|
29
29
|
)}
|
|
30
30
|
>
|
|
31
|
-
<
|
|
32
|
-
<
|
|
31
|
+
<PageHeader className="border-b border-border pb-5">
|
|
32
|
+
<PageHeaderContent>
|
|
33
33
|
{back !== null && (
|
|
34
34
|
<a
|
|
35
35
|
href={back.href}
|
|
@@ -39,7 +39,7 @@ export function PanelPage({
|
|
|
39
39
|
</a>
|
|
40
40
|
)}
|
|
41
41
|
|
|
42
|
-
<
|
|
42
|
+
<PageTitle>{title}</PageTitle>
|
|
43
43
|
|
|
44
44
|
{regions.lede !== undefined && (
|
|
45
45
|
<p className="max-w-prose text-sm text-muted-foreground">{regions.lede}</p>
|
|
@@ -47,12 +47,10 @@ export function PanelPage({
|
|
|
47
47
|
{regions.meta !== undefined && (
|
|
48
48
|
<p className="text-xs text-muted-foreground">{regions.meta}</p>
|
|
49
49
|
)}
|
|
50
|
-
</
|
|
50
|
+
</PageHeaderContent>
|
|
51
51
|
|
|
52
|
-
{regions.actions !== undefined &&
|
|
53
|
-
|
|
54
|
-
)}
|
|
55
|
-
</div>
|
|
52
|
+
{regions.actions !== undefined && <PageHeaderActions>{regions.actions}</PageHeaderActions>}
|
|
53
|
+
</PageHeader>
|
|
56
54
|
|
|
57
55
|
{children}
|
|
58
56
|
</main>
|
|
@@ -3,7 +3,7 @@ import type { PanelShellModel } from '@meith/theme-kit'
|
|
|
3
3
|
import { EYEBROW, MUTED_LINK } from '../shared'
|
|
4
4
|
|
|
5
5
|
const RAIL =
|
|
6
|
-
'flex flex-col gap-4
|
|
6
|
+
'flex flex-col gap-4 lg:px-6 lg:sticky lg:w-64 lg:shrink-0 lg:self-start lg:pt-8 lg:pr-0'
|
|
7
7
|
|
|
8
8
|
function SwitchGlyph() {
|
|
9
9
|
return (
|
|
@@ -24,7 +24,7 @@ function SwitchGlyph() {
|
|
|
24
24
|
|
|
25
25
|
export function PanelShell({ links, linksLabel, regions, children }: PanelShellModel) {
|
|
26
26
|
return (
|
|
27
|
-
<div className="mx-auto flex w-full max-w-
|
|
27
|
+
<div className="mx-auto flex w-full max-w-7xl flex-1 flex-col lg:flex-row lg:gap-8">
|
|
28
28
|
<aside className={`${RAIL} lg:top-14`}>
|
|
29
29
|
{regions.nav}
|
|
30
30
|
|
package/src/slots/post-bit.tsx
CHANGED
|
@@ -237,7 +237,7 @@ export function PostBit({ post, select, regions, copy }: PostBitSlotModel & { co
|
|
|
237
237
|
) : (
|
|
238
238
|
<div className={`flex-1 py-4 sm:py-5 ${BODY_X}`}>
|
|
239
239
|
<div
|
|
240
|
-
className="prose-md text-[0.9375rem] sm:text-base"
|
|
240
|
+
className="prose-md max-w-prose text-[0.9375rem] sm:text-base"
|
|
241
241
|
dangerouslySetInnerHTML={{ __html: post.bodyHtml }}
|
|
242
242
|
/>
|
|
243
243
|
|
package/src/slots/post-form.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { PostFormModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import { Alert, AlertDescription, AlertTitle, buttonVariants, Card, cn } from '@meith/ui'
|
|
3
|
+
import { Alert, AlertDescription, AlertTitle, buttonVariants, Card, cn, PageTitle } from '@meith/ui'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { pageAt } from '../shared'
|
|
6
6
|
|
|
7
7
|
export function PostForm({
|
|
8
8
|
heading,
|
|
@@ -17,7 +17,7 @@ export function PostForm({
|
|
|
17
17
|
return (
|
|
18
18
|
<div className={`${pageAt('max-w-3xl')} flex w-full flex-col gap-5 py-6 sm:py-8`}>
|
|
19
19
|
<div className="flex flex-wrap items-center justify-between gap-x-4 gap-y-2">
|
|
20
|
-
<
|
|
20
|
+
<PageTitle>{heading}</PageTitle>
|
|
21
21
|
<a href={cancelHref} className={cn(buttonVariants({ variant: 'ghost', size: 'sm' }))}>
|
|
22
22
|
{cancelLabel}
|
|
23
23
|
</a>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { RedirectNoticeModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import { buttonVariants, Card, CardContent } from '@meith/ui'
|
|
3
|
+
import { buttonVariants, Card, CardContent, PageTitle } from '@meith/ui'
|
|
4
4
|
|
|
5
5
|
export function RedirectNotice({
|
|
6
6
|
message,
|
|
@@ -16,7 +16,7 @@ export function RedirectNotice({
|
|
|
16
16
|
<p className="text-xs font-medium tracking-wide text-muted-foreground uppercase">
|
|
17
17
|
{c('redirecting')}
|
|
18
18
|
</p>
|
|
19
|
-
<
|
|
19
|
+
<PageTitle className="mt-1 sm:text-2xl">{c('pleaseWait')}</PageTitle>
|
|
20
20
|
<p className="mt-2 text-sm text-muted-foreground">{message}</p>
|
|
21
21
|
|
|
22
22
|
<div className="mt-5 flex flex-wrap items-center gap-3">
|
|
@@ -13,6 +13,11 @@ import {
|
|
|
13
13
|
Field,
|
|
14
14
|
Input,
|
|
15
15
|
NativeSelect,
|
|
16
|
+
NavTabs,
|
|
17
|
+
PageDescription,
|
|
18
|
+
PageHeader,
|
|
19
|
+
PageHeaderContent,
|
|
20
|
+
PageTitle,
|
|
16
21
|
} from '@meith/ui'
|
|
17
22
|
|
|
18
23
|
import { LINK, pageAt, Stamp } from '../shared'
|
|
@@ -37,16 +42,18 @@ export function SearchResults({
|
|
|
37
42
|
tabIndex={-1}
|
|
38
43
|
className={`${pageAt('max-w-3xl')} flex flex-1 flex-col gap-6 py-8`}
|
|
39
44
|
>
|
|
40
|
-
<
|
|
41
|
-
<
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
<PageHeader>
|
|
46
|
+
<PageHeaderContent>
|
|
47
|
+
<PageTitle>
|
|
48
|
+
{c('resultsFor')} {c('openQuote')}
|
|
49
|
+
{terms}
|
|
50
|
+
{c('closeQuote')}
|
|
51
|
+
</PageTitle>
|
|
52
|
+
<PageDescription>
|
|
53
|
+
{c('searched')} <Stamp at={searchedAt} />. {c('searchedNote')}
|
|
54
|
+
</PageDescription>
|
|
55
|
+
</PageHeaderContent>
|
|
56
|
+
</PageHeader>
|
|
50
57
|
|
|
51
58
|
{refine !== undefined && <Refine {...refine} copy={copy} />}
|
|
52
59
|
|
|
@@ -147,32 +154,13 @@ function Refine({
|
|
|
147
154
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
148
155
|
<p className="text-sm font-medium text-foreground">{summary}</p>
|
|
149
156
|
|
|
150
|
-
<
|
|
151
|
-
aria-label={sortsLabel}
|
|
152
|
-
className="inline-flex items-center gap-1 rounded-lg border border-border bg-surface p-1"
|
|
153
|
-
>
|
|
154
|
-
{sorts.map((sort) => (
|
|
155
|
-
<a
|
|
156
|
-
key={sort.label}
|
|
157
|
-
href={sort.href}
|
|
158
|
-
{...(sort.isCurrent ? { 'aria-current': 'true' as const } : {})}
|
|
159
|
-
className={cn(
|
|
160
|
-
'rounded-md px-2.5 py-1 text-xs whitespace-nowrap transition-colors',
|
|
161
|
-
sort.isCurrent
|
|
162
|
-
? 'bg-card font-semibold text-foreground shadow-sm'
|
|
163
|
-
: 'font-medium text-muted-foreground hover:text-foreground',
|
|
164
|
-
)}
|
|
165
|
-
>
|
|
166
|
-
{sort.label}
|
|
167
|
-
</a>
|
|
168
|
-
))}
|
|
169
|
-
</nav>
|
|
157
|
+
<NavTabs label={sortsLabel} tabs={sorts} />
|
|
170
158
|
|
|
171
159
|
{applied.map((chip) => (
|
|
172
160
|
<a
|
|
173
161
|
key={chip.label}
|
|
174
162
|
href={chip.removeHref}
|
|
175
|
-
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"
|
|
163
|
+
className="inline-flex min-h-8 pointer-coarse:min-h-11 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"
|
|
176
164
|
>
|
|
177
165
|
{chip.label}
|
|
178
166
|
<span aria-hidden="true">{c('removeFilterX')}</span>
|
|
@@ -184,7 +172,7 @@ function Refine({
|
|
|
184
172
|
<form
|
|
185
173
|
method="get"
|
|
186
174
|
action={action}
|
|
187
|
-
className="
|
|
175
|
+
className="grid grid-cols-1 items-end gap-3 border-t border-border pt-3 sm:grid-cols-2"
|
|
188
176
|
>
|
|
189
177
|
{choices.map((choice) => (
|
|
190
178
|
<Filter
|
|
@@ -197,7 +185,7 @@ function Refine({
|
|
|
197
185
|
|
|
198
186
|
<button
|
|
199
187
|
type="submit"
|
|
200
|
-
className={cn(buttonVariants({ variant: 'secondary'
|
|
188
|
+
className={cn(buttonVariants({ variant: 'secondary' }), 'justify-self-start')}
|
|
201
189
|
>
|
|
202
190
|
{submitLabel}
|
|
203
191
|
</button>
|
|
@@ -227,16 +215,11 @@ function Filter({
|
|
|
227
215
|
const id = `filter-${name}`
|
|
228
216
|
|
|
229
217
|
return (
|
|
230
|
-
<span className="
|
|
218
|
+
<span className="flex min-w-0 flex-col gap-2">
|
|
231
219
|
<label htmlFor={id} className="text-xs font-medium text-muted-foreground">
|
|
232
220
|
{label}
|
|
233
221
|
</label>
|
|
234
|
-
<NativeSelect
|
|
235
|
-
id={id}
|
|
236
|
-
name={name}
|
|
237
|
-
defaultValue={selected?.value ?? ''}
|
|
238
|
-
className="h-8 w-auto min-w-0 py-0 text-xs"
|
|
239
|
-
>
|
|
222
|
+
<NativeSelect id={id} name={name} defaultValue={selected?.value ?? ''} className="w-full">
|
|
240
223
|
{options.map((option) => (
|
|
241
224
|
<option key={option.value} value={option.value}>
|
|
242
225
|
{option.label}
|
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-3.5 gap-y-2 px-4 py-
|
|
22
|
+
className={`grid grid-cols-[auto_minmax(0,1fr)] gap-x-3.5 gap-y-2 px-4 py-4 transition-colors hover:bg-muted/50 sm:px-5 @3xl/card:grid-cols-[auto_minmax(0,1fr)_6rem_13rem] @3xl/card:items-center @3xl/card:gap-x-5 ${hidden ? tint : ''}`}
|
|
23
23
|
>
|
|
24
24
|
<span className="flex items-center gap-2.5">
|
|
25
25
|
{select !== null && (
|
|
@@ -41,7 +41,7 @@ export function ThreadRow({
|
|
|
41
41
|
</span>
|
|
42
42
|
</span>
|
|
43
43
|
|
|
44
|
-
<div className="min-w-0">
|
|
44
|
+
<div className="min-w-0 [overflow-wrap:anywhere]">
|
|
45
45
|
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
|
|
46
46
|
{thread.prefix !== null && <Prefix prefix={thread.prefix} />}
|
|
47
47
|
{thread.isSticky && <Badge tone="pinned">{c('pinned')}</Badge>}
|
|
@@ -68,9 +68,9 @@ export function ThreadRow({
|
|
|
68
68
|
</p>
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
|
-
<div className="col-start-2 flex min-w-0 flex-col gap-y-1 text-xs text-muted-foreground
|
|
71
|
+
<div className="col-start-2 flex min-w-0 flex-col gap-y-1 text-xs text-muted-foreground @3xl/card:contents">
|
|
72
72
|
<Figures
|
|
73
|
-
className="
|
|
73
|
+
className="@3xl/card:col-start-3 @3xl/card:justify-self-end"
|
|
74
74
|
items={[
|
|
75
75
|
{
|
|
76
76
|
label: c('repliesLabel'),
|
|
@@ -87,18 +87,18 @@ export function ThreadRow({
|
|
|
87
87
|
]}
|
|
88
88
|
/>
|
|
89
89
|
|
|
90
|
-
<div className="order-first flex min-w-0 max-w-full flex-wrap gap-x-1
|
|
90
|
+
<div className="order-first flex min-w-0 max-w-full flex-wrap gap-x-1 @3xl/card:order-none @3xl/card:col-start-4 @3xl/card:block @3xl/card:border-l @3xl/card:border-border @3xl/card:pl-5">
|
|
91
91
|
{thread.lastPost === null ? (
|
|
92
92
|
<span className="text-thread-moved">{c('noRepliesYet')}</span>
|
|
93
93
|
) : (
|
|
94
94
|
<>
|
|
95
95
|
<a
|
|
96
96
|
href={thread.lastPost.href}
|
|
97
|
-
className={`font-medium text-foreground
|
|
97
|
+
className={`font-medium text-foreground @3xl/card:block ${LINK}`}
|
|
98
98
|
>
|
|
99
99
|
{c('latestReply')}
|
|
100
100
|
</a>
|
|
101
|
-
<span className="
|
|
101
|
+
<span className="@3xl/card:mt-0.5 @3xl/card:block @3xl/card:truncate">
|
|
102
102
|
{c('by')} <UserRef user={thread.lastPost.author} className="font-normal" />{' '}
|
|
103
103
|
{c('dot')} <Stamp at={thread.lastPost.at} />
|
|
104
104
|
</span>
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import type { SlotCopy, ThreadViewModel } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
Badge,
|
|
5
|
+
buttonVariants,
|
|
6
|
+
cn,
|
|
7
|
+
PageHeader,
|
|
8
|
+
PageHeaderActions,
|
|
9
|
+
PageHeaderContent,
|
|
10
|
+
PageTitle,
|
|
11
|
+
} from '@meith/ui'
|
|
4
12
|
|
|
5
|
-
import { Counts, PAGE_BODY,
|
|
13
|
+
import { Counts, PAGE_BODY, Prefix } from '../shared'
|
|
6
14
|
|
|
7
15
|
export function ThreadView({
|
|
8
16
|
thread,
|
|
@@ -16,9 +24,9 @@ export function ThreadView({
|
|
|
16
24
|
|
|
17
25
|
return (
|
|
18
26
|
<div className={PAGE_BODY}>
|
|
19
|
-
<
|
|
20
|
-
<
|
|
21
|
-
<
|
|
27
|
+
<PageHeader>
|
|
28
|
+
<PageHeaderContent>
|
|
29
|
+
<PageTitle>{thread.title}</PageTitle>
|
|
22
30
|
|
|
23
31
|
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
24
32
|
{thread.prefix !== null && <Prefix prefix={thread.prefix} />}
|
|
@@ -43,9 +51,9 @@ export function ThreadView({
|
|
|
43
51
|
]}
|
|
44
52
|
/>
|
|
45
53
|
</div>
|
|
46
|
-
</
|
|
54
|
+
</PageHeaderContent>
|
|
47
55
|
|
|
48
|
-
<
|
|
56
|
+
<PageHeaderActions>
|
|
49
57
|
{watch != null && (
|
|
50
58
|
<form action={watch.action} method="post">
|
|
51
59
|
<button type="submit" className={buttonVariants({ variant: 'outline' })}>
|
|
@@ -74,8 +82,8 @@ export function ThreadView({
|
|
|
74
82
|
{c('replyAction')}
|
|
75
83
|
</a>
|
|
76
84
|
)}
|
|
77
|
-
</
|
|
78
|
-
</
|
|
85
|
+
</PageHeaderActions>
|
|
86
|
+
</PageHeader>
|
|
79
87
|
|
|
80
88
|
{regions.tools !== undefined && (
|
|
81
89
|
<div className="-mb-1 flex flex-col gap-3 empty:hidden">{regions.tools}</div>
|