@papernote/ui 2.0.2 → 2.0.4
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/dist/components/FilterBar.d.ts +1 -1
- package/dist/components/FilterBar.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +105 -75
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +105 -75
- package/dist/index.js.map +1 -1
- package/dist/styles.css +36 -28
- package/package.json +1 -1
- package/src/components/FilterBar.tsx +130 -53
- package/src/styles/index.css +15 -0
package/dist/styles.css
CHANGED
|
@@ -1424,6 +1424,10 @@
|
|
|
1424
1424
|
--tw-translate-x: calc(var(--spacing) * 0);
|
|
1425
1425
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1426
1426
|
}
|
|
1427
|
+
.translate-x-1 {
|
|
1428
|
+
--tw-translate-x: calc(var(--spacing) * 1);
|
|
1429
|
+
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1430
|
+
}
|
|
1427
1431
|
.translate-x-4 {
|
|
1428
1432
|
--tw-translate-x: calc(var(--spacing) * 4);
|
|
1429
1433
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
@@ -1432,6 +1436,10 @@
|
|
|
1432
1436
|
--tw-translate-x: calc(var(--spacing) * 5);
|
|
1433
1437
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1434
1438
|
}
|
|
1439
|
+
.translate-x-6 {
|
|
1440
|
+
--tw-translate-x: calc(var(--spacing) * 6);
|
|
1441
|
+
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
1442
|
+
}
|
|
1435
1443
|
.translate-x-7 {
|
|
1436
1444
|
--tw-translate-x: calc(var(--spacing) * 7);
|
|
1437
1445
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
@@ -5081,27 +5089,27 @@
|
|
|
5081
5089
|
}
|
|
5082
5090
|
}
|
|
5083
5091
|
.dark\:border-blue-800 {
|
|
5084
|
-
|
|
5092
|
+
&:where(.dark, .dark *) {
|
|
5085
5093
|
border-color: var(--color-blue-800);
|
|
5086
5094
|
}
|
|
5087
5095
|
}
|
|
5088
5096
|
.dark\:border-error-800 {
|
|
5089
|
-
|
|
5097
|
+
&:where(.dark, .dark *) {
|
|
5090
5098
|
border-color: var(--color-error-800);
|
|
5091
5099
|
}
|
|
5092
5100
|
}
|
|
5093
5101
|
.dark\:border-ink-700 {
|
|
5094
|
-
|
|
5102
|
+
&:where(.dark, .dark *) {
|
|
5095
5103
|
border-color: var(--color-ink-700);
|
|
5096
5104
|
}
|
|
5097
5105
|
}
|
|
5098
5106
|
.dark\:border-warning-800 {
|
|
5099
|
-
|
|
5107
|
+
&:where(.dark, .dark *) {
|
|
5100
5108
|
border-color: var(--color-warning-800);
|
|
5101
5109
|
}
|
|
5102
5110
|
}
|
|
5103
5111
|
.dark\:bg-blue-900\/20 {
|
|
5104
|
-
|
|
5112
|
+
&:where(.dark, .dark *) {
|
|
5105
5113
|
background-color: color-mix(in srgb, oklch(37.9% 0.146 265.522) 20%, transparent);
|
|
5106
5114
|
@supports (color: color-mix(in lab, red, red)) {
|
|
5107
5115
|
background-color: color-mix(in oklab, var(--color-blue-900) 20%, transparent);
|
|
@@ -5109,7 +5117,7 @@
|
|
|
5109
5117
|
}
|
|
5110
5118
|
}
|
|
5111
5119
|
.dark\:bg-error-900\/20 {
|
|
5112
|
-
|
|
5120
|
+
&:where(.dark, .dark *) {
|
|
5113
5121
|
background-color: color-mix(in srgb, #7f1d1d 20%, transparent);
|
|
5114
5122
|
@supports (color: color-mix(in lab, red, red)) {
|
|
5115
5123
|
background-color: color-mix(in oklab, var(--color-error-900) 20%, transparent);
|
|
@@ -5117,27 +5125,27 @@
|
|
|
5117
5125
|
}
|
|
5118
5126
|
}
|
|
5119
5127
|
.dark\:bg-ink-700 {
|
|
5120
|
-
|
|
5128
|
+
&:where(.dark, .dark *) {
|
|
5121
5129
|
background-color: var(--color-ink-700);
|
|
5122
5130
|
}
|
|
5123
5131
|
}
|
|
5124
5132
|
.dark\:bg-ink-800 {
|
|
5125
|
-
|
|
5133
|
+
&:where(.dark, .dark *) {
|
|
5126
5134
|
background-color: var(--color-ink-800);
|
|
5127
5135
|
}
|
|
5128
5136
|
}
|
|
5129
5137
|
.dark\:bg-ink-900 {
|
|
5130
|
-
|
|
5138
|
+
&:where(.dark, .dark *) {
|
|
5131
5139
|
background-color: var(--color-ink-900);
|
|
5132
5140
|
}
|
|
5133
5141
|
}
|
|
5134
5142
|
.dark\:bg-primary-900 {
|
|
5135
|
-
|
|
5143
|
+
&:where(.dark, .dark *) {
|
|
5136
5144
|
background-color: var(--color-primary-900);
|
|
5137
5145
|
}
|
|
5138
5146
|
}
|
|
5139
5147
|
.dark\:bg-primary-900\/20 {
|
|
5140
|
-
|
|
5148
|
+
&:where(.dark, .dark *) {
|
|
5141
5149
|
background-color: color-mix(in srgb, #0f172a 20%, transparent);
|
|
5142
5150
|
@supports (color: color-mix(in lab, red, red)) {
|
|
5143
5151
|
background-color: color-mix(in oklab, var(--color-primary-900) 20%, transparent);
|
|
@@ -5145,17 +5153,17 @@
|
|
|
5145
5153
|
}
|
|
5146
5154
|
}
|
|
5147
5155
|
.dark\:bg-slate-800 {
|
|
5148
|
-
|
|
5156
|
+
&:where(.dark, .dark *) {
|
|
5149
5157
|
background-color: var(--color-slate-800);
|
|
5150
5158
|
}
|
|
5151
5159
|
}
|
|
5152
5160
|
.dark\:bg-warning-900 {
|
|
5153
|
-
|
|
5161
|
+
&:where(.dark, .dark *) {
|
|
5154
5162
|
background-color: var(--color-warning-900);
|
|
5155
5163
|
}
|
|
5156
5164
|
}
|
|
5157
5165
|
.dark\:bg-warning-900\/20 {
|
|
5158
|
-
|
|
5166
|
+
&:where(.dark, .dark *) {
|
|
5159
5167
|
background-color: color-mix(in srgb, #78350f 20%, transparent);
|
|
5160
5168
|
@supports (color: color-mix(in lab, red, red)) {
|
|
5161
5169
|
background-color: color-mix(in oklab, var(--color-warning-900) 20%, transparent);
|
|
@@ -5163,62 +5171,62 @@
|
|
|
5163
5171
|
}
|
|
5164
5172
|
}
|
|
5165
5173
|
.dark\:fill-ink-300 {
|
|
5166
|
-
|
|
5174
|
+
&:where(.dark, .dark *) {
|
|
5167
5175
|
fill: var(--color-ink-300);
|
|
5168
5176
|
}
|
|
5169
5177
|
}
|
|
5170
5178
|
.dark\:text-blue-200 {
|
|
5171
|
-
|
|
5179
|
+
&:where(.dark, .dark *) {
|
|
5172
5180
|
color: var(--color-blue-200);
|
|
5173
5181
|
}
|
|
5174
5182
|
}
|
|
5175
5183
|
.dark\:text-error-200 {
|
|
5176
|
-
|
|
5184
|
+
&:where(.dark, .dark *) {
|
|
5177
5185
|
color: var(--color-error-200);
|
|
5178
5186
|
}
|
|
5179
5187
|
}
|
|
5180
5188
|
.dark\:text-ink-100 {
|
|
5181
|
-
|
|
5189
|
+
&:where(.dark, .dark *) {
|
|
5182
5190
|
color: var(--color-ink-100);
|
|
5183
5191
|
}
|
|
5184
5192
|
}
|
|
5185
5193
|
.dark\:text-ink-300 {
|
|
5186
|
-
|
|
5194
|
+
&:where(.dark, .dark *) {
|
|
5187
5195
|
color: var(--color-ink-300);
|
|
5188
5196
|
}
|
|
5189
5197
|
}
|
|
5190
5198
|
.dark\:text-ink-400 {
|
|
5191
|
-
|
|
5199
|
+
&:where(.dark, .dark *) {
|
|
5192
5200
|
color: var(--color-ink-400);
|
|
5193
5201
|
}
|
|
5194
5202
|
}
|
|
5195
5203
|
.dark\:text-primary-300 {
|
|
5196
|
-
|
|
5204
|
+
&:where(.dark, .dark *) {
|
|
5197
5205
|
color: var(--color-primary-300);
|
|
5198
5206
|
}
|
|
5199
5207
|
}
|
|
5200
5208
|
.dark\:text-primary-400 {
|
|
5201
|
-
|
|
5209
|
+
&:where(.dark, .dark *) {
|
|
5202
5210
|
color: var(--color-primary-400);
|
|
5203
5211
|
}
|
|
5204
5212
|
}
|
|
5205
5213
|
.dark\:text-slate-400 {
|
|
5206
|
-
|
|
5214
|
+
&:where(.dark, .dark *) {
|
|
5207
5215
|
color: var(--color-slate-400);
|
|
5208
5216
|
}
|
|
5209
5217
|
}
|
|
5210
5218
|
.dark\:text-warning-200 {
|
|
5211
|
-
|
|
5219
|
+
&:where(.dark, .dark *) {
|
|
5212
5220
|
color: var(--color-warning-200);
|
|
5213
5221
|
}
|
|
5214
5222
|
}
|
|
5215
5223
|
.dark\:text-warning-300 {
|
|
5216
|
-
|
|
5224
|
+
&:where(.dark, .dark *) {
|
|
5217
5225
|
color: var(--color-warning-300);
|
|
5218
5226
|
}
|
|
5219
5227
|
}
|
|
5220
5228
|
.dark\:hover\:bg-slate-700 {
|
|
5221
|
-
|
|
5229
|
+
&:where(.dark, .dark *) {
|
|
5222
5230
|
&:hover {
|
|
5223
5231
|
@media (hover: hover) {
|
|
5224
5232
|
background-color: var(--color-slate-700);
|
|
@@ -5227,7 +5235,7 @@
|
|
|
5227
5235
|
}
|
|
5228
5236
|
}
|
|
5229
5237
|
.dark\:hover\:bg-slate-800 {
|
|
5230
|
-
|
|
5238
|
+
&:where(.dark, .dark *) {
|
|
5231
5239
|
&:hover {
|
|
5232
5240
|
@media (hover: hover) {
|
|
5233
5241
|
background-color: var(--color-slate-800);
|
|
@@ -5236,7 +5244,7 @@
|
|
|
5236
5244
|
}
|
|
5237
5245
|
}
|
|
5238
5246
|
.dark\:hover\:text-slate-100 {
|
|
5239
|
-
|
|
5247
|
+
&:where(.dark, .dark *) {
|
|
5240
5248
|
&:hover {
|
|
5241
5249
|
@media (hover: hover) {
|
|
5242
5250
|
color: var(--color-slate-100);
|
package/package.json
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { X, Search } from
|
|
2
|
-
import Input from
|
|
3
|
-
import Select, { type SelectOption } from
|
|
4
|
-
import Button from
|
|
1
|
+
import { X, Search } from "lucide-react";
|
|
2
|
+
import Input from "./Input";
|
|
3
|
+
import Select, { type SelectOption } from "./Select";
|
|
4
|
+
import Button from "./Button";
|
|
5
5
|
|
|
6
6
|
export interface FilterConfig {
|
|
7
7
|
key: string;
|
|
8
8
|
label: string;
|
|
9
|
-
type:
|
|
9
|
+
type:
|
|
10
|
+
| "text"
|
|
11
|
+
| "search"
|
|
12
|
+
| "select"
|
|
13
|
+
| "date"
|
|
14
|
+
| "number"
|
|
15
|
+
| "boolean"
|
|
16
|
+
| "dateRange"
|
|
17
|
+
| "toggle"
|
|
18
|
+
| "switch"
|
|
19
|
+
| "multiSelect";
|
|
10
20
|
placeholder?: string;
|
|
11
21
|
options?: Array<{ label: string; value: unknown }>;
|
|
12
22
|
}
|
|
@@ -24,7 +34,7 @@ export default function FilterBar({
|
|
|
24
34
|
filters,
|
|
25
35
|
values,
|
|
26
36
|
onChange,
|
|
27
|
-
className =
|
|
37
|
+
className = "",
|
|
28
38
|
onClear,
|
|
29
39
|
showClearButton = false,
|
|
30
40
|
}: FilterBarProps) {
|
|
@@ -41,13 +51,15 @@ export default function FilterBar({
|
|
|
41
51
|
} else {
|
|
42
52
|
// Default clear: set all values to null/empty
|
|
43
53
|
const clearedValues: Record<string, unknown> = {};
|
|
44
|
-
filters.forEach(filter => {
|
|
45
|
-
if (filter.type ===
|
|
46
|
-
clearedValues[filter.key] =
|
|
47
|
-
} else if (filter.type ===
|
|
54
|
+
filters.forEach((filter) => {
|
|
55
|
+
if (filter.type === "text" || filter.type === "search") {
|
|
56
|
+
clearedValues[filter.key] = "";
|
|
57
|
+
} else if (filter.type === "dateRange") {
|
|
48
58
|
clearedValues[filter.key] = { from: undefined, to: undefined };
|
|
49
|
-
} else if (filter.type ===
|
|
59
|
+
} else if (filter.type === "multiSelect") {
|
|
50
60
|
clearedValues[filter.key] = [];
|
|
61
|
+
} else if (filter.type === "switch") {
|
|
62
|
+
clearedValues[filter.key] = false;
|
|
51
63
|
} else {
|
|
52
64
|
clearedValues[filter.key] = null;
|
|
53
65
|
}
|
|
@@ -60,20 +72,20 @@ export default function FilterBar({
|
|
|
60
72
|
const value = values[filter.key];
|
|
61
73
|
|
|
62
74
|
switch (filter.type) {
|
|
63
|
-
case
|
|
75
|
+
case "text":
|
|
64
76
|
return (
|
|
65
77
|
<Input
|
|
66
78
|
type="text"
|
|
67
79
|
placeholder={filter.placeholder || `Filter by ${filter.label}`}
|
|
68
|
-
value={(value as string) ||
|
|
80
|
+
value={(value as string) || ""}
|
|
69
81
|
onChange={(e) => handleFilterChange(filter.key, e.target.value)}
|
|
70
82
|
/>
|
|
71
83
|
);
|
|
72
84
|
|
|
73
|
-
case
|
|
85
|
+
case "select": {
|
|
74
86
|
const selectOptions: SelectOption[] = [
|
|
75
|
-
{ value:
|
|
76
|
-
...(filter.options?.map(opt => ({
|
|
87
|
+
{ value: "", label: `All ${filter.label}` },
|
|
88
|
+
...(filter.options?.map((opt) => ({
|
|
77
89
|
value: String(opt.value),
|
|
78
90
|
label: opt.label,
|
|
79
91
|
})) || []),
|
|
@@ -82,60 +94,62 @@ export default function FilterBar({
|
|
|
82
94
|
return (
|
|
83
95
|
<Select
|
|
84
96
|
options={selectOptions}
|
|
85
|
-
value={String(value ||
|
|
86
|
-
onChange={(newValue) =>
|
|
97
|
+
value={String(value || "")}
|
|
98
|
+
onChange={(newValue) =>
|
|
99
|
+
handleFilterChange(filter.key, newValue || null)
|
|
100
|
+
}
|
|
87
101
|
/>
|
|
88
102
|
);
|
|
89
103
|
}
|
|
90
104
|
|
|
91
|
-
case
|
|
105
|
+
case "date":
|
|
92
106
|
return (
|
|
93
107
|
<input
|
|
94
108
|
type="date"
|
|
95
|
-
value={(value as string) ||
|
|
109
|
+
value={(value as string) || ""}
|
|
96
110
|
onChange={(e) => handleFilterChange(filter.key, e.target.value)}
|
|
97
111
|
className="input"
|
|
98
112
|
/>
|
|
99
113
|
);
|
|
100
114
|
|
|
101
|
-
case
|
|
115
|
+
case "number":
|
|
102
116
|
return (
|
|
103
117
|
<input
|
|
104
118
|
type="number"
|
|
105
119
|
placeholder={filter.placeholder || `Filter by ${filter.label}`}
|
|
106
|
-
value={value !== null && value !== undefined ? String(value) :
|
|
120
|
+
value={value !== null && value !== undefined ? String(value) : ""}
|
|
107
121
|
onChange={(e) =>
|
|
108
122
|
handleFilterChange(
|
|
109
123
|
filter.key,
|
|
110
|
-
e.target.value ? Number(e.target.value) : null
|
|
124
|
+
e.target.value ? Number(e.target.value) : null,
|
|
111
125
|
)
|
|
112
126
|
}
|
|
113
127
|
className="input"
|
|
114
128
|
/>
|
|
115
129
|
);
|
|
116
130
|
|
|
117
|
-
case
|
|
131
|
+
case "boolean": {
|
|
118
132
|
const boolOptions: SelectOption[] = [
|
|
119
|
-
{ value:
|
|
120
|
-
{ value:
|
|
121
|
-
{ value:
|
|
133
|
+
{ value: "", label: "All" },
|
|
134
|
+
{ value: "true", label: "Yes" },
|
|
135
|
+
{ value: "false", label: "No" },
|
|
122
136
|
];
|
|
123
137
|
|
|
124
138
|
return (
|
|
125
139
|
<Select
|
|
126
140
|
options={boolOptions}
|
|
127
|
-
value={value === null || value === undefined ?
|
|
141
|
+
value={value === null || value === undefined ? "" : String(value)}
|
|
128
142
|
onChange={(newValue) =>
|
|
129
143
|
handleFilterChange(
|
|
130
144
|
filter.key,
|
|
131
|
-
newValue ===
|
|
145
|
+
newValue === "" ? null : newValue === "true",
|
|
132
146
|
)
|
|
133
147
|
}
|
|
134
148
|
/>
|
|
135
149
|
);
|
|
136
150
|
}
|
|
137
151
|
|
|
138
|
-
case
|
|
152
|
+
case "search":
|
|
139
153
|
return (
|
|
140
154
|
<div className="relative">
|
|
141
155
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
@@ -144,22 +158,25 @@ export default function FilterBar({
|
|
|
144
158
|
<input
|
|
145
159
|
type="text"
|
|
146
160
|
placeholder={filter.placeholder || `Search ${filter.label}...`}
|
|
147
|
-
value={(value as string) ||
|
|
161
|
+
value={(value as string) || ""}
|
|
148
162
|
onChange={(e) => handleFilterChange(filter.key, e.target.value)}
|
|
149
163
|
className="input pl-9"
|
|
150
164
|
/>
|
|
151
165
|
</div>
|
|
152
166
|
);
|
|
153
167
|
|
|
154
|
-
case
|
|
168
|
+
case "dateRange": {
|
|
155
169
|
const rangeValue = (value as { from?: string; to?: string }) || {};
|
|
156
170
|
return (
|
|
157
171
|
<div className="flex items-center gap-2">
|
|
158
172
|
<input
|
|
159
173
|
type="date"
|
|
160
|
-
value={rangeValue.from ||
|
|
174
|
+
value={rangeValue.from || ""}
|
|
161
175
|
onChange={(e) =>
|
|
162
|
-
handleFilterChange(filter.key, {
|
|
176
|
+
handleFilterChange(filter.key, {
|
|
177
|
+
...rangeValue,
|
|
178
|
+
from: e.target.value || undefined,
|
|
179
|
+
})
|
|
163
180
|
}
|
|
164
181
|
className="input text-sm"
|
|
165
182
|
aria-label={`${filter.label} from`}
|
|
@@ -167,9 +184,12 @@ export default function FilterBar({
|
|
|
167
184
|
<span className="text-ink-400 text-xs">to</span>
|
|
168
185
|
<input
|
|
169
186
|
type="date"
|
|
170
|
-
value={rangeValue.to ||
|
|
187
|
+
value={rangeValue.to || ""}
|
|
171
188
|
onChange={(e) =>
|
|
172
|
-
handleFilterChange(filter.key, {
|
|
189
|
+
handleFilterChange(filter.key, {
|
|
190
|
+
...rangeValue,
|
|
191
|
+
to: e.target.value || undefined,
|
|
192
|
+
})
|
|
173
193
|
}
|
|
174
194
|
className="input text-sm"
|
|
175
195
|
aria-label={`${filter.label} to`}
|
|
@@ -178,25 +198,34 @@ export default function FilterBar({
|
|
|
178
198
|
);
|
|
179
199
|
}
|
|
180
200
|
|
|
181
|
-
case
|
|
201
|
+
case "toggle": {
|
|
182
202
|
const toggleOptions: SelectOption[] = [
|
|
183
|
-
{ value:
|
|
184
|
-
{ value:
|
|
185
|
-
{ value:
|
|
203
|
+
{ value: "", label: "All" },
|
|
204
|
+
{ value: "true", label: "Yes" },
|
|
205
|
+
{ value: "false", label: "No" },
|
|
186
206
|
];
|
|
187
|
-
const currentVal =
|
|
207
|
+
const currentVal =
|
|
208
|
+
value === null || value === undefined ? "" : String(value);
|
|
188
209
|
return (
|
|
189
|
-
<div
|
|
210
|
+
<div
|
|
211
|
+
className="flex rounded-lg border border-paper-300 overflow-hidden"
|
|
212
|
+
role="group"
|
|
213
|
+
>
|
|
190
214
|
{toggleOptions.map((opt) => (
|
|
191
215
|
<button
|
|
192
216
|
key={opt.value}
|
|
193
217
|
type="button"
|
|
194
|
-
onClick={() =>
|
|
218
|
+
onClick={() =>
|
|
219
|
+
handleFilterChange(
|
|
220
|
+
filter.key,
|
|
221
|
+
opt.value === "" ? null : opt.value === "true",
|
|
222
|
+
)
|
|
223
|
+
}
|
|
195
224
|
className={`px-3 py-1.5 text-xs font-medium transition-colors ${
|
|
196
225
|
currentVal === opt.value
|
|
197
|
-
?
|
|
198
|
-
:
|
|
199
|
-
} ${opt.value !==
|
|
226
|
+
? "bg-accent-500 text-white"
|
|
227
|
+
: "bg-white text-ink-600 hover:bg-paper-50"
|
|
228
|
+
} ${opt.value !== "" ? "border-l border-paper-300" : ""}`}
|
|
200
229
|
>
|
|
201
230
|
{opt.label}
|
|
202
231
|
</button>
|
|
@@ -205,13 +234,44 @@ export default function FilterBar({
|
|
|
205
234
|
);
|
|
206
235
|
}
|
|
207
236
|
|
|
208
|
-
case
|
|
237
|
+
case "switch": {
|
|
238
|
+
// Single binary toggle — use when the filter is naturally on/off
|
|
239
|
+
// (e.g. "Mine only", "Archived"), unlike `boolean` / `toggle` which
|
|
240
|
+
// present an All/Yes/No tri-state. Stored value is a plain boolean.
|
|
241
|
+
const checked = value === true;
|
|
242
|
+
return (
|
|
243
|
+
<button
|
|
244
|
+
type="button"
|
|
245
|
+
role="switch"
|
|
246
|
+
aria-checked={checked}
|
|
247
|
+
onClick={() => handleFilterChange(filter.key, !checked)}
|
|
248
|
+
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-accent-400 focus:ring-offset-2 ${
|
|
249
|
+
checked ? "bg-accent-500" : "bg-paper-300"
|
|
250
|
+
}`}
|
|
251
|
+
>
|
|
252
|
+
<span
|
|
253
|
+
className={`inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform ${
|
|
254
|
+
checked ? "translate-x-6" : "translate-x-1"
|
|
255
|
+
}`}
|
|
256
|
+
/>
|
|
257
|
+
<span className="sr-only">{filter.label}</span>
|
|
258
|
+
</button>
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
case "multiSelect": {
|
|
209
263
|
const selectedValues = Array.isArray(value) ? (value as string[]) : [];
|
|
210
264
|
const msOptions = filter.options || [];
|
|
211
265
|
return (
|
|
212
266
|
<div className="relative">
|
|
213
267
|
<Select
|
|
214
|
-
options={[
|
|
268
|
+
options={[
|
|
269
|
+
{ value: "", label: `All ${filter.label}` },
|
|
270
|
+
...msOptions.map((o) => ({
|
|
271
|
+
value: String(o.value),
|
|
272
|
+
label: o.label,
|
|
273
|
+
})),
|
|
274
|
+
]}
|
|
215
275
|
value=""
|
|
216
276
|
onChange={(newValue) => {
|
|
217
277
|
if (!newValue) {
|
|
@@ -224,11 +284,23 @@ export default function FilterBar({
|
|
|
224
284
|
{selectedValues.length > 0 && (
|
|
225
285
|
<div className="flex flex-wrap gap-1 mt-1">
|
|
226
286
|
{selectedValues.map((sv) => {
|
|
227
|
-
const opt = msOptions.find(o => String(o.value) === sv);
|
|
287
|
+
const opt = msOptions.find((o) => String(o.value) === sv);
|
|
228
288
|
return (
|
|
229
|
-
<span
|
|
289
|
+
<span
|
|
290
|
+
key={sv}
|
|
291
|
+
className="inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-accent-100 text-accent-700 rounded-full"
|
|
292
|
+
>
|
|
230
293
|
{opt?.label || sv}
|
|
231
|
-
<button
|
|
294
|
+
<button
|
|
295
|
+
type="button"
|
|
296
|
+
onClick={() =>
|
|
297
|
+
handleFilterChange(
|
|
298
|
+
filter.key,
|
|
299
|
+
selectedValues.filter((v) => v !== sv),
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
className="hover:text-accent-900"
|
|
303
|
+
>
|
|
232
304
|
<X className="h-3 w-3" />
|
|
233
305
|
</button>
|
|
234
306
|
</span>
|
|
@@ -248,12 +320,17 @@ export default function FilterBar({
|
|
|
248
320
|
if (filters.length === 0) return null;
|
|
249
321
|
|
|
250
322
|
return (
|
|
251
|
-
<div
|
|
323
|
+
<div
|
|
324
|
+
className={`bg-white bg-subtle-grain border border-paper-200 rounded-lg shadow-sm p-4 ${className}`}
|
|
325
|
+
>
|
|
252
326
|
<div className="flex items-start justify-between gap-4 flex-wrap">
|
|
253
327
|
{/* Filters */}
|
|
254
328
|
<div className="flex-1 flex flex-wrap gap-4">
|
|
255
329
|
{filters.map((filter) => (
|
|
256
|
-
<div
|
|
330
|
+
<div
|
|
331
|
+
key={filter.key}
|
|
332
|
+
className="flex flex-col space-y-1 min-w-[200px]"
|
|
333
|
+
>
|
|
257
334
|
<label className="label">{filter.label}</label>
|
|
258
335
|
{renderFilter(filter)}
|
|
259
336
|
</div>
|
package/src/styles/index.css
CHANGED
|
@@ -6,6 +6,21 @@
|
|
|
6
6
|
@import "tailwindcss";
|
|
7
7
|
@import "./theme.css";
|
|
8
8
|
|
|
9
|
+
/* Class-based dark mode.
|
|
10
|
+
*
|
|
11
|
+
* Without this, Tailwind v4 compiles every `dark:` utility into a
|
|
12
|
+
* `@media (prefers-color-scheme: dark)` block — so dark styles activate
|
|
13
|
+
* from the OS theme setting rather than app state. Consumers that want
|
|
14
|
+
* to control dark mode programmatically (e.g. a theme toggle that adds
|
|
15
|
+
* `<html class="dark">`) can't override the OS preference.
|
|
16
|
+
*
|
|
17
|
+
* This variant makes `dark:` resolve to a `.dark` class selector
|
|
18
|
+
* instead. Apps that want media-query behavior can opt back in via
|
|
19
|
+
* their own `@custom-variant dark (@media (prefers-color-scheme: dark));`
|
|
20
|
+
* override.
|
|
21
|
+
*/
|
|
22
|
+
@custom-variant dark (&:where(.dark, .dark *));
|
|
23
|
+
|
|
9
24
|
/* Content sources for class detection */
|
|
10
25
|
@source "../components/**/*.{tsx,ts}";
|
|
11
26
|
@source inline("bg-sky-100 bg-sky-50 bg-sky-100/50 bg-amber-100 bg-amber-50 bg-amber-100/50 bg-emerald-100 bg-emerald-50 bg-emerald-100/50 bg-pink-100 bg-pink-50 bg-pink-100/50");
|