@meith/theme-raidframe 0.2.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/LICENSE.md +165 -0
- package/package.json +33 -0
- package/src/index.ts +3 -0
- package/src/shared.tsx +133 -0
- package/src/slots/announcement.tsx +40 -0
- package/src/slots/auth-page.tsx +53 -0
- package/src/slots/board-index.tsx +35 -0
- package/src/slots/board-stats.tsx +52 -0
- package/src/slots/category-block.tsx +37 -0
- package/src/slots/discovery-view.tsx +122 -0
- package/src/slots/error-notice.tsx +26 -0
- package/src/slots/footer.tsx +34 -0
- package/src/slots/forum-display.tsx +81 -0
- package/src/slots/forum-jump.tsx +45 -0
- package/src/slots/forum-row.tsx +66 -0
- package/src/slots/header.tsx +67 -0
- package/src/slots/latest-posts.tsx +42 -0
- package/src/slots/latest-threads.tsx +42 -0
- package/src/slots/member-profile.tsx +102 -0
- package/src/slots/navigation.tsx +31 -0
- package/src/slots/notice.tsx +32 -0
- package/src/slots/pagination.tsx +48 -0
- package/src/slots/panel-nav.tsx +110 -0
- package/src/slots/panel-page.tsx +72 -0
- package/src/slots/panel-shell.tsx +33 -0
- package/src/slots/post-actions.tsx +29 -0
- package/src/slots/post-bit.tsx +225 -0
- package/src/slots/post-form.tsx +44 -0
- package/src/slots/redirect-notice.tsx +26 -0
- package/src/slots/search-form.tsx +102 -0
- package/src/slots/search-results.tsx +108 -0
- package/src/slots/shell.tsx +40 -0
- package/src/slots/subforum-list.tsx +26 -0
- package/src/slots/thread-row.tsx +74 -0
- package/src/slots/thread-view.tsx +75 -0
- package/src/slots/user-panel.tsx +118 -0
- package/src/slots/who-is-online.tsx +64 -0
- package/src/theme.ts +84 -0
- package/src/tokens.ts +104 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { SearchResultsModel } from '@meith/theme-kit'
|
|
2
|
+
|
|
3
|
+
import { BUTTON_PRIMARY, Frame, HEADING, MICRO, RULE, Stamp } from '../shared'
|
|
4
|
+
|
|
5
|
+
export function SearchResults({
|
|
6
|
+
terms,
|
|
7
|
+
searchedAt,
|
|
8
|
+
hits,
|
|
9
|
+
nextHref,
|
|
10
|
+
nextLabel,
|
|
11
|
+
newSearchHref,
|
|
12
|
+
within,
|
|
13
|
+
}: SearchResultsModel) {
|
|
14
|
+
return (
|
|
15
|
+
<main
|
|
16
|
+
id="board-content"
|
|
17
|
+
tabIndex={-1}
|
|
18
|
+
className="mx-auto flex w-full max-w-4xl flex-1 flex-col gap-4 p-4"
|
|
19
|
+
>
|
|
20
|
+
<div className="border border-border bg-card shadow-elevation">
|
|
21
|
+
<div className="px-4 py-3">
|
|
22
|
+
<p className={MICRO}>search</p>
|
|
23
|
+
<h1 className={`${HEADING} text-xl text-foreground`}>
|
|
24
|
+
Results for “{terms}”
|
|
25
|
+
</h1>
|
|
26
|
+
<p className={`${MICRO} mt-1 normal-case`}>
|
|
27
|
+
<span className="uppercase">run</span> <Stamp at={searchedAt} />
|
|
28
|
+
<span className="text-border">{' | '}</span>
|
|
29
|
+
<span>checked against your access every time this page is opened</span>
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
<div className={RULE} aria-hidden="true" />
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<Frame>
|
|
36
|
+
{hits.length === 0 ? (
|
|
37
|
+
<div className="px-4 py-6 text-center">
|
|
38
|
+
<p className={`${HEADING} text-sm text-foreground`}>Nothing matched</p>
|
|
39
|
+
<p className="mt-1 text-sm text-muted-foreground">
|
|
40
|
+
Or nothing you can see does. Try fewer words, or a different spelling.
|
|
41
|
+
</p>
|
|
42
|
+
</div>
|
|
43
|
+
) : (
|
|
44
|
+
<ul className="divide-y divide-border">
|
|
45
|
+
{hits.map((hit) => (
|
|
46
|
+
<li key={hit.postId} className="flex flex-col gap-1 px-4 py-3 hover:bg-secondary/50">
|
|
47
|
+
<a href={hit.href} className="text-sm font-medium text-foreground hover:text-primary">
|
|
48
|
+
{hit.threadTitle}
|
|
49
|
+
</a>
|
|
50
|
+
<p
|
|
51
|
+
className="text-sm text-muted-foreground [&_b]:font-semibold [&_b]:text-primary"
|
|
52
|
+
dangerouslySetInnerHTML={{ __html: hit.excerptHtml }}
|
|
53
|
+
/>
|
|
54
|
+
<p className={`${MICRO} normal-case`}>
|
|
55
|
+
{hit.authorUsername}
|
|
56
|
+
<span className="text-border">{' | '}</span>
|
|
57
|
+
<Stamp at={hit.postedAt} />
|
|
58
|
+
</p>
|
|
59
|
+
</li>
|
|
60
|
+
))}
|
|
61
|
+
</ul>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{nextHref !== null && (
|
|
65
|
+
<p className="border-t border-border px-4 py-2">
|
|
66
|
+
<a href={nextHref} className={`${MICRO} hover:text-primary`}>
|
|
67
|
+
{nextLabel} →
|
|
68
|
+
</a>
|
|
69
|
+
</p>
|
|
70
|
+
)}
|
|
71
|
+
</Frame>
|
|
72
|
+
|
|
73
|
+
<Frame>
|
|
74
|
+
<form method="get" action={within.action} className="flex flex-col gap-3 px-4 py-4">
|
|
75
|
+
<div>
|
|
76
|
+
<label htmlFor="search-within" className={`${MICRO} mb-1 block`}>
|
|
77
|
+
{within.label}
|
|
78
|
+
</label>
|
|
79
|
+
<input
|
|
80
|
+
id="search-within"
|
|
81
|
+
type="search"
|
|
82
|
+
name={within.field}
|
|
83
|
+
defaultValue={within.value}
|
|
84
|
+
autoComplete="off"
|
|
85
|
+
aria-describedby="search-within-hint"
|
|
86
|
+
className="w-full border border-input bg-surface px-3 py-2 text-sm text-foreground focus-visible:border-ring"
|
|
87
|
+
/>
|
|
88
|
+
<p id="search-within-hint" className="mt-1.5 text-xs text-muted-foreground">
|
|
89
|
+
{within.hint}
|
|
90
|
+
</p>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div>
|
|
94
|
+
<button type="submit" className={BUTTON_PRIMARY}>
|
|
95
|
+
{within.submitLabel}
|
|
96
|
+
</button>
|
|
97
|
+
</div>
|
|
98
|
+
</form>
|
|
99
|
+
</Frame>
|
|
100
|
+
|
|
101
|
+
<p>
|
|
102
|
+
<a href={newSearchHref} className={`${MICRO} hover:text-primary`}>
|
|
103
|
+
start a new search
|
|
104
|
+
</a>
|
|
105
|
+
</p>
|
|
106
|
+
</main>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ShellModel } from '@meith/theme-kit'
|
|
2
|
+
|
|
3
|
+
const APP_REGIONS = `
|
|
4
|
+
[data-theme-raidframe] .font-heading:not([class*="tracking-"]) {
|
|
5
|
+
text-transform: uppercase;
|
|
6
|
+
letter-spacing: 0.09em;
|
|
7
|
+
}
|
|
8
|
+
`
|
|
9
|
+
|
|
10
|
+
export function Shell({ viewer, children }: ShellModel) {
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
className="relative min-h-dvh bg-background text-foreground"
|
|
14
|
+
data-theme-raidframe=""
|
|
15
|
+
data-viewer={viewer.isGuest ? 'guest' : 'member'}
|
|
16
|
+
>
|
|
17
|
+
<style>{APP_REGIONS}</style>
|
|
18
|
+
|
|
19
|
+
<div
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
className="pointer-events-none fixed inset-x-0 top-0 h-80 bg-[radial-gradient(70%_100%_at_50%_0%,var(--color-primary),transparent_72%)] opacity-10"
|
|
22
|
+
/>
|
|
23
|
+
<div
|
|
24
|
+
aria-hidden="true"
|
|
25
|
+
className="pointer-events-none fixed inset-x-0 bottom-0 h-64 bg-[radial-gradient(60%_100%_at_50%_100%,var(--color-forum-unread),transparent_75%)] opacity-[0.07]"
|
|
26
|
+
/>
|
|
27
|
+
|
|
28
|
+
<a
|
|
29
|
+
href="#board-content"
|
|
30
|
+
className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:border focus:border-primary focus:bg-card focus:px-3 focus:py-2 focus:font-mono focus:text-xs focus:uppercase"
|
|
31
|
+
>
|
|
32
|
+
Skip to content
|
|
33
|
+
</a>
|
|
34
|
+
|
|
35
|
+
<div className="relative mx-auto flex min-h-dvh w-full max-w-6xl flex-col border-x border-border bg-background/80">
|
|
36
|
+
{children}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SubforumListModel } from '@meith/theme-kit'
|
|
2
|
+
|
|
3
|
+
import { MICRO, NUMERIC } from '../shared'
|
|
4
|
+
|
|
5
|
+
export function SubforumList({ forums }: SubforumListModel) {
|
|
6
|
+
if (forums.length === 0) return null
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<nav
|
|
10
|
+
aria-label="Subforums"
|
|
11
|
+
className="flex flex-wrap items-center gap-2 border border-border bg-card px-3 py-2"
|
|
12
|
+
>
|
|
13
|
+
<span className={MICRO}>subforums</span>
|
|
14
|
+
{forums.map((forum) => (
|
|
15
|
+
<a
|
|
16
|
+
key={forum.href}
|
|
17
|
+
href={forum.href}
|
|
18
|
+
className="inline-flex items-center gap-1.5 border border-border bg-surface px-2 py-0.5 font-mono text-[0.625rem] tracking-[0.1em] text-foreground uppercase hover:border-primary/60 hover:text-primary"
|
|
19
|
+
>
|
|
20
|
+
{forum.title}
|
|
21
|
+
<span className={`${NUMERIC} text-muted-foreground`}>{forum.threadCount}</span>
|
|
22
|
+
</a>
|
|
23
|
+
))}
|
|
24
|
+
</nav>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ThreadRowSlotModel } from '@meith/theme-kit'
|
|
2
|
+
|
|
3
|
+
import { MICRO, NUMERIC, ReadPip, Stamp, UserRef } from '../shared'
|
|
4
|
+
|
|
5
|
+
const TAG = 'border px-1.5 py-px font-mono text-[0.625rem] font-bold uppercase tracking-[0.12em]'
|
|
6
|
+
|
|
7
|
+
export function ThreadRow({ thread, select }: ThreadRowSlotModel) {
|
|
8
|
+
return (
|
|
9
|
+
<tr className="border-t border-border align-top hover:bg-secondary/50">
|
|
10
|
+
<td className="px-3 py-2.5">
|
|
11
|
+
<div className="flex flex-wrap items-baseline gap-2">
|
|
12
|
+
{select !== null && (
|
|
13
|
+
<label className="flex items-center">
|
|
14
|
+
<span className="sr-only">{select.label}</span>
|
|
15
|
+
<input
|
|
16
|
+
type="checkbox"
|
|
17
|
+
name={select.name}
|
|
18
|
+
value={select.value}
|
|
19
|
+
form={select.formId}
|
|
20
|
+
className="size-3.5 accent-primary"
|
|
21
|
+
/>
|
|
22
|
+
</label>
|
|
23
|
+
)}
|
|
24
|
+
|
|
25
|
+
<ReadPip unread={thread.isUnread} />
|
|
26
|
+
|
|
27
|
+
{thread.prefix !== null && (
|
|
28
|
+
<span className={`${TAG} border-primary/60 text-primary`}>{thread.prefix.label}</span>
|
|
29
|
+
)}
|
|
30
|
+
|
|
31
|
+
<a href={thread.href} className="font-medium text-foreground hover:text-primary">
|
|
32
|
+
{thread.title}
|
|
33
|
+
</a>
|
|
34
|
+
{thread.isUnread && <span className="sr-only">(new posts)</span>}
|
|
35
|
+
|
|
36
|
+
{thread.isSticky && (
|
|
37
|
+
<span className={`${TAG} border-thread-pinned/60 text-thread-pinned`}>pinned</span>
|
|
38
|
+
)}
|
|
39
|
+
{thread.isLocked && (
|
|
40
|
+
<span className={`${TAG} border-thread-locked/60 text-thread-locked`}>locked</span>
|
|
41
|
+
)}
|
|
42
|
+
{thread.isMoved && (
|
|
43
|
+
<span className={`${TAG} border-thread-moved/60 text-thread-moved`}>moved</span>
|
|
44
|
+
)}
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<p className={`${MICRO} mt-1 normal-case`}>
|
|
48
|
+
<span className="uppercase">started by</span>{' '}
|
|
49
|
+
<UserRef user={thread.author} className="hover:text-primary" />
|
|
50
|
+
</p>
|
|
51
|
+
</td>
|
|
52
|
+
|
|
53
|
+
<td className={`w-16 px-2 py-2.5 text-right text-xs ${NUMERIC} text-foreground`}>
|
|
54
|
+
{thread.replyCount}
|
|
55
|
+
</td>
|
|
56
|
+
<td className={`w-16 px-2 py-2.5 text-right text-xs ${NUMERIC} text-muted-foreground`}>
|
|
57
|
+
{thread.viewCount}
|
|
58
|
+
</td>
|
|
59
|
+
|
|
60
|
+
<td className="hidden w-52 px-3 py-2.5 text-xs text-muted-foreground sm:table-cell">
|
|
61
|
+
{thread.lastPost === null ? (
|
|
62
|
+
<span className={MICRO}>—</span>
|
|
63
|
+
) : (
|
|
64
|
+
<a href={thread.lastPost.href} className="block hover:text-primary">
|
|
65
|
+
<Stamp at={thread.lastPost.at} className="text-foreground" />
|
|
66
|
+
<span className={`${MICRO} mt-0.5 block normal-case`}>
|
|
67
|
+
by <UserRef user={thread.lastPost.author} linked={false} />
|
|
68
|
+
</span>
|
|
69
|
+
</a>
|
|
70
|
+
)}
|
|
71
|
+
</td>
|
|
72
|
+
</tr>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ThreadViewModel } from '@meith/theme-kit'
|
|
2
|
+
|
|
3
|
+
import { BUTTON, BUTTON_PRIMARY, HEADING, MICRO, NUMERIC, RULE } from '../shared'
|
|
4
|
+
|
|
5
|
+
const TAG = 'border px-1.5 py-px font-mono text-[0.625rem] font-bold uppercase tracking-[0.12em]'
|
|
6
|
+
|
|
7
|
+
export function ThreadView({ thread, forum, replyHref, markReadAction, regions }: ThreadViewModel) {
|
|
8
|
+
const reply =
|
|
9
|
+
replyHref === null ? null : (
|
|
10
|
+
<a href={replyHref} className={BUTTON_PRIMARY}>
|
|
11
|
+
post reply
|
|
12
|
+
</a>
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex flex-col gap-4 p-4">
|
|
17
|
+
<div className="border border-border bg-card shadow-elevation">
|
|
18
|
+
<div className="flex flex-wrap items-start justify-between gap-3 px-4 py-3">
|
|
19
|
+
<div className="min-w-0">
|
|
20
|
+
<p className={`${MICRO} normal-case`}>
|
|
21
|
+
<a href={forum.href} className="uppercase hover:text-primary">
|
|
22
|
+
{forum.label}
|
|
23
|
+
</a>
|
|
24
|
+
</p>
|
|
25
|
+
<h1 className={`${HEADING} text-xl text-foreground`}>{thread.title}</h1>
|
|
26
|
+
|
|
27
|
+
<p className="mt-1.5 flex flex-wrap items-center gap-2">
|
|
28
|
+
{thread.isSticky && (
|
|
29
|
+
<span className={`${TAG} border-thread-pinned/60 text-thread-pinned`}>pinned</span>
|
|
30
|
+
)}
|
|
31
|
+
{thread.isLocked && (
|
|
32
|
+
<span className={`${TAG} border-thread-locked/60 text-thread-locked`}>locked</span>
|
|
33
|
+
)}
|
|
34
|
+
<span className={`${MICRO} ${NUMERIC} normal-case`}>
|
|
35
|
+
<span className="text-foreground">{thread.replyCount}</span> replies
|
|
36
|
+
<span className="text-border">{' | '}</span>
|
|
37
|
+
<span className="text-foreground">{thread.viewCount}</span> views
|
|
38
|
+
</span>
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
{reply}
|
|
43
|
+
</div>
|
|
44
|
+
<div className={RULE} aria-hidden="true" />
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{regions.tools !== undefined && (
|
|
48
|
+
<div className="flex flex-col gap-2 empty:hidden">{regions.tools}</div>
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{regions.pagination}
|
|
52
|
+
|
|
53
|
+
<div className="flex flex-col gap-3">{regions.posts}</div>
|
|
54
|
+
|
|
55
|
+
{regions.pagination}
|
|
56
|
+
|
|
57
|
+
{regions.afterContent !== undefined && (
|
|
58
|
+
<div className="flex flex-col gap-2 empty:hidden">{regions.afterContent}</div>
|
|
59
|
+
)}
|
|
60
|
+
|
|
61
|
+
{regions.quickReply}
|
|
62
|
+
|
|
63
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
64
|
+
{reply}
|
|
65
|
+
{markReadAction !== null && (
|
|
66
|
+
<form action={markReadAction} method="post">
|
|
67
|
+
<button type="submit" className={BUTTON}>
|
|
68
|
+
mark read
|
|
69
|
+
</button>
|
|
70
|
+
</form>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
)
|
|
75
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Menu } from '@meith/ui/menu'
|
|
2
|
+
import type { UserPanelModel } from '@meith/theme-kit'
|
|
3
|
+
|
|
4
|
+
import { BUTTON, BUTTON_PRIMARY, MICRO } from '../shared'
|
|
5
|
+
|
|
6
|
+
const COUNT =
|
|
7
|
+
'inline-flex items-center gap-1 border px-1.5 py-0.5 font-mono text-[0.625rem] font-bold uppercase tracking-[0.12em] tabular-nums'
|
|
8
|
+
|
|
9
|
+
function Caret() {
|
|
10
|
+
return (
|
|
11
|
+
<svg
|
|
12
|
+
aria-hidden="true"
|
|
13
|
+
viewBox="0 0 10 10"
|
|
14
|
+
className="size-2 text-primary"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
strokeWidth="1.8"
|
|
18
|
+
strokeLinecap="square"
|
|
19
|
+
>
|
|
20
|
+
<path d="M1.5 3.5 5 7l3.5-3.5" />
|
|
21
|
+
</svg>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function UserPanel({
|
|
26
|
+
viewer,
|
|
27
|
+
links,
|
|
28
|
+
unreadNotifications,
|
|
29
|
+
unreadMessages,
|
|
30
|
+
children,
|
|
31
|
+
}: UserPanelModel) {
|
|
32
|
+
if (viewer.isGuest) {
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
35
|
+
{links.map((link, index) => (
|
|
36
|
+
<a key={link.href} href={link.href} className={index === 0 ? BUTTON_PRIMARY : BUTTON}>
|
|
37
|
+
{link.label}
|
|
38
|
+
</a>
|
|
39
|
+
))}
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const name = viewer.username ?? 'signed in'
|
|
45
|
+
const hrefFor = (label: string): string | null =>
|
|
46
|
+
links.find((link) => link.label === label)?.href ?? null
|
|
47
|
+
const notificationsHref = hrefFor('Notifications')
|
|
48
|
+
const messagesHref = hrefFor('Messages')
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
52
|
+
<noscript
|
|
53
|
+
dangerouslySetInnerHTML={{
|
|
54
|
+
__html:
|
|
55
|
+
'<style>[data-account="menu"]{display:none}[data-account="plain"]{display:flex!important}</style>',
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
|
|
59
|
+
{unreadNotifications > 0 && (
|
|
60
|
+
<a
|
|
61
|
+
href={notificationsHref ?? undefined}
|
|
62
|
+
className={`${COUNT} border-primary/60 bg-primary/15 text-primary hover:border-primary`}
|
|
63
|
+
>
|
|
64
|
+
{unreadNotifications} new
|
|
65
|
+
</a>
|
|
66
|
+
)}
|
|
67
|
+
{unreadMessages > 0 && (
|
|
68
|
+
<a
|
|
69
|
+
href={messagesHref ?? undefined}
|
|
70
|
+
className={`${COUNT} border-forum-unread/60 bg-forum-unread/15 text-forum-unread hover:border-forum-unread`}
|
|
71
|
+
>
|
|
72
|
+
{unreadMessages} pm
|
|
73
|
+
</a>
|
|
74
|
+
)}
|
|
75
|
+
|
|
76
|
+
<div data-account="menu" className="flex min-w-0 items-center">
|
|
77
|
+
<Menu
|
|
78
|
+
label="Your account"
|
|
79
|
+
items={links}
|
|
80
|
+
triggerClassName="rounded-none border-border bg-card px-2 py-1 hover:border-primary/70 hover:bg-card"
|
|
81
|
+
trigger={
|
|
82
|
+
<>
|
|
83
|
+
<span
|
|
84
|
+
aria-hidden="true"
|
|
85
|
+
className="size-1.5 rotate-45 bg-moderation-approved shadow-[0_0_6px_var(--color-moderation-approved)]"
|
|
86
|
+
/>
|
|
87
|
+
<span className="font-mono text-[0.6875rem] font-semibold tracking-[0.12em] text-foreground uppercase">
|
|
88
|
+
{name}
|
|
89
|
+
</span>
|
|
90
|
+
<Caret />
|
|
91
|
+
</>
|
|
92
|
+
}
|
|
93
|
+
>
|
|
94
|
+
{children}
|
|
95
|
+
</Menu>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<nav
|
|
99
|
+
data-account="plain"
|
|
100
|
+
style={{ display: 'none' }}
|
|
101
|
+
aria-label="Your account"
|
|
102
|
+
className="flex-wrap items-center gap-x-3 gap-y-1"
|
|
103
|
+
>
|
|
104
|
+
<span className={`${MICRO} text-foreground`}>{name}</span>
|
|
105
|
+
{links.map((link) => (
|
|
106
|
+
<a
|
|
107
|
+
key={link.href}
|
|
108
|
+
href={link.href}
|
|
109
|
+
className={`${MICRO} hover:text-primary`}
|
|
110
|
+
>
|
|
111
|
+
{link.label}
|
|
112
|
+
</a>
|
|
113
|
+
))}
|
|
114
|
+
{children}
|
|
115
|
+
</nav>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { WhoIsOnlineModel } from '@meith/theme-kit'
|
|
2
|
+
|
|
3
|
+
import { Frame, MICRO, NUMERIC, PanelHead, Stamp, UserRef } from '../shared'
|
|
4
|
+
|
|
5
|
+
export function WhoIsOnline({
|
|
6
|
+
guestCount,
|
|
7
|
+
members,
|
|
8
|
+
total,
|
|
9
|
+
recordCount,
|
|
10
|
+
recordAt,
|
|
11
|
+
fullListHref,
|
|
12
|
+
}: WhoIsOnlineModel) {
|
|
13
|
+
return (
|
|
14
|
+
<Frame aria-labelledby="who-is-online-heading">
|
|
15
|
+
<PanelHead
|
|
16
|
+
id="who-is-online-heading"
|
|
17
|
+
title="Online now"
|
|
18
|
+
aside={
|
|
19
|
+
<span className="inline-flex items-center gap-1.5 border border-moderation-approved/50 bg-moderation-approved/10 px-1.5 py-0.5">
|
|
20
|
+
<span
|
|
21
|
+
aria-hidden="true"
|
|
22
|
+
className="size-1.5 bg-moderation-approved shadow-[0_0_6px_var(--color-moderation-approved)]"
|
|
23
|
+
/>
|
|
24
|
+
<span
|
|
25
|
+
className={`${NUMERIC} text-[0.625rem] font-bold tracking-[0.12em] text-moderation-approved uppercase`}
|
|
26
|
+
>
|
|
27
|
+
{total} online
|
|
28
|
+
</span>
|
|
29
|
+
</span>
|
|
30
|
+
}
|
|
31
|
+
/>
|
|
32
|
+
|
|
33
|
+
<p className="px-3 py-2.5 text-xs">
|
|
34
|
+
{members.length === 0 ? (
|
|
35
|
+
<span className={MICRO}>no members</span>
|
|
36
|
+
) : (
|
|
37
|
+
members.map((member, index) => (
|
|
38
|
+
<span key={member.username}>
|
|
39
|
+
{index > 0 && <span className="text-border">{' | '}</span>}
|
|
40
|
+
<UserRef user={member} className="text-foreground hover:text-primary" />
|
|
41
|
+
{member.isInvisible && <span className={`${MICRO} ml-1`}>hidden</span>}
|
|
42
|
+
</span>
|
|
43
|
+
))
|
|
44
|
+
)}
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<p className={`${MICRO} border-t border-border px-3 py-1.5 normal-case`}>
|
|
48
|
+
<a href={fullListHref} className="uppercase hover:text-primary">
|
|
49
|
+
full list
|
|
50
|
+
</a>
|
|
51
|
+
<span className="text-border">{' | '}</span>
|
|
52
|
+
<span className="uppercase">{guestCount} guests</span>
|
|
53
|
+
<span className="text-border">{' | '}</span>
|
|
54
|
+
<span className="uppercase">record {recordCount}</span>
|
|
55
|
+
{recordAt !== null && (
|
|
56
|
+
<>
|
|
57
|
+
{' '}
|
|
58
|
+
<Stamp at={recordAt} />
|
|
59
|
+
</>
|
|
60
|
+
)}
|
|
61
|
+
</p>
|
|
62
|
+
</Frame>
|
|
63
|
+
)
|
|
64
|
+
}
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { defaultTheme } from '@meith/theme-default'
|
|
2
|
+
import { defineTheme } from '@meith/theme-kit'
|
|
3
|
+
|
|
4
|
+
import { Announcement } from './slots/announcement'
|
|
5
|
+
import { AuthPage } from './slots/auth-page'
|
|
6
|
+
import { BoardIndex } from './slots/board-index'
|
|
7
|
+
import { BoardStats } from './slots/board-stats'
|
|
8
|
+
import { CategoryBlock } from './slots/category-block'
|
|
9
|
+
import { DiscoveryView } from './slots/discovery-view'
|
|
10
|
+
import { ErrorNotice } from './slots/error-notice'
|
|
11
|
+
import { Footer } from './slots/footer'
|
|
12
|
+
import { ForumDisplay } from './slots/forum-display'
|
|
13
|
+
import { ForumJump } from './slots/forum-jump'
|
|
14
|
+
import { ForumRow } from './slots/forum-row'
|
|
15
|
+
import { Header } from './slots/header'
|
|
16
|
+
import { LatestPosts } from './slots/latest-posts'
|
|
17
|
+
import { LatestThreads } from './slots/latest-threads'
|
|
18
|
+
import { MemberProfile } from './slots/member-profile'
|
|
19
|
+
import { Navigation } from './slots/navigation'
|
|
20
|
+
import { Notice } from './slots/notice'
|
|
21
|
+
import { Pagination } from './slots/pagination'
|
|
22
|
+
import { PanelNav } from './slots/panel-nav'
|
|
23
|
+
import { PanelPage, PanelSection } from './slots/panel-page'
|
|
24
|
+
import { PanelShell } from './slots/panel-shell'
|
|
25
|
+
import { PostActions } from './slots/post-actions'
|
|
26
|
+
import { PostBit } from './slots/post-bit'
|
|
27
|
+
import { PostForm } from './slots/post-form'
|
|
28
|
+
import { RedirectNotice } from './slots/redirect-notice'
|
|
29
|
+
import { SearchForm } from './slots/search-form'
|
|
30
|
+
import { SearchResults } from './slots/search-results'
|
|
31
|
+
import { Shell } from './slots/shell'
|
|
32
|
+
import { SubforumList } from './slots/subforum-list'
|
|
33
|
+
import { ThreadRow } from './slots/thread-row'
|
|
34
|
+
import { ThreadView } from './slots/thread-view'
|
|
35
|
+
import { UserPanel } from './slots/user-panel'
|
|
36
|
+
import { WhoIsOnline } from './slots/who-is-online'
|
|
37
|
+
|
|
38
|
+
export const raidframeTheme = defineTheme({
|
|
39
|
+
key: 'raidframe',
|
|
40
|
+
title: 'Raidframe',
|
|
41
|
+
extends: defaultTheme,
|
|
42
|
+
slots: {
|
|
43
|
+
Shell,
|
|
44
|
+
Header,
|
|
45
|
+
UserPanel,
|
|
46
|
+
Navigation,
|
|
47
|
+
Footer,
|
|
48
|
+
Notice,
|
|
49
|
+
Announcement,
|
|
50
|
+
|
|
51
|
+
BoardIndex,
|
|
52
|
+
CategoryBlock,
|
|
53
|
+
ForumRow,
|
|
54
|
+
BoardStats,
|
|
55
|
+
WhoIsOnline,
|
|
56
|
+
LatestThreads,
|
|
57
|
+
LatestPosts,
|
|
58
|
+
|
|
59
|
+
ForumDisplay,
|
|
60
|
+
ThreadRow,
|
|
61
|
+
SubforumList,
|
|
62
|
+
Pagination,
|
|
63
|
+
|
|
64
|
+
ThreadView,
|
|
65
|
+
PostBit,
|
|
66
|
+
PostActions,
|
|
67
|
+
|
|
68
|
+
PostForm,
|
|
69
|
+
MemberProfile,
|
|
70
|
+
SearchForm,
|
|
71
|
+
SearchResults,
|
|
72
|
+
DiscoveryView,
|
|
73
|
+
ForumJump,
|
|
74
|
+
|
|
75
|
+
PanelShell,
|
|
76
|
+
PanelNav,
|
|
77
|
+
PanelPage,
|
|
78
|
+
PanelSection,
|
|
79
|
+
AuthPage,
|
|
80
|
+
|
|
81
|
+
RedirectNotice,
|
|
82
|
+
ErrorNotice,
|
|
83
|
+
},
|
|
84
|
+
})
|