Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 2m24s
Build & Deploy / 🏗️ Build (push) Failing after 3m40s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
192 lines
4.5 KiB
TypeScript
192 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import React, { useState } from "react";
|
|
import { useField } from "@payloadcms/ui";
|
|
import * as LucideIcons from "lucide-react";
|
|
|
|
const COMMON_ICONS = [
|
|
"Check",
|
|
"X",
|
|
"AlertTriangle",
|
|
"Info",
|
|
"ArrowRight",
|
|
"ArrowUpRight",
|
|
"ChevronRight",
|
|
"Settings",
|
|
"Tool",
|
|
"Terminal",
|
|
"Code",
|
|
"Database",
|
|
"Server",
|
|
"Cpu",
|
|
"Zap",
|
|
"Shield",
|
|
"Lock",
|
|
"Key",
|
|
"Eye",
|
|
"Search",
|
|
"Filter",
|
|
"BarChart",
|
|
"LineChart",
|
|
"PieChart",
|
|
"TrendingUp",
|
|
"TrendingDown",
|
|
"Users",
|
|
"User",
|
|
"Briefcase",
|
|
"Building",
|
|
"Globe",
|
|
"Mail",
|
|
"FileText",
|
|
"File",
|
|
"Folder",
|
|
"Image",
|
|
"Video",
|
|
"MessageSquare",
|
|
"Clock",
|
|
"Calendar",
|
|
"CheckCircle",
|
|
"XCircle",
|
|
"Play",
|
|
"Pause",
|
|
"Activity",
|
|
"Box",
|
|
"Layers",
|
|
"Layout",
|
|
"Monitor",
|
|
"Smartphone",
|
|
"Tablet",
|
|
"Wifi",
|
|
"Cloud",
|
|
"Crosshair",
|
|
"Target",
|
|
"Trophy",
|
|
"Star",
|
|
"Heart",
|
|
];
|
|
|
|
export default function IconSelectorField({ path }: { path: string }) {
|
|
const { value, setValue } = useField<string>({ path });
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
|
|
const filteredIcons = COMMON_ICONS.filter((name) =>
|
|
name.toLowerCase().includes(searchTerm.toLowerCase()),
|
|
);
|
|
|
|
const handleIconClick = (iconName: string) => {
|
|
// Toggle off if clicking the current value
|
|
if (value === iconName) {
|
|
setValue(null);
|
|
} else {
|
|
setValue(iconName);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="field-type" style={{ marginBottom: "1.5rem" }}>
|
|
<label
|
|
className="field-label"
|
|
style={{
|
|
display: "block",
|
|
marginBottom: "8px",
|
|
fontSize: "0.875rem",
|
|
fontWeight: 500,
|
|
}}
|
|
>
|
|
Icon Selection (Lucide)
|
|
</label>
|
|
|
|
<input
|
|
type="text"
|
|
placeholder="Search icons..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
style={{
|
|
padding: "8px 12px",
|
|
border: "1px solid var(--theme-elevation-200)",
|
|
borderRadius: "4px",
|
|
background: "var(--theme-elevation-50)",
|
|
color: "var(--theme-text)",
|
|
fontSize: "0.875rem",
|
|
width: "100%",
|
|
marginBottom: "12px",
|
|
boxSizing: "border-box",
|
|
}}
|
|
/>
|
|
|
|
<div
|
|
style={{
|
|
display: "grid",
|
|
gridTemplateColumns: "repeat(auto-fill, minmax(40px, 1fr))",
|
|
gap: "8px",
|
|
maxHeight: "200px",
|
|
overflowY: "auto",
|
|
padding: "4px",
|
|
}}
|
|
>
|
|
{filteredIcons.map((iconName) => {
|
|
const Icon = (LucideIcons as any)[iconName];
|
|
if (!Icon) return null;
|
|
const isSelected = value === iconName;
|
|
|
|
return (
|
|
<button
|
|
key={iconName}
|
|
type="button"
|
|
title={iconName}
|
|
onClick={() => handleIconClick(iconName)}
|
|
style={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
width: "40px",
|
|
height: "40px",
|
|
borderRadius: "6px",
|
|
background: isSelected
|
|
? "var(--theme-elevation-200)"
|
|
: "transparent",
|
|
border: isSelected
|
|
? "1px solid var(--theme-elevation-400)"
|
|
: "1px solid var(--theme-elevation-150)",
|
|
color: isSelected
|
|
? "var(--theme-text)"
|
|
: "var(--theme-elevation-500)",
|
|
cursor: "pointer",
|
|
transition: "all 0.1s ease",
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = isSelected
|
|
? "var(--theme-elevation-200)"
|
|
: "var(--theme-elevation-100)";
|
|
e.currentTarget.style.color = "var(--theme-text)";
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = isSelected
|
|
? "var(--theme-elevation-200)"
|
|
: "transparent";
|
|
e.currentTarget.style.color = isSelected
|
|
? "var(--theme-text)"
|
|
: "var(--theme-elevation-500)";
|
|
}}
|
|
>
|
|
<Icon size={20} strokeWidth={1.5} />
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{filteredIcons.length === 0 && (
|
|
<div
|
|
style={{
|
|
fontSize: "0.875rem",
|
|
color: "var(--theme-elevation-500)",
|
|
marginTop: "8px",
|
|
}}
|
|
>
|
|
No matching icons found.
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|