minka-ds 0.1.7 → 0.1.9
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
|
@@ -21,13 +21,14 @@ import { Tabs, TabsList, TabsTrigger } from "./tabs"
|
|
|
21
21
|
interface FilterCategory {
|
|
22
22
|
id: string
|
|
23
23
|
label: string
|
|
24
|
-
type?: "list" | "date" | "amount"
|
|
24
|
+
type?: "list" | "date" | "amount" | "hours"
|
|
25
25
|
values?: string[]
|
|
26
26
|
renderValue?: (value: string) => React.ReactNode
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
type AmountValue = { exact: number } | { min?: number; max?: number }
|
|
30
|
-
type
|
|
30
|
+
type HoursValue = { from: string; to: string }
|
|
31
|
+
type CategoryValue = string | DateRange | AmountValue | HoursValue
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
type Step = 1 | 2 | 3
|
|
@@ -58,6 +59,8 @@ function FilterCombobox({
|
|
|
58
59
|
const [amountExact, setAmountExact] = React.useState("")
|
|
59
60
|
const [amountMin, setAmountMin] = React.useState("")
|
|
60
61
|
const [amountMax, setAmountMax] = React.useState("")
|
|
62
|
+
const [hoursInput, setHoursInput] = React.useState("")
|
|
63
|
+
const [hoursInputTo, setHoursInputTo] = React.useState("")
|
|
61
64
|
const [search, setSearch] = React.useState("")
|
|
62
65
|
|
|
63
66
|
const containerRef = React.useRef<HTMLDivElement>(null)
|
|
@@ -90,6 +93,8 @@ function FilterCombobox({
|
|
|
90
93
|
setAmountExact("")
|
|
91
94
|
setAmountMin("")
|
|
92
95
|
setAmountMax("")
|
|
96
|
+
setHoursInput("")
|
|
97
|
+
setHoursInputTo("")
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
function handleToggle() {
|
|
@@ -117,6 +122,16 @@ function FilterCombobox({
|
|
|
117
122
|
return
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
if (cat.type === "hours") {
|
|
126
|
+
setSelectedCategory(cat)
|
|
127
|
+
setSelectedValues(new Set())
|
|
128
|
+
setHoursInput("")
|
|
129
|
+
setHoursInputTo("")
|
|
130
|
+
setSearch("")
|
|
131
|
+
setStep(2)
|
|
132
|
+
return
|
|
133
|
+
}
|
|
134
|
+
|
|
120
135
|
if (cat.type === "amount") {
|
|
121
136
|
const custom = existing.find((v): v is AmountValue =>
|
|
122
137
|
typeof v === "object" && ("exact" in v || "min" in v || "max" in v)
|
|
@@ -160,6 +175,12 @@ function FilterCombobox({
|
|
|
160
175
|
handleClose()
|
|
161
176
|
}
|
|
162
177
|
|
|
178
|
+
function applyCustomHours() {
|
|
179
|
+
if (!selectedCategory || !hoursInput || !hoursInputTo) return
|
|
180
|
+
onApply(selectedCategory.id, [{ from: hoursInput, to: hoursInputTo }])
|
|
181
|
+
handleClose()
|
|
182
|
+
}
|
|
183
|
+
|
|
163
184
|
function applyCustomAmount() {
|
|
164
185
|
if (!selectedCategory) return
|
|
165
186
|
let value: AmountValue
|
|
@@ -190,10 +211,11 @@ function FilterCombobox({
|
|
|
190
211
|
|
|
191
212
|
const isDate = selectedCategory?.type === "date"
|
|
192
213
|
const isAmount = selectedCategory?.type === "amount"
|
|
214
|
+
const isHours = selectedCategory?.type === "hours"
|
|
193
215
|
|
|
194
216
|
const step2AllValues = selectedCategory?.values ?? []
|
|
195
217
|
|
|
196
|
-
const showSearch = step2AllValues.length > 4
|
|
218
|
+
const showSearch = step2AllValues.length > 4 && selectedCategory?.type !== "hours"
|
|
197
219
|
const step2Filtered = showSearch
|
|
198
220
|
? step2AllValues.filter(v => v.toLowerCase().includes(search.toLowerCase()))
|
|
199
221
|
: step2AllValues
|
|
@@ -218,7 +240,7 @@ function FilterCombobox({
|
|
|
218
240
|
dropdownAlign === "right" ? "absolute right-0 top-full mt-1.5 overflow-hidden [border-radius:var(--radius-popover)]" : "absolute left-0 top-full mt-1.5 overflow-hidden [border-radius:var(--radius-popover)]",
|
|
219
241
|
"bg-[var(--color-bg-overlay)] shadow-[var(--shadow-popover)] ring-1 ring-[var(--color-border-subtle)]",
|
|
220
242
|
"[z-index:var(--z-floating)]",
|
|
221
|
-
step === 3 && isDate ? "w-auto" : "w-56"
|
|
243
|
+
step === 3 && isDate ? "w-auto" : step === 3 && isHours ? "w-80" : "w-56"
|
|
222
244
|
)}>
|
|
223
245
|
|
|
224
246
|
{/* Step 1 — category list (multi-category mode only) */}
|
|
@@ -251,17 +273,28 @@ function FilterCombobox({
|
|
|
251
273
|
? <EmptyRow />
|
|
252
274
|
: step2Filtered.map(value => (
|
|
253
275
|
<li key={value}>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
276
|
+
{selectedCategory.type === "hours" ? (
|
|
277
|
+
<PickerRow onClick={() => { onApply(selectedCategory.id, [value]); handleClose() }}>
|
|
278
|
+
{selectedCategory.renderValue?.(value) ?? value}
|
|
279
|
+
</PickerRow>
|
|
280
|
+
) : (
|
|
281
|
+
<CheckRow
|
|
282
|
+
checked={selectedValues.has(value)}
|
|
283
|
+
onToggle={() => toggleValue(value)}
|
|
284
|
+
>
|
|
285
|
+
{selectedCategory.renderValue?.(value) ?? value}
|
|
286
|
+
</CheckRow>
|
|
287
|
+
)}
|
|
260
288
|
</li>
|
|
261
289
|
))
|
|
262
290
|
}
|
|
291
|
+
{selectedCategory.type === "hours" && (
|
|
292
|
+
<li>
|
|
293
|
+
<PickerRow onClick={() => setStep(3)}>Custom range</PickerRow>
|
|
294
|
+
</li>
|
|
295
|
+
)}
|
|
263
296
|
</ul>
|
|
264
|
-
{selectedValues.size > 0 && (
|
|
297
|
+
{selectedValues.size > 0 && selectedCategory.type !== "hours" && (
|
|
265
298
|
<div className="p-1">
|
|
266
299
|
<Button size="sm" className="w-full" onClick={applyValues}>
|
|
267
300
|
Apply
|
|
@@ -293,6 +326,39 @@ function FilterCombobox({
|
|
|
293
326
|
</>
|
|
294
327
|
)}
|
|
295
328
|
|
|
329
|
+
{/* Step 3 — hours custom range */}
|
|
330
|
+
{step === 3 && isHours && (
|
|
331
|
+
<>
|
|
332
|
+
<StepHeader
|
|
333
|
+
title="Custom range"
|
|
334
|
+
onBack={() => setStep(2)}
|
|
335
|
+
/>
|
|
336
|
+
<div className="p-2 flex flex-col gap-2">
|
|
337
|
+
<div className="flex items-center gap-2">
|
|
338
|
+
<Input
|
|
339
|
+
type="time"
|
|
340
|
+
value={hoursInput}
|
|
341
|
+
onChange={e => setHoursInput(e.target.value)}
|
|
342
|
+
className="flex-1"
|
|
343
|
+
/>
|
|
344
|
+
<span className="text-body-sm text-[var(--color-text-muted)] shrink-0">–</span>
|
|
345
|
+
<Input
|
|
346
|
+
type="time"
|
|
347
|
+
value={hoursInputTo}
|
|
348
|
+
onChange={e => setHoursInputTo(e.target.value)}
|
|
349
|
+
className="flex-1"
|
|
350
|
+
/>
|
|
351
|
+
</div>
|
|
352
|
+
{hoursInput !== "" && hoursInputTo !== "" &&
|
|
353
|
+
!isNaN(parseFloat(hoursInput)) && !isNaN(parseFloat(hoursInputTo)) && (
|
|
354
|
+
<Button size="sm" className="w-full" onClick={applyCustomHours}>
|
|
355
|
+
Apply
|
|
356
|
+
</Button>
|
|
357
|
+
)}
|
|
358
|
+
</div>
|
|
359
|
+
</>
|
|
360
|
+
)}
|
|
361
|
+
|
|
296
362
|
{/* Step 3 — custom amount */}
|
|
297
363
|
{step === 3 && isAmount && (
|
|
298
364
|
<>
|
|
@@ -446,4 +512,4 @@ function EmptyRow() {
|
|
|
446
512
|
// ── Exports ────────────────────────────────────────────────────────────────────
|
|
447
513
|
|
|
448
514
|
export { FilterCombobox }
|
|
449
|
-
export type { FilterCategory, CategoryValue, AmountValue }
|
|
515
|
+
export type { FilterCategory, CategoryValue, AmountValue, HoursValue }
|