Skip to content

Commit dd723f7

Browse files
authored
conf: add schedule filters (graphql#1556)
1 parent a4f1584 commit dd723f7

File tree

6 files changed

+387
-130
lines changed

6 files changed

+387
-130
lines changed

Diff for: package.json

+3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
},
1919
"dependencies": {
2020
"@graphql-tools/schema": "10.0.0",
21+
"@headlessui/react": "^1.7.17",
22+
"@heroicons/react": "^2.0.18",
2123
"@radix-ui/react-aspect-ratio": "1.0.2",
2224
"@radix-ui/react-icons": "1.3.0",
2325
"@reach/router": "1.3.4",
26+
"@tailwindcss/forms": "^0.5.6",
2427
"@weknow/gatsby-remark-twitter": "0.2.3",
2528
"assert": "2.0.0",
2629
"clsx": "1.2.1",

Diff for: src/components/Conf/Schedule/Filters.tsx

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import React from "react"
2+
import { Menu, Popover, Transition } from "@headlessui/react"
3+
import { ChevronDownIcon, XMarkIcon } from "@heroicons/react/20/solid"
4+
import clsx from "clsx"
5+
6+
type FiltersProps = {
7+
categories: Array<{ name: string; options: string[] }>
8+
filterState: Record<string, string[]>
9+
onFilterChange: (category: string, option: string, checked: boolean) => void
10+
onReset: () => void
11+
}
12+
13+
export default function Filters({
14+
categories,
15+
filterState,
16+
onFilterChange,
17+
onReset,
18+
}: FiltersProps) {
19+
return (
20+
<div className="">
21+
<div aria-labelledby="filter-heading">
22+
<div className="border-b border-gray-200 pb-4">
23+
<div className="flex items-center justify-between">
24+
{Object.values(filterState).flat().length > 0 && (
25+
<button
26+
onClick={onReset}
27+
className="cursor-pointer flex items-center gap-x-2 px-2 py-1 bg-gray-200 hover:bg-gray-300 rounded-md text-sm font-medium text-gray-700 hover:text-gray-900"
28+
>
29+
Reset filters <XMarkIcon className="h-4 w-4 inline-block" />
30+
</button>
31+
)}
32+
<Menu as="div" className="relative inline-block text-left">
33+
<Transition
34+
enter="transition ease-out duration-100"
35+
enterFrom="transform opacity-0 scale-95"
36+
enterTo="transform opacity-100 scale-100"
37+
leave="transition ease-in duration-75"
38+
leaveFrom="transform opacity-100 scale-100"
39+
leaveTo="transform opacity-0 scale-95"
40+
>
41+
<Menu.Items className="absolute left-0 z-10 mt-2 w-40 origin-top-left rounded-md shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none">
42+
<div className="py-1">
43+
{categories.map(option => (
44+
<Menu.Item key={option.name}>
45+
<span
46+
className={clsx(
47+
filterState[option.name].length > 0
48+
? "font-medium text-gray-900"
49+
: "text-gray-500"
50+
)}
51+
>
52+
{option.name}
53+
</span>
54+
</Menu.Item>
55+
))}
56+
</div>
57+
</Menu.Items>
58+
</Transition>
59+
</Menu>
60+
61+
<div className="">
62+
<div className="flow-root">
63+
<Popover.Group className="flex items-baseline space-x-8">
64+
{categories.map((section, sectionIdx) => (
65+
<Popover
66+
as="div"
67+
key={section.name}
68+
id={`desktop-menu-${sectionIdx}`}
69+
className="relative inline-block text-left"
70+
>
71+
<div>
72+
<Popover.Button className="bg-inherit p-1 px-2 group inline-flex items-center justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
73+
<span>{section.name}</span>
74+
{filterState[section.name].length ? (
75+
<span className="ml-1.5 rounded bg-gray-200 px-1.5 py-0.5 text-xs font-semibold tabular-nums text-gray-700">
76+
{filterState[section.name].length}
77+
</span>
78+
) : null}
79+
<ChevronDownIcon
80+
className="-mr-1 ml-1 h-5 w-5 shrink-0 text-gray-400 group-hover:text-gray-500"
81+
aria-hidden="true"
82+
/>
83+
</Popover.Button>
84+
</div>
85+
86+
<Popover.Panel className="absolute right-0 z-10 mt-2 origin-top-right rounded-md bg-white p-4 shadow-lg border border-black focus:outline-none">
87+
<form className="space-y-4 border border-black">
88+
{section.options.map((option, optionIdx) => (
89+
<div key={option} className="flex items-center">
90+
<input
91+
id={`filter-${section.name}-${optionIdx}`}
92+
name={`${section.name}[]`}
93+
defaultValue={option}
94+
onChange={e => {
95+
const { checked, value } = e.target
96+
onFilterChange(section.name, value, checked)
97+
}}
98+
checked={filterState[section.name].includes(
99+
option
100+
)}
101+
type="checkbox"
102+
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
103+
/>
104+
<label
105+
htmlFor={`filter-${section.name}-${optionIdx}`}
106+
className="ml-3 whitespace-nowrap pr-6 text-sm font-medium text-gray-900"
107+
>
108+
{option}
109+
</label>
110+
</div>
111+
))}
112+
</form>
113+
</Popover.Panel>
114+
</Popover>
115+
))}
116+
</Popover.Group>
117+
</div>
118+
</div>
119+
</div>
120+
</div>
121+
</div>
122+
</div>
123+
)
124+
}

0 commit comments

Comments
 (0)