a tool for shared writing and social publishing
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};