Skip to content

Commit

Permalink
Merge pull request #159 from rsksmart/feat/filter-component
Browse files Browse the repository at this point in the history
Filter component update
  • Loading branch information
owans authored Nov 27, 2024
2 parents 658ea5a + 9677b59 commit 2da1b28
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 57 deletions.
6 changes: 2 additions & 4 deletions src/components/Filter/FilterItem/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,10 @@ export default function FilterItem({value, subtitle, title, description, color =
{subtitle}
</span>
)}
<h3 className="m-0 h1">{title}</h3>
<h3 className="m-0 h1" dangerouslySetInnerHTML={{__html:title}}></h3>
</div>
{description && (
<div className={`markdown fw-medium`}>
{description}
</div>
<div className={`markdown fw-medium`} dangerouslySetInnerHTML={{__html:description}}></div>
)}
{linkHref && (
<Button href={linkHref} stretched={true} className={clsx('mt-auto', !linkTitle && 'py-6 px-20')}>
Expand Down
221 changes: 168 additions & 53 deletions src/components/Filter/index.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,176 @@
import React, {useEffect, useState} from "react";
import clsx from "clsx";
import styles from './styles.module.scss';
import React, { useEffect, useState } from 'react'
import clsx from 'clsx'
import styles from './styles.module.scss'
import Translate from '@docusaurus/Translate'

import Translate from '@docusaurus/Translate';
export default function Filter ({ values, children, className, disableSearch, disableUpdateHash, ...props }) {
const [selectedValues, setSelectedValues] = useState([])
const [searchQuery, setSearchQuery] = useState('')
const [showMore, setShowMore] = useState(false)
const childItems = (Array.isArray(children) ? children : [children]).filter(Boolean)
const [items, setItems] = useState(childItems)

export default function Filter ({values, children, className, ...props}) {
const [selectedValue, setSelectedValue] = useState('all');
const childItems = (Array.isArray(children) ? children : [children]).filter(
Boolean,
);

const [items, setitems] = useState(childItems);
useEffect(() => {
if (!disableUpdateHash) {
const hash = new URLSearchParams(window.location.hash.substring(1)).get('filters')
if (hash) {
setSelectedValues(hash.split(','))
}
}
}, [disableUpdateHash])

useEffect(() => {
setitems(childItems.filter(item => {
const itemValues = item.props.value ? item.props.value.replace(/ /g, '').split(',') : [];
return selectedValue === 'all' || itemValues.includes(selectedValue)
setItems(childItems.filter(item => {
const itemValues = item.props.value ? item.props.value.replace(/ /g, '').split(',') : []
const matchesFilter = selectedValues.length === 0 || selectedValues.some(value => itemValues.includes(value))
const matchesSearch = disableSearch || searchQuery.length < 2 || item.props.title.toLowerCase().includes(searchQuery.toLowerCase()) || item.props.description.toLowerCase().includes(searchQuery.toLowerCase())
return matchesFilter && matchesSearch
}))
}, [selectedValue]);

return <div
className={clsx(``, className)}
{...props}
>
<ul
role="tablist"
aria-orientation="horizontal"
className={clsx(
'list-unstyled d-flex gap-8 flex-wrap m-0 mb-24 p-0',
className,
)}>
<li className={'m-0'}>
<button
onClick={() => setSelectedValue('all')}
className={clsx(`btn btn-outline`, selectedValue === 'all' && 'active')}>
<Translate
id="theme.common.all">
All
</Translate>
</button>
</li>
{values.map(({value, label}) => (
<li className={`m-0`} key={value}>
<button
onClick={() => setSelectedValue(value)}
className={clsx(`btn btn-outline`, selectedValue === value && 'active')}>
{label ?? value}
</button>
</li>
))}
</ul>
}, [selectedValues, searchQuery, disableSearch])

useEffect(() => {
if (!disableUpdateHash) {
if (selectedValues.length > 0) {
history.replaceState(null, '', `#filters=${selectedValues.join(',')}`)
} else {
history.replaceState(null, '', window.location.pathname + window.location.search)
}
}
}, [selectedValues, disableUpdateHash])

const toggleValue = (value) => {
setSelectedValues(prevSelectedValues =>
prevSelectedValues.includes(value)
? prevSelectedValues.filter(selectedValue => selectedValue !== value)
: [...prevSelectedValues, value]
)
}

const clearFilters = () => {
setSelectedValues([])
setSearchQuery('')
}

const clearSearch = () => {
setSearchQuery('')
}

const highlightText = (text, query) => {
if (!query || query.length < 2) return text
const regex = new RegExp(`(${query})`, 'gi')
return text.replace(regex, '<mark>$1</mark>')
}

const toggleShowMore = () => {
setShowMore(!showMore)
}

const displayedValues = showMore
? values
: values.slice(0, 4).concat(values.slice(4).filter(({ value }) => selectedValues.includes(value)))

const collapsedItemCount = values.slice(4).filter(({ value }) => !selectedValues.includes(value)).length

return (
<div className={clsx(``, className)} {...props}>
{(!disableSearch || selectedValues.length > 0) && (
<div className="d-flex flex-column gap-12 flex-md-row mb-24 align-items-start align-items-md-stretch">
{!disableSearch && (
<div className="position-relative flex-grow-1 align-self-stretch">
<svg width="20" height="20" className="text-body opacity-75 position-absolute z-1 start-0 top-50 translate-middle-y ms-16 pe-none">
<use xlinkHref="#icon-search"></use>
</svg>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Enter search query (more than 2 characters)"
className="form-control px-48"
/>
{searchQuery.length > 0 && (
<button onClick={clearSearch} type="button" className={clsx(styles.SearchClearBtn, 'btn-blank d-flex text-body position-absolute end-0 top-50 translate-middle-y p-16 z-1')}>
<svg width={16} height={16}>
<use xlinkHref="#icon-close"/>
</svg>
</button>
)}
</div>
)}
{selectedValues.length > 0 && (
<button onClick={clearFilters} className="btn btn-no-shadow d-none d-md-flex">
Clear all filters
<svg width={16} height={16}>
<use xlinkHref="#icon-close-circle"></use>
</svg>
</button>
)}
</div>
)}
<div className="mb-32 d-flex flex-column flex-md-row justify-content-md-between gap-12 align-items-start">
<ul
role="tablist"
aria-orientation="horizontal"
className={clsx(
'list-unstyled d-flex gap-8 flex-wrap m-0 p-0',
className,
)}>
<li className={'m-0'}>
<button
onClick={() => setSelectedValues([])}
className={clsx(`btn btn-outline`, selectedValues.length === 0 && 'active')}>
<Translate id="theme.common.all">All</Translate>
</button>
</li>
{displayedValues.map(({ value, label }) => (
<li className={`m-0`} key={value}>
<button
onClick={() => toggleValue(value)}
className={clsx(`btn btn-outline`, selectedValues.includes(value) && 'active')}>
{label ?? value}
</button>
</li>
))}
</ul>
<div className="d-flex justify-content-between gap-8 align-self-stretch align-self-md-start">
{values.length > 4 && (
<button onClick={toggleShowMore} className="btn btn-no-shadow">
{showMore ? `Show less` : `Show ${collapsedItemCount} more`}
</button>
)}
{selectedValues.length > 0 && (
<button onClick={clearFilters} className="btn btn-no-shadow d-md-none">
Clear all filters
<svg width={16} height={16}>
<use xlinkHref="#icon-close-circle"></use>
</svg>
</button>
)}
</div>
</div>


<div className={clsx(styles.FilterGrid)}>
{items.map((tabItem, i) =>
<React.Fragment key={i}>
{tabItem}
</React.Fragment>
)}
{items.length > 0 ? <>
{items.map((tabItem, i) =>
<React.Fragment key={i}>
{React.cloneElement(tabItem, {
title: highlightText(tabItem.props.title, searchQuery),
description: highlightText(tabItem.props.description, searchQuery)
})}
</React.Fragment>
)}
</>
:
<p>
<Translate
id="theme.SearchPage.noResultsText"
description="The paragraph for empty search result">
No results were found
</Translate>
</p>
}

</div>
</div>
</div>
)
}
6 changes: 6 additions & 0 deletions src/components/Filter/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,9 @@
grid-template-columns: repeat(2, 1fr);
}
}
.SearchClearBtn{
opacity: 0.5;
&:hover{
opacity: 1;
}
}
2 changes: 2 additions & 0 deletions src/pages/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,8 @@ title="Card title first line" description="Mauris maecenas et amet arcu urna int
```jsx
<Filter
disableSearch={false} //optional. set true to hide search bar
disableUpdateHash={false} //optional. set true to disable updating url hash on filter change
values={[
{label: 'Apps', value: 'apps'},
{label: 'Exchanges', value: 'exchanges'},
Expand Down
3 changes: 3 additions & 0 deletions src/scss/base/_content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,6 @@ code{
border: none;
border-radius: 4px;
}
mark, .mark{
padding: 0 0.15em;
}
4 changes: 4 additions & 0 deletions src/theme/Layout/SVGSprite/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2da1b28

Please sign in to comment.