@meith/theme-raidframe 0.3.0 → 0.4.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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meith/theme-raidframe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A gaming theme for Meith — guild, raid and match boards — built on the default theme's slots.",
|
|
5
5
|
"license": "LGPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@meith/theme-
|
|
24
|
-
"@meith/theme-
|
|
25
|
-
"@meith/ui": "^0.
|
|
23
|
+
"@meith/theme-default": "^0.4.0",
|
|
24
|
+
"@meith/theme-kit": "^0.4.0",
|
|
25
|
+
"@meith/ui": "^0.4.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"react": "^19.2.0"
|
|
@@ -13,7 +13,7 @@ export function CategoryBlock({ category, children }: CategoryBlockModel) {
|
|
|
13
13
|
</p>
|
|
14
14
|
)}
|
|
15
15
|
|
|
16
|
-
<table className="w-full border-collapse text-sm">
|
|
16
|
+
<table className="w-full table-fixed border-collapse text-sm">
|
|
17
17
|
<thead>
|
|
18
18
|
<tr className={`${MICRO} text-left`}>
|
|
19
19
|
<th scope="col" className="px-3 py-1.5 font-semibold">
|
package/src/slots/panel-page.tsx
CHANGED
|
@@ -8,12 +8,23 @@ const LABEL: Record<PanelKind, string> = {
|
|
|
8
8
|
admincp: 'control panel',
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function PanelPage({
|
|
11
|
+
export function PanelPage({
|
|
12
|
+
panel,
|
|
13
|
+
title,
|
|
14
|
+
back,
|
|
15
|
+
frame,
|
|
16
|
+
width,
|
|
17
|
+
gap,
|
|
18
|
+
regions,
|
|
19
|
+
children,
|
|
20
|
+
}: PanelPageModel) {
|
|
12
21
|
return (
|
|
13
22
|
<main
|
|
14
23
|
id="board-content"
|
|
15
24
|
tabIndex={-1}
|
|
16
|
-
className={`flex w-full flex-col p-4 ${
|
|
25
|
+
className={`flex w-full flex-col p-4 ${
|
|
26
|
+
frame === 'standalone' ? 'mx-auto flex-1' : ''
|
|
27
|
+
} ${width === 'wide' ? 'max-w-none' : 'max-w-4xl'} ${
|
|
17
28
|
gap === 'loose' ? 'gap-6' : 'gap-4'
|
|
18
29
|
}`}
|
|
19
30
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { OptionModel, SearchFormModel } from '@meith/theme-kit'
|
|
1
|
+
import type { OptionModel, SearchAdvancedModel, SearchFormModel } from '@meith/theme-kit'
|
|
2
2
|
|
|
3
3
|
import { BUTTON_PRIMARY, Frame, MICRO, PanelHead } from '../shared'
|
|
4
4
|
|
|
@@ -34,6 +34,60 @@ function Choice({
|
|
|
34
34
|
)
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function Advanced({ label, isOpen, author, toggles, choices }: SearchAdvancedModel) {
|
|
38
|
+
return (
|
|
39
|
+
<details open={isOpen} className="border border-border">
|
|
40
|
+
<summary className={`${MICRO} cursor-default px-3 py-2 select-none`}>{label}</summary>
|
|
41
|
+
|
|
42
|
+
<div className="flex flex-col gap-4 border-t border-border px-3 py-3">
|
|
43
|
+
<div>
|
|
44
|
+
<label htmlFor="search-author" className={`${MICRO} mb-1 block`}>
|
|
45
|
+
{author.label}
|
|
46
|
+
</label>
|
|
47
|
+
<input
|
|
48
|
+
id="search-author"
|
|
49
|
+
type="text"
|
|
50
|
+
name={author.field}
|
|
51
|
+
defaultValue={author.value}
|
|
52
|
+
placeholder={author.placeholder}
|
|
53
|
+
autoComplete="off"
|
|
54
|
+
aria-describedby="search-author-hint"
|
|
55
|
+
className={CONTROL}
|
|
56
|
+
/>
|
|
57
|
+
<p id="search-author-hint" className="mt-1.5 text-xs text-muted-foreground">
|
|
58
|
+
{author.hint}
|
|
59
|
+
</p>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{toggles.map((toggle) => (
|
|
63
|
+
<label key={toggle.field} className="flex items-center gap-2 text-sm select-none">
|
|
64
|
+
<input
|
|
65
|
+
type="checkbox"
|
|
66
|
+
name={toggle.field}
|
|
67
|
+
value={toggle.value}
|
|
68
|
+
defaultChecked={toggle.isOn}
|
|
69
|
+
className="size-4 border border-input accent-primary"
|
|
70
|
+
/>
|
|
71
|
+
{toggle.label}
|
|
72
|
+
</label>
|
|
73
|
+
))}
|
|
74
|
+
|
|
75
|
+
<div className="grid gap-4 sm:grid-cols-3">
|
|
76
|
+
{choices.map((choice) => (
|
|
77
|
+
<Choice
|
|
78
|
+
key={choice.field}
|
|
79
|
+
id={`search-${choice.field}`}
|
|
80
|
+
label={choice.label}
|
|
81
|
+
name={choice.field}
|
|
82
|
+
options={choice.options}
|
|
83
|
+
/>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</details>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
37
91
|
export function SearchForm({
|
|
38
92
|
action,
|
|
39
93
|
fields,
|
|
@@ -43,6 +97,7 @@ export function SearchForm({
|
|
|
43
97
|
sorts,
|
|
44
98
|
hint,
|
|
45
99
|
errorMessage,
|
|
100
|
+
advanced,
|
|
46
101
|
}: SearchFormModel) {
|
|
47
102
|
const described = [
|
|
48
103
|
hint === null ? null : 'search-hint',
|
|
@@ -91,6 +146,8 @@ export function SearchForm({
|
|
|
91
146
|
<Choice id="search-sort" label="Sort by" name={fields.sort} options={sorts} />
|
|
92
147
|
</div>
|
|
93
148
|
|
|
149
|
+
{advanced !== undefined && <Advanced {...advanced} />}
|
|
150
|
+
|
|
94
151
|
<div>
|
|
95
152
|
<button type="submit" className={BUTTON_PRIMARY}>
|
|
96
153
|
search
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import type { SearchResultsModel } from '@meith/theme-kit'
|
|
1
|
+
import type { OptionModel, SearchRefineModel, SearchResultsModel } from '@meith/theme-kit'
|
|
2
2
|
|
|
3
3
|
import { BUTTON_PRIMARY, Frame, HEADING, MICRO, RULE, Stamp } from '../shared'
|
|
4
4
|
|
|
5
|
+
const CONTROL =
|
|
6
|
+
'w-full border border-input bg-surface px-3 py-2 text-sm text-foreground focus-visible:border-ring'
|
|
7
|
+
|
|
5
8
|
export function SearchResults({
|
|
6
9
|
terms,
|
|
7
10
|
searchedAt,
|
|
@@ -10,6 +13,7 @@ export function SearchResults({
|
|
|
10
13
|
nextLabel,
|
|
11
14
|
newSearchHref,
|
|
12
15
|
within,
|
|
16
|
+
refine,
|
|
13
17
|
}: SearchResultsModel) {
|
|
14
18
|
return (
|
|
15
19
|
<main
|
|
@@ -32,6 +36,8 @@ export function SearchResults({
|
|
|
32
36
|
<div className={RULE} aria-hidden="true" />
|
|
33
37
|
</div>
|
|
34
38
|
|
|
39
|
+
{refine !== undefined && <Refine {...refine} />}
|
|
40
|
+
|
|
35
41
|
<Frame>
|
|
36
42
|
{hits.length === 0 ? (
|
|
37
43
|
<div className="px-4 py-6 text-center">
|
|
@@ -72,6 +78,15 @@ export function SearchResults({
|
|
|
72
78
|
|
|
73
79
|
<Frame>
|
|
74
80
|
<form method="get" action={within.action} className="flex flex-col gap-3 px-4 py-4">
|
|
81
|
+
{(within.hidden ?? []).map((field) => (
|
|
82
|
+
<input
|
|
83
|
+
key={`${field.name}-${field.value}`}
|
|
84
|
+
type="hidden"
|
|
85
|
+
name={field.name}
|
|
86
|
+
value={field.value}
|
|
87
|
+
/>
|
|
88
|
+
))}
|
|
89
|
+
|
|
75
90
|
<div>
|
|
76
91
|
<label htmlFor="search-within" className={`${MICRO} mb-1 block`}>
|
|
77
92
|
{within.label}
|
|
@@ -83,7 +98,7 @@ export function SearchResults({
|
|
|
83
98
|
defaultValue={within.value}
|
|
84
99
|
autoComplete="off"
|
|
85
100
|
aria-describedby="search-within-hint"
|
|
86
|
-
className=
|
|
101
|
+
className={CONTROL}
|
|
87
102
|
/>
|
|
88
103
|
<p id="search-within-hint" className="mt-1.5 text-xs text-muted-foreground">
|
|
89
104
|
{within.hint}
|
|
@@ -106,3 +121,111 @@ export function SearchResults({
|
|
|
106
121
|
</main>
|
|
107
122
|
)
|
|
108
123
|
}
|
|
124
|
+
|
|
125
|
+
function Refine({
|
|
126
|
+
action,
|
|
127
|
+
label,
|
|
128
|
+
summary,
|
|
129
|
+
note,
|
|
130
|
+
sorts,
|
|
131
|
+
sortsLabel,
|
|
132
|
+
choices,
|
|
133
|
+
submitLabel,
|
|
134
|
+
applied,
|
|
135
|
+
clearHref,
|
|
136
|
+
}: SearchRefineModel) {
|
|
137
|
+
return (
|
|
138
|
+
<section
|
|
139
|
+
aria-label={label}
|
|
140
|
+
className="flex flex-col gap-2.5 border border-border bg-card px-4 py-3 shadow-elevation"
|
|
141
|
+
>
|
|
142
|
+
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
143
|
+
<p className="text-sm font-medium text-foreground">{summary}</p>
|
|
144
|
+
|
|
145
|
+
<nav aria-label={sortsLabel} className="flex items-center gap-1">
|
|
146
|
+
{sorts.map((sort) => (
|
|
147
|
+
<a
|
|
148
|
+
key={sort.label}
|
|
149
|
+
href={sort.href}
|
|
150
|
+
{...(sort.isCurrent ? { 'aria-current': 'true' as const } : {})}
|
|
151
|
+
className={`${MICRO} border px-2 py-1 ${
|
|
152
|
+
sort.isCurrent
|
|
153
|
+
? 'border-primary bg-primary/10 text-primary'
|
|
154
|
+
: 'border-transparent hover:text-primary'
|
|
155
|
+
}`}
|
|
156
|
+
>
|
|
157
|
+
{sort.label}
|
|
158
|
+
</a>
|
|
159
|
+
))}
|
|
160
|
+
</nav>
|
|
161
|
+
|
|
162
|
+
{applied.map((chip) => (
|
|
163
|
+
<a
|
|
164
|
+
key={chip.label}
|
|
165
|
+
href={chip.removeHref}
|
|
166
|
+
className={`${MICRO} inline-flex items-center gap-1.5 border border-primary bg-primary/10 px-2 py-1 normal-case text-primary hover:bg-primary/20`}
|
|
167
|
+
>
|
|
168
|
+
{chip.label}
|
|
169
|
+
<span aria-hidden="true">×</span>
|
|
170
|
+
<span className="sr-only">— remove this filter</span>
|
|
171
|
+
</a>
|
|
172
|
+
))}
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<form
|
|
176
|
+
method="get"
|
|
177
|
+
action={action}
|
|
178
|
+
className="flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-border pt-2.5"
|
|
179
|
+
>
|
|
180
|
+
{choices.map((choice) => (
|
|
181
|
+
<RefineChoice key={choice.field} {...choice} />
|
|
182
|
+
))}
|
|
183
|
+
|
|
184
|
+
<button type="submit" className={`${BUTTON_PRIMARY} h-8 px-3`}>
|
|
185
|
+
{submitLabel}
|
|
186
|
+
</button>
|
|
187
|
+
|
|
188
|
+
{clearHref !== null && (
|
|
189
|
+
<a href={clearHref} className={`${MICRO} hover:text-primary`}>
|
|
190
|
+
clear filters
|
|
191
|
+
</a>
|
|
192
|
+
)}
|
|
193
|
+
</form>
|
|
194
|
+
|
|
195
|
+
{note !== null && <p className="text-xs text-muted-foreground">{note}</p>}
|
|
196
|
+
</section>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function RefineChoice({
|
|
201
|
+
field,
|
|
202
|
+
label,
|
|
203
|
+
options,
|
|
204
|
+
}: {
|
|
205
|
+
field: string
|
|
206
|
+
label: string
|
|
207
|
+
options: readonly OptionModel[]
|
|
208
|
+
}) {
|
|
209
|
+
const selected = options.find((option) => option.isSelected)
|
|
210
|
+
const id = `search-refine-${field}`
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<span className="inline-flex items-center gap-1.5">
|
|
214
|
+
<label htmlFor={id} className={MICRO}>
|
|
215
|
+
{label}
|
|
216
|
+
</label>
|
|
217
|
+
<select
|
|
218
|
+
id={id}
|
|
219
|
+
name={field}
|
|
220
|
+
defaultValue={selected?.value ?? ''}
|
|
221
|
+
className="h-8 border border-input bg-surface px-2 text-xs text-foreground focus-visible:border-ring"
|
|
222
|
+
>
|
|
223
|
+
{options.map((option) => (
|
|
224
|
+
<option key={option.value} value={option.value}>
|
|
225
|
+
{option.label}
|
|
226
|
+
</option>
|
|
227
|
+
))}
|
|
228
|
+
</select>
|
|
229
|
+
</span>
|
|
230
|
+
)
|
|
231
|
+
}
|
package/src/slots/user-panel.tsx
CHANGED
|
@@ -27,6 +27,8 @@ export function UserPanel({
|
|
|
27
27
|
links,
|
|
28
28
|
unreadNotifications,
|
|
29
29
|
unreadMessages,
|
|
30
|
+
notificationsHref,
|
|
31
|
+
messagesHref,
|
|
30
32
|
children,
|
|
31
33
|
}: UserPanelModel) {
|
|
32
34
|
if (viewer.isGuest) {
|
|
@@ -42,10 +44,6 @@ export function UserPanel({
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
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
47
|
|
|
50
48
|
return (
|
|
51
49
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -58,7 +56,7 @@ export function UserPanel({
|
|
|
58
56
|
|
|
59
57
|
{unreadNotifications > 0 && (
|
|
60
58
|
<a
|
|
61
|
-
href={notificationsHref
|
|
59
|
+
href={notificationsHref}
|
|
62
60
|
className={`${COUNT} border-primary/60 bg-primary/15 text-primary hover:border-primary`}
|
|
63
61
|
>
|
|
64
62
|
{unreadNotifications} new
|
|
@@ -66,7 +64,7 @@ export function UserPanel({
|
|
|
66
64
|
)}
|
|
67
65
|
{unreadMessages > 0 && (
|
|
68
66
|
<a
|
|
69
|
-
href={messagesHref
|
|
67
|
+
href={messagesHref}
|
|
70
68
|
className={`${COUNT} border-forum-unread/60 bg-forum-unread/15 text-forum-unread hover:border-forum-unread`}
|
|
71
69
|
>
|
|
72
70
|
{unreadMessages} pm
|