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