Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | 10x 2x 572x 12x 12x 12x 12x 12x 1x 1x 12x 1x 1x 12x 3x 96x 1x 2x 2867x 1x | import { useState, useMemo } from 'react'
import { MagnifyingGlass } from '@phosphor-icons/react'
import { ICON_OPTIONS, type IconEntry } from '../utils/iconRegistry'
import { ACCENT_COLORS } from '../utils/typeColors'
import { cn } from '@/lib/utils'
function filterIcons(icons: IconEntry[], query: string): IconEntry[] {
if (!query) return icons
const lower = query.toLowerCase()
return icons.filter((o) => o.name.includes(lower))
}
interface TypeCustomizePopoverProps {
currentIcon: string | null
currentColor: string | null
onChangeIcon: (icon: string) => void
onChangeColor: (color: string) => void
onClose: () => void
}
export function TypeCustomizePopover({
currentIcon,
currentColor,
onChangeIcon,
onChangeColor,
onClose,
}: TypeCustomizePopoverProps) {
const [selectedColor, setSelectedColor] = useState(currentColor)
const [selectedIcon, setSelectedIcon] = useState(currentIcon)
const [search, setSearch] = useState('')
const filteredIcons = useMemo(() => filterIcons(ICON_OPTIONS, search), [search])
const handleColorClick = (key: string) => {
setSelectedColor(key)
onChangeColor(key)
}
const handleIconClick = (name: string) => {
setSelectedIcon(name)
onChangeIcon(name)
}
return (
<div
className="bg-popover text-popover-foreground z-50 rounded-lg border shadow-md"
style={{ width: 280, padding: 12 }}
onClick={(e) => e.stopPropagation()}
onContextMenu={(e) => e.stopPropagation()}
>
{/* Color section */}
<div className="font-mono-overline mb-2 text-muted-foreground">COLOR</div>
<div className="flex gap-2 mb-3 flex-wrap">
{ACCENT_COLORS.map((c) => (
<button
key={c.key}
className={cn(
"flex items-center justify-center rounded-full border-2 cursor-pointer transition-all",
selectedColor === c.key ? "border-foreground scale-110" : "border-transparent hover:scale-105",
)}
style={{ width: 24, height: 24, backgroundColor: c.css, border: selectedColor === c.key ? '2px solid var(--foreground)' : '2px solid transparent' }}
onClick={() => handleColorClick(c.key)}
title={c.label}
/>
))}
</div>
{/* Icon section */}
<div className="font-mono-overline mb-2 text-muted-foreground">ICON</div>
{/* Search input */}
<div className="relative mb-2">
<MagnifyingGlass
size={14}
className="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground pointer-events-none"
/>
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Search icons…"
className="w-full rounded border border-border bg-background pl-7 pr-2 py-1 text-[12px] text-foreground placeholder:text-muted-foreground outline-none focus:border-primary"
/>
</div>
{/* Icon grid */}
<div className="flex flex-wrap gap-1 overflow-y-auto" style={{ maxHeight: 240 }}>
{filteredIcons.length === 0 ? (
<div className="w-full py-6 text-center text-[12px] text-muted-foreground">
No icons found
</div>
) : (
filteredIcons.map(({ name, Icon }) => (
<button
key={name}
className={cn(
"flex items-center justify-center rounded cursor-pointer transition-colors",
selectedIcon === name
? "bg-primary/10 text-primary"
: "text-muted-foreground hover:bg-accent hover:text-foreground",
)}
style={{ width: 30, height: 30 }}
onClick={() => handleIconClick(name)}
title={name}
>
<Icon size={16} />
</button>
))
)}
</div>
{/* Done button */}
<div className="mt-3 flex justify-end">
<button
className="rounded px-3 py-1 text-[12px] font-medium text-muted-foreground hover:text-foreground hover:bg-accent cursor-pointer transition-colors border-none bg-transparent"
onClick={onClose}
>
Done
</button>
</div>
</div>
)
}
|