filters

Filters with large size controls

Filters with large size controls

Загрузка превью…

src/filters/c-filters-5.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-5.tsx)
  заводит три независимых стейта (`smallFilters`/`mediumFilters`/
  `largeFilters`), но фактически рендерит только один `<Filters>` с
  `size="lg"` и `filters={smallFilters}` (не `largeFilters`, несмотря на
  `size="lg"`) — `mediumFilters`/`largeFilters` объявлены и никогда не
  используются в JSX. Перенесено буквально (правило 4a: верность
  оригиналу важнее «улучшений») — порт не подставляет `largeFilters`
  вместо `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="lg" :filters="smallFilters" :fields="fields" @change="noop">
      <template #trigger>
        <Button variant="outline" size="icon-sm">
          <ListFilterIcon />
        </Button>
      </template>
    </Filters>
  </div>
</template>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-filters-5.json

Зависимости реестра

Источник: порт из ReUI (Keenthemes, MIT)