a tool for shared writing and social publishing
1import { pickers } from "../ThemeSetter";
2import { theme } from "tailwind.config";
3import { PageBackgroundColorPicker } from "../Pickers/PageThemePickers";
4import { Color, ColorSwatch } from "react-aria-components";
5import { BlockImageSmall } from "components/Icons/BlockImageSmall";
6import { ColorPicker } from "../Pickers/ColorPicker";
7import { CloseContrastSmall } from "components/Icons/CloseContrastSmall";
8import * as Slider from "@radix-ui/react-slider";
9import { Toggle } from "components/Toggle";
10import { DeleteSmall } from "components/Icons/DeleteSmall";
11import { ImageState } from "../PubThemeSetter";
12import { Radio } from "components/Checkbox";
13import { Input } from "components/Input";
14
15export const BackgroundPicker = (props: {
16 backgroundColor: Color;
17 setBackgroundColor: (c: Color) => void;
18 pageBackground: Color;
19 setPageBackground: (c: Color) => void;
20 openPicker: pickers;
21 setOpenPicker: (p: pickers) => void;
22 bgImage: ImageState | null;
23 setBgImage: (i: ImageState | null) => void;
24 hasPageBackground: boolean;
25 setHasPageBackground: (s: boolean) => void;
26}) => {
27 return (
28 <>
29 {props.bgImage && props.bgImage !== null ? (
30 <BackgroundImagePicker
31 bgColor={props.backgroundColor}
32 bgImage={props.bgImage}
33 setBgImage={props.setBgImage}
34 thisPicker={"page-background-image"}
35 openPicker={props.openPicker}
36 setOpenPicker={props.setOpenPicker}
37 closePicker={() => props.setOpenPicker("null")}
38 setValue={props.setBackgroundColor}
39 />
40 ) : (
41 <div className="relative">
42 <ColorPicker
43 label={"Background"}
44 value={props.backgroundColor}
45 setValue={props.setBackgroundColor}
46 thisPicker={"leaflet"}
47 openPicker={props.openPicker}
48 setOpenPicker={props.setOpenPicker}
49 closePicker={() => props.setOpenPicker("null")}
50 alpha={!!props.bgImage}
51 />
52 {!props.bgImage && (
53 <label
54 className={`
55 text-[#969696] hover:cursor-pointer shrink-0
56 absolute top-0 right-0
57 `}
58 >
59 <BlockImageSmall />
60 <div className="hidden">
61 <input
62 type="file"
63 accept="image/*"
64 hidden
65 onChange={async (e) => {
66 let file = e.currentTarget.files?.[0];
67 if (file) {
68 const reader = new FileReader();
69 reader.onload = (e) => {
70 props.setBgImage({
71 src: e.target?.result as string,
72 file,
73 repeat: null,
74 });
75 props.setOpenPicker("page-background-image");
76 };
77 reader.readAsDataURL(file);
78 }
79 }}
80 />
81 </div>
82 </label>
83 )}
84 </div>
85 )}
86 <PageBackgroundColorPicker
87 label={"Containers"}
88 value={props.pageBackground}
89 setValue={props.setPageBackground}
90 thisPicker={"page"}
91 openPicker={props.openPicker}
92 setOpenPicker={props.setOpenPicker}
93 alpha={props.hasPageBackground ? true : false}
94 />
95 <hr className="border-border-light" />
96 <div className="flex gap-2 items-center">
97 <Toggle
98 toggleOn={props.hasPageBackground}
99 setToggleOn={() => {
100 props.setHasPageBackground(!props.hasPageBackground);
101 props.hasPageBackground &&
102 props.openPicker === "page" &&
103 props.setOpenPicker("null");
104 }}
105 disabledColor1="#8C8C8C"
106 disabledColor2="#DBDBDB"
107 />
108 <button
109 className="flex gap-2 items-center"
110 onClick={() => {
111 props.setHasPageBackground(!props.hasPageBackground);
112 props.hasPageBackground && props.setOpenPicker("null");
113 }}
114 >
115 <div className="font-bold">Page Background</div>
116 <div className="italic text-[#8C8C8C]">
117 {props.hasPageBackground ? "" : "hidden"}
118 </div>
119 </button>
120 </div>
121 </>
122 );
123};
124
125const BackgroundImagePicker = (props: {
126 disabled?: boolean;
127 bgImage: ImageState | null;
128 setBgImage: (i: ImageState | null) => void;
129 bgColor: Color;
130 openPicker: pickers;
131 thisPicker: pickers;
132 setOpenPicker: (thisPicker: pickers) => void;
133 closePicker: () => void;
134 setValue: (c: Color) => void;
135}) => {
136 let open = props.openPicker == props.thisPicker;
137
138 return (
139 <>
140 <div className="bgPickerColorLabel flex gap-2 items-center">
141 <button
142 disabled={props.disabled}
143 onClick={() => {
144 if (props.openPicker === props.thisPicker) {
145 props.setOpenPicker("null");
146 } else {
147 props.setOpenPicker(props.thisPicker);
148 }
149 }}
150 className="flex gap-2 items-center disabled:text-tertiary grow"
151 >
152 <ColorSwatch
153 color={props.bgColor}
154 className={`w-6 h-6 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C] ${props.disabled ? "opacity-50" : ""}`}
155 style={{
156 backgroundImage: props.bgImage
157 ? `url(${props.bgImage.src})`
158 : undefined,
159 backgroundPosition: "center",
160 backgroundSize: "cover",
161 }}
162 />
163 <strong className={` text-[#595959]`}>Background</strong>
164 <div className="italic text-[#8C8C8C]">image</div>
165 </button>
166 <div className="flex gap-1 text-[#8C8C8C]">
167 <button onClick={() => props.setBgImage(null)}>
168 <DeleteSmall />
169 </button>
170 <label className="hover:cursor-pointer ">
171 <BlockImageSmall />
172 <div className="hidden">
173 <input
174 type="file"
175 accept="image/*"
176 hidden
177 onChange={async (e) => {
178 let file = e.currentTarget.files?.[0];
179 if (file) {
180 const reader = new FileReader();
181 reader.onload = (e) => {
182 if (!props.bgImage) return;
183 props.setBgImage({
184 ...props.bgImage,
185 src: e.target?.result as string,
186 file,
187 });
188 };
189 reader.readAsDataURL(file);
190 }
191 }}
192 />
193 </div>
194 </label>
195 </div>
196 </div>
197 {open && (
198 <div className="pageImagePicker flex flex-col gap-2">
199 <ImageSettings
200 bgImage={props.bgImage}
201 setBgImage={props.setBgImage}
202 />
203 </div>
204 )}
205 </>
206 );
207};
208
209export const ImageSettings = (props: {
210 bgImage: ImageState | null;
211 setBgImage: (i: ImageState | null) => void;
212}) => {
213 return (
214 <>
215 <div className="themeBGImageControls font-bold flex flex-col gap-1 items-center px-3">
216 <label htmlFor="cover" className="w-full">
217 <Radio
218 radioCheckedClassName="text-[#595959]!"
219 radioEmptyClassName="text-[#969696]!"
220 type="radio"
221 id="cover"
222 name="bg-image-options"
223 value="cover"
224 checked={!props.bgImage?.repeat}
225 onChange={async (e) => {
226 if (!e.currentTarget.checked) return;
227 if (!props.bgImage) return;
228 props.setBgImage({ ...props.bgImage, repeat: null });
229 }}
230 >
231 <div
232 className={`w-full cursor-pointer ${!props.bgImage?.repeat ? "text-[#595959]" : " text-[#969696]"}`}
233 >
234 cover
235 </div>
236 </Radio>
237 </label>
238 <label htmlFor="repeat" className="pb-3 w-full">
239 <Radio
240 type="radio"
241 id="repeat"
242 name="bg-image-options"
243 value="repeat"
244 radioCheckedClassName="text-[#595959]!"
245 radioEmptyClassName="text-[#969696]!"
246 checked={!!props.bgImage?.repeat}
247 onChange={async (e) => {
248 if (!e.currentTarget.checked) return;
249 if (!props.bgImage) return;
250 props.setBgImage({ ...props.bgImage, repeat: 500 });
251 }}
252 >
253 <div className="flex flex-col gap-2 w-full">
254 <div className="flex gap-2">
255 <div
256 className={`shink-0 grow-0 w-fit z-10 cursor-pointer ${props.bgImage?.repeat ? "text-[#595959]" : " text-[#969696]"}`}
257 >
258 repeat
259 </div>
260 <div
261 className={`flex font-normal ${props.bgImage?.repeat ? "text-[#969696]" : " text-[#C3C3C3]"}`}
262 >
263 <Input
264 type="number"
265 className="w-10 text-right appearance-none"
266 max={3000}
267 min={10}
268 value={props.bgImage?.repeat || 500}
269 onChange={(e) => {
270 if (!props.bgImage) return;
271 props.setBgImage({
272 ...props.bgImage,
273 repeat: parseInt(e.currentTarget.value),
274 });
275 }}
276 />{" "}
277 px
278 </div>
279 </div>
280 <Slider.Root
281 className={`relative grow flex items-center select-none touch-none w-full h-fit px-1 `}
282 value={[props.bgImage?.repeat || 500]}
283 max={3000}
284 min={10}
285 step={10}
286 onValueChange={(value) => {
287 if (!props.bgImage) return;
288 props.setBgImage({ ...props.bgImage, repeat: value[0] });
289 }}
290 >
291 <Slider.Track
292 className={`${props.bgImage?.repeat ? "bg-[#595959]" : " bg-[#C3C3C3]"} relative grow rounded-full h-[3px]`}
293 ></Slider.Track>
294 <Slider.Thumb
295 className={`
296 flex w-4 h-4 rounded-full border-2 border-white cursor-pointer
297 ${props.bgImage?.repeat ? "bg-[#595959]" : " bg-[#C3C3C3] "}
298 ${props.bgImage?.repeat && "shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]"} `}
299 aria-label="Volume"
300 />
301 </Slider.Root>
302 </div>
303 </Radio>
304 </label>
305 </div>
306 </>
307 );
308};