a tool for shared writing and social publishing
at update/reader 215 lines 8.2 kB view raw
1import * as Slider from "@radix-ui/react-slider"; 2import { Input } from "components/Input"; 3import { Radio } from "components/Checkbox"; 4import { useEntity, useReplicache } from "src/replicache"; 5import { pickers } from "../ThemeSetter"; 6import { useState, useEffect } from "react"; 7 8export const PageWidthSetter = (props: { 9 entityID: string; 10 openPicker: pickers; 11 thisPicker: pickers; 12 setOpenPicker: (thisPicker: pickers) => void; 13 closePicker: () => void; 14}) => { 15 let { rep } = useReplicache(); 16 17 let defaultPreset = 624; 18 let widePreset = 768; 19 let pageWidth = useEntity(props.entityID, "theme/page-width")?.data.value; 20 let currentValue = pageWidth || defaultPreset; 21 let [interimValue, setInterimValue] = useState<number>(currentValue); 22 let [selectedPreset, setSelectedPreset] = useState< 23 "default" | "wide" | "custom" 24 >( 25 currentValue === defaultPreset 26 ? "default" 27 : currentValue === widePreset 28 ? "wide" 29 : "custom", 30 ); 31 let min = 320; 32 let max = 1200; 33 34 let open = props.openPicker == props.thisPicker; 35 36 // Update interim value when current value changes 37 useEffect(() => { 38 setInterimValue(currentValue); 39 }, [currentValue]); 40 41 const setPageWidth = (value: number) => { 42 rep?.mutate.assertFact({ 43 entity: props.entityID, 44 attribute: "theme/page-width", 45 data: { 46 type: "number", 47 value: value, 48 }, 49 }); 50 }; 51 52 return ( 53 <div className="pageWidthSetter flex flex-col gap-2 px-2 py-[6px] border border-[#CCCCCC] rounded-md"> 54 <div className="flex flex-col gap-2"> 55 <div className="flex gap-2 items-center"> 56 <button 57 className="font-bold text-[#000000] shrink-0 grow-0 w-full flex gap-2 items-start text-left" 58 onClick={() => { 59 if (props.openPicker === props.thisPicker) { 60 props.setOpenPicker("null"); 61 } else { 62 props.setOpenPicker(props.thisPicker); 63 } 64 }} 65 > 66 Max Page Width 67 <span className="flex font-normal text-[#969696]"> 68 {currentValue}px 69 </span> 70 </button> 71 </div> 72 {open && ( 73 <div className="flex flex-col gap-1 px-3"> 74 <label htmlFor="default" className="w-full"> 75 <Radio 76 radioCheckedClassName="text-[#595959]!" 77 radioEmptyClassName="text-[#969696]!" 78 type="radio" 79 id="default" 80 name="page-width-options" 81 value="default" 82 checked={selectedPreset === "default"} 83 onChange={(e) => { 84 if (!e.currentTarget.checked) return; 85 setSelectedPreset("default"); 86 setPageWidth(defaultPreset); 87 }} 88 > 89 <div 90 className={`w-full cursor-pointer ${selectedPreset === "default" ? "text-[#595959]" : "text-[#969696]"}`} 91 > 92 default ({defaultPreset}px) 93 </div> 94 </Radio> 95 </label> 96 <label htmlFor="wide" className="w-full"> 97 <Radio 98 radioCheckedClassName="text-[#595959]!" 99 radioEmptyClassName="text-[#969696]!" 100 type="radio" 101 id="wide" 102 name="page-width-options" 103 value="wide" 104 checked={selectedPreset === "wide"} 105 onChange={(e) => { 106 if (!e.currentTarget.checked) return; 107 setSelectedPreset("wide"); 108 setPageWidth(widePreset); 109 }} 110 > 111 <div 112 className={`w-full cursor-pointer ${selectedPreset === "wide" ? "text-[#595959]" : "text-[#969696]"}`} 113 > 114 wide ({widePreset}px) 115 </div> 116 </Radio> 117 </label> 118 <label htmlFor="custom" className="pb-3 w-full"> 119 <Radio 120 type="radio" 121 id="custom" 122 name="page-width-options" 123 value="custom" 124 radioCheckedClassName="text-[#595959]!" 125 radioEmptyClassName="text-[#969696]!" 126 checked={selectedPreset === "custom"} 127 onChange={(e) => { 128 if (!e.currentTarget.checked) return; 129 setSelectedPreset("custom"); 130 if (selectedPreset !== "custom") { 131 setPageWidth(currentValue); 132 setInterimValue(currentValue); 133 } 134 }} 135 > 136 <div className="flex flex-col w-full"> 137 <div className="flex gap-2"> 138 <div 139 className={`shrink-0 grow-0 w-fit z-10 cursor-pointer ${selectedPreset === "custom" ? "text-[#595959]" : "text-[#969696]"}`} 140 > 141 custom 142 </div> 143 <div 144 className={`flex font-normal ${selectedPreset === "custom" ? "text-[#969696]" : "text-[#C3C3C3]"}`} 145 > 146 <Input 147 type="number" 148 className="w-10 text-right appearance-none bg-transparent" 149 max={max} 150 min={min} 151 value={interimValue} 152 onChange={(e) => { 153 setInterimValue(parseInt(e.currentTarget.value)); 154 }} 155 onKeyDown={(e) => { 156 if (e.key === "Enter" || e.key === "Escape") { 157 e.preventDefault(); 158 let clampedValue = interimValue; 159 if (!isNaN(interimValue)) { 160 clampedValue = Math.max( 161 min, 162 Math.min(max, interimValue), 163 ); 164 setInterimValue(clampedValue); 165 } 166 setPageWidth(clampedValue); 167 } 168 }} 169 onBlur={() => { 170 let clampedValue = interimValue; 171 if (!isNaN(interimValue)) { 172 clampedValue = Math.max( 173 min, 174 Math.min(max, interimValue), 175 ); 176 setInterimValue(clampedValue); 177 } 178 setPageWidth(clampedValue); 179 }} 180 /> 181 px 182 </div> 183 </div> 184 <Slider.Root 185 className={`relative grow flex items-center select-none touch-none w-full h-fit px-1`} 186 value={[interimValue]} 187 max={max} 188 min={min} 189 step={16} 190 onValueChange={(value) => { 191 setInterimValue(value[0]); 192 }} 193 onValueCommit={(value) => { 194 setPageWidth(value[0]); 195 }} 196 > 197 <Slider.Track 198 className={`${selectedPreset === "custom" ? "bg-[#595959]" : "bg-[#C3C3C3]"} relative grow rounded-full h-[3px] my-2`} 199 /> 200 <Slider.Thumb 201 className={`flex w-4 h-4 rounded-full border-2 border-white cursor-pointer 202 ${selectedPreset === "custom" ? "bg-[#595959] shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]" : "bg-[#C3C3C3]"} 203 `} 204 aria-label="Max Page Width" 205 /> 206 </Slider.Root> 207 </div> 208 </Radio> 209 </label> 210 </div> 211 )} 212 </div> 213 </div> 214 ); 215};