Skip to content

Commit b7032e8

Browse files
committed
Clean up code after removing tags from the input
1 parent 7f73048 commit b7032e8

File tree

2 files changed

+97
-200
lines changed

2 files changed

+97
-200
lines changed
Lines changed: 89 additions & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import type { ReactNode } from "react"
2-
import { useCallback } from "react"
32
import { clsx } from "clsx"
43
import { CheckboxIcon } from "@/app/conf/_design-system/pixelarticons/checkbox-icon"
54

@@ -16,194 +15,112 @@ interface CheckboxTreeProps {
1615
items: CheckboxTreeItem[]
1716
selectedValues: string[]
1817
onSelectionChange: (next: string[]) => void
19-
emptyFallback?: ReactNode
18+
depth?: number
2019
}
2120

2221
export function CheckboxTree({
2322
items,
2423
selectedValues,
2524
onSelectionChange,
26-
emptyFallback,
25+
depth = 0,
2726
}: CheckboxTreeProps) {
28-
const toggleValue = useCallback(
29-
(value: string) => {
30-
const next = selectedValues.includes(value)
31-
? selectedValues.filter(tag => tag !== value)
32-
: [...selectedValues, value]
33-
onSelectionChange(next)
34-
},
35-
[selectedValues, onSelectionChange],
36-
)
27+
return (
28+
<div>
29+
{items.map(item => {
30+
const isSelectable = Boolean(item.value)
31+
const isDisabled = item.disabled
32+
const isChecked = isSelectable
33+
? selectedValues.includes(item.value!)
34+
: false
35+
const checkboxId = `checkbox-tree-${item.id}`
3736

38-
const renderTree = (nodes: CheckboxTreeItem[], depth: number): ReactNode => {
39-
return nodes.map(node => {
40-
const isSelectable = Boolean(node.value)
41-
const isDisabled = node.disabled
42-
const isChecked = isSelectable
43-
? selectedValues.includes(node.value!)
44-
: false
45-
const checkboxId = `checkbox-tree-${node.id}`
37+
const toggleValue = (value: string) => {
38+
const next = selectedValues.includes(value)
39+
? selectedValues.filter(tag => tag !== value)
40+
: [...selectedValues, value]
41+
onSelectionChange(next)
42+
}
4643

47-
return (
48-
<div key={node.id}>
49-
<div
50-
className="flex items-start gap-2 py-1"
51-
style={{
52-
paddingInlineStart: depth > 0 ? (depth - 1) * 16 : 0,
53-
}}
54-
>
55-
{isSelectable ? (
56-
<label
57-
htmlFor={checkboxId}
58-
className={clsx(
59-
"flex grow items-center gap-2",
60-
isDisabled
61-
? "cursor-not-allowed text-neu-500"
62-
: "cursor-pointer",
63-
)}
64-
aria-disabled={isDisabled}
65-
>
66-
<span className="flex shrink-0 items-center">
67-
<input
68-
id={checkboxId}
69-
type="checkbox"
70-
checked={isChecked}
71-
onChange={() => {
72-
if (!isDisabled) toggleValue(node.value!)
73-
}}
74-
disabled={isDisabled}
75-
className="peer sr-only"
76-
/>
77-
<CheckboxIcon
78-
checked={isChecked}
79-
className={clsx(
80-
"pointer-events-none size-5 transition-colors peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-1 peer-focus-visible:outline-neu-900",
81-
isDisabled ? "text-neu-300" : undefined,
82-
)}
83-
aria-hidden
84-
/>
85-
</span>
86-
<span
44+
return (
45+
<div key={item.id}>
46+
<div
47+
className="flex items-start gap-2 py-1"
48+
style={{ paddingInlineStart: (depth - 1) * 16 }}
49+
>
50+
{isSelectable ? (
51+
<label
52+
htmlFor={checkboxId}
8753
className={clsx(
88-
"min-w-0 grow truncate text-left",
89-
isDisabled ? "text-neu-500" : "text-neu-800",
54+
"flex grow items-center gap-2",
55+
isDisabled
56+
? "cursor-not-allowed text-neu-500"
57+
: "cursor-pointer",
9058
)}
59+
aria-disabled={isDisabled}
9160
>
92-
{node.label}
93-
</span>
94-
{node.count ? ( // we intentionally don't display 0
61+
<span className="flex shrink-0 items-center">
62+
<input
63+
id={checkboxId}
64+
type="checkbox"
65+
checked={isChecked}
66+
onChange={() => {
67+
if (!isDisabled) toggleValue(item.value!)
68+
}}
69+
disabled={isDisabled}
70+
className="peer sr-only"
71+
/>
72+
<CheckboxIcon
73+
checked={isChecked}
74+
className={clsx(
75+
"pointer-events-none size-5 peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-1 peer-focus-visible:outline-neu-900",
76+
isDisabled ? "text-neu-300" : undefined,
77+
)}
78+
aria-hidden
79+
/>
80+
</span>
9581
<span
9682
className={clsx(
97-
"ml-auto shrink-0 text-xs",
98-
isDisabled ? "text-neu-400" : "text-neu-700",
83+
"min-w-0 grow truncate text-left",
84+
isDisabled ? "text-neu-500" : "text-neu-800",
9985
)}
10086
>
101-
{node.count}
87+
{item.label}
10288
</span>
103-
) : null}
104-
</label>
105-
) : (
106-
<div
107-
className={clsx(
108-
"typography-menu mt-4 text-sm xl:mt-10",
109-
isDisabled ? "text-neu-500" : "text-neu-900",
110-
)}
111-
aria-disabled={isDisabled}
112-
>
113-
{node.label}
114-
</div>
115-
)}
116-
</div>
117-
118-
{node.children && node.children.length > 0 ? (
119-
<div>{renderTree(node.children, depth + 1)}</div>
120-
) : null}
121-
</div>
122-
)
123-
})
124-
}
125-
126-
if (items.length === 0) {
127-
return (
128-
<div className="py-4 text-sm text-neu-500">
129-
{emptyFallback ?? "No matches"}
130-
</div>
131-
)
132-
}
133-
134-
return <div>{renderTree(items, 0)}</div>
135-
}
136-
if (selectedValues.includes(value)) {
137-
onSelectionChange(selectedValues.filter(tag => tag !== value))
138-
} else {
139-
onSelectionChange([...selectedValues, value])
140-
}
141-
}
142-
143-
const renderTree = (nodes: PreparedTree[]): ReactNode => {
144-
return nodes.map(node => {
145-
const isSelectable = Boolean(node.value)
146-
const isChecked = isSelectable
147-
? selectedValues.includes(node.value!)
148-
: false
149-
const checkboxId = `checkbox-tree-${node.id}`
89+
{item.count ? ( // we intentionally don't display 0
90+
<span
91+
className={clsx(
92+
"ml-auto shrink-0 text-xs",
93+
isDisabled ? "text-neu-400" : "text-neu-700",
94+
)}
95+
>
96+
{item.count}
97+
</span>
98+
) : null}
99+
</label>
100+
) : (
101+
<div
102+
className={clsx(
103+
"typography-menu mt-4 text-sm xl:mt-10",
104+
isDisabled ? "text-neu-500" : "text-neu-900",
105+
)}
106+
aria-disabled={isDisabled}
107+
>
108+
{item.label}
109+
</div>
110+
)}
111+
</div>
150112

151-
return (
152-
<div key={node.id}>
153-
<div
154-
className="flex items-start gap-2 py-1"
155-
style={{ paddingInlineStart: (node.depth - 1) * 16 }}
156-
>
157-
{isSelectable ? (
158-
<label
159-
htmlFor={checkboxId}
160-
className="flex grow cursor-pointer items-center gap-2"
161-
>
162-
<span className="flex shrink-0 items-center">
163-
<input
164-
id={checkboxId}
165-
type="checkbox"
166-
checked={isChecked}
167-
onChange={() => toggleValue(node.value!)}
168-
className="peer sr-only"
169-
/>
170-
<CheckboxIcon
171-
checked={isChecked}
172-
className="pointer-events-none size-5 transition-colors peer-focus-visible:outline peer-focus-visible:outline-2 peer-focus-visible:outline-offset-1 peer-focus-visible:outline-neu-900"
173-
aria-hidden
174-
/>
175-
</span>
176-
<span className="min-w-0 grow truncate text-left text-neu-800">
177-
{node.label}
178-
</span>
179-
{node.count ? ( // we intentionally don't display 0
180-
<span className="ml-auto shrink-0 text-xs text-neu-700">
181-
{node.count}
182-
</span>
183-
) : null}
184-
</label>
185-
) : (
186-
<div className="typography-menu mt-4 text-sm text-neu-900 xl:mt-10">
187-
{node.label}
188-
</div>
189-
)}
113+
{item.children && item.children.length > 0 ? (
114+
<CheckboxTree
115+
items={item.children}
116+
selectedValues={selectedValues}
117+
onSelectionChange={onSelectionChange}
118+
depth={depth + 1}
119+
/>
120+
) : null}
190121
</div>
191-
192-
{node.children && node.children.length > 0 ? (
193-
<div>{renderTree(node.children)}</div>
194-
) : null}
195-
</div>
196-
)
197-
})
198-
}
199-
200-
if (filteredTree.length === 0) {
201-
return (
202-
<div className="py-4 text-sm text-neu-500">
203-
{emptyFallback ?? "No matches"}
204-
</div>
205-
)
206-
}
207-
208-
return <div>{renderTree(filteredTree)}</div>
122+
)
123+
})}
124+
</div>
125+
)
209126
}

src/components/tools-and-libraries.tsx

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,8 @@ export function CodePage({ allTags, data }: CodePageProps) {
6767

6868
const [searchParams, setSearchParams] = useSearchParamsState()
6969
const [search, setSearch] = useState("")
70-
const selectedTags = useMemo(() => {
71-
const values = searchParams.getAll(TAG_PARAM_KEY)
72-
if (values.length === 0) return []
73-
return values.flatMap(value =>
74-
value.split("_").map(part => part.trim()).filter(Boolean),
75-
)
76-
}, [searchParams])
70+
71+
const selectedTags = searchParams.getAll(TAG_PARAM_KEY)
7772

7873
const updateTags = useCallback(
7974
(updater: (prev: string[]) => string[]) => {
@@ -108,27 +103,13 @@ export function CodePage({ allTags, data }: CodePageProps) {
108103
)
109104

110105
const mounted = useMounted()
111-
const [isBackspacePressed, setIsBackspacePressed] = useState(false)
112-
113-
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
114-
e => {
115-
if (e.key === "Backspace" && !search) {
116-
if (isBackspacePressed) {
117-
setIsBackspacePressed(false)
118-
updateTags(prevTags => prevTags.slice(0, -1))
119-
} else {
120-
setIsBackspacePressed(true)
121-
}
122-
}
123-
},
124-
[isBackspacePressed, search, updateTags],
125-
)
126106

127107
const { newData, tagCounts } = useMemo(() => {
128108
const filteredData = mounted
129109
? data.filter(({ tags }) => {
130110
return (
131-
!selectedTags.length || selectedTags.every(tag => tags.includes(tag))
111+
!selectedTags.length ||
112+
selectedTags.every(tag => tags.includes(tag))
132113
)
133114
})
134115
: data
@@ -245,7 +226,9 @@ export function CodePage({ allTags, data }: CodePageProps) {
245226
return {
246227
...item,
247228
disabled: isDisabled,
248-
...(orderedChildren ? { children: orderedChildren } : { children: undefined }),
229+
...(orderedChildren
230+
? { children: orderedChildren }
231+
: { children: undefined }),
249232
}
250233
}
251234

@@ -258,10 +241,9 @@ export function CodePage({ allTags, data }: CodePageProps) {
258241

259242
const handleTreeSelection = useCallback(
260243
(next: string[]) => {
261-
setIsBackspacePressed(false)
262244
updateTags(() => next)
263245
},
264-
[setIsBackspacePressed, updateTags],
246+
[updateTags],
265247
)
266248

267249
const selectedTagsAsString = useMemo(() => {
@@ -316,7 +298,6 @@ export function CodePage({ allTags, data }: CodePageProps) {
316298
// TODO: This should also do a fuzzy full text search, not just search on tags.
317299
value={search}
318300
onChange={e => setSearch(e.target.value)}
319-
onKeyDown={handleKeyDown}
320301
placeholder="Filter tags..."
321302
className="bg-transparent focus:outline-none"
322303
/>
@@ -325,7 +306,6 @@ export function CodePage({ allTags, data }: CodePageProps) {
325306
items={filterTreeItems}
326307
selectedValues={selectedTags}
327308
onSelectionChange={handleTreeSelection}
328-
emptyFallback="No categories found"
329309
/>
330310
</aside>
331311

0 commit comments

Comments
 (0)