filters
Filters with small size controls
Filters with small size controls
Загрузка превью…
src/filters/c-filters-4.vue
<!--
Расхождение с апстримом: `IconPlaceholder` заменена инлайновым `<svg>`
(lucide-react v0.545.0 "tag"/"mail"/"globe"/"clock"/"circle-alert"/
"circle-check"/"ban"/"circle"/"star"/"list-filter") — см. docs/PORTING.md §5.
Фиксированное состояние (docs/PORTING.md §3): меню "Add Filter" и попапы
значений не раскрываются, `@change` — no-op. Апстрим (c-filters-4.tsx)
заводит три независимых стейта (`smallFilters`/`mediumFilters`/
`largeFilters`), но фактически рендерит только один `<Filters>` с
`size="sm"` и `filters={smallFilters}` — `mediumFilters`/`largeFilters`
объявлены и никогда не используются в JSX. Перенесено буквально
(правило 4a: верность оригиналу важнее «улучшений») — порт не
подставляет их вместо `smallFilters`.
-->
<script setup lang="ts">
import { h, type VNodeChild } from "vue"
import { Filters, createFilter, type Filter, type FilterFieldConfig } from "@/components/reui/filters"
import { Button } from "@/components/ui/button"
function TagIcon() {
return h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "size-3.5",
},
[
h("path", {
d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",
}),
h("circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" }),
]
)
}
function MailIcon() {
return h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "size-3.5",
},
[
h("path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" }),
h("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
]
)
}
function GlobeIcon() {
return h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: "size-3.5",
},
[
h("circle", { cx: "12", cy: "12", r: "10" }),
h("path", { d: "M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20" }),
h("path", { d: "M2 12h20" }),
]
)
}
function ClockIcon(className = "size-3.5") {
return () =>
h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: className,
},
[h("path", { d: "M12 6v6l4 2" }), h("circle", { cx: "12", cy: "12", r: "10" })]
)
}
function CircleAlertIcon(className: string) {
return () =>
h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: className,
},
[
h("circle", { cx: "12", cy: "12", r: "10" }),
h("line", { x1: "12", x2: "12", y1: "8", y2: "12" }),
h("line", { x1: "12", x2: "12.01", y1: "16", y2: "16" }),
]
)
}
function CircleCheckIcon(className: string) {
return () =>
h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: className,
},
[h("circle", { cx: "12", cy: "12", r: "10" }), h("path", { d: "m9 12 2 2 4-4" })]
)
}
function BanIcon(className: string) {
return () =>
h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: className,
},
[h("path", { d: "M4.929 4.929 19.07 19.071" }), h("circle", { cx: "12", cy: "12", r: "10" })]
)
}
function CircleIcon(className: string) {
return () =>
h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: className,
},
[h("circle", { cx: "12", cy: "12", r: "10" })]
)
}
function StarIcon(className: string) {
return () =>
h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
class: className,
},
[
h("path", {
d: "M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z",
}),
]
)
}
function ListFilterIcon() {
return h(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "24",
height: "24",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
"stroke-width": "2",
"stroke-linecap": "round",
"stroke-linejoin": "round",
},
[h("path", { d: "M2 5h20" }), h("path", { d: "M6 12h12" }), h("path", { d: "M9 19h6" })]
)
}
const statusIcons: Record<string, () => VNodeChild> = {
todo: ClockIcon("text-primary"),
"in-progress": CircleAlertIcon("text-yellow-500"),
done: CircleCheckIcon("text-green-500"),
cancelled: BanIcon("text-destructive"),
}
function statusIcon(status: string) {
return statusIcons[status] ?? CircleIcon("text-muted-foreground")
}
const priorityColors: Record<string, string> = {
low: "text-green-500",
medium: "text-yellow-500",
high: "text-orange-500",
urgent: "text-red-500",
}
function priorityIcon(priority: string) {
return StarIcon(priorityColors[priority] ?? "")
}
const fields: FilterFieldConfig<string>[] = [
{
key: "text",
label: "Text",
icon: TagIcon,
type: "text",
class: "w-36",
placeholder: "Search text...",
},
{
key: "email",
label: "Email",
icon: MailIcon,
type: "text",
class: "w-48",
placeholder: "user@example.com",
},
{
key: "website",
label: "Website",
icon: GlobeIcon,
type: "text",
class: "w-40",
placeholder: "https://example.com",
},
{
key: "status",
label: "Status",
icon: ClockIcon(),
type: "select",
searchable: false,
class: "w-[200px]",
options: [
{ value: "todo", label: "To Do", icon: statusIcon("todo") },
{ value: "in-progress", label: "In Progress", icon: statusIcon("in-progress") },
{ value: "done", label: "Done", icon: statusIcon("done") },
{ value: "cancelled", label: "Cancelled", icon: statusIcon("cancelled") },
],
},
{
key: "priority",
label: "Priority",
icon: CircleAlertIcon("size-3.5"),
type: "multiselect",
class: "w-[180px]",
options: [
{ value: "low", label: "Low", icon: priorityIcon("low") },
{ value: "medium", label: "Medium", icon: priorityIcon("medium") },
{ value: "high", label: "High", icon: priorityIcon("high") },
{ value: "urgent", label: "Urgent", icon: priorityIcon("urgent") },
],
},
]
const smallFilters: Filter<string>[] = [createFilter("priority", "is_any_of", ["high", "urgent"])]
function noop() {}
</script>
<template>
<div class="flex grow flex-col content-start items-start gap-2.5 space-y-6 self-start">
<Filters size="sm" :filters="smallFilters" :fields="fields" @change="noop">
<template #trigger>
<Button variant="outline" size="icon-sm">
<ListFilterIcon />
</Button>
</template>
</Filters>
</div>
</template>