a tool for shared writing and social publishing
at main 749 lines 25 kB view raw
1"use client"; 2 3import { 4 ColorPicker as SpectrumColorPicker, 5 parseColor, 6 Color, 7 ColorThumb, 8 ColorSlider, 9 Input, 10 ColorField, 11 SliderTrack, 12 ColorSwatch, 13} from "react-aria-components"; 14import { Checkbox } from "components/Checkbox"; 15import { useMemo, useState } from "react"; 16import { ReplicacheMutators, useEntity, useReplicache } from "src/replicache"; 17import { useColorAttribute } from "components/ThemeManager/useColorAttribute"; 18import { Separator } from "components/Layout"; 19import { onMouseDown } from "src/utils/iosInputMouseDown"; 20import { pickers, setColorAttribute } from "../ThemeSetter"; 21import { ImageInput, ImageSettings } from "./ImagePicker"; 22 23import { ColorPicker, thumbStyle } from "./ColorPicker"; 24import { FontPicker } from "./TextPickers"; 25import { BlockImageSmall } from "components/Icons/BlockImageSmall"; 26import { Replicache } from "replicache"; 27import { CanvasBackgroundPattern } from "components/Canvas"; 28import { Toggle } from "components/Toggle"; 29import { DeleteSmall } from "components/Icons/DeleteSmall"; 30 31export const PageThemePickers = (props: { 32 entityID: string; 33 openPicker: pickers; 34 setOpenPicker: (thisPicker: pickers) => void; 35 home?: boolean; 36 hideFonts?: boolean; 37}) => { 38 let { rep } = useReplicache(); 39 let set = useMemo(() => { 40 return setColorAttribute(rep, props.entityID); 41 }, [rep, props.entityID]); 42 43 let pageType = useEntity(props.entityID, "page/type")?.data.value || "doc"; 44 let primaryValue = useColorAttribute(props.entityID, "theme/primary"); 45 let headingFontId = useEntity(props.entityID, "theme/heading-font")?.data.value; 46 let bodyFontId = useEntity(props.entityID, "theme/body-font")?.data.value; 47 48 return ( 49 <div 50 className="pageThemeBG flex flex-col gap-2 h-full text-primary bg-bg-leaflet p-2 rounded-md border border-primary shadow-[0_0_0_1px_rgb(var(--bg-page))]" 51 style={{ backgroundColor: "rgba(var(--bg-page), 0.6)" }} 52 > 53 {pageType === "canvas" && ( 54 <> 55 <CanvasBGPatternPicker entityID={props.entityID} rep={rep} />{" "} 56 <hr className="border-border-light w-full" /> 57 </> 58 )} 59 <TextPickers 60 value={primaryValue} 61 setValue={set("theme/primary")} 62 openPicker={props.openPicker} 63 setOpenPicker={props.setOpenPicker} 64 /> 65 {!props.home && !props.hideFonts && ( 66 <> 67 <FontPicker 68 label="Heading" 69 value={headingFontId} 70 onChange={(fontId) => rep?.mutate.assertFact({ entity: props.entityID, attribute: "theme/heading-font", data: { type: "string", value: fontId } })} 71 /> 72 <FontPicker 73 label="Body" 74 value={bodyFontId} 75 onChange={(fontId) => rep?.mutate.assertFact({ entity: props.entityID, attribute: "theme/body-font", data: { type: "string", value: fontId } })} 76 /> 77 </> 78 )} 79 </div> 80 ); 81}; 82 83// Page background picker for subpages - shows Page/Containers color with optional background image 84export const SubpageBackgroundPicker = (props: { 85 entityID: string; 86 openPicker: pickers; 87 setOpenPicker: (p: pickers) => void; 88}) => { 89 let { rep, rootEntity } = useReplicache(); 90 let set = useMemo(() => { 91 return setColorAttribute(rep, props.entityID); 92 }, [rep, props.entityID]); 93 94 let pageValue = useColorAttribute(props.entityID, "theme/card-background"); 95 let pageBGImage = useEntity(props.entityID, "theme/card-background-image"); 96 let rootPageBorderHidden = useEntity(rootEntity, "theme/card-border-hidden"); 97 let entityPageBorderHidden = useEntity( 98 props.entityID, 99 "theme/card-border-hidden", 100 ); 101 let pageBorderHidden = 102 (entityPageBorderHidden || rootPageBorderHidden)?.data.value || false; 103 let hasPageBackground = !pageBorderHidden; 104 105 // Label is "Page" when page background is visible, "Containers" when hidden 106 let label = hasPageBackground ? "Page" : "Containers"; 107 108 // If root page border is hidden, only show color picker (no image support) 109 if (!hasPageBackground) { 110 return ( 111 <ColorPicker 112 label={label} 113 helpText={"Affects menus, tooltips and some block backgrounds"} 114 value={pageValue} 115 setValue={set("theme/card-background")} 116 thisPicker="page" 117 openPicker={props.openPicker} 118 setOpenPicker={props.setOpenPicker} 119 closePicker={() => props.setOpenPicker("null")} 120 alpha 121 /> 122 ); 123 } 124 125 return ( 126 <> 127 {pageBGImage && ( 128 <SubpageBackgroundImagePicker 129 entityID={props.entityID} 130 openPicker={props.openPicker} 131 setOpenPicker={props.setOpenPicker} 132 setValue={set("theme/card-background")} 133 /> 134 )} 135 <div className="relative"> 136 <ColorPicker 137 label={label} 138 value={pageValue} 139 setValue={set("theme/card-background")} 140 thisPicker="page" 141 openPicker={props.openPicker} 142 setOpenPicker={props.setOpenPicker} 143 closePicker={() => props.setOpenPicker("null")} 144 alpha 145 /> 146 {!pageBGImage && ( 147 <label className="text-[#969696] hover:cursor-pointer shrink-0 absolute top-0 right-0"> 148 <BlockImageSmall /> 149 <div className="hidden"> 150 <ImageInput 151 entityID={props.entityID} 152 onChange={() => props.setOpenPicker("page-background-image")} 153 card 154 /> 155 </div> 156 </label> 157 )} 158 </div> 159 </> 160 ); 161}; 162 163const SubpageBackgroundImagePicker = (props: { 164 entityID: string; 165 openPicker: pickers; 166 setOpenPicker: (p: pickers) => void; 167 setValue: (c: Color) => void; 168}) => { 169 let { rep } = useReplicache(); 170 let bgImage = useEntity(props.entityID, "theme/card-background-image"); 171 let bgRepeat = useEntity( 172 props.entityID, 173 "theme/card-background-image-repeat", 174 ); 175 let bgColor = useColorAttribute(props.entityID, "theme/card-background"); 176 let bgAlpha = 177 useEntity(props.entityID, "theme/card-background-image-opacity")?.data 178 .value || 1; 179 let alphaColor = useMemo(() => { 180 return parseColor(`rgba(0,0,0,${bgAlpha})`); 181 }, [bgAlpha]); 182 let open = props.openPicker === "page-background-image"; 183 184 return ( 185 <> 186 <div className="bgPickerColorLabel flex gap-2 items-center"> 187 <button 188 onClick={() => { 189 props.setOpenPicker(open ? "null" : "page-background-image"); 190 }} 191 className="flex gap-2 items-center grow" 192 > 193 <ColorSwatch 194 color={bgColor} 195 className="w-6 h-6 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C]" 196 style={{ 197 backgroundImage: bgImage?.data.src 198 ? `url(${bgImage.data.src})` 199 : undefined, 200 backgroundPosition: "center", 201 backgroundSize: "cover", 202 }} 203 /> 204 <strong className="text-[#595959]">Page</strong> 205 <div className="italic text-[#8C8C8C]">image</div> 206 </button> 207 208 <SpectrumColorPicker 209 value={alphaColor} 210 onChange={(c) => { 211 let alpha = c.getChannelValue("alpha"); 212 rep?.mutate.assertFact({ 213 entity: props.entityID, 214 attribute: "theme/card-background-image-opacity", 215 data: { type: "number", value: alpha }, 216 }); 217 }} 218 > 219 <Separator classname="h-4! my-1 border-[#C3C3C3]!" /> 220 <ColorField className="w-fit pl-[6px]" channel="alpha"> 221 <Input 222 onMouseDown={onMouseDown} 223 onFocus={(e) => { 224 e.currentTarget.setSelectionRange( 225 0, 226 e.currentTarget.value.length - 1, 227 ); 228 }} 229 onKeyDown={(e) => { 230 if (e.key === "Enter") { 231 e.currentTarget.blur(); 232 } else return; 233 }} 234 className="w-[48px] bg-transparent outline-hidden" 235 /> 236 </ColorField> 237 </SpectrumColorPicker> 238 239 <div className="flex gap-1 text-[#8C8C8C]"> 240 <button 241 onClick={() => { 242 if (bgImage) rep?.mutate.retractFact({ factID: bgImage.id }); 243 if (bgRepeat) rep?.mutate.retractFact({ factID: bgRepeat.id }); 244 }} 245 > 246 <DeleteSmall /> 247 </button> 248 <label className="hover:cursor-pointer"> 249 <BlockImageSmall /> 250 <div className="hidden"> 251 <ImageInput 252 entityID={props.entityID} 253 onChange={() => props.setOpenPicker("page-background-image")} 254 card 255 /> 256 </div> 257 </label> 258 </div> 259 </div> 260 {open && ( 261 <div className="pageImagePicker flex flex-col gap-2"> 262 <ImageSettings 263 entityID={props.entityID} 264 card 265 setValue={props.setValue} 266 /> 267 <div className="flex flex-col gap-2 pr-2 pl-8 -mt-2 mb-2"> 268 <hr className="border-[#DBDBDB]" /> 269 <SpectrumColorPicker 270 value={alphaColor} 271 onChange={(c) => { 272 let alpha = c.getChannelValue("alpha"); 273 rep?.mutate.assertFact({ 274 entity: props.entityID, 275 attribute: "theme/card-background-image-opacity", 276 data: { type: "number", value: alpha }, 277 }); 278 }} 279 > 280 <ColorSlider 281 colorSpace="hsb" 282 className="w-full mt-1 rounded-full" 283 style={{ 284 backgroundImage: `url(/transparent-bg.png)`, 285 backgroundRepeat: "repeat", 286 backgroundSize: "8px", 287 }} 288 channel="alpha" 289 > 290 <SliderTrack className="h-2 w-full rounded-md"> 291 <ColorThumb className={`${thumbStyle} mt-[4px]`} /> 292 </SliderTrack> 293 </ColorSlider> 294 </SpectrumColorPicker> 295 </div> 296 </div> 297 )} 298 </> 299 ); 300}; 301 302// Unified background picker for leaflets - matches structure of BackgroundPicker for publications 303export const LeafletBackgroundPicker = (props: { 304 entityID: string; 305 openPicker: pickers; 306 setOpenPicker: (p: pickers) => void; 307}) => { 308 let { rep } = useReplicache(); 309 let set = useMemo(() => { 310 return setColorAttribute(rep, props.entityID); 311 }, [rep, props.entityID]); 312 313 let leafletBgValue = useColorAttribute( 314 props.entityID, 315 "theme/page-background", 316 ); 317 let pageValue = useColorAttribute(props.entityID, "theme/card-background"); 318 let leafletBGImage = useEntity(props.entityID, "theme/background-image"); 319 let leafletBGRepeat = useEntity( 320 props.entityID, 321 "theme/background-image-repeat", 322 ); 323 let pageBorderHidden = useEntity(props.entityID, "theme/card-border-hidden"); 324 let hasPageBackground = !pageBorderHidden?.data.value; 325 326 // When page background is hidden and no background image, only show the Background picker 327 let showPagePicker = hasPageBackground || !!leafletBGImage; 328 329 return ( 330 <> 331 {/* Background color/image picker */} 332 {leafletBGImage ? ( 333 <LeafletBackgroundImagePicker 334 entityID={props.entityID} 335 openPicker={props.openPicker} 336 setOpenPicker={props.setOpenPicker} 337 /> 338 ) : ( 339 <div className="relative"> 340 <ColorPicker 341 label="Background" 342 value={leafletBgValue} 343 setValue={set("theme/page-background")} 344 thisPicker="leaflet" 345 openPicker={props.openPicker} 346 setOpenPicker={props.setOpenPicker} 347 closePicker={() => props.setOpenPicker("null")} 348 /> 349 <label className="text-[#969696] hover:cursor-pointer shrink-0 absolute top-0 right-0"> 350 <BlockImageSmall /> 351 <div className="hidden"> 352 <ImageInput 353 entityID={props.entityID} 354 onChange={() => props.setOpenPicker("leaflet")} 355 /> 356 </div> 357 </label> 358 </div> 359 )} 360 361 {/* Page/Containers color picker - only shown when page background is visible OR there's a bg image */} 362 {showPagePicker && ( 363 <ColorPicker 364 label={hasPageBackground ? "Page" : "Containers"} 365 helpText={ 366 hasPageBackground 367 ? undefined 368 : "Affects menus, tooltips and some block backgrounds" 369 } 370 value={pageValue} 371 setValue={set("theme/card-background")} 372 thisPicker="page" 373 openPicker={props.openPicker} 374 setOpenPicker={props.setOpenPicker} 375 closePicker={() => props.setOpenPicker("null")} 376 alpha 377 /> 378 )} 379 380 <hr className="border-[#CCCCCC]" /> 381 382 {/* Page Background toggle */} 383 <PageBorderHider 384 entityID={props.entityID} 385 openPicker={props.openPicker} 386 setOpenPicker={props.setOpenPicker} 387 /> 388 </> 389 ); 390}; 391 392const LeafletBackgroundImagePicker = (props: { 393 entityID: string; 394 openPicker: pickers; 395 setOpenPicker: (p: pickers) => void; 396}) => { 397 let { rep } = useReplicache(); 398 let bgImage = useEntity(props.entityID, "theme/background-image"); 399 let bgRepeat = useEntity(props.entityID, "theme/background-image-repeat"); 400 let bgColor = useColorAttribute(props.entityID, "theme/page-background"); 401 let open = props.openPicker === "leaflet"; 402 403 return ( 404 <> 405 <div className="bgPickerColorLabel flex gap-2 items-center"> 406 <button 407 onClick={() => { 408 props.setOpenPicker(open ? "null" : "leaflet"); 409 }} 410 className="flex gap-2 items-center grow" 411 > 412 <ColorSwatch 413 color={bgColor} 414 className="w-6 h-6 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C]" 415 style={{ 416 backgroundImage: bgImage?.data.src 417 ? `url(${bgImage.data.src})` 418 : undefined, 419 backgroundPosition: "center", 420 backgroundSize: "cover", 421 }} 422 /> 423 <strong className="text-[#595959]">Background</strong> 424 <div className="italic text-[#8C8C8C]">image</div> 425 </button> 426 <div className="flex gap-1 text-[#8C8C8C]"> 427 <button 428 onClick={() => { 429 if (bgImage) rep?.mutate.retractFact({ factID: bgImage.id }); 430 if (bgRepeat) rep?.mutate.retractFact({ factID: bgRepeat.id }); 431 }} 432 > 433 <DeleteSmall /> 434 </button> 435 <label className="hover:cursor-pointer"> 436 <BlockImageSmall /> 437 <div className="hidden"> 438 <ImageInput 439 entityID={props.entityID} 440 onChange={() => props.setOpenPicker("leaflet")} 441 /> 442 </div> 443 </label> 444 </div> 445 </div> 446 {open && ( 447 <div className="pageImagePicker flex flex-col gap-2"> 448 <ImageSettings entityID={props.entityID} setValue={() => {}} /> 449 </div> 450 )} 451 </> 452 ); 453}; 454 455export const PageBackgroundColorPicker = (props: { 456 disabled?: boolean; 457 label: string; 458 openPicker: pickers; 459 thisPicker: pickers; 460 setOpenPicker: (thisPicker: pickers) => void; 461 setValue: (c: Color) => void; 462 value: Color; 463 alpha?: boolean; 464 helpText?: string; 465}) => { 466 return ( 467 <ColorPicker 468 disabled={props.disabled} 469 label={props.label} 470 helpText={props.helpText} 471 value={props.value} 472 setValue={props.setValue} 473 thisPicker={"page"} 474 openPicker={props.openPicker} 475 setOpenPicker={props.setOpenPicker} 476 closePicker={() => props.setOpenPicker("null")} 477 alpha={props.alpha} 478 /> 479 ); 480}; 481 482export const PageBackgroundImagePicker = (props: { 483 disabled?: boolean; 484 entityID: string; 485 openPicker: pickers; 486 thisPicker: pickers; 487 setOpenPicker: (thisPicker: pickers) => void; 488 closePicker: () => void; 489 setValue: (c: Color) => void; 490 home?: boolean; 491}) => { 492 let bgImage = useEntity(props.entityID, "theme/card-background-image"); 493 let bgRepeat = useEntity( 494 props.entityID, 495 "theme/card-background-image-repeat", 496 ); 497 let bgColor = useColorAttribute(props.entityID, "theme/card-background"); 498 let bgAlpha = 499 useEntity(props.entityID, "theme/card-background-image-opacity")?.data 500 .value || 1; 501 let alphaColor = useMemo(() => { 502 return parseColor(`rgba(0,0,0,${bgAlpha})`); 503 }, [bgAlpha]); 504 let open = props.openPicker == props.thisPicker; 505 let { rep } = useReplicache(); 506 507 return ( 508 <> 509 <div className="bgPickerColorLabel flex gap-2 items-center"> 510 <button 511 disabled={props.disabled} 512 onClick={() => { 513 if (props.openPicker === props.thisPicker) { 514 props.setOpenPicker("null"); 515 } else { 516 props.setOpenPicker(props.thisPicker); 517 } 518 }} 519 className="flex gap-2 items-center disabled:text-[#969696]" 520 > 521 <ColorSwatch 522 color={bgColor} 523 className={`w-6 h-6 rounded-full border-2 border-white shadow-[0_0_0_1px_#8C8C8C] ${props.disabled ? "opacity-50" : ""}`} 524 style={{ 525 backgroundImage: bgImage?.data.src 526 ? `url(${bgImage.data.src})` 527 : undefined, 528 backgroundPosition: "center", 529 backgroundSize: "cover", 530 }} 531 /> 532 <strong 533 className={`${props.disabled ? "text-[#969696]" : " text-[#272727] "}`} 534 > 535 Page 536 </strong> 537 <div className="">Image</div> 538 </button> 539 540 <SpectrumColorPicker 541 value={alphaColor} 542 onChange={(c) => { 543 let alpha = c.getChannelValue("alpha"); 544 rep?.mutate.assertFact({ 545 entity: props.entityID, 546 attribute: "theme/card-background-image-opacity", 547 data: { type: "number", value: alpha }, 548 }); 549 }} 550 > 551 <Separator classname="h-4! my-1 border-[#C3C3C3]!" /> 552 <ColorField className="w-fit pl-[6px]" channel="alpha"> 553 <Input 554 disabled={props.disabled} 555 onMouseDown={onMouseDown} 556 onFocus={(e) => { 557 e.currentTarget.setSelectionRange( 558 0, 559 e.currentTarget.value.length - 1, 560 ); 561 }} 562 onKeyDown={(e) => { 563 if (e.key === "Enter") { 564 e.currentTarget.blur(); 565 } else return; 566 }} 567 className={`w-[48px] bg-transparent outline-hidden disabled:text-[#969696]`} 568 /> 569 </ColorField> 570 </SpectrumColorPicker> 571 <div className="flex gap-1 justify-end grow text-[#969696]"> 572 <button 573 onClick={() => { 574 if (bgImage) rep?.mutate.retractFact({ factID: bgImage.id }); 575 if (bgRepeat) rep?.mutate.retractFact({ factID: bgRepeat.id }); 576 }} 577 > 578 <DeleteSmall /> 579 </button> 580 <label> 581 <BlockImageSmall /> 582 <div className="hidden"> 583 <ImageInput 584 entityID={props.entityID} 585 onChange={() => props.setOpenPicker("page-background-image")} 586 card 587 /> 588 </div> 589 </label> 590 </div> 591 </div> 592 {open && ( 593 <div className="pageImagePicker flex flex-col gap-2"> 594 <ImageSettings 595 entityID={props.entityID} 596 card 597 setValue={props.setValue} 598 /> 599 <div className="flex flex-col gap-2 pr-2 pl-8 -mt-2 mb-2"> 600 <hr className="border-[#DBDBDB]" /> 601 <SpectrumColorPicker 602 value={alphaColor} 603 onChange={(c) => { 604 let alpha = c.getChannelValue("alpha"); 605 rep?.mutate.assertFact({ 606 entity: props.entityID, 607 attribute: "theme/card-background-image-opacity", 608 data: { type: "number", value: alpha }, 609 }); 610 }} 611 > 612 <ColorSlider 613 colorSpace="hsb" 614 className="w-full mt-1 rounded-full" 615 style={{ 616 backgroundImage: `url(/transparent-bg.png)`, 617 backgroundRepeat: "repeat", 618 backgroundSize: "8px", 619 }} 620 channel="alpha" 621 > 622 <SliderTrack className="h-2 w-full rounded-md"> 623 <ColorThumb className={`${thumbStyle} mt-[4px]`} /> 624 </SliderTrack> 625 </ColorSlider> 626 </SpectrumColorPicker> 627 </div> 628 </div> 629 )} 630 </> 631 ); 632}; 633 634const CanvasBGPatternPicker = (props: { 635 entityID: string; 636 rep: Replicache<ReplicacheMutators> | null; 637}) => { 638 let selectedPattern = useEntity(props.entityID, "canvas/background-pattern") 639 ?.data.value; 640 return ( 641 <div className="flex gap-2 h-8 "> 642 <button 643 className={`w-full rounded-md bg-bg-page border ${selectedPattern === "grid" ? "outline-solid outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 644 onMouseDown={() => { 645 props.rep && 646 props.rep.mutate.assertFact({ 647 entity: props.entityID, 648 attribute: "canvas/background-pattern", 649 data: { type: "canvas-pattern-union", value: "grid" }, 650 }); 651 }} 652 > 653 <CanvasBackgroundPattern pattern="grid" scale={0.5} /> 654 </button> 655 <button 656 className={`w-full rounded-md bg-bg-page border ${selectedPattern === "dot" ? "outline-solid outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 657 onMouseDown={() => { 658 props.rep && 659 props.rep.mutate.assertFact({ 660 entity: props.entityID, 661 attribute: "canvas/background-pattern", 662 data: { type: "canvas-pattern-union", value: "dot" }, 663 }); 664 }} 665 > 666 <CanvasBackgroundPattern pattern="dot" scale={0.5} /> 667 </button> 668 <button 669 className={`w-full rounded-md bg-bg-page border ${selectedPattern === "plain" ? "outline-solid outline-tertiary border-tertiary" : "transparent-outline hover:outline-border border-border "}`} 670 onMouseDown={() => { 671 props.rep && 672 props.rep.mutate.assertFact({ 673 entity: props.entityID, 674 attribute: "canvas/background-pattern", 675 data: { type: "canvas-pattern-union", value: "plain" }, 676 }); 677 }} 678 > 679 <CanvasBackgroundPattern pattern="plain" /> 680 </button> 681 </div> 682 ); 683}; 684 685export const TextPickers = (props: { 686 openPicker: pickers; 687 setOpenPicker: (thisPicker: pickers) => void; 688 value: Color; 689 setValue: (c: Color) => void; 690}) => { 691 return ( 692 <ColorPicker 693 label="Text" 694 value={props.value} 695 setValue={props.setValue} 696 thisPicker={"text"} 697 openPicker={props.openPicker} 698 setOpenPicker={props.setOpenPicker} 699 closePicker={() => props.setOpenPicker("null")} 700 /> 701 ); 702}; 703 704export const PageBorderHider = (props: { 705 entityID: string; 706 setOpenPicker: (p: pickers) => void; 707 openPicker: pickers; 708}) => { 709 let { rep, rootEntity } = useReplicache(); 710 let rootPageBorderHidden = useEntity(rootEntity, "theme/card-border-hidden"); 711 let entityPageBorderHidden = useEntity( 712 props.entityID, 713 "theme/card-border-hidden", 714 ); 715 let pageBorderHidden = 716 (entityPageBorderHidden || rootPageBorderHidden)?.data.value || false; 717 718 function handleToggle() { 719 rep?.mutate.assertFact({ 720 entity: props.entityID, 721 attribute: "theme/card-border-hidden", 722 data: { type: "boolean", value: !pageBorderHidden }, 723 }); 724 725 (pageBorderHidden && props.openPicker === "page") || 726 (props.openPicker === "page-background-image" && 727 props.setOpenPicker("null")); 728 } 729 730 return ( 731 <> 732 <Toggle 733 toggle={!pageBorderHidden} 734 onToggle={() => { 735 handleToggle(); 736 }} 737 disabledColor1="#8C8C8C" 738 disabledColor2="#DBDBDB" 739 > 740 <div className="flex gap-2"> 741 <div className="font-bold">Page Background</div> 742 <div className="italic text-[#8C8C8C]"> 743 {pageBorderHidden ? "none" : ""} 744 </div> 745 </div> 746 </Toggle> 747 </> 748 ); 749};