Write on the margins of the internet. Powered by the AT Protocol. margin.at
extension web atproto comments

emoji picker for collections

+332 -65
+5
web/bun.lock
··· 13 13 "autoprefixer": "^10.4.24", 14 14 "clsx": "^2.1.1", 15 15 "date-fns": "^4.1.0", 16 + "emoji-picker-react": "^4.18.0", 16 17 "lucide-react": "^0.563.0", 17 18 "nanostores": "^1.1.0", 18 19 "postcss": "^8.5.6", ··· 593 594 594 595 "electron-to-chromium": ["electron-to-chromium@1.5.286", "", {}, "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A=="], 595 596 597 + "emoji-picker-react": ["emoji-picker-react@4.18.0", "", { "dependencies": { "flairup": "1.0.0" }, "peerDependencies": { "react": ">=16" } }, "sha512-vLTrLfApXAIciguGE57pXPWs9lPLBspbEpPMiUq03TIli2dHZBiB+aZ0R9/Wat0xmTfcd4AuEzQgSYxEZ8C88Q=="], 598 + 596 599 "emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="], 597 600 598 601 "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], ··· 680 683 "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], 681 684 682 685 "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], 686 + 687 + "flairup": ["flairup@1.0.0", "", {}, "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA=="], 683 688 684 689 "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], 685 690
+1
web/package.json
··· 19 19 "autoprefixer": "^10.4.24", 20 20 "clsx": "^2.1.1", 21 21 "date-fns": "^4.1.0", 22 + "emoji-picker-react": "^4.18.0", 22 23 "lucide-react": "^0.563.0", 23 24 "nanostores": "^1.1.0", 24 25 "postcss": "^8.5.6",
+86 -22
web/src/components/modals/AddToCollectionModal.tsx
··· 9 9 } from "lucide-react"; 10 10 import CollectionIcon from "../common/CollectionIcon"; 11 11 import { ICON_MAP } from "../common/iconMap"; 12 + import EmojiPicker, { Theme } from "emoji-picker-react"; 12 13 import { useStore } from "@nanostores/react"; 13 14 import { $user } from "../../store/auth"; 15 + import { $theme } from "../../store/theme"; 14 16 import { 15 17 getCollections, 16 18 addCollectionItem, ··· 31 33 annotationUri, 32 34 }: AddToCollectionModalProps) { 33 35 const user = useStore($user); 36 + const theme = useStore($theme); 34 37 const [collections, setCollections] = useState<Collection[]>([]); 35 38 const [loading, setLoading] = useState(true); 36 39 const [addingTo, setAddingTo] = useState<string | null>(null); ··· 41 44 const [newName, setNewName] = useState(""); 42 45 const [newDescription, setNewDescription] = useState(""); 43 46 const [newIcon, setNewIcon] = useState(""); 47 + const [activeTab, setActiveTab] = useState<"icon" | "emoji">("icon"); 44 48 const [creating, setCreating] = useState(false); 45 49 46 50 useEffect(() => { ··· 96 100 if (!newName.trim()) return; 97 101 try { 98 102 setCreating(true); 99 - const iconValue = newIcon ? `icon:${newIcon}` : undefined; 103 + const iconValue = newIcon 104 + ? ICON_MAP[newIcon] 105 + ? `icon:${newIcon}` 106 + : newIcon 107 + : undefined; 100 108 const newCollection = await createCollection( 101 109 newName.trim(), 102 110 newDescription.trim() || undefined, ··· 184 192 <label className="block text-sm font-medium text-surface-700 dark:text-surface-300 mb-2"> 185 193 Icon 186 194 </label> 187 - <div className="grid grid-cols-8 gap-1.5 max-h-32 overflow-y-auto p-2 bg-surface-50 dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700"> 188 - {Object.keys(ICON_MAP).map((iconName) => { 189 - const isSelected = newIcon === iconName; 190 - return ( 191 - <button 192 - key={iconName} 193 - type="button" 194 - onClick={() => setNewIcon(isSelected ? "" : iconName)} 195 - className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all ${ 196 - isSelected 197 - ? "bg-primary-600 text-white" 198 - : "hover:bg-surface-200 dark:hover:bg-surface-700 text-surface-600 dark:text-surface-400" 199 - }`} 200 - title={iconName} 201 - > 202 - <CollectionIcon icon={`icon:${iconName}`} size={16} /> 203 - </button> 204 - ); 205 - })} 195 + 196 + <div className="flex gap-2 mb-3 bg-surface-100 dark:bg-surface-800 p-1 rounded-xl"> 197 + <button 198 + type="button" 199 + onClick={() => setActiveTab("icon")} 200 + className={`flex-1 py-1.5 text-sm font-medium rounded-lg transition-colors ${ 201 + activeTab === "icon" 202 + ? "bg-white dark:bg-surface-700 text-surface-900 dark:text-white shadow-sm" 203 + : "text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-200" 204 + }`} 205 + > 206 + Icons 207 + </button> 208 + <button 209 + type="button" 210 + onClick={() => setActiveTab("emoji")} 211 + className={`flex-1 py-1.5 text-sm font-medium rounded-lg transition-colors ${ 212 + activeTab === "emoji" 213 + ? "bg-white dark:bg-surface-700 text-surface-900 dark:text-white shadow-sm" 214 + : "text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-200" 215 + }`} 216 + > 217 + Emojis 218 + </button> 206 219 </div> 220 + 221 + {activeTab === "icon" ? ( 222 + <div className="grid grid-cols-8 gap-1.5 max-h-60 overflow-y-auto p-2 bg-surface-50 dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 custom-scrollbar"> 223 + {Object.keys(ICON_MAP).map((iconName) => { 224 + const isSelected = newIcon === iconName; 225 + return ( 226 + <button 227 + key={iconName} 228 + type="button" 229 + onClick={() => setNewIcon(isSelected ? "" : iconName)} 230 + className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all ${ 231 + isSelected 232 + ? "bg-primary-600 text-white" 233 + : "hover:bg-surface-200 dark:hover:bg-surface-700 text-surface-600 dark:text-surface-400" 234 + }`} 235 + title={iconName} 236 + > 237 + <CollectionIcon icon={`icon:${iconName}`} size={16} /> 238 + </button> 239 + ); 240 + })} 241 + </div> 242 + ) : ( 243 + <div className="w-full bg-surface-50 dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 overflow-hidden"> 244 + <EmojiPicker 245 + className="custom-emoji-picker" 246 + onEmojiClick={(emojiData) => setNewIcon(emojiData.emoji)} 247 + autoFocusSearch={false} 248 + width="100%" 249 + height={300} 250 + previewConfig={{ showPreview: false }} 251 + skinTonesDisabled 252 + lazyLoadEmojis 253 + theme={ 254 + theme === "dark" || 255 + (theme === "system" && 256 + window.matchMedia("(prefers-color-scheme: dark)") 257 + .matches) 258 + ? (Theme.DARK as Theme) 259 + : (Theme.LIGHT as Theme) 260 + } 261 + /> 262 + </div> 263 + )} 264 + 207 265 {newIcon && ( 208 - <p className="mt-1 text-xs text-surface-500"> 209 - Selected: {newIcon} 266 + <p className="mt-2 text-sm text-surface-600 dark:text-surface-300 flex items-center gap-2"> 267 + Selected: 268 + <span className="inline-flex items-center justify-center w-8 h-8 bg-surface-100 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700"> 269 + <CollectionIcon 270 + icon={ICON_MAP[newIcon] ? `icon:${newIcon}` : newIcon} 271 + size={18} 272 + /> 273 + </span> 210 274 </p> 211 275 )} 212 276 </div>
+97 -23
web/src/components/modals/EditCollectionModal.tsx
··· 2 2 import { X, Loader2 } from "lucide-react"; 3 3 import CollectionIcon from "../common/CollectionIcon"; 4 4 import { ICON_MAP } from "../common/iconMap"; 5 + import EmojiPicker, { Theme } from "emoji-picker-react"; 5 6 import { updateCollection, type Collection } from "../../api/client"; 7 + import { useStore } from "@nanostores/react"; 8 + import { $theme } from "../../store/theme"; 6 9 7 10 interface EditCollectionModalProps { 8 11 isOpen: boolean; ··· 19 22 }: EditCollectionModalProps) { 20 23 const [name, setName] = useState(collection.name); 21 24 const [description, setDescription] = useState(collection.description || ""); 22 - const [icon, setIcon] = useState(collection.icon?.replace("icon:", "") || ""); 25 + const initialIsIcon = collection.icon?.startsWith("icon:") ?? false; 26 + const initialIconValue = collection.icon?.replace("icon:", "") || ""; 27 + 28 + const [activeTab, setActiveTab] = useState<"icon" | "emoji">( 29 + initialIsIcon || !collection.icon ? "icon" : "emoji", 30 + ); 31 + const [icon, setIcon] = useState(initialIconValue); 23 32 const [loading, setLoading] = useState(false); 24 33 const [error, setError] = useState<string | null>(null); 34 + const theme = useStore($theme); 25 35 26 36 useEffect(() => { 27 37 if (isOpen) { 28 38 setName(collection.name); 29 39 setDescription(collection.description || ""); 40 + 41 + const isIcon = collection.icon?.startsWith("icon:") ?? false; 42 + setActiveTab(isIcon || !collection.icon ? "icon" : "emoji"); 30 43 setIcon(collection.icon?.replace("icon:", "") || ""); 44 + 31 45 setError(null); 32 46 document.body.style.overflow = "hidden"; 33 47 } ··· 43 57 try { 44 58 setLoading(true); 45 59 setError(null); 46 - const iconValue = icon ? `icon:${icon}` : undefined; 60 + const iconValue = icon 61 + ? ICON_MAP[icon] 62 + ? `icon:${icon}` 63 + : icon 64 + : undefined; 47 65 const updated = await updateCollection( 48 66 collection.uri, 49 67 name.trim(), ··· 121 139 <label className="block text-sm font-medium text-surface-700 dark:text-surface-300 mb-2"> 122 140 Icon 123 141 </label> 124 - <div className="grid grid-cols-8 gap-1.5 max-h-32 overflow-y-auto p-2 bg-surface-50 dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700"> 125 - {Object.keys(ICON_MAP).map((iconName) => { 126 - const isSelected = icon === iconName; 127 - return ( 128 - <button 129 - key={iconName} 130 - type="button" 131 - onClick={() => setIcon(isSelected ? "" : iconName)} 132 - className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all ${ 133 - isSelected 134 - ? "bg-primary-600 text-white" 135 - : "hover:bg-surface-200 dark:hover:bg-surface-700 text-surface-600 dark:text-surface-400" 136 - }`} 137 - title={iconName} 138 - > 139 - <CollectionIcon icon={`icon:${iconName}`} size={16} /> 140 - </button> 141 - ); 142 - })} 142 + 143 + <div className="flex gap-2 mb-3 bg-surface-100 dark:bg-surface-800 p-1 rounded-xl"> 144 + <button 145 + type="button" 146 + onClick={() => setActiveTab("icon")} 147 + className={`flex-1 py-1.5 text-sm font-medium rounded-lg transition-colors ${ 148 + activeTab === "icon" 149 + ? "bg-white dark:bg-surface-700 text-surface-900 dark:text-white shadow-sm" 150 + : "text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-200" 151 + }`} 152 + > 153 + Icons 154 + </button> 155 + <button 156 + type="button" 157 + onClick={() => setActiveTab("emoji")} 158 + className={`flex-1 py-1.5 text-sm font-medium rounded-lg transition-colors ${ 159 + activeTab === "emoji" 160 + ? "bg-white dark:bg-surface-700 text-surface-900 dark:text-white shadow-sm" 161 + : "text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-200" 162 + }`} 163 + > 164 + Emojis 165 + </button> 143 166 </div> 167 + 168 + {activeTab === "icon" ? ( 169 + <div className="grid grid-cols-8 gap-1.5 max-h-60 overflow-y-auto p-2 bg-surface-50 dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 custom-scrollbar"> 170 + {Object.keys(ICON_MAP).map((iconName) => { 171 + const isSelected = icon === iconName; 172 + return ( 173 + <button 174 + key={iconName} 175 + type="button" 176 + onClick={() => setIcon(isSelected ? "" : iconName)} 177 + className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all ${ 178 + isSelected 179 + ? "bg-primary-600 text-white" 180 + : "hover:bg-surface-200 dark:hover:bg-surface-700 text-surface-600 dark:text-surface-400" 181 + }`} 182 + title={iconName} 183 + > 184 + <CollectionIcon icon={`icon:${iconName}`} size={16} /> 185 + </button> 186 + ); 187 + })} 188 + </div> 189 + ) : ( 190 + <div className="w-full bg-surface-50 dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 overflow-hidden"> 191 + <EmojiPicker 192 + className="custom-emoji-picker" 193 + onEmojiClick={(emojiData) => setIcon(emojiData.emoji)} 194 + autoFocusSearch={false} 195 + width="100%" 196 + height={300} 197 + previewConfig={{ showPreview: false }} 198 + skinTonesDisabled 199 + lazyLoadEmojis 200 + theme={ 201 + theme === "dark" || 202 + (theme === "system" && 203 + window.matchMedia("(prefers-color-scheme: dark)") 204 + .matches) 205 + ? (Theme.DARK as Theme) 206 + : (Theme.LIGHT as Theme) 207 + } 208 + /> 209 + </div> 210 + )} 211 + 144 212 {icon && ( 145 - <p className="mt-1 text-xs text-surface-500"> 146 - Selected: {icon} 213 + <p className="mt-2 text-sm text-surface-600 dark:text-surface-300 flex items-center gap-2"> 214 + Selected: 215 + <span className="inline-flex items-center justify-center w-8 h-8 bg-surface-100 dark:bg-surface-800 rounded-lg border border-surface-200 dark:border-surface-700"> 216 + <CollectionIcon 217 + icon={ICON_MAP[icon] ? `icon:${icon}` : icon} 218 + size={18} 219 + /> 220 + </span> 147 221 </p> 148 222 )} 149 223 </div>
+63
web/src/styles/global.css
··· 71 71 .custom-scrollbar::-webkit-scrollbar-thumb { 72 72 @apply bg-surface-300 dark:bg-surface-700 rounded-full hover:bg-surface-400 dark:hover:bg-surface-600 transition-colors; 73 73 } 74 + 75 + .custom-emoji-picker { 76 + --epr-bg-color: transparent !important; 77 + --epr-category-label-bg-color: transparent !important; 78 + --epr-text-color: theme("colors.surface.900") !important; 79 + --epr-picker-border-color: theme("colors.surface.200") !important; 80 + 81 + --epr-search-input-bg-color: theme("colors.surface.50") !important; 82 + --epr-search-input-text-color: theme("colors.surface.900") !important; 83 + 84 + --epr-hover-bg-color: theme("colors.surface.200") !important; 85 + --epr-focus-bg-color: theme("colors.primary.100") !important; 86 + --epr-highlight-color: theme("colors.primary.500") !important; 87 + 88 + font-family: inherit !important; 89 + border: none !important; 90 + } 91 + 92 + [data-theme="dark"] .custom-emoji-picker { 93 + --epr-text-color: theme("colors.white") !important; 94 + --epr-picker-border-color: theme("colors.surface.700") !important; 95 + 96 + --epr-search-input-bg-color: theme("colors.surface.800") !important; 97 + --epr-search-input-text-color: theme("colors.white") !important; 98 + 99 + --epr-hover-bg-color: theme("colors.surface.700") !important; 100 + --epr-focus-bg-color: theme("colors.primary.900") !important; 101 + } 102 + 103 + .custom-emoji-picker .epr-search-container input { 104 + border-radius: 0.5rem !important; 105 + border: 1px solid theme("colors.surface.200") !important; 106 + background-color: theme("colors.surface.50") !important; 107 + } 108 + 109 + [data-theme="dark"] .custom-emoji-picker .epr-search-container input { 110 + border: 1px solid theme("colors.surface.700") !important; 111 + background-color: theme("colors.surface.800") !important; 112 + } 113 + 114 + .custom-emoji-picker .epr-header-overlay { 115 + padding: 0 !important; 116 + } 117 + 118 + .custom-emoji-picker .epr-emoji-category-label { 119 + position: static !important; 120 + background-color: transparent !important; 121 + height: 32px !important; 122 + line-height: 32px !important; 123 + } 124 + 125 + .custom-emoji-picker .epr-body::-webkit-scrollbar { 126 + width: 6px; 127 + height: 6px; 128 + } 129 + 130 + .custom-emoji-picker .epr-body::-webkit-scrollbar-track { 131 + @apply bg-transparent; 132 + } 133 + 134 + .custom-emoji-picker .epr-body::-webkit-scrollbar-thumb { 135 + @apply bg-surface-300 dark:bg-surface-700 rounded-full hover:bg-surface-400 dark:hover:bg-surface-600 transition-colors; 136 + } 74 137 } 75 138 76 139 @keyframes fadeIn {
+80 -20
web/src/views/collections/Collections.tsx
··· 9 9 import { ICON_MAP } from "../../components/common/iconMap"; 10 10 import { useStore } from "@nanostores/react"; 11 11 import { $user } from "../../store/auth"; 12 + import EmojiPicker, { Theme } from "emoji-picker-react"; 13 + import { $theme } from "../../store/theme"; 12 14 import type { Collection } from "../../types"; 13 15 import { formatDistanceToNow } from "date-fns"; 14 16 import { clsx } from "clsx"; ··· 16 18 17 19 export default function Collections() { 18 20 const user = useStore($user); 21 + const theme = useStore($theme); 19 22 const [collections, setCollections] = useState<Collection[]>([]); 20 23 const [loading, setLoading] = useState(true); 21 24 const [showCreateModal, setShowCreateModal] = useState(false); 22 25 const [newItemName, setNewItemName] = useState(""); 23 26 const [newItemDesc, setNewItemDesc] = useState(""); 24 27 const [newItemIcon, setNewItemIcon] = useState("folder"); 28 + const [activeTab, setActiveTab] = useState<"icon" | "emoji">("icon"); 25 29 const [creating, setCreating] = useState(false); 26 30 27 31 const fetchCollections = async () => { ··· 45 49 if (!newItemName.trim()) return; 46 50 47 51 setCreating(true); 48 - const res = await createCollection(newItemName, newItemDesc); 52 + const finalIcon = ICON_MAP[newItemIcon] 53 + ? `icon:${newItemIcon}` 54 + : newItemIcon; 55 + 56 + const res = await createCollection(newItemName, newItemDesc, finalIcon); 49 57 if (res) { 50 58 setCollections([res, ...collections]); 51 59 setShowCreateModal(false); 52 60 setNewItemName(""); 53 61 setNewItemDesc(""); 54 62 setNewItemIcon("folder"); 63 + setActiveTab("icon"); 55 64 fetchCollections(); 56 65 } 57 66 setCreating(false); ··· 187 196 <label className="block text-sm font-medium text-surface-700 dark:text-surface-300 mb-2"> 188 197 Icon 189 198 </label> 190 - <div className="grid grid-cols-7 gap-1.5 p-3 bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 rounded-xl max-h-28 overflow-y-auto"> 191 - {Object.keys(ICON_MAP).map((key) => { 192 - const Icon = ICON_MAP[key]; 193 - return ( 194 - <button 195 - key={key} 196 - type="button" 197 - onClick={() => setNewItemIcon(key)} 198 - className={clsx( 199 - "p-2 rounded-lg flex items-center justify-center transition-all", 200 - newItemIcon === key 201 - ? "bg-primary-100 dark:bg-primary-900/50 text-primary-600 dark:text-primary-400 ring-2 ring-primary-500" 202 - : "hover:bg-surface-100 dark:hover:bg-surface-700 text-surface-500 dark:text-surface-400", 203 - )} 204 - > 205 - <Icon size={18} /> 206 - </button> 207 - ); 208 - })} 199 + 200 + <div className="flex gap-2 mb-3 bg-surface-100 dark:bg-surface-800 p-1 rounded-xl"> 201 + <button 202 + type="button" 203 + onClick={() => setActiveTab("icon")} 204 + className={`flex-1 py-1.5 text-sm font-medium rounded-lg transition-colors ${ 205 + activeTab === "icon" 206 + ? "bg-white dark:bg-surface-700 text-surface-900 dark:text-white shadow-sm" 207 + : "text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-200" 208 + }`} 209 + > 210 + Icons 211 + </button> 212 + <button 213 + type="button" 214 + onClick={() => setActiveTab("emoji")} 215 + className={`flex-1 py-1.5 text-sm font-medium rounded-lg transition-colors ${ 216 + activeTab === "emoji" 217 + ? "bg-white dark:bg-surface-700 text-surface-900 dark:text-white shadow-sm" 218 + : "text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-200" 219 + }`} 220 + > 221 + Emojis 222 + </button> 209 223 </div> 224 + 225 + {activeTab === "icon" ? ( 226 + <div className="grid grid-cols-7 gap-1.5 p-3 bg-surface-50 dark:bg-surface-800 border border-surface-200 dark:border-surface-700 rounded-xl max-h-48 overflow-y-auto custom-scrollbar"> 227 + {Object.keys(ICON_MAP).map((key) => { 228 + const Icon = ICON_MAP[key]; 229 + return ( 230 + <button 231 + key={key} 232 + type="button" 233 + onClick={() => setNewItemIcon(key)} 234 + className={clsx( 235 + "p-2 rounded-lg flex items-center justify-center transition-all", 236 + newItemIcon === key 237 + ? "bg-primary-100 dark:bg-primary-900/50 text-primary-600 dark:text-primary-400 ring-2 ring-primary-500" 238 + : "hover:bg-surface-100 dark:hover:bg-surface-700 text-surface-500 dark:text-surface-400", 239 + )} 240 + > 241 + <Icon size={18} /> 242 + </button> 243 + ); 244 + })} 245 + </div> 246 + ) : ( 247 + <div className="w-full bg-surface-50 dark:bg-surface-800 rounded-xl border border-surface-200 dark:border-surface-700 overflow-hidden"> 248 + <EmojiPicker 249 + className="custom-emoji-picker" 250 + onEmojiClick={(emojiData) => 251 + setNewItemIcon(emojiData.emoji) 252 + } 253 + autoFocusSearch={false} 254 + width="100%" 255 + height={300} 256 + previewConfig={{ showPreview: false }} 257 + skinTonesDisabled 258 + lazyLoadEmojis 259 + theme={ 260 + theme === "dark" || 261 + (theme === "system" && 262 + window.matchMedia("(prefers-color-scheme: dark)") 263 + .matches) 264 + ? (Theme.DARK as Theme) 265 + : (Theme.LIGHT as Theme) 266 + } 267 + /> 268 + </div> 269 + )} 210 270 </div> 211 271 <div className="mb-6"> 212 272 <label className="block text-sm font-medium text-surface-700 dark:text-surface-300 mb-2">