Cartzilla component

Shop filters

Filter widgets for the sidebar, allowing users to refine product listings based on various attributes like price, brand, size, etc.

Checkboxes

Brands

'use client'

import { useState } from 'react'
import Accordion from 'react-bootstrap/Accordion'
import Stack from 'react-bootstrap/Stack'
import FormCheck from 'react-bootstrap/FormCheck'
import SearchFilter from '@/components/search-filter'
import SimpleBar from 'simplebar-react'
import 'simplebar-react/dist/simplebar.min.css'

export default function ShopFiltersCheckboxesDemo() {

  // Brands (checkboxes) array
  const brands = [
    { name: 'Adidas', quantity: 425, selected: true },
    { name: 'Ann Taylor', quantity: 15 },
    { name: 'Armani', quantity: 18 },
    { name: 'Banana Republic', quantity: 103 },
    { name: 'Bilabong', quantity: 27, selected: true },
    { name: 'Birkenstock', quantity: 10 },
    { name: 'Calvin Klein', quantity: 365 },
    { name: 'Columbia', quantity: 508 },
    { name: 'Converse', quantity: 176 },
    { name: 'Dockers', quantity: 54 },
    { name: 'Fruit of the Loom', quantity: 739 },
    { name: 'Hanes', quantity: 92 },
    { name: 'Jimmy Choo', quantity: 17 },
    { name: "Levi's", quantity: 361 },
    { name: "Men's Wearhouse", quantity: 75 },
    { name: 'New Balance', quantity: 218 },
    { name: 'Nike', quantity: 810 },
  ]

  const [filteredBrands, setFilteredBrands] = useState(brands)

  // Function to filter brands based on the query
  const filterBrands = (brand: (typeof brands)[number], query: string): boolean => {
    return brand.name.toLowerCase().includes(query)
  }

  return (
    <Accordion defaultActiveKey="0" style={{ maxWidth: 280 }}>
      <Accordion.Item className="border-0" eventKey="0">
        <Accordion.Button as="h3" className="h6 cursor-pointer p-0" id="headingBrands">
          Brands
        </Accordion.Button>
        <Accordion.Body aria-labelledby="headingBrands">
          <SearchFilter
            items={brands}
            filterFn={filterBrands}
            onFilteredItems={setFilteredBrands}
            placeholder="Search"
            className="mb-3"
          />
          <SimpleBar data-simplebar-auto-hide="false" style={{ height: 210 }}>
            <Stack gap={2}>
              {filteredBrands.length > 0 ? (
                <>
                  {filteredBrands.map(({ name, quantity, selected }, index) => (
                    <FormCheck key={index} id={`brand-${index}`}>
                      <FormCheck.Input defaultChecked={selected} />
                      <FormCheck.Label className="text-body-emphasis">
                        {name}
                        <span className="fs-xs text-body-secondary ms-1">({quantity})</span>
                      </FormCheck.Label>
                    </FormCheck>
                  ))}
                </>
              ) : (
                <p className="fs-sm text-body-secondary mb-0">Nothing found</p>
              )}
            </Stack>
          </SimpleBar>
        </Accordion.Body>
      </Accordion.Item>
    </Accordion>
  )
}

Button toggles

Color

Size

'use client'

import { useState } from 'react'
import Accordion from 'react-bootstrap/Accordion'
import Stack from 'react-bootstrap/Stack'
import ToggleButton from 'react-bootstrap/ToggleButton'

export default function ShopFiltersButtonTogglesDemo() {
  const [selectedColors, setSelectedColors] = useState<string[]>(['Green'])
  const [selectedSizes, setSelectedSizes] = useState<string[]>(['XXS'])

  const handleColorToggle = (colorName: string) => {
    setSelectedColors((prevColors) =>
      prevColors.includes(colorName) ? prevColors.filter((color) => color !== colorName) : [...prevColors, colorName]
    )
  }

  const handleSizeToggle = (sizeName: string) => {
    setSelectedSizes((prevSizes) =>
      prevSizes.includes(sizeName) ? prevSizes.filter((size) => size !== sizeName) : [...prevSizes, sizeName]
    )
  }

  return (
    <>
      {/* Collapsible list of color filters based on <ToggleButton> */}
      <Accordion defaultActiveKey="0" style={{ maxWidth: 280 }}>
        <Accordion.Item className="border-0" eventKey="0">
          <Accordion.Button as="h3" className="h6 cursor-pointer p-0" id="headingColor">
            Color
          </Accordion.Button>
          <Accordion.Body aria-labelledby="headingColor">
            <Stack gap={2}>
              {[
                { colorName: 'Green', colorHex: '#8cd1ab' },
                { colorName: 'Coral red', colorHex: '#ee7976' },
                { colorName: 'Pink', colorHex: '#df8fbf' },
                { colorName: 'Sky blue', colorHex: '#9acbf1' },
                { colorName: 'Black', colorHex: '#364254' },
                { colorName: 'White', colorHex: '#e0e5eb' },
              ].map(({ colorName, colorHex }, index) => (
                <div key={index} className="d-flex align-items-center mb-1">
                  <ToggleButton
                    type="checkbox"
                    id={`color-${index}`}
                    variant="color fs-xl"
                    value={colorName}
                    checked={selectedColors.includes(colorName)}
                    onChange={() => handleColorToggle(colorName)}
                    style={{ color: colorHex }}
                  />
                  <label htmlFor={`color-${index}`} className="fs-sm ms-2">
                    {colorName}
                  </label>
                </div>
              ))}
            </Stack>
          </Accordion.Body>
        </Accordion.Item>
      </Accordion>

      {/* Collapsible list of size filters based on <ToggleButton> */}
        <Accordion className="w-100" defaultActiveKey="0" style={{ maxWidth: 280 }}>
          <Accordion.Item className="border-0" eventKey="0">
            <Accordion.Button as="h3" className="h6 cursor-pointer p-0" id="headingSize">
              Size
            </Accordion.Button>
            <Accordion.Body aria-labelledby="headingSize">
              <Stack direction="horizontal" gap={2} className="flex-wrap">
                {['XXS', 'XS', 'S', 'M', 'L', 'XL', '2XL', '40', '42', '44', '45', '46', '48', '50', '52'].map(
                  (size, index) => (
                    <ToggleButton
                      key={index}
                      type="checkbox"
                      id={`size-${index}`}
                      variant="outline-secondary"
                      size="sm"
                      value={size}
                      checked={selectedSizes.includes(size)}
                      onChange={() => handleSizeToggle(size)}
                      style={{ letterSpacing: -0.6 }}
                    >
                      {size}
                    </ToggleButton>
                  )
                )}
              </Stack>
            </Accordion.Body>
          </Accordion.Item>
        </Accordion>
    </>
  )
}

Range slider

Price

'use client'

import { useState } from 'react'
import Accordion from 'react-bootstrap/Accordion'
import Stack from 'react-bootstrap/Stack'
import FormControl from 'react-bootstrap/FormControl'
import RangeSlider from '@/components/forms/range-slider'

export default function ShopFiltersRangeSliderDemo() {
  const [values, setValues] = useState([50, 150])

  const handleInputChange = (index: number, value: string) => {
    const numericValue = Math.max(0, Math.min(1000, Number(value)))
    const updatedValues = [...values]
    updatedValues[index] = numericValue
    setValues(updatedValues)
  }

  return (
    <Accordion defaultActiveKey="0" style={{ maxWidth: 280 }}>
      <Accordion.Item className="border-0" eventKey="0">
        <Accordion.Button as="h3" className="h6 cursor-pointer p-0" id="headingPrice">
          Price
        </Accordion.Button>
        <Accordion.Body className="pt-4" aria-labelledby="headingPrice">
          <RangeSlider
            min={0}
            max={200}
            step={1}
            value={values}
            onValueChange={setValues}
            minStepsBetweenThumbs={1}
            tooltipPrefix="$"
            className="pt-1 mt-0"
          />
          <Stack direction="horizontal" gap={2}>
            <div className="position-relative w-50">
              <i className="ci-dollar-sign position-absolute top-50 start-0 translate-middle-y ms-3"/>
              <FormControl
                type="number"
                min={0}
                max={values[1] - 1}
                value={values[0]}
                onChange={(e) => handleInputChange(0, e.target.value)}
                className="form-icon-start"
              />
            </div>
            <i className="ci-minus text-body-emphasis mx-2"/>
            <div className="position-relative w-50">
              <i className="ci-dollar-sign position-absolute top-50 start-0 translate-middle-y ms-3"/>
              <FormControl
                type="number"
                min={values[0] + 1}
                max={1000}
                value={values[1]}
                onChange={(e) => handleInputChange(1, e.target.value)}
                className="form-icon-start"
              />
            </div>
          </Stack>
        </Accordion.Body>
      </Accordion.Item>
    </Accordion>
  )
}

Selected filters

Filter

import Stack from 'react-bootstrap/Stack'
import Button from 'react-bootstrap/Button'

export default function ShopFiltersSelectedDemo() {
  return (
    <div style={{ maxWidth: 280 }}>
      <div className="d-flex align-items-center justify-content-between mb-3">
        <h4 className="h6 mb-0">Filter</h4>
        <Button
          variant="secondary"
          size="sm"
          className="bg-transparent border-0 text-decoration-underline p-0 ms-2"
        >
          Clear all
        </Button>
      </div>
      <Stack direction="horizontal" gap={2} className="flex-wrap">
        {['Sale', 'Adidas', 'Bilabong', 'Size: XXS', '$40 - $150'].map((filter, index) => (
          <Button key={index} variant="secondary" size="sm">
            <i className="ci-close fs-sm ms-n1 me-1"/>
            {filter}
          </Button>
        ))}
      </Stack>
    </div>
  )
}
Top