@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.
Files changed (39) hide show
  1. package/LICENSE.md +165 -0
  2. package/package.json +33 -0
  3. package/src/index.ts +3 -0
  4. package/src/shared.tsx +133 -0
  5. package/src/slots/announcement.tsx +40 -0
  6. package/src/slots/auth-page.tsx +53 -0
  7. package/src/slots/board-index.tsx +35 -0
  8. package/src/slots/board-stats.tsx +52 -0
  9. package/src/slots/category-block.tsx +37 -0
  10. package/src/slots/discovery-view.tsx +122 -0
  11. package/src/slots/error-notice.tsx +26 -0
  12. package/src/slots/footer.tsx +34 -0
  13. package/src/slots/forum-display.tsx +81 -0
  14. package/src/slots/forum-jump.tsx +45 -0
  15. package/src/slots/forum-row.tsx +66 -0
  16. package/src/slots/header.tsx +67 -0
  17. package/src/slots/latest-posts.tsx +42 -0
  18. package/src/slots/latest-threads.tsx +42 -0
  19. package/src/slots/member-profile.tsx +102 -0
  20. package/src/slots/navigation.tsx +31 -0
  21. package/src/slots/notice.tsx +32 -0
  22. package/src/slots/pagination.tsx +48 -0
  23. package/src/slots/panel-nav.tsx +110 -0
  24. package/src/slots/panel-page.tsx +72 -0
  25. package/src/slots/panel-shell.tsx +33 -0
  26. package/src/slots/post-actions.tsx +29 -0
  27. package/src/slots/post-bit.tsx +225 -0
  28. package/src/slots/post-form.tsx +44 -0
  29. package/src/slots/redirect-notice.tsx +26 -0
  30. package/src/slots/search-form.tsx +102 -0
  31. package/src/slots/search-results.tsx +108 -0
  32. package/src/slots/shell.tsx +40 -0
  33. package/src/slots/subforum-list.tsx +26 -0
  34. package/src/slots/thread-row.tsx +74 -0
  35. package/src/slots/thread-view.tsx +75 -0
  36. package/src/slots/user-panel.tsx +118 -0
  37. package/src/slots/who-is-online.tsx +64 -0
  38. package/src/theme.ts +84 -0
  39. 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 &ldquo;{terms}&rdquo;
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
+ })