{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "p-slider-22",
  "description": "Price slider with histogram",
  "registryDependencies": [
    "@coss/button",
    "@coss/input-group",
    "@coss/number-field",
    "@coss/slider"
  ],
  "files": [
    {
      "path": "registry/default/particles/p-slider-22.tsx",
      "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { Button } from \"@/registry/default/ui/button\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupText,\n} from \"@/registry/default/ui/input-group\";\nimport {\n  NumberField,\n  NumberFieldInput,\n} from \"@/registry/default/ui/number-field\";\nimport { Slider } from \"@/registry/default/ui/slider\";\n\nconst items = [\n  { id: 1, price: 80 },\n  { id: 2, price: 95 },\n  { id: 3, price: 110 },\n  { id: 4, price: 125 },\n  { id: 5, price: 130 },\n  { id: 6, price: 140 },\n  { id: 7, price: 145 },\n  { id: 8, price: 150 },\n  { id: 9, price: 155 },\n  { id: 10, price: 165 },\n  { id: 11, price: 175 },\n  { id: 12, price: 185 },\n  { id: 13, price: 195 },\n  { id: 14, price: 205 },\n  { id: 15, price: 215 },\n  { id: 16, price: 225 },\n  { id: 17, price: 235 },\n  { id: 18, price: 245 },\n  { id: 19, price: 255 },\n  { id: 20, price: 260 },\n  { id: 21, price: 265 },\n  { id: 22, price: 270 },\n  { id: 23, price: 275 },\n  { id: 24, price: 280 },\n  { id: 25, price: 285 },\n  { id: 26, price: 290 },\n  { id: 27, price: 290 },\n  { id: 28, price: 295 },\n  { id: 29, price: 295 },\n  { id: 30, price: 295 },\n  { id: 31, price: 298 },\n  { id: 32, price: 299 },\n  { id: 33, price: 300 },\n  { id: 34, price: 305 },\n  { id: 35, price: 310 },\n  { id: 36, price: 315 },\n  { id: 37, price: 320 },\n  { id: 38, price: 325 },\n  { id: 39, price: 330 },\n  { id: 40, price: 335 },\n  { id: 41, price: 340 },\n  { id: 42, price: 345 },\n  { id: 43, price: 350 },\n  { id: 44, price: 355 },\n  { id: 45, price: 360 },\n  { id: 46, price: 365 },\n  { id: 47, price: 365 },\n  { id: 48, price: 375 },\n  { id: 49, price: 380 },\n  { id: 50, price: 385 },\n  { id: 51, price: 390 },\n  { id: 52, price: 395 },\n  { id: 53, price: 400 },\n  { id: 54, price: 405 },\n  { id: 55, price: 410 },\n  { id: 56, price: 415 },\n  { id: 57, price: 420 },\n  { id: 58, price: 425 },\n  { id: 59, price: 430 },\n  { id: 60, price: 435 },\n  { id: 61, price: 440 },\n  { id: 62, price: 445 },\n  { id: 63, price: 450 },\n  { id: 64, price: 455 },\n  { id: 65, price: 460 },\n  { id: 66, price: 465 },\n  { id: 67, price: 470 },\n  { id: 68, price: 475 },\n  { id: 69, price: 480 },\n  { id: 70, price: 485 },\n  { id: 71, price: 490 },\n  { id: 72, price: 495 },\n  { id: 73, price: 495 },\n  { id: 74, price: 498 },\n  { id: 75, price: 499 },\n  { id: 76, price: 500 },\n  { id: 77, price: 500 },\n  { id: 78, price: 500 },\n  { id: 79, price: 515 },\n  { id: 80, price: 530 },\n  { id: 81, price: 545 },\n  { id: 82, price: 560 },\n  { id: 83, price: 575 },\n  { id: 84, price: 590 },\n  { id: 85, price: 605 },\n  { id: 86, price: 620 },\n  { id: 87, price: 635 },\n  { id: 88, price: 650 },\n  { id: 89, price: 655 },\n  { id: 90, price: 660 },\n  { id: 91, price: 665 },\n  { id: 92, price: 670 },\n  { id: 93, price: 675 },\n  { id: 94, price: 680 },\n  { id: 95, price: 685 },\n  { id: 96, price: 690 },\n  { id: 97, price: 695 },\n  { id: 98, price: 700 },\n  { id: 99, price: 700 },\n  { id: 100, price: 700 },\n  { id: 101, price: 700 },\n  { id: 102, price: 700 },\n  { id: 103, price: 700 },\n  { id: 104, price: 725 },\n  { id: 105, price: 750 },\n  { id: 106, price: 775 },\n  { id: 107, price: 800 },\n  { id: 108, price: 815 },\n  { id: 109, price: 830 },\n  { id: 110, price: 845 },\n  { id: 111, price: 845 },\n  { id: 112, price: 845 },\n  { id: 113, price: 870 },\n  { id: 114, price: 875 },\n  { id: 115, price: 880 },\n  { id: 116, price: 885 },\n  { id: 117, price: 890 },\n  { id: 118, price: 895 },\n  { id: 119, price: 898 },\n  { id: 120, price: 900 },\n];\n\nconst tickCount = 40;\nconst min = Math.min(...items.map((item) => item.price));\nconst max = Math.max(...items.map((item) => item.price));\nconst priceStep = (max - min) / tickCount;\n\nconst itemCounts = Array.from({ length: tickCount }, (_, tick) => {\n  const rangeMin = min + tick * priceStep;\n  const rangeMax = min + (tick + 1) * priceStep;\n  return items.filter((item) => item.price >= rangeMin && item.price < rangeMax)\n    .length;\n});\n\nconst maxCount = Math.max(...itemCounts);\n\nexport default function Particle() {\n  const [values, setValues] = useState([200, 780]);\n\n  const updateValue = (index: number, newValue: number | null) => {\n    const v = newValue ?? min;\n    setValues((prev) => {\n      const next = [...prev];\n      if (index === 0) {\n        // Min value: clamp to not exceed max value\n        next[0] = Math.min(v, prev[1] ?? max);\n      } else {\n        // Max value: clamp to not go below min value\n        next[1] = Math.max(v, prev[0] ?? min);\n      }\n      return next;\n    });\n  };\n\n  const countItemsInRange = () =>\n    items.filter(\n      (item) =>\n        item.price >= (values[0] ?? min) && item.price <= (values[1] ?? max),\n    ).length;\n\n  const isBarInSelectedRange = (index: number) => {\n    const rangeMin = min + index * priceStep;\n    const rangeMax = min + (index + 1) * priceStep;\n    return (\n      countItemsInRange() > 0 &&\n      rangeMin <= (values[1] ?? max) &&\n      rangeMax >= (values[0] ?? min)\n    );\n  };\n\n  return (\n    <div className=\"flex flex-col gap-4\">\n      <div>\n        <div aria-hidden=\"true\" className=\"flex h-12 w-full items-end px-3\">\n          {itemCounts.map((count, i) => (\n            <div\n              className=\"flex flex-1 justify-center\"\n              key={String(i)}\n              style={{ height: `${(count / maxCount) * 100}%` }}\n            >\n              <span\n                className=\"mx-px size-full bg-primary/20 data-[selected=true]:bg-primary/50\"\n                data-selected={isBarInSelectedRange(i)}\n              />\n            </div>\n          ))}\n        </div>\n        <Slider\n          aria-label=\"Price range\"\n          className=\"*:min-w-0!\"\n          max={max}\n          min={min}\n          onValueChange={(v) => setValues(Array.isArray(v) ? [...v] : [v])}\n          value={values}\n        />\n      </div>\n\n      <div className=\"flex items-center justify-between gap-4\">\n        <InputGroup>\n          <NumberField\n            aria-label=\"Minimum price\"\n            max={values[1]}\n            min={min}\n            onValueChange={(v) => updateValue(0, v)}\n            value={values[0]}\n          >\n            <NumberFieldInput className=\"text-left\" />\n          </NumberField>\n          <InputGroupAddon>\n            <InputGroupText>$</InputGroupText>\n          </InputGroupAddon>\n        </InputGroup>\n        <InputGroup>\n          <NumberField\n            aria-label=\"Maximum price\"\n            max={max}\n            min={values[0]}\n            onValueChange={(v) => updateValue(1, v)}\n            value={values[1]}\n          >\n            <NumberFieldInput className=\"text-left\" />\n          </NumberField>\n          <InputGroupAddon>\n            <InputGroupText>$</InputGroupText>\n          </InputGroupAddon>\n        </InputGroup>\n      </div>\n\n      <Button className=\"w-full\" variant=\"outline\">\n        Show {countItemsInRange()} items\n      </Button>\n    </div>\n  );\n}\n",
      "type": "registry:block"
    }
  ],
  "meta": {
    "className": "**:data-[slot=preview]:w-full **:data-[slot=preview]:max-w-64"
  },
  "categories": [
    "slider",
    "number field",
    "input group",
    "button",
    "filter"
  ],
  "type": "registry:block"
}