diff --git a/bun.lock b/bun.lock index d016b90..09cc6a3 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,7 @@ "clsx": "^2.1.1", "drizzle-orm": "^0.45.1", "hono": "^4.12.8", + "lucide-react": "^0.577.0", "react": "^19.2.4", "react-dom": "^19.2.4", "tailwindcss": "^4.2.1", @@ -432,6 +433,8 @@ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + "lucide-react": ["lucide-react@0.577.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], diff --git a/package.json b/package.json index 5a01208..e7e836b 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "clsx": "^2.1.1", "drizzle-orm": "^0.45.1", "hono": "^4.12.8", + "lucide-react": "^0.577.0", "react": "^19.2.4", "react-dom": "^19.2.4", "tailwindcss": "^4.2.1", diff --git a/src/client/lib/iconData.ts b/src/client/lib/iconData.ts new file mode 100644 index 0000000..b4db8f6 --- /dev/null +++ b/src/client/lib/iconData.ts @@ -0,0 +1,249 @@ +import { icons } from "lucide-react"; + +// --- Emoji to Lucide icon mapping (for migration/fallback) --- + +export const EMOJI_TO_ICON_MAP: Record = { + "\u{1F4E6}": "package", + "\u{1F3D5}\uFE0F": "tent", + "\u{26FA}": "tent", + "\u{1F6B2}": "bike", + "\u{1F4F7}": "camera", + "\u{1F392}": "backpack", + "\u{1F455}": "shirt", + "\u{1F527}": "wrench", + "\u{1F373}": "cooking-pot", + "\u{1F3AE}": "gamepad-2", + "\u{1F4BB}": "laptop", + "\u{1F3D4}\uFE0F": "mountain-snow", + "\u{26F0}\uFE0F": "mountain", + "\u{1F3D6}\uFE0F": "umbrella-off", + "\u{1F9ED}": "compass", + "\u{1F526}": "flashlight", + "\u{1F50B}": "battery", + "\u{1F4F1}": "smartphone", + "\u{1F3A7}": "headphones", + "\u{1F9E4}": "hand", + "\u{1F9E3}": "scarf", + "\u{1F45F}": "footprints", + "\u{1F97E}": "footprints", + "\u{1F9E2}": "hard-hat", + "\u{1F576}\uFE0F": "glasses", + "\u{1F52A}": "pocket-knife", + "\u{1FA93}": "axe", + "\u{1F4A1}": "lightbulb", + "\u{2699}\uFE0F": "cog", + "\u{1F3C6}": "trophy", + "\u{1F3AF}": "target", + "\u{2728}": "sparkles", +}; + +// --- Icon groups for the picker --- + +export interface IconEntry { + name: string; + keywords: string[]; +} + +export interface IconGroup { + name: string; + icon: string; + icons: IconEntry[]; +} + +export const iconGroups: IconGroup[] = [ + { + name: "Outdoor", + icon: "tent", + icons: [ + { name: "tent", keywords: ["camp", "shelter", "outdoor"] }, + { name: "campfire", keywords: ["fire", "camp", "warmth"] }, + { name: "mountain", keywords: ["peak", "hike", "climb"] }, + { name: "mountain-snow", keywords: ["peak", "snow", "alpine", "winter"] }, + { name: "compass", keywords: ["navigate", "direction", "orientation"] }, + { name: "map", keywords: ["navigate", "trail", "route"] }, + { name: "map-pin", keywords: ["location", "waypoint", "marker"] }, + { name: "binoculars", keywords: ["view", "watch", "birding", "optics"] }, + { name: "tree-pine", keywords: ["forest", "nature", "woods"] }, + { name: "trees", keywords: ["forest", "nature", "woods"] }, + { name: "sun", keywords: ["weather", "bright", "day"] }, + { name: "cloud-rain", keywords: ["weather", "rain", "storm"] }, + { name: "snowflake", keywords: ["winter", "cold", "snow"] }, + { name: "wind", keywords: ["weather", "breeze", "gust"] }, + { name: "flame", keywords: ["fire", "heat", "burn"] }, + { name: "leaf", keywords: ["nature", "plant", "green"] }, + { name: "flower-2", keywords: ["nature", "plant", "bloom"] }, + { name: "sunrise", keywords: ["morning", "dawn", "sky"] }, + { name: "sunset", keywords: ["evening", "dusk", "sky"] }, + { name: "moon", keywords: ["night", "lunar", "dark"] }, + { name: "star", keywords: ["night", "sky", "favorite"] }, + { name: "thermometer", keywords: ["temperature", "weather", "heat"] }, + ], + }, + { + name: "Travel", + icon: "backpack", + icons: [ + { name: "backpack", keywords: ["bag", "pack", "hike", "carry"] }, + { name: "luggage", keywords: ["bag", "suitcase", "travel"] }, + { name: "plane", keywords: ["flight", "air", "travel"] }, + { name: "car", keywords: ["drive", "vehicle", "road"] }, + { name: "bike", keywords: ["cycle", "bicycle", "ride"] }, + { name: "ship", keywords: ["boat", "water", "sail"] }, + { name: "train-front", keywords: ["rail", "transit", "transport"] }, + { name: "map-pinned", keywords: ["location", "destination", "pinned"] }, + { name: "globe", keywords: ["world", "earth", "international"] }, + { name: "ticket", keywords: ["pass", "admission", "entry"] }, + { name: "route", keywords: ["path", "trail", "direction"] }, + { name: "navigation", keywords: ["direction", "arrow", "gps"] }, + { name: "milestone", keywords: ["marker", "progress", "distance"] }, + { name: "fuel", keywords: ["gas", "petrol", "energy"] }, + { name: "parking-meter", keywords: ["park", "meter", "time"] }, + ], + }, + { + name: "Sports", + icon: "dumbbell", + icons: [ + { name: "dumbbell", keywords: ["gym", "weight", "exercise", "fitness"] }, + { name: "trophy", keywords: ["win", "award", "competition"] }, + { name: "medal", keywords: ["award", "prize", "achievement"] }, + { name: "timer", keywords: ["stopwatch", "time", "race"] }, + { name: "heart-pulse", keywords: ["health", "cardio", "fitness"] }, + { name: "footprints", keywords: ["walk", "hike", "track", "shoes"] }, + { name: "gauge", keywords: ["speed", "meter", "performance"] }, + { name: "target", keywords: ["aim", "goal", "archery"] }, + { name: "flag", keywords: ["finish", "race", "mark"] }, + { name: "swords", keywords: ["fight", "fencing", "combat"] }, + { name: "shield", keywords: ["protect", "defense", "guard"] }, + { name: "zap", keywords: ["energy", "power", "fast", "lightning"] }, + ], + }, + { + name: "Electronics", + icon: "laptop", + icons: [ + { name: "laptop", keywords: ["computer", "notebook", "pc"] }, + { name: "smartphone", keywords: ["phone", "mobile", "cell"] }, + { name: "tablet-smartphone", keywords: ["tablet", "device", "screen"] }, + { name: "headphones", keywords: ["audio", "music", "listen"] }, + { name: "camera", keywords: ["photo", "picture", "lens"] }, + { name: "battery", keywords: ["power", "charge", "energy"] }, + { name: "bluetooth", keywords: ["wireless", "connect", "pair"] }, + { name: "wifi", keywords: ["internet", "wireless", "connect"] }, + { name: "usb", keywords: ["cable", "connect", "port"] }, + { name: "monitor", keywords: ["screen", "display", "desktop"] }, + { name: "keyboard", keywords: ["type", "input", "keys"] }, + { name: "mouse", keywords: ["click", "pointer", "input"] }, + { name: "gamepad-2", keywords: ["game", "controller", "play", "sim"] }, + { name: "speaker", keywords: ["audio", "sound", "music"] }, + { name: "radio", keywords: ["communication", "broadcast", "signal"] }, + { name: "tv", keywords: ["television", "screen", "monitor"] }, + { name: "plug", keywords: ["power", "electric", "connect"] }, + { name: "cable", keywords: ["wire", "connect", "cord"] }, + { name: "cpu", keywords: ["processor", "chip", "computing"] }, + { name: "hard-drive", keywords: ["storage", "disk", "data"] }, + ], + }, + { + name: "Clothing", + icon: "shirt", + icons: [ + { name: "shirt", keywords: ["clothing", "top", "apparel"] }, + { name: "glasses", keywords: ["eyewear", "sunglasses", "vision"] }, + { name: "watch", keywords: ["time", "wrist", "accessory"] }, + { name: "gem", keywords: ["jewelry", "precious", "accessory"] }, + { name: "scissors", keywords: ["cut", "tailor", "craft"] }, + { name: "ruler", keywords: ["measure", "length", "size"] }, + { name: "palette", keywords: ["color", "art", "design"] }, + ], + }, + { + name: "Cooking", + icon: "cooking-pot", + icons: [ + { name: "cooking-pot", keywords: ["pot", "cook", "kitchen", "stove"] }, + { name: "utensils", keywords: ["fork", "knife", "eating", "cutlery"] }, + { name: "cup-soda", keywords: ["drink", "beverage", "cup"] }, + { name: "coffee", keywords: ["drink", "hot", "brew", "mug"] }, + { name: "beef", keywords: ["meat", "food", "protein"] }, + { name: "fish", keywords: ["seafood", "food", "protein"] }, + { name: "apple", keywords: ["fruit", "food", "snack"] }, + { name: "wheat", keywords: ["grain", "food", "bread"] }, + { name: "flame-kindling", keywords: ["fire", "stove", "cook"] }, + { name: "refrigerator", keywords: ["cold", "store", "cool"] }, + { name: "microwave", keywords: ["heat", "cook", "kitchen"] }, + ], + }, + { + name: "Tools", + icon: "wrench", + icons: [ + { name: "wrench", keywords: ["fix", "repair", "tool"] }, + { name: "hammer", keywords: ["build", "nail", "tool"] }, + { name: "screwdriver", keywords: ["fix", "screw", "tool"] }, + { name: "drill", keywords: ["bore", "hole", "tool"] }, + { name: "ruler", keywords: ["measure", "length", "tool"] }, + { name: "flashlight", keywords: ["light", "torch", "dark"] }, + { name: "pocket-knife", keywords: ["blade", "cut", "multi-tool"] }, + { name: "axe", keywords: ["chop", "wood", "hatchet"] }, + { name: "shovel", keywords: ["dig", "earth", "garden"] }, + { name: "paintbrush", keywords: ["paint", "art", "coat"] }, + { name: "scissors", keywords: ["cut", "trim", "snip"] }, + { name: "cog", keywords: ["gear", "settings", "mechanical"] }, + { name: "nut", keywords: ["bolt", "fastener", "hardware"] }, + ], + }, + { + name: "General", + icon: "package", + icons: [ + { name: "package", keywords: ["box", "parcel", "container", "default"] }, + { name: "box", keywords: ["container", "storage", "crate"] }, + { name: "tag", keywords: ["label", "price", "mark"] }, + { name: "bookmark", keywords: ["save", "favorite", "mark"] }, + { name: "archive", keywords: ["store", "old", "save"] }, + { name: "folder", keywords: ["organize", "file", "directory"] }, + { name: "grid-3x3", keywords: ["grid", "layout", "table"] }, + { name: "list", keywords: ["items", "bullet", "todo"] }, + { name: "layers", keywords: ["stack", "overlap", "level"] }, + { name: "circle-dot", keywords: ["dot", "center", "radio"] }, + { name: "square", keywords: ["shape", "box", "block"] }, + { name: "hexagon", keywords: ["shape", "six", "polygon"] }, + { name: "triangle", keywords: ["shape", "warning", "three"] }, + { name: "heart", keywords: ["love", "favorite", "like"] }, + { name: "star", keywords: ["favorite", "rate", "highlight"] }, + { name: "plus", keywords: ["add", "new", "create"] }, + { name: "check", keywords: ["done", "complete", "yes"] }, + { name: "x", keywords: ["close", "remove", "delete"] }, + ], + }, +]; + +// --- LucideIcon render component --- + +function toPascalCase(str: string): string { + return str + .split("-") + .map((s) => s.charAt(0).toUpperCase() + s.slice(1)) + .join(""); +} + +interface LucideIconProps { + name: string; + size?: number; + className?: string; +} + +export function LucideIcon({ + name, + size = 20, + className = "", +}: LucideIconProps) { + const pascalName = toPascalCase(name); + const IconComponent = icons[pascalName as keyof typeof icons]; + if (!IconComponent) { + const FallbackIcon = icons.Package; + return ; + } + return ; +}