@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/src/slots/header.tsx
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import type { HeaderModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy, linkTarget } from '@meith/theme-kit'
|
|
3
3
|
|
|
4
|
-
import { PAGE } from '../shared'
|
|
4
|
+
import { BELOW_HEADER, HEADER_HEIGHT, PAGE } from '../shared'
|
|
5
5
|
|
|
6
6
|
const DESKTOP_LINK =
|
|
7
|
-
'inline-flex h-
|
|
7
|
+
'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
8
|
|
|
9
9
|
const MOBILE_LINK =
|
|
10
|
-
'flex min-h-11 items-center rounded-
|
|
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
11
|
|
|
12
12
|
const SUMMARY =
|
|
13
13
|
'cursor-default list-none select-none [&::-webkit-details-marker]:hidden [&::marker]:content-none'
|
|
14
14
|
|
|
15
15
|
function BoardMark({ boardTitle, logo }: Pick<HeaderModel, 'boardTitle' | 'logo'>) {
|
|
16
|
-
if (logo === undefined)
|
|
16
|
+
if (logo === undefined) {
|
|
17
|
+
return (
|
|
18
|
+
<>
|
|
19
|
+
<span
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
className="flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-sm font-bold text-primary-foreground shadow-sm"
|
|
22
|
+
>
|
|
23
|
+
{(Array.from(boardTitle.trim())[0] ?? '?').toUpperCase()}
|
|
24
|
+
</span>
|
|
25
|
+
<span className="truncate">{boardTitle}</span>
|
|
26
|
+
</>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
17
29
|
|
|
18
30
|
const image = (
|
|
19
31
|
<img
|
|
@@ -38,13 +50,13 @@ function Submenu({ items }: { items: HeaderModel['navigation'][number]['submenu'
|
|
|
38
50
|
if (items === undefined || items.length === 0) return null
|
|
39
51
|
|
|
40
52
|
return (
|
|
41
|
-
<ul className="invisible absolute top-full left-0 z-30 min-w-
|
|
53
|
+
<ul className="invisible absolute top-full left-0 z-30 mt-1 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">
|
|
42
54
|
{items.map((child) => (
|
|
43
55
|
<li key={child.href}>
|
|
44
56
|
<a
|
|
45
57
|
href={child.href}
|
|
46
58
|
{...linkTarget(child)}
|
|
47
|
-
className="flex items-center rounded-
|
|
59
|
+
className="flex items-center rounded-lg px-2.5 py-1.5 text-sm text-muted-foreground pointer-coarse:min-h-11 hover:bg-muted hover:text-foreground"
|
|
48
60
|
>
|
|
49
61
|
{child.label}
|
|
50
62
|
</a>
|
|
@@ -103,7 +115,7 @@ function MobileNavItem({ item }: { item: HeaderModel['navigation'][number] }) {
|
|
|
103
115
|
<li>
|
|
104
116
|
<details data-nav-disclosure name="default-header-submenu" className="group/submenu">
|
|
105
117
|
<summary
|
|
106
|
-
className={`${SUMMARY} flex min-h-11 items-center justify-between gap-2 rounded-
|
|
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`}
|
|
107
119
|
>
|
|
108
120
|
<a
|
|
109
121
|
href={item.href}
|
|
@@ -141,30 +153,35 @@ export function Header({
|
|
|
141
153
|
const c = (key: string) => fromSlotCopy(copy, `default.header.${key}`)
|
|
142
154
|
|
|
143
155
|
const hasNavigation = navigation.length > 0
|
|
156
|
+
const opensMenus = navigation.some(
|
|
157
|
+
(item) => item.submenu !== undefined && item.submenu.length > 0,
|
|
158
|
+
)
|
|
144
159
|
|
|
145
160
|
return (
|
|
146
|
-
<header className="
|
|
147
|
-
<div className={`${PAGE} flex
|
|
161
|
+
<header className="sticky top-0 z-40 border-b border-b-border bg-card/95 backdrop-blur supports-[backdrop-filter]:bg-card/85">
|
|
162
|
+
<div className={`${PAGE} flex ${HEADER_HEIGHT} items-center gap-x-3 sm:gap-x-4`}>
|
|
148
163
|
<a
|
|
149
164
|
href={homeHref}
|
|
150
|
-
className="me-auto
|
|
165
|
+
className="me-auto flex min-w-0 items-center gap-2.5 text-lg font-semibold tracking-tight text-foreground transition-colors hover:text-primary"
|
|
151
166
|
>
|
|
152
167
|
<BoardMark boardTitle={boardTitle} logo={logo} />
|
|
153
168
|
</a>
|
|
154
169
|
|
|
155
|
-
{children}
|
|
170
|
+
<div className="flex min-w-0 shrink-0 items-center gap-2 sm:gap-3">{children}</div>
|
|
156
171
|
|
|
157
172
|
{hasNavigation && (
|
|
158
173
|
<nav aria-label={c('sections')} data-nav-view="mobile" className="lg:hidden">
|
|
159
174
|
<details data-nav-disclosure className="group/menu">
|
|
160
175
|
<summary
|
|
161
|
-
className={`${SUMMARY} flex size-
|
|
176
|
+
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`}
|
|
162
177
|
>
|
|
163
178
|
<MenuGlyph />
|
|
164
179
|
<span className="sr-only">{c('sections')}</span>
|
|
165
180
|
</summary>
|
|
166
181
|
|
|
167
|
-
<div
|
|
182
|
+
<div
|
|
183
|
+
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`}
|
|
184
|
+
>
|
|
168
185
|
<ul className={`${PAGE} flex flex-col gap-0.5 py-2`}>
|
|
169
186
|
{navigation.map((item) => (
|
|
170
187
|
<MobileNavItem key={item.href} item={item} />
|
|
@@ -178,10 +195,20 @@ export function Header({
|
|
|
178
195
|
|
|
179
196
|
{hasNavigation && (
|
|
180
197
|
<nav aria-label={c('sections')} className="hidden border-t border-border lg:block">
|
|
181
|
-
<div
|
|
198
|
+
<div
|
|
199
|
+
className={
|
|
200
|
+
opensMenus
|
|
201
|
+
? PAGE
|
|
202
|
+
: `${PAGE} overflow-x-auto [mask-image:linear-gradient(to_right,black_calc(100%-1.5rem),transparent)] [scrollbar-width:none]`
|
|
203
|
+
}
|
|
204
|
+
>
|
|
182
205
|
<ul
|
|
183
206
|
data-nav-view="desktop"
|
|
184
|
-
className=
|
|
207
|
+
className={
|
|
208
|
+
opensMenus
|
|
209
|
+
? '-mx-3 flex flex-wrap items-center gap-0.5 py-1'
|
|
210
|
+
: '-mx-3 flex items-center gap-0.5 py-1 pr-6 whitespace-nowrap'
|
|
211
|
+
}
|
|
185
212
|
>
|
|
186
213
|
{navigation.map((item) => (
|
|
187
214
|
<li key={item.href} className="group relative shrink-0">
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { LatestPostsModel, SlotCopy } from '@meith/theme-kit'
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
3
|
import {
|
|
4
|
+
Avatar,
|
|
4
5
|
Card,
|
|
5
6
|
CardContent,
|
|
6
7
|
CardHeader,
|
|
@@ -10,15 +11,15 @@ import {
|
|
|
10
11
|
EmptyTitle,
|
|
11
12
|
} from '@meith/ui'
|
|
12
13
|
|
|
13
|
-
import { LINK, MUTED_LINK, NUMERIC,
|
|
14
|
+
import { LINK, MUTED_LINK, NUMERIC, Stamp, UserRef } from '../shared'
|
|
14
15
|
|
|
15
16
|
export function LatestPosts({ posts, capturedAt, copy }: LatestPostsModel & { copy: SlotCopy }) {
|
|
16
17
|
const c = (key: string) => fromSlotCopy(copy, `default.latestPosts.${key}`)
|
|
17
18
|
|
|
18
19
|
return (
|
|
19
|
-
<Card aria-labelledby="latest-posts-heading">
|
|
20
|
-
<CardHeader className=
|
|
21
|
-
<CardTitle id="latest-posts-heading" className="text-
|
|
20
|
+
<Card aria-labelledby="latest-posts-heading" className="rounded-xl">
|
|
21
|
+
<CardHeader className="bg-card">
|
|
22
|
+
<CardTitle id="latest-posts-heading" className="text-sm">
|
|
22
23
|
{c('heading')}
|
|
23
24
|
</CardTitle>
|
|
24
25
|
<p className={`text-xs text-muted-foreground ${NUMERIC}`}>
|
|
@@ -35,25 +36,36 @@ export function LatestPosts({ posts, capturedAt, copy }: LatestPostsModel & { co
|
|
|
35
36
|
<CardContent className="px-0 py-0">
|
|
36
37
|
<ul className="divide-y divide-border">
|
|
37
38
|
{posts.map((post) => (
|
|
38
|
-
<li
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
{
|
|
44
|
-
|
|
39
|
+
<li
|
|
40
|
+
key={post.href}
|
|
41
|
+
className="flex gap-3 px-4 py-2.5 transition-colors hover:bg-muted/50"
|
|
42
|
+
>
|
|
43
|
+
<Avatar
|
|
44
|
+
src={null}
|
|
45
|
+
name={post.author.username}
|
|
46
|
+
size={28}
|
|
47
|
+
className="mt-0.5 rounded-full"
|
|
48
|
+
/>
|
|
49
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
50
|
+
<a
|
|
51
|
+
href={post.href}
|
|
52
|
+
className={`truncate text-sm font-medium text-foreground ${LINK}`}
|
|
53
|
+
>
|
|
54
|
+
{post.threadTitle}
|
|
55
|
+
</a>
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
57
|
+
{post.excerpt !== '' && (
|
|
58
|
+
<p className="line-clamp-2 text-xs text-muted-foreground">{post.excerpt}</p>
|
|
59
|
+
)}
|
|
49
60
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
<p className="truncate text-xs text-muted-foreground">
|
|
62
|
+
<UserRef user={post.author} className="font-normal" /> {c('in')}{' '}
|
|
63
|
+
<a href={post.forum.href} className={MUTED_LINK}>
|
|
64
|
+
{post.forum.label}
|
|
65
|
+
</a>{' '}
|
|
66
|
+
{c('dot')} <Stamp at={post.postedAt} />
|
|
67
|
+
</p>
|
|
68
|
+
</div>
|
|
57
69
|
</li>
|
|
58
70
|
))}
|
|
59
71
|
</ul>
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
EmptyTitle,
|
|
11
11
|
} from '@meith/ui'
|
|
12
12
|
|
|
13
|
-
import { LINK, MUTED_LINK, NUMERIC,
|
|
13
|
+
import { LINK, MUTED_LINK, NUMERIC, PILL, Stamp, UserRef } from '../shared'
|
|
14
14
|
|
|
15
15
|
export function LatestThreads({
|
|
16
16
|
threads,
|
|
@@ -20,9 +20,9 @@ export function LatestThreads({
|
|
|
20
20
|
const c = (key: string) => fromSlotCopy(copy, `default.latestThreads.${key}`)
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<Card aria-labelledby="latest-threads-heading">
|
|
24
|
-
<CardHeader className=
|
|
25
|
-
<CardTitle id="latest-threads-heading" className="text-
|
|
23
|
+
<Card aria-labelledby="latest-threads-heading" className="rounded-xl">
|
|
24
|
+
<CardHeader className="bg-card">
|
|
25
|
+
<CardTitle id="latest-threads-heading" className="text-sm">
|
|
26
26
|
{c('heading')}
|
|
27
27
|
</CardTitle>
|
|
28
28
|
<p className={`text-xs text-muted-foreground ${NUMERIC}`}>
|
|
@@ -39,17 +39,23 @@ export function LatestThreads({
|
|
|
39
39
|
<CardContent className="px-0 py-0">
|
|
40
40
|
<ul className="divide-y divide-border">
|
|
41
41
|
{threads.map((thread) => (
|
|
42
|
-
<li
|
|
43
|
-
|
|
42
|
+
<li
|
|
43
|
+
key={thread.href}
|
|
44
|
+
className="flex flex-col gap-0.5 px-4 py-2.5 transition-colors hover:bg-muted/50"
|
|
45
|
+
>
|
|
46
|
+
<div className="flex items-start justify-between gap-2">
|
|
44
47
|
<a
|
|
45
48
|
href={thread.href}
|
|
46
|
-
className={`
|
|
49
|
+
className={`line-clamp-2 text-sm font-medium text-foreground ${LINK}`}
|
|
47
50
|
>
|
|
48
51
|
{thread.title}
|
|
49
52
|
</a>
|
|
50
|
-
<span className={
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
<span className={`${PILL} shrink-0 ${NUMERIC}`}>
|
|
54
|
+
{thread.replyCount.label}
|
|
55
|
+
<span className="sr-only">
|
|
56
|
+
{' '}
|
|
57
|
+
{thread.replyCount.value === 1 ? c('reply.one') : c('reply.other')}
|
|
58
|
+
</span>
|
|
53
59
|
</span>
|
|
54
60
|
</div>
|
|
55
61
|
|
|
@@ -22,22 +22,33 @@ export function MemberProfile({
|
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<div className={PAGE_BODY}>
|
|
25
|
-
<Card>
|
|
26
|
-
<CardContent className="flex flex-col gap-4
|
|
25
|
+
<Card className="rounded-xl">
|
|
26
|
+
<CardContent className="flex flex-col gap-4 px-5 py-5 sm:flex-row sm:items-center sm:justify-between sm:px-6">
|
|
27
27
|
<div className="flex min-w-0 items-center gap-4">
|
|
28
|
-
<Avatar src={avatarUrl} name={user.username} size={
|
|
28
|
+
<Avatar src={avatarUrl} name={user.username} size={80} className="rounded-2xl" />
|
|
29
29
|
|
|
30
30
|
<div className="min-w-0">
|
|
31
31
|
<h1
|
|
32
|
-
className={cn(
|
|
32
|
+
className={cn(
|
|
33
|
+
'font-heading text-2xl font-semibold tracking-tight break-words',
|
|
34
|
+
user.nameClass,
|
|
35
|
+
)}
|
|
33
36
|
>
|
|
34
37
|
{user.username}
|
|
35
38
|
</h1>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<span
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
<p className="mt-1.5 flex flex-wrap gap-1.5">
|
|
40
|
+
{groupTags(groups, title).map((group) => (
|
|
41
|
+
<span
|
|
42
|
+
key={group.title}
|
|
43
|
+
className={cn(
|
|
44
|
+
'inline-flex items-center rounded-full bg-muted px-2 py-0.5 text-xs font-medium text-muted-foreground',
|
|
45
|
+
group.nameClass,
|
|
46
|
+
)}
|
|
47
|
+
>
|
|
48
|
+
{group.title}
|
|
49
|
+
</span>
|
|
50
|
+
))}
|
|
51
|
+
</p>
|
|
41
52
|
</div>
|
|
42
53
|
</div>
|
|
43
54
|
|
|
@@ -62,18 +73,20 @@ export function MemberProfile({
|
|
|
62
73
|
)}
|
|
63
74
|
</CardContent>
|
|
64
75
|
|
|
65
|
-
<dl className="grid grid-cols-3
|
|
66
|
-
<div>
|
|
67
|
-
<dd className={`text-lg font-semibold text-
|
|
76
|
+
<dl className="grid grid-cols-3 divide-x divide-border border-t border-border">
|
|
77
|
+
<div className="px-5 py-3.5 sm:px-6">
|
|
78
|
+
<dd className={`text-lg font-semibold text-foreground ${NUMERIC}`}>
|
|
79
|
+
{postCount.label}
|
|
80
|
+
</dd>
|
|
68
81
|
<dt className="text-xs text-muted-foreground">{c('postsLabel')}</dt>
|
|
69
82
|
</div>
|
|
70
|
-
<div>
|
|
83
|
+
<div className="px-5 py-3.5 sm:px-6">
|
|
71
84
|
<dd className="text-sm font-medium text-foreground">
|
|
72
85
|
<Stamp at={joinedAt} />
|
|
73
86
|
</dd>
|
|
74
87
|
<dt className="text-xs text-muted-foreground">{c('joinedLabel')}</dt>
|
|
75
88
|
</div>
|
|
76
|
-
<div>
|
|
89
|
+
<div className="px-5 py-3.5 sm:px-6">
|
|
77
90
|
<dd className="text-sm font-medium text-foreground">
|
|
78
91
|
{lastVisitAt === null ? c('never') : <Stamp at={lastVisitAt} />}
|
|
79
92
|
</dd>
|
|
@@ -83,18 +96,18 @@ export function MemberProfile({
|
|
|
83
96
|
</Card>
|
|
84
97
|
|
|
85
98
|
{fields.length > 0 && (
|
|
86
|
-
<Card aria-labelledby="profile-fields-heading">
|
|
87
|
-
<CardHeader>
|
|
99
|
+
<Card aria-labelledby="profile-fields-heading" className="rounded-xl">
|
|
100
|
+
<CardHeader className="bg-card">
|
|
88
101
|
<CardTitle id="profile-fields-heading" className="text-sm">
|
|
89
102
|
{c('about')}
|
|
90
103
|
</CardTitle>
|
|
91
104
|
</CardHeader>
|
|
92
|
-
<CardContent>
|
|
93
|
-
<dl className="grid gap-x-6 gap-y-
|
|
105
|
+
<CardContent className="px-5 py-4">
|
|
106
|
+
<dl className="grid gap-x-6 gap-y-4 sm:grid-cols-2">
|
|
94
107
|
{fields.map((field) => (
|
|
95
108
|
<div key={field.label}>
|
|
96
109
|
<dt className="text-xs text-muted-foreground">{field.label}</dt>
|
|
97
|
-
<dd className="text-sm break-words">{field.value}</dd>
|
|
110
|
+
<dd className="mt-0.5 text-sm break-words">{field.value}</dd>
|
|
98
111
|
</div>
|
|
99
112
|
))}
|
|
100
113
|
</dl>
|
|
@@ -103,13 +116,13 @@ export function MemberProfile({
|
|
|
103
116
|
)}
|
|
104
117
|
|
|
105
118
|
{signatureHtml !== null && (
|
|
106
|
-
<Card aria-labelledby="profile-signature-heading">
|
|
107
|
-
<CardHeader>
|
|
119
|
+
<Card aria-labelledby="profile-signature-heading" className="rounded-xl">
|
|
120
|
+
<CardHeader className="bg-card">
|
|
108
121
|
<CardTitle id="profile-signature-heading" className="text-sm">
|
|
109
122
|
{c('signature')}
|
|
110
123
|
</CardTitle>
|
|
111
124
|
</CardHeader>
|
|
112
|
-
<CardContent>
|
|
125
|
+
<CardContent className="px-5 py-4">
|
|
113
126
|
<div
|
|
114
127
|
className="prose-md text-sm text-muted-foreground"
|
|
115
128
|
dangerouslySetInnerHTML={{ __html: signatureHtml }}
|
package/src/slots/navigation.tsx
CHANGED
|
@@ -16,21 +16,21 @@ export function Navigation({ items, copy }: NavigationModel & { copy: SlotCopy }
|
|
|
16
16
|
return (
|
|
17
17
|
<li key={item.href} className="flex shrink-0 items-center">
|
|
18
18
|
{index > 0 && (
|
|
19
|
-
<span aria-hidden="true" className="px-
|
|
19
|
+
<span aria-hidden="true" className="px-1 text-muted-foreground/50 select-none">
|
|
20
20
|
{c('separator')}
|
|
21
21
|
</span>
|
|
22
22
|
)}
|
|
23
23
|
{isLast ? (
|
|
24
24
|
<span
|
|
25
25
|
aria-current="page"
|
|
26
|
-
className="max-w-[
|
|
26
|
+
className="max-w-[28ch] truncate px-1.5 py-1 font-medium text-foreground sm:max-w-none"
|
|
27
27
|
>
|
|
28
28
|
{item.label}
|
|
29
29
|
</span>
|
|
30
30
|
) : (
|
|
31
31
|
<a
|
|
32
32
|
href={item.href}
|
|
33
|
-
className="rounded-md px-1.5 py-1 transition-colors hover:bg-
|
|
33
|
+
className="rounded-md px-1.5 py-1 transition-colors hover:bg-muted hover:text-foreground"
|
|
34
34
|
>
|
|
35
35
|
{item.label}
|
|
36
36
|
</a>
|
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 rounded-full')
|
|
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 px-2',
|
|
56
|
+
'min-w-8 rounded-full px-2',
|
|
57
57
|
NUMERIC,
|
|
58
58
|
entry.isCurrent ? '' : 'hidden sm:inline-flex',
|
|
59
59
|
)}
|
package/src/slots/panel-nav.tsx
CHANGED
|
@@ -3,14 +3,14 @@ import { fromSlotCopy } from '@meith/theme-kit'
|
|
|
3
3
|
import { cn, Disclosure } from '@meith/ui'
|
|
4
4
|
|
|
5
5
|
import { PanelIcon } from '../panel-icons'
|
|
6
|
+
import { BELOW_HEADER } from '../shared'
|
|
6
7
|
|
|
7
8
|
const ITEM =
|
|
8
|
-
'relative flex items-center gap-
|
|
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'
|
|
9
10
|
|
|
10
|
-
const HERE =
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const ELSEWHERE = 'text-muted-foreground hover:bg-accent hover:text-foreground'
|
|
11
|
+
const HERE = 'bg-primary/10 font-semibold text-primary [&_svg]:text-primary'
|
|
12
|
+
const OPEN = 'font-semibold text-foreground hover:bg-muted'
|
|
13
|
+
const ELSEWHERE = 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
14
14
|
|
|
15
15
|
function Count({ count, copy }: { count: number; copy: SlotCopy }) {
|
|
16
16
|
const c = (key: string) => fromSlotCopy(copy, `default.panelNav.${key}`)
|
|
@@ -19,7 +19,7 @@ function Count({ count, copy }: { count: number; copy: SlotCopy }) {
|
|
|
19
19
|
<>
|
|
20
20
|
<span
|
|
21
21
|
aria-hidden="true"
|
|
22
|
-
className="ml-auto min-w-5 rounded-full bg-primary px-1.5 py-0.5 text-center text-
|
|
22
|
+
className="ml-auto min-w-5 rounded-full bg-primary px-1.5 py-0.5 text-center text-[0.6875rem] leading-4 font-semibold tabular-nums text-primary-foreground"
|
|
23
23
|
>
|
|
24
24
|
{count > 99 ? c('countCap') : count}
|
|
25
25
|
</span>
|
|
@@ -42,7 +42,7 @@ function Item({
|
|
|
42
42
|
const body = (
|
|
43
43
|
<>
|
|
44
44
|
{item.icon !== null && (
|
|
45
|
-
<span className="text-muted-foreground/
|
|
45
|
+
<span className="text-muted-foreground/70 transition-colors group-hover/item:text-foreground">
|
|
46
46
|
<PanelIcon icon={item.icon} />
|
|
47
47
|
</span>
|
|
48
48
|
)}
|
|
@@ -81,11 +81,11 @@ function Sections({
|
|
|
81
81
|
}: Pick<PanelNavModel, 'label' | 'sections'> & { copy: SlotCopy }) {
|
|
82
82
|
return (
|
|
83
83
|
<nav aria-label={label} className="text-sm">
|
|
84
|
-
<ul className="flex flex-col gap-0.5
|
|
84
|
+
<ul className="flex flex-col gap-0.5">
|
|
85
85
|
{sections.map((section) => (
|
|
86
86
|
<li
|
|
87
87
|
key={section.href}
|
|
88
|
-
className={cn(section.isOverview && 'mb-1 border-b border-border pb-1')}
|
|
88
|
+
className={cn(section.isOverview && 'mb-1.5 border-b border-border pb-1.5')}
|
|
89
89
|
>
|
|
90
90
|
<Item
|
|
91
91
|
item={section}
|
|
@@ -94,12 +94,12 @@ function Sections({
|
|
|
94
94
|
/>
|
|
95
95
|
|
|
96
96
|
{section.isOpen && section.children.length > 0 && (
|
|
97
|
-
<ul className="
|
|
97
|
+
<ul className="my-1 ml-5 flex flex-col gap-0.5 border-l-2 border-border pl-2">
|
|
98
98
|
{section.children.map((child) => (
|
|
99
99
|
<li key={child.href}>
|
|
100
100
|
<Item
|
|
101
101
|
item={child}
|
|
102
|
-
className={child.current === 'here' ? HERE : ELSEWHERE}
|
|
102
|
+
className={cn('py-1.5', child.current === 'here' ? HERE : ELSEWHERE)}
|
|
103
103
|
copy={copy}
|
|
104
104
|
/>
|
|
105
105
|
</li>
|
|
@@ -121,7 +121,9 @@ export function PanelNav({
|
|
|
121
121
|
}: PanelNavModel & { copy: SlotCopy }) {
|
|
122
122
|
return (
|
|
123
123
|
<>
|
|
124
|
-
<div
|
|
124
|
+
<div
|
|
125
|
+
className={`sticky ${BELOW_HEADER} z-20 -mx-4 bg-background/95 px-4 py-2 backdrop-blur-sm sm:-mx-6 sm:px-6 lg:hidden`}
|
|
126
|
+
>
|
|
125
127
|
<Disclosure
|
|
126
128
|
summary={label}
|
|
127
129
|
contentClassName="p-2"
|
|
@@ -131,7 +133,7 @@ export function PanelNav({
|
|
|
131
133
|
</Disclosure>
|
|
132
134
|
</div>
|
|
133
135
|
|
|
134
|
-
<div className="hidden rounded-
|
|
136
|
+
<div className="hidden rounded-xl border border-border bg-card p-2 shadow-elevation lg:block">
|
|
135
137
|
<Sections label={label} sections={sections} copy={copy} />
|
|
136
138
|
</div>
|
|
137
139
|
</>
|
package/src/slots/panel-page.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import type { PanelPageModel, PanelSectionModel, SlotCopy } from '@meith/theme-k
|
|
|
2
2
|
import { fromSlotCopy } from '@meith/theme-kit'
|
|
3
3
|
import { cn } from '@meith/ui'
|
|
4
4
|
|
|
5
|
-
import { MUTED_LINK, PAGE, pageAt } from '../shared'
|
|
5
|
+
import { MUTED_LINK, PAGE, PAGE_TITLE, pageAt, SECTION_TITLE } from '../shared'
|
|
6
6
|
|
|
7
7
|
export function PanelPage({
|
|
8
8
|
title,
|
|
@@ -21,25 +21,28 @@ export function PanelPage({
|
|
|
21
21
|
id="board-content"
|
|
22
22
|
tabIndex={-1}
|
|
23
23
|
className={cn(
|
|
24
|
-
'flex w-full flex-col py-8',
|
|
24
|
+
'flex w-full flex-col py-6 lg:py-8',
|
|
25
25
|
frame === 'standalone'
|
|
26
26
|
? `flex-1 ${width === 'wide' ? PAGE : pageAt('max-w-4xl')}`
|
|
27
|
-
: `px-6 ${width === 'wide' ? 'max-w-none' : 'max-w-4xl'}`,
|
|
27
|
+
: `px-4 sm:px-6 ${width === 'wide' ? 'max-w-none' : 'max-w-4xl'}`,
|
|
28
28
|
gap === 'loose' ? 'gap-8' : 'gap-6',
|
|
29
29
|
)}
|
|
30
30
|
>
|
|
31
|
-
<div className="flex flex-wrap items-
|
|
31
|
+
<div className="flex flex-wrap items-end justify-between gap-x-6 gap-y-3 border-b border-border pb-5">
|
|
32
32
|
<div className="flex min-w-0 flex-col gap-1">
|
|
33
33
|
{back !== null && (
|
|
34
|
-
<a
|
|
35
|
-
{
|
|
34
|
+
<a
|
|
35
|
+
href={back.href}
|
|
36
|
+
className={`mb-1 inline-flex w-fit items-center gap-1 text-sm font-medium ${MUTED_LINK}`}
|
|
37
|
+
>
|
|
38
|
+
<span aria-hidden="true">{c('back')}</span> {back.label}
|
|
36
39
|
</a>
|
|
37
40
|
)}
|
|
38
41
|
|
|
39
|
-
<h1 className=
|
|
42
|
+
<h1 className={PAGE_TITLE}>{title}</h1>
|
|
40
43
|
|
|
41
44
|
{regions.lede !== undefined && (
|
|
42
|
-
<p className="text-sm text-muted-foreground">{regions.lede}</p>
|
|
45
|
+
<p className="max-w-prose text-sm text-muted-foreground">{regions.lede}</p>
|
|
43
46
|
)}
|
|
44
47
|
{regions.meta !== undefined && (
|
|
45
48
|
<p className="text-xs text-muted-foreground">{regions.meta}</p>
|
|
@@ -61,7 +64,7 @@ export function PanelSection({ title, headingId, regions, children }: PanelSecti
|
|
|
61
64
|
<section aria-labelledby={headingId} className="flex flex-col gap-3">
|
|
62
65
|
<div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-1">
|
|
63
66
|
<div className="flex min-w-0 flex-col gap-0.5">
|
|
64
|
-
<h2 id={headingId} className=
|
|
67
|
+
<h2 id={headingId} className={SECTION_TITLE}>
|
|
65
68
|
{title}
|
|
66
69
|
</h2>
|
|
67
70
|
{regions.description !== undefined && (
|
|
@@ -1,32 +1,48 @@
|
|
|
1
1
|
import type { PanelShellModel } from '@meith/theme-kit'
|
|
2
2
|
|
|
3
|
-
import { MUTED_LINK } from '../shared'
|
|
3
|
+
import { BELOW_DESKTOP_HEADER, EYEBROW, MUTED_LINK } from '../shared'
|
|
4
4
|
|
|
5
5
|
const RAIL =
|
|
6
|
-
'flex flex-col gap-4 px-4 pt-
|
|
6
|
+
'flex flex-col gap-4 px-4 pt-5 sm:px-6 lg:sticky lg:w-64 lg:shrink-0 lg:self-start lg:pt-8 lg:pr-0'
|
|
7
|
+
|
|
8
|
+
function SwitchGlyph() {
|
|
9
|
+
return (
|
|
10
|
+
<svg
|
|
11
|
+
aria-hidden="true"
|
|
12
|
+
viewBox="0 0 16 16"
|
|
13
|
+
className="size-3.5 shrink-0 text-muted-foreground/70"
|
|
14
|
+
fill="none"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
strokeWidth="1.5"
|
|
17
|
+
strokeLinecap="round"
|
|
18
|
+
strokeLinejoin="round"
|
|
19
|
+
>
|
|
20
|
+
<path d="M6 3.5 9.5 7 6 10.5M3 7h6.5M13 3v8" />
|
|
21
|
+
</svg>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
7
24
|
|
|
8
25
|
export function PanelShell({ panel, links, linksLabel, regions, children }: PanelShellModel) {
|
|
9
26
|
return (
|
|
10
|
-
<div className="mx-auto flex w-full max-w-6xl flex-1 flex-col lg:flex-row">
|
|
11
|
-
<aside className={`${RAIL} ${panel === 'admincp' ? 'lg:top-14' :
|
|
27
|
+
<div className="mx-auto flex w-full max-w-6xl flex-1 flex-col lg:flex-row lg:gap-8">
|
|
28
|
+
<aside className={`${RAIL} ${panel === 'admincp' ? 'lg:top-14' : BELOW_DESKTOP_HEADER}`}>
|
|
12
29
|
{regions.nav}
|
|
13
30
|
|
|
14
31
|
{links.length > 0 && (
|
|
15
32
|
<nav
|
|
16
33
|
aria-label={linksLabel}
|
|
17
|
-
className="hidden rounded-
|
|
34
|
+
className="hidden rounded-xl border border-border bg-card p-2 shadow-elevation lg:block"
|
|
18
35
|
>
|
|
19
|
-
<p className=
|
|
20
|
-
{linksLabel}
|
|
21
|
-
</p>
|
|
36
|
+
<p className={`${EYEBROW} px-3 pt-1.5 pb-1.5`}>{linksLabel}</p>
|
|
22
37
|
<ul className="flex flex-col gap-0.5 text-sm">
|
|
23
38
|
{links.map((link) => (
|
|
24
39
|
<li key={link.href}>
|
|
25
40
|
<a
|
|
26
41
|
href={link.href}
|
|
27
|
-
className={`
|
|
42
|
+
className={`flex items-center gap-2.5 rounded-lg px-3 py-2 ${MUTED_LINK} hover:bg-muted hover:no-underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring`}
|
|
28
43
|
>
|
|
29
|
-
|
|
44
|
+
<SwitchGlyph />
|
|
45
|
+
<span className="min-w-0 flex-1 truncate">{link.label}</span>
|
|
30
46
|
</a>
|
|
31
47
|
</li>
|
|
32
48
|
))}
|
|
@@ -40,10 +56,16 @@ export function PanelShell({ panel, links, linksLabel, regions, children }: Pane
|
|
|
40
56
|
{links.length > 0 && (
|
|
41
57
|
<nav
|
|
42
58
|
aria-label={linksLabel}
|
|
43
|
-
className="flex flex-wrap gap-x-
|
|
59
|
+
className="flex flex-wrap items-center gap-x-2 gap-y-2 px-4 pt-2 pb-6 text-sm sm:px-6 lg:hidden"
|
|
44
60
|
>
|
|
61
|
+
<span className={`${EYEBROW} me-1`}>{linksLabel}</span>
|
|
45
62
|
{links.map((link) => (
|
|
46
|
-
<a
|
|
63
|
+
<a
|
|
64
|
+
key={link.href}
|
|
65
|
+
href={link.href}
|
|
66
|
+
className="inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1 text-xs font-medium text-muted-foreground transition-colors hover:border-primary/40 hover:text-primary"
|
|
67
|
+
>
|
|
68
|
+
<SwitchGlyph />
|
|
47
69
|
{link.label}
|
|
48
70
|
</a>
|
|
49
71
|
))}
|