@meith/theme-default 0.1.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.
@@ -0,0 +1,102 @@
1
+ import { Avatar, Badge, buttonVariants } from '@meith/ui'
2
+ import { Menu } from '@meith/ui/menu'
3
+ import type { UserPanelModel } from '@meith/theme-kit'
4
+
5
+ import { MUTED_LINK } from '../shared'
6
+
7
+ export function UserPanel({
8
+ viewer,
9
+ links,
10
+ unreadNotifications,
11
+ unreadMessages,
12
+ children,
13
+ }: UserPanelModel) {
14
+ if (viewer.isGuest) {
15
+ return (
16
+ <div className="flex items-center gap-2">
17
+ {links.map((link, index) => (
18
+ <a
19
+ key={link.href}
20
+ href={link.href}
21
+ className={buttonVariants({
22
+ variant: index === 0 ? 'primary' : 'outline',
23
+ size: 'sm',
24
+ })}
25
+ >
26
+ {link.label}
27
+ </a>
28
+ ))}
29
+ </div>
30
+ )
31
+ }
32
+
33
+ const name = viewer.username ?? 'Signed in'
34
+
35
+ return (
36
+ <div className="flex min-w-0 flex-col items-start gap-1 sm:items-end">
37
+ <noscript
38
+ dangerouslySetInnerHTML={{
39
+ __html:
40
+ '<style>[data-account="menu"]{display:none}[data-account="plain"]{display:flex!important}</style>',
41
+ }}
42
+ />
43
+
44
+ <div data-account="menu" className="flex min-w-0 items-center gap-2">
45
+ {unreadNotifications > 0 && (
46
+ <Badge tone="solid">
47
+ {unreadNotifications}
48
+ <span className="sr-only"> unread notifications</span>
49
+ <span aria-hidden="true"> new</span>
50
+ </Badge>
51
+ )}
52
+ {unreadMessages > 0 && (
53
+ <Badge tone="outline">
54
+ {unreadMessages}
55
+ <span className="sr-only"> unread messages</span>
56
+ <span aria-hidden="true"> unread</span>
57
+ </Badge>
58
+ )}
59
+
60
+ <Menu
61
+ label="Your account"
62
+ items={links}
63
+ trigger={
64
+ <>
65
+ <Avatar src={viewer.avatarUrl} name={name} size={24} />
66
+ <span className="max-w-40 truncate font-medium text-foreground">{name}</span>
67
+ <svg
68
+ aria-hidden="true"
69
+ viewBox="0 0 12 12"
70
+ className="size-3 shrink-0 text-muted-foreground"
71
+ fill="none"
72
+ stroke="currentColor"
73
+ strokeWidth="1.5"
74
+ strokeLinecap="round"
75
+ strokeLinejoin="round"
76
+ >
77
+ <path d="m3 4.5 3 3 3-3" />
78
+ </svg>
79
+ </>
80
+ }
81
+ >
82
+ {children}
83
+ </Menu>
84
+ </div>
85
+
86
+ <nav
87
+ data-account="plain"
88
+ style={{ display: 'none' }}
89
+ aria-label="Your account"
90
+ className="flex-wrap items-center gap-x-3 gap-y-1 text-xs sm:justify-end"
91
+ >
92
+ <span className="font-medium text-foreground">{name}</span>
93
+ {links.map((link) => (
94
+ <a key={link.href} href={link.href} className={MUTED_LINK}>
95
+ {link.label}
96
+ </a>
97
+ ))}
98
+ {children}
99
+ </nav>
100
+ </div>
101
+ )
102
+ }
@@ -0,0 +1,94 @@
1
+ import { Badge } from '@meith/ui'
2
+ import type { OnlineMemberModel, WhoIsOnlineModel } from '@meith/theme-kit'
3
+
4
+ import { LINK, NUMERIC, Stamp, UserRef } from '../shared'
5
+
6
+ const VISIBLE_NAMES = 12
7
+
8
+ export function WhoIsOnline({
9
+ guestCount,
10
+ members,
11
+ total,
12
+ recordCount,
13
+ recordAt,
14
+ fullListHref,
15
+ }: WhoIsOnlineModel) {
16
+ const shown = members.slice(0, VISIBLE_NAMES)
17
+ const rest = members.slice(VISIBLE_NAMES)
18
+
19
+ return (
20
+ <section
21
+ aria-labelledby="who-is-online-heading"
22
+ className="flex flex-wrap items-baseline gap-x-3 gap-y-1"
23
+ >
24
+ <h2 id="who-is-online-heading" className="sr-only">
25
+ Who&rsquo;s online
26
+ </h2>
27
+
28
+ <span className={NUMERIC}>
29
+ <span className="font-medium text-foreground">{total.toLocaleString('en')}</span> online —{' '}
30
+ {members.length.toLocaleString('en')} {members.length === 1 ? 'member' : 'members'},{' '}
31
+ {guestCount.toLocaleString('en')} {guestCount === 1 ? 'guest' : 'guests'}
32
+ </span>
33
+
34
+ {members.length === 0 ? (
35
+ <span>
36
+ {guestCount === 0
37
+ ? 'Nobody is reading the board right now.'
38
+ : 'Only guests are reading the board right now.'}
39
+ </span>
40
+ ) : (
41
+ <div className="min-w-0">
42
+ {shown.map((member, index) => (
43
+ <Name key={member.userId ?? member.username} member={member} first={index === 0} />
44
+ ))}
45
+
46
+ {rest.length > 0 && (
47
+ <details className="inline">
48
+ <summary
49
+ className={`inline cursor-default list-none ${LINK} [&::-webkit-details-marker]:hidden [&::marker]:content-none`}
50
+ >
51
+ {' and '}
52
+ <span className={NUMERIC}>{rest.length.toLocaleString('en')}</span> more
53
+ </summary>
54
+ {': '}
55
+ {rest.map((member, index) => (
56
+ <Name
57
+ key={member.userId ?? member.username}
58
+ member={member}
59
+ first={index === 0}
60
+ />
61
+ ))}
62
+ </details>
63
+ )}
64
+ </div>
65
+ )}
66
+
67
+ <span className="ms-auto flex flex-wrap items-baseline gap-x-3 gap-y-1">
68
+ <a href={fullListHref} className={`font-medium text-foreground ${LINK}`}>
69
+ See everyone online
70
+ </a>
71
+ {recordAt !== null && (
72
+ <span className={NUMERIC}>
73
+ Record: {recordCount.toLocaleString('en')} on <Stamp at={recordAt} />
74
+ </span>
75
+ )}
76
+ </span>
77
+ </section>
78
+ )
79
+ }
80
+
81
+ function Name({ member, first }: { member: OnlineMemberModel; first: boolean }) {
82
+ return (
83
+ <>
84
+ {!first && ', '}
85
+ <UserRef user={member} />
86
+ {member.isInvisible && (
87
+ <>
88
+ {' '}
89
+ <Badge tone="neutral">Invisible</Badge>
90
+ </>
91
+ )}
92
+ </>
93
+ )
94
+ }
package/src/theme.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { defineTheme } from '@meith/theme-kit'
2
+
3
+ import { BoardIndex } from './slots/board-index'
4
+ import { BoardStats } from './slots/board-stats'
5
+ import { CategoryBlock } from './slots/category-block'
6
+ import { WhoIsOnline } from './slots/who-is-online'
7
+ import { Footer } from './slots/footer'
8
+ import { ForumRow } from './slots/forum-row'
9
+ import { LatestPosts } from './slots/latest-posts'
10
+ import { LatestThreads } from './slots/latest-threads'
11
+ import { ForumDisplay } from './slots/forum-display'
12
+ import { Header } from './slots/header'
13
+ import { Navigation } from './slots/navigation'
14
+ import { Notice } from './slots/notice'
15
+ import { Announcement } from './slots/announcement'
16
+ import { Shell } from './slots/shell'
17
+ import { UserPanel } from './slots/user-panel'
18
+ import { Pagination } from './slots/pagination'
19
+ import { SubforumList } from './slots/subforum-list'
20
+ import { ThreadRow } from './slots/thread-row'
21
+ import { ThreadView } from './slots/thread-view'
22
+ import { PostBit } from './slots/post-bit'
23
+ import { PostForm } from './slots/post-form'
24
+ import { PostActions } from './slots/post-actions'
25
+ import { MemberProfile } from './slots/member-profile'
26
+ import { ForumJump } from './slots/forum-jump'
27
+ import { SearchForm } from './slots/search-form'
28
+ import { RedirectNotice } from './slots/redirect-notice'
29
+ import { ErrorNotice } from './slots/error-notice'
30
+
31
+ export const defaultTheme = defineTheme({
32
+ key: 'default',
33
+ title: 'Default',
34
+ slots: {
35
+ Shell,
36
+ Header,
37
+ UserPanel,
38
+ Navigation,
39
+ Footer,
40
+ Notice,
41
+ Announcement,
42
+
43
+ BoardIndex,
44
+ CategoryBlock,
45
+ ForumRow,
46
+ BoardStats,
47
+ WhoIsOnline,
48
+ LatestThreads,
49
+ LatestPosts,
50
+
51
+ ForumDisplay,
52
+ ThreadRow,
53
+ SubforumList,
54
+ Pagination,
55
+
56
+ ThreadView,
57
+ PostBit,
58
+ PostActions,
59
+
60
+ PostForm,
61
+
62
+ MemberProfile,
63
+
64
+ SearchForm,
65
+ ForumJump,
66
+
67
+ RedirectNotice,
68
+ ErrorNotice,
69
+ },
70
+ })
package/src/tokens.ts ADDED
@@ -0,0 +1,156 @@
1
+ export const TOKEN_NAMES = [
2
+ 'background',
3
+ 'foreground',
4
+ 'surface',
5
+ 'card',
6
+ 'card-foreground',
7
+ 'primary',
8
+ 'primary-foreground',
9
+ 'primary-hover',
10
+ 'secondary',
11
+ 'secondary-foreground',
12
+ 'muted',
13
+ 'muted-foreground',
14
+ 'accent',
15
+ 'accent-foreground',
16
+ 'destructive',
17
+ 'destructive-foreground',
18
+ 'border',
19
+ 'input',
20
+ 'ring',
21
+ 'shadow-tint',
22
+ 'radius',
23
+ 'density-unit',
24
+ 'font-mono-stack',
25
+ 'font-sans-stack',
26
+ 'font-heading-stack',
27
+ 'elevation',
28
+ 'forum-unread',
29
+ 'forum-read',
30
+ 'forum-locked',
31
+ 'thread-pinned',
32
+ 'thread-locked',
33
+ 'thread-moved',
34
+ 'thread-unapproved',
35
+ 'thread-deleted',
36
+ 'post-highlight',
37
+ 'post-own',
38
+ 'post-unapproved',
39
+ 'moderation-pending',
40
+ 'moderation-approved',
41
+ 'moderation-rejected',
42
+ 'group-admin',
43
+ 'group-supermod',
44
+ 'group-mod',
45
+ 'group-banned',
46
+ ] as const
47
+
48
+ export type TokenName = (typeof TOKEN_NAMES)[number]
49
+
50
+ export const SCHEME_INDEPENDENT_TOKENS = [
51
+ 'radius',
52
+ 'density-unit',
53
+ 'font-mono-stack',
54
+ 'font-sans-stack',
55
+ 'font-heading-stack',
56
+ 'elevation',
57
+ ] as const
58
+
59
+ export const LIGHT_TOKENS: Record<TokenName, string> = {
60
+ background: 'oklch(0.968 0 0)',
61
+ foreground: 'oklch(0.205 0 0)',
62
+ surface: 'oklch(0.941 0 0)',
63
+ card: 'oklch(1 0 0)',
64
+ 'card-foreground': 'oklch(0.205 0 0)',
65
+ primary: 'oklch(0.508 0.105 165.6)',
66
+ 'primary-foreground': 'oklch(0.985 0 0)',
67
+ 'primary-hover': 'oklch(0.434 0.089 165.6)',
68
+ secondary: 'oklch(0.94 0 0)',
69
+ 'secondary-foreground': 'oklch(0.269 0 0)',
70
+ muted: 'oklch(0.958 0 0)',
71
+ 'muted-foreground': 'oklch(0.494 0 0)',
72
+ accent: 'oklch(0.951 0 0)',
73
+ 'accent-foreground': 'oklch(0.205 0 0)',
74
+ destructive: 'oklch(0.514 0.183 28.5)',
75
+ 'destructive-foreground': 'oklch(0.985 0 0)',
76
+ border: 'oklch(0.905 0 0)',
77
+ input: 'oklch(0.64 0 0)',
78
+ ring: 'oklch(0.508 0.105 165.6)',
79
+ 'shadow-tint': 'oklch(0.205 0 0 / 8%)',
80
+ radius: '0.5rem',
81
+ 'density-unit': '0.25rem',
82
+ 'font-mono-stack': 'ui-monospace, "SFMono-Regular", "Menlo", monospace',
83
+ 'font-sans-stack': 'var(--font-inter), ui-sans-serif, system-ui, sans-serif',
84
+ 'font-heading-stack': 'var(--font-sans-stack)',
85
+ elevation: '0 1px 2px var(--shadow-tint), 0 12px 32px -16px var(--shadow-tint)',
86
+ 'forum-unread': 'oklch(0.205 0 0)',
87
+ 'forum-read': 'oklch(0.556 0 0)',
88
+ 'forum-locked': 'oklch(0.554 0.135 32)',
89
+ 'thread-pinned': 'oklch(0.55 0.12 68)',
90
+ 'thread-locked': 'oklch(0.554 0.135 32)',
91
+ 'thread-moved': 'oklch(0.556 0 0)',
92
+ 'thread-unapproved': 'oklch(0.545 0.11 88)',
93
+ 'thread-deleted': 'oklch(0.487 0.164 26)',
94
+ 'post-highlight': 'oklch(0.964 0.036 92)',
95
+ 'post-own': 'oklch(0.972 0 0)',
96
+ 'post-unapproved': 'oklch(0.956 0.035 84)',
97
+ 'moderation-pending': 'oklch(0.55 0.12 68)',
98
+ 'moderation-approved': 'oklch(0.518 0.113 150)',
99
+ 'moderation-rejected': 'oklch(0.554 0.135 32)',
100
+ 'group-admin': 'oklch(0.505 0.169 27)',
101
+ 'group-supermod': 'oklch(0.496 0.153 305)',
102
+ 'group-mod': 'oklch(0.489 0.127 250)',
103
+ 'group-banned': 'oklch(0.556 0 0)',
104
+ }
105
+
106
+ export const DARK_TOKENS: Record<TokenName, string> = {
107
+ background: 'oklch(0.15 0 0)',
108
+ foreground: 'oklch(0.967 0 0)',
109
+ surface: 'oklch(0.17 0 0)',
110
+ card: 'oklch(0.196 0 0)',
111
+ 'card-foreground': 'oklch(0.967 0 0)',
112
+ primary: 'oklch(0.773 0.153 163.2)',
113
+ 'primary-foreground': 'oklch(0.181 0.029 166.6)',
114
+ 'primary-hover': 'oklch(0.84 0.139 166.8)',
115
+ secondary: 'oklch(0.256 0 0)',
116
+ 'secondary-foreground': 'oklch(0.94 0 0)',
117
+ muted: 'oklch(0.228 0 0)',
118
+ 'muted-foreground': 'oklch(0.702 0 0)',
119
+ accent: 'oklch(0.276 0 0)',
120
+ 'accent-foreground': 'oklch(0.967 0 0)',
121
+ destructive: 'oklch(0.674 0.174 26)',
122
+ 'destructive-foreground': 'oklch(0.18 0 0)',
123
+ border: 'oklch(0.31 0 0)',
124
+ input: 'oklch(0.52 0 0)',
125
+ ring: 'oklch(0.773 0.153 163.2)',
126
+ 'shadow-tint': 'oklch(0 0 0 / 45%)',
127
+ radius: '0.5rem',
128
+ 'density-unit': '0.25rem',
129
+ 'font-mono-stack': 'ui-monospace, "SFMono-Regular", "Menlo", monospace',
130
+ 'font-sans-stack': 'var(--font-inter), ui-sans-serif, system-ui, sans-serif',
131
+ 'font-heading-stack': 'var(--font-sans-stack)',
132
+ elevation: '0 1px 2px var(--shadow-tint), 0 12px 32px -16px var(--shadow-tint)',
133
+ 'forum-unread': 'oklch(0.967 0 0)',
134
+ 'forum-read': 'oklch(0.6 0 0)',
135
+ 'forum-locked': 'oklch(0.708 0.13 34)',
136
+ 'thread-pinned': 'oklch(0.769 0.13 74)',
137
+ 'thread-locked': 'oklch(0.708 0.13 34)',
138
+ 'thread-moved': 'oklch(0.6 0 0)',
139
+ 'thread-unapproved': 'oklch(0.777 0.12 85)',
140
+ 'thread-deleted': 'oklch(0.664 0.157 27)',
141
+ 'post-highlight': 'oklch(0.276 0.043 92)',
142
+ 'post-own': 'oklch(0.221 0 0)',
143
+ 'post-unapproved': 'oklch(0.263 0.04 85)',
144
+ 'moderation-pending': 'oklch(0.769 0.13 74)',
145
+ 'moderation-approved': 'oklch(0.716 0.126 151)',
146
+ 'moderation-rejected': 'oklch(0.708 0.13 34)',
147
+ 'group-admin': 'oklch(0.687 0.166 27)',
148
+ 'group-supermod': 'oklch(0.703 0.144 305)',
149
+ 'group-mod': 'oklch(0.694 0.122 250)',
150
+ 'group-banned': 'oklch(0.6 0 0)',
151
+ }
152
+
153
+ export const BROWSER_THEME_COLOR = {
154
+ light: '#f4f4f4',
155
+ dark: '#0b0b0b',
156
+ } as const