a tool for shared writing and social publishing
at main 179 lines 5.3 kB view raw
1"use client"; 2 3import { Input } from "components/Input"; 4import { useState } from "react"; 5import { Menu } from "components/Menu"; 6import * as DropdownMenu from "@radix-ui/react-dropdown-menu"; 7import { useIsMobile } from "src/hooks/isMobile"; 8import { 9 fonts, 10 defaultFontId, 11 FontConfig, 12 isCustomFontId, 13 parseGoogleFontInput, 14 createCustomFontId, 15 getFontConfig, 16} from "src/fonts"; 17 18export const FontPicker = (props: { 19 label: string; 20 value: string | undefined; 21 onChange: (fontId: string) => void; 22}) => { 23 let isMobile = useIsMobile(); 24 let [showCustomInput, setShowCustomInput] = useState(false); 25 let [customFontValue, setCustomFontValue] = useState(""); 26 let fontId = props.value || defaultFontId; 27 let font = getFontConfig(fontId); 28 let isCustom = isCustomFontId(fontId); 29 30 let fontList = Object.values(fonts).sort((a, b) => 31 a.displayName.localeCompare(b.displayName), 32 ); 33 34 const handleCustomSubmit = () => { 35 const parsed = parseGoogleFontInput(customFontValue); 36 if (parsed) { 37 const customId = createCustomFontId( 38 parsed.fontName, 39 parsed.googleFontsFamily, 40 ); 41 props.onChange(customId); 42 setShowCustomInput(false); 43 setCustomFontValue(""); 44 } 45 }; 46 47 return ( 48 <Menu 49 asChild 50 trigger={ 51 <button className="flex gap-2 items-center w-full !outline-none min-w-0"> 52 <div 53 className={`w-6 h-6 rounded-md border border-border relative text-sm bg-bg-page shrink-0 ${props.label === "Heading" ? "font-bold" : "text-secondary"}`} 54 > 55 <div className="absolute top-1/2 left-1/2 -translate-y-1/2 -translate-x-1/2 "> 56 Aa 57 </div> 58 </div> 59 <div className="font-bold shrink-0">{props.label}</div> 60 <div className="truncate">{font.displayName}</div> 61 </button> 62 } 63 side={isMobile ? "bottom" : "right"} 64 align="start" 65 className="w-[250px] !gap-0 !outline-none max-h-72 " 66 > 67 {showCustomInput ? ( 68 <div className="p-2 flex flex-col gap-2"> 69 <div className="text-sm text-secondary"> 70 Paste a Google Font name 71 </div> 72 <Input 73 value={customFontValue} 74 className="w-full" 75 placeholder="e.g. Roboto, Open Sans, Playfair Display" 76 autoFocus 77 onChange={(e) => setCustomFontValue(e.currentTarget.value)} 78 onKeyDown={(e) => { 79 if (e.key === "Enter") { 80 e.preventDefault(); 81 handleCustomSubmit(); 82 } else if (e.key === "Escape") { 83 setShowCustomInput(false); 84 setCustomFontValue(""); 85 } 86 }} 87 /> 88 <div className="flex gap-2"> 89 <button 90 className="flex-1 px-2 py-1 text-sm rounded-md bg-accent-1 text-accent-2 hover:opacity-80" 91 onClick={handleCustomSubmit} 92 > 93 Add Font 94 </button> 95 <button 96 className="px-2 py-1 text-sm rounded-md text-secondary hover:bg-border-light" 97 onClick={() => { 98 setShowCustomInput(false); 99 setCustomFontValue(""); 100 }} 101 > 102 Cancel 103 </button> 104 </div> 105 </div> 106 ) : ( 107 <div className="flex flex-col h-full overflow-auto gap-0 py-1"> 108 {fontList.map((fontOption) => { 109 return ( 110 <FontOption 111 key={fontOption.id} 112 onSelect={() => { 113 props.onChange(fontOption.id); 114 }} 115 font={fontOption} 116 selected={fontOption.id === fontId} 117 /> 118 ); 119 })} 120 {isCustom && ( 121 <FontOption 122 key={fontId} 123 onSelect={() => {}} 124 font={font} 125 selected={true} 126 /> 127 )} 128 <hr className="mx-2 my-1 border-border" /> 129 <DropdownMenu.Item 130 onSelect={(e) => { 131 e.preventDefault(); 132 setShowCustomInput(true); 133 }} 134 className={` 135 fontOption 136 z-10 px-1 py-0.5 137 text-left text-secondary 138 data-[highlighted]:bg-border-light data-[highlighted]:text-secondary 139 hover:bg-border-light hover:text-secondary 140 outline-none 141 cursor-pointer 142 `} 143 > 144 <div className="px-2 py-0 rounded-md">Custom Google Font...</div> 145 </DropdownMenu.Item> 146 </div> 147 )} 148 </Menu> 149 ); 150}; 151 152const FontOption = (props: { 153 onSelect: () => void; 154 font: FontConfig; 155 selected: boolean; 156}) => { 157 return ( 158 <DropdownMenu.RadioItem 159 value={props.font.id} 160 onSelect={props.onSelect} 161 className={` 162 fontOption 163 z-10 px-1 py-0.5 164 text-left text-secondary 165 data-[highlighted]:bg-border-light data-[highlighted]:text-secondary 166 hover:bg-border-light hover:text-secondary 167 outline-none 168 cursor-pointer 169 170 `} 171 > 172 <div 173 className={`px-2 py-0 rounded-md ${props.selected && "bg-accent-1 text-accent-2"}`} 174 > 175 {props.font.displayName} 176 </div> 177 </DropdownMenu.RadioItem> 178 ); 179};