@meith/theme-default 0.3.3 → 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 +3 -3
- package/src/slots/search-form.tsx +51 -1
- package/src/slots/search-results.tsx +134 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meith/theme-default",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Meith's default theme — every slot and every token, and the reference for what a complete theme fills.",
|
|
5
5
|
"license": "LGPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@meith/theme-kit": "^0.
|
|
24
|
-
"@meith/ui": "^0.
|
|
23
|
+
"@meith/theme-kit": "^0.4.0",
|
|
24
|
+
"@meith/ui": "^0.4.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"react": "^19.2.0"
|
|
@@ -4,12 +4,13 @@ import {
|
|
|
4
4
|
AlertTitle,
|
|
5
5
|
Card,
|
|
6
6
|
CardContent,
|
|
7
|
+
Disclosure,
|
|
7
8
|
Field,
|
|
8
9
|
Input,
|
|
9
10
|
NativeSelect,
|
|
10
11
|
buttonVariants,
|
|
11
12
|
} from '@meith/ui'
|
|
12
|
-
import type { OptionModel, SearchFormModel } from '@meith/theme-kit'
|
|
13
|
+
import type { OptionModel, SearchAdvancedModel, SearchFormModel } from '@meith/theme-kit'
|
|
13
14
|
|
|
14
15
|
export function SearchForm({
|
|
15
16
|
action,
|
|
@@ -20,6 +21,7 @@ export function SearchForm({
|
|
|
20
21
|
sorts,
|
|
21
22
|
hint,
|
|
22
23
|
errorMessage,
|
|
24
|
+
advanced,
|
|
23
25
|
}: SearchFormModel) {
|
|
24
26
|
return (
|
|
25
27
|
<Card>
|
|
@@ -48,6 +50,8 @@ export function SearchForm({
|
|
|
48
50
|
<Choice label="Sort by" name={fields.sort} options={sorts} />
|
|
49
51
|
</div>
|
|
50
52
|
|
|
53
|
+
{advanced !== undefined && <Advanced {...advanced} />}
|
|
54
|
+
|
|
51
55
|
<div>
|
|
52
56
|
<button type="submit" className={buttonVariants({ variant: 'primary' })}>
|
|
53
57
|
Search
|
|
@@ -67,6 +71,52 @@ export function SearchForm({
|
|
|
67
71
|
)
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
function Advanced({ label, isOpen, author, toggles, choices }: SearchAdvancedModel) {
|
|
75
|
+
return (
|
|
76
|
+
<Disclosure summary={label} open={isOpen}>
|
|
77
|
+
<div className="flex flex-col gap-4">
|
|
78
|
+
<Field name={author.field} label={author.label} description={author.hint}>
|
|
79
|
+
{(control) => (
|
|
80
|
+
<Input
|
|
81
|
+
{...control}
|
|
82
|
+
defaultValue={author.value}
|
|
83
|
+
placeholder={author.placeholder}
|
|
84
|
+
autoComplete="off"
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
87
|
+
</Field>
|
|
88
|
+
|
|
89
|
+
{toggles.map((toggle) => (
|
|
90
|
+
<label
|
|
91
|
+
key={toggle.field}
|
|
92
|
+
className="flex items-center gap-2 text-sm text-foreground select-none"
|
|
93
|
+
>
|
|
94
|
+
<input
|
|
95
|
+
type="checkbox"
|
|
96
|
+
name={toggle.field}
|
|
97
|
+
value={toggle.value}
|
|
98
|
+
defaultChecked={toggle.isOn}
|
|
99
|
+
className="size-4 rounded border border-input accent-primary"
|
|
100
|
+
/>
|
|
101
|
+
{toggle.label}
|
|
102
|
+
</label>
|
|
103
|
+
))}
|
|
104
|
+
|
|
105
|
+
<div className="grid gap-4 sm:grid-cols-3">
|
|
106
|
+
{choices.map((choice) => (
|
|
107
|
+
<Choice
|
|
108
|
+
key={choice.field}
|
|
109
|
+
label={choice.label}
|
|
110
|
+
name={choice.field}
|
|
111
|
+
options={choice.options}
|
|
112
|
+
/>
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</Disclosure>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
70
120
|
function Choice({
|
|
71
121
|
label,
|
|
72
122
|
name,
|
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
EmptyTitle,
|
|
9
9
|
Field,
|
|
10
10
|
Input,
|
|
11
|
+
NativeSelect,
|
|
11
12
|
buttonVariants,
|
|
13
|
+
cn,
|
|
12
14
|
} from '@meith/ui'
|
|
13
|
-
import type { SearchResultsModel } from '@meith/theme-kit'
|
|
15
|
+
import type { OptionModel, SearchRefineModel, SearchResultsModel } from '@meith/theme-kit'
|
|
14
16
|
|
|
15
17
|
import { LINK, Stamp, pageAt } from '../shared'
|
|
16
18
|
|
|
@@ -22,6 +24,7 @@ export function SearchResults({
|
|
|
22
24
|
nextLabel,
|
|
23
25
|
newSearchHref,
|
|
24
26
|
within,
|
|
27
|
+
refine,
|
|
25
28
|
}: SearchResultsModel) {
|
|
26
29
|
return (
|
|
27
30
|
<main
|
|
@@ -39,6 +42,8 @@ export function SearchResults({
|
|
|
39
42
|
</p>
|
|
40
43
|
</div>
|
|
41
44
|
|
|
45
|
+
{refine !== undefined && <Refine {...refine} />}
|
|
46
|
+
|
|
42
47
|
<Card>
|
|
43
48
|
{hits.length === 0 ? (
|
|
44
49
|
<Empty>
|
|
@@ -78,6 +83,15 @@ export function SearchResults({
|
|
|
78
83
|
<Card>
|
|
79
84
|
<CardContent className="p-4 sm:p-5">
|
|
80
85
|
<form method="get" action={within.action} className="flex flex-col gap-4">
|
|
86
|
+
{(within.hidden ?? []).map((field) => (
|
|
87
|
+
<input
|
|
88
|
+
key={`${field.name}-${field.value}`}
|
|
89
|
+
type="hidden"
|
|
90
|
+
name={field.name}
|
|
91
|
+
value={field.value}
|
|
92
|
+
/>
|
|
93
|
+
))}
|
|
94
|
+
|
|
81
95
|
<Field name={within.field} label={within.label} description={within.hint}>
|
|
82
96
|
{(control) => (
|
|
83
97
|
<Input
|
|
@@ -104,3 +118,122 @@ export function SearchResults({
|
|
|
104
118
|
</main>
|
|
105
119
|
)
|
|
106
120
|
}
|
|
121
|
+
|
|
122
|
+
function Refine({
|
|
123
|
+
action,
|
|
124
|
+
label,
|
|
125
|
+
summary,
|
|
126
|
+
note,
|
|
127
|
+
sorts,
|
|
128
|
+
sortsLabel,
|
|
129
|
+
choices,
|
|
130
|
+
submitLabel,
|
|
131
|
+
applied,
|
|
132
|
+
clearHref,
|
|
133
|
+
}: SearchRefineModel) {
|
|
134
|
+
return (
|
|
135
|
+
<section
|
|
136
|
+
aria-label={label}
|
|
137
|
+
className="flex flex-col gap-2.5 rounded-lg border border-border bg-card px-3 py-2.5"
|
|
138
|
+
>
|
|
139
|
+
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
140
|
+
<p className="text-sm font-medium text-foreground">{summary}</p>
|
|
141
|
+
|
|
142
|
+
<nav
|
|
143
|
+
aria-label={sortsLabel}
|
|
144
|
+
className="flex items-center rounded-md border border-border p-0.5"
|
|
145
|
+
>
|
|
146
|
+
{sorts.map((sort) => (
|
|
147
|
+
<a
|
|
148
|
+
key={sort.label}
|
|
149
|
+
href={sort.href}
|
|
150
|
+
{...(sort.isCurrent ? { 'aria-current': 'true' as const } : {})}
|
|
151
|
+
className={`rounded px-2.5 py-1 text-xs font-medium ${
|
|
152
|
+
sort.isCurrent
|
|
153
|
+
? 'bg-secondary text-foreground'
|
|
154
|
+
: 'text-muted-foreground hover:bg-muted hover:text-foreground'
|
|
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="inline-flex items-center gap-1.5 rounded-full border border-primary/40 bg-primary/10 px-2.5 py-1 text-xs font-medium text-foreground 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
|
+
<Filter
|
|
182
|
+
key={choice.field}
|
|
183
|
+
label={choice.label}
|
|
184
|
+
name={choice.field}
|
|
185
|
+
options={choice.options}
|
|
186
|
+
/>
|
|
187
|
+
))}
|
|
188
|
+
|
|
189
|
+
<button
|
|
190
|
+
type="submit"
|
|
191
|
+
className={cn(buttonVariants({ variant: 'secondary', size: 'sm' }), 'h-8')}
|
|
192
|
+
>
|
|
193
|
+
{submitLabel}
|
|
194
|
+
</button>
|
|
195
|
+
|
|
196
|
+
{clearHref !== null && (
|
|
197
|
+
<a href={clearHref} className={`text-xs font-medium text-foreground ${LINK}`}>
|
|
198
|
+
Clear filters
|
|
199
|
+
</a>
|
|
200
|
+
)}
|
|
201
|
+
</form>
|
|
202
|
+
|
|
203
|
+
{note !== null && <p className="text-xs text-muted-foreground">{note}</p>}
|
|
204
|
+
</section>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function Filter({
|
|
209
|
+
label,
|
|
210
|
+
name,
|
|
211
|
+
options,
|
|
212
|
+
}: {
|
|
213
|
+
label: string
|
|
214
|
+
name: string
|
|
215
|
+
options: readonly OptionModel[]
|
|
216
|
+
}) {
|
|
217
|
+
const selected = options.find((option) => option.isSelected)
|
|
218
|
+
const id = `filter-${name}`
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<span className="inline-flex items-center gap-1.5">
|
|
222
|
+
<label htmlFor={id} className="text-xs font-medium text-muted-foreground">
|
|
223
|
+
{label}
|
|
224
|
+
</label>
|
|
225
|
+
<NativeSelect
|
|
226
|
+
id={id}
|
|
227
|
+
name={name}
|
|
228
|
+
defaultValue={selected?.value ?? ''}
|
|
229
|
+
className="h-8 w-auto min-w-0 py-0 text-xs"
|
|
230
|
+
>
|
|
231
|
+
{options.map((option) => (
|
|
232
|
+
<option key={option.value} value={option.value}>
|
|
233
|
+
{option.label}
|
|
234
|
+
</option>
|
|
235
|
+
))}
|
|
236
|
+
</NativeSelect>
|
|
237
|
+
</span>
|
|
238
|
+
)
|
|
239
|
+
}
|