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