a tool for shared writing and social publishing
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

deleted test files

celine 1384037a f9b9dcc8

-1454
-212
app/test/Blocks.tsx
··· 1 - import { useState } from "react"; 2 - import { 3 - BlockCardSmall, 4 - CloseContrastSmall, 5 - CloseTiny, 6 - BlockImageSmall, 7 - LinkSmall, 8 - BlockLinkSmall, 9 - } from "../../components/Icons"; 10 - import { theme } from "../../tailwind.config"; 11 - import useMeasure from "react-use-measure"; 12 - 13 - export const TextBlock = (props: { defaultValue: string; lines: number }) => { 14 - let [value, setValue] = useState(props.defaultValue); 15 - let [focus, setFocus] = useState(false); 16 - 17 - return ( 18 - <div className="textBlockWrapper relative group/text pb-3"> 19 - <textarea 20 - className={`textBlock w-full p-0 border-none outline-none resize-none align-top bg-transparent`} 21 - style={{ 22 - height: `${props.lines * 24}px`, 23 - }} 24 - defaultValue={props.defaultValue} 25 - onChange={(e) => setValue(e.target.value)} 26 - onFocus={() => setFocus(true)} 27 - onBlur={() => setFocus(false)} 28 - /> 29 - <div className="blockTypeSelectorWrapper absolute top-0 right-0"> 30 - <BlockTypeSelector focus={focus} empty={value === ""} /> 31 - </div> 32 - </div> 33 - //add button to select block type when empty 34 - ); 35 - }; 36 - 37 - const BlockTypeSelector = (props: { focus: boolean; empty: boolean }) => { 38 - return ( 39 - <div 40 - className={`blockTypeSelector 41 - ${props.focus && props.empty ? "block" : "hidden"} 42 - ${props.empty && "group-hover/text:block"}`} 43 - > 44 - <div className="flex gap-1 "> 45 - <button className="text-tertiary hover:text-accent"> 46 - <BlockImageSmall /> 47 - </button> 48 - 49 - <button className="text-tertiary hover:text-accent"> 50 - <BlockCardSmall /> 51 - </button> 52 - 53 - <button className="text-tertiary hover:text-accent"> 54 - <BlockLinkSmall /> 55 - </button> 56 - </div> 57 - </div> 58 - ); 59 - }; 60 - export const ImageBlock = (props: { src: string; cardHeight: number }) => { 61 - return ( 62 - <div className="pb-4 pt-2 relative group/image flex w-fit place-self-center justify-center"> 63 - <button className="absolute right-2 top-6 z-10 hidden group-hover/image:block"> 64 - pt-2 pb-2 px-2 grow min-w-0{" "} 65 - <CloseContrastSmall 66 - fill={theme.colors.primary} 67 - stroke={theme.colors["bg-card"]} 68 - />{" "} 69 - </button> 70 - <img 71 - src={props.src} 72 - alt="image" 73 - className={`w-max relative`} 74 - style={{ maxHeight: `calc(${props.cardHeight}px - 48px)` }} 75 - /> 76 - </div> 77 - ); 78 - }; 79 - 80 - const ImageRemoveButton = () => { 81 - return ( 82 - <svg 83 - width="24" 84 - height="24" 85 - viewBox="0 0 24 24" 86 - fill="none" 87 - xmlns="http://www.w3.org/2000/svg" 88 - > 89 - <path 90 - fillRule="evenodd" 91 - clipRule="evenodd" 92 - d="M19.5314 17.2686C20.1562 17.8935 20.1562 18.9065 19.5314 19.5314C18.9065 20.1562 17.8935 20.1562 17.2686 19.5314L12 14.2627L6.73137 19.5314C6.10653 20.1562 5.09347 20.1562 4.46863 19.5314C3.84379 18.9065 3.84379 17.8935 4.46863 17.2686L9.73726 12L4.46863 6.73137C3.84379 6.10653 3.84379 5.09347 4.46863 4.46863C5.09347 3.84379 6.10653 3.84379 6.73137 4.46863L12 9.73726L17.2686 4.46863C17.8935 3.84379 18.9065 3.84379 19.5314 4.46863C20.1562 5.09347 20.1562 6.10653 19.5314 6.73137L14.2627 12L19.5314 17.2686Z" 93 - fill={theme.colors.primary} 94 - /> 95 - <path 96 - fillRule="evenodd" 97 - clipRule="evenodd" 98 - d="M17.2686 4.46863C17.8935 3.84379 18.9065 3.84379 19.5314 4.46863C20.1562 5.09347 20.1562 6.10653 19.5314 6.73137L14.2627 12L19.5314 17.2686C20.1562 17.8935 20.1562 18.9065 19.5314 19.5314C18.9065 20.1562 17.8935 20.1562 17.2686 19.5314L12 14.2627L6.73137 19.5314C6.10653 20.1562 5.09347 20.1562 4.46863 19.5314C3.84379 18.9065 3.84379 17.8935 4.46863 17.2686L9.73726 12L4.46863 6.73137C3.84379 6.10653 3.84379 5.09347 4.46863 4.46863C5.09347 3.84379 6.10653 3.84379 6.73137 4.46863L12 9.73726L17.2686 4.46863ZM12 7.61594L16.208 3.40797C17.4186 2.19734 19.3814 2.19734 20.592 3.40797C21.8027 4.61859 21.8027 6.58141 20.592 7.79203L16.3841 12L20.592 16.208C21.8027 17.4186 21.8027 19.3814 20.592 20.592C19.3814 21.8027 17.4186 21.8027 16.208 20.592L12 16.3841L7.79203 20.592C6.58141 21.8027 4.61859 21.8027 3.40797 20.592C2.19734 19.3814 2.19734 17.4186 3.40797 16.208L7.61594 12L3.40797 7.79203C2.19734 6.5814 2.19734 4.6186 3.40797 3.40797C4.61859 2.19734 6.58141 2.19734 7.79203 3.40797L12 7.61594Z" 99 - fill={theme.colors["bg-card"]} 100 - /> 101 - </svg> 102 - ); 103 - }; 104 - 105 - export const CardBlock = (props: { 106 - title?: string; 107 - body?: string; 108 - screenshot?: string; 109 - cardHeight?: number; 110 - }) => { 111 - let [blockRef, { width: blockWidth }] = useMeasure(); 112 - 113 - return ( 114 - <div 115 - ref={blockRef} 116 - className="cardBlockWrapper relative group w-full h-[104px] mb-3 border border-border outline outline-1 outline-transparent hover:outline-border rounded-lg flex overflow-hidden" 117 - > 118 - {/* 119 - all headers are reduced to h3 styling to keep the card block consistent and legible 120 - the card block will render as much text as can fit in the block without overflowing 121 - if the text overflows, it will be truncated and an ellipsis will be added to the end of the text 122 - 123 - what happens in there is no text content at all? 124 - what happens if the title spans two lines, how much space is left for the body? 125 - 126 - I think the logic i used is flawed and won't stand up to reality so let's figure this out in earnest later 127 - */} 128 - 129 - {/* TODO: implement long press to select and bring up options (such as remove)*/} 130 - <div className="absolute top-0.5 right-0.5 group-hover:block hidden text-border hover:text-accent "> 131 - <CloseTiny /> 132 - </div> 133 - 134 - <div className="cardBlockContent grow p-2"> 135 - {props.title && ( 136 - <div className={`w-full grow text-base font-bold line-clamp-3`}> 137 - {props.title} 138 - </div> 139 - )} 140 - {props.body && ( 141 - <div 142 - className={`w-full grow text-sm ${!props.title ? "line-clamp-4" : "line-clamp-3"}`} 143 - > 144 - {props.body} 145 - </div> 146 - )} 147 - </div> 148 - 149 - <div 150 - className={`cardBlockPreview w-[120px] m-2 -mb-2 bg-cover shrink-0 rounded-t-md border border-border-light `} 151 - style={{ backgroundImage: `url(${props.screenshot})` }} 152 - /> 153 - </div> 154 - ); 155 - }; 156 - 157 - export const ExternalLinkBlock = () => { 158 - let [title, setTitle] = useState("Title"); 159 - let [description, setDescription] = useState( 160 - "hello, this is a little description. I want it to be a little bit long so that I can see if it wrapping around but it thought it was long enought and it wasn't actually so im adding a little more on", 161 - ); 162 - 163 - return ( 164 - <a 165 - href="www.google.com" 166 - className="externalLinkBlock relative group h-[104px] mb-3 flex border border-border hover:border-accent outline outline-1 outline-transparent hover:outline-accent rounded-lg overflow-hidden" 167 - > 168 - <div className="absolute top-0.5 right-0.5 group-hover:block hidden text-accent"> 169 - <CloseTiny /> 170 - </div> 171 - <div className="pt-2 pb-2 px-2 grow min-w-0"> 172 - <div className="flex flex-col w-full min-w-0 h-full grow "> 173 - <textarea 174 - // when this textarea is replaced a responsive one, 175 - // make it such that the text area is only as wide as it's contents 176 - // such that click anything but the literaly words of the title and description will nav you to the link 177 - // and clicking the title or description will allow you to edit them 178 - className={`linkBlockTitle bg-transparent -mb-0.5 border-none text-base font-bold outline-none resize-none align-top border h-[24px] line-clamp-1`} 179 - defaultValue={title} 180 - onClick={(e) => { 181 - e.preventDefault(); 182 - e.stopPropagation(); 183 - }} 184 - onChange={(e) => { 185 - setTitle(e.target.value); 186 - }} 187 - /> 188 - 189 - <textarea 190 - className={`linkBlockDescription text-sm bg-transparent border-none outline-none resize-none align-top grow line-clamp-2`} 191 - defaultValue={description} 192 - onClick={(e) => { 193 - e.preventDefault(); 194 - e.stopPropagation(); 195 - }} 196 - onChange={(e) => { 197 - setDescription(e.target.value); 198 - }} 199 - /> 200 - <div className="inline-block place-self-end w-full text-xs text-tertiary italic line-clamp-1 truncate group-hover:text-accent"> 201 - https://www.flickr.com/photos/biodivlibrary/https://www.flickr.com/photos/biodivlibrary/https://www.flickr.com/photos/biodivlibrary/ 202 - </div> 203 - </div> 204 - </div> 205 - 206 - <div 207 - className={`linkBlockPreview w-[120px] m-2 -mb-2 bg-cover shrink-0 rounded-t-md border border-border group-hover:border-accent`} 208 - style={{ backgroundImage: `url(./test-link.png)` }} 209 - /> 210 - </a> 211 - ); 212 - };
-198
app/test/Card.tsx
··· 1 - import React from "react"; 2 - import { ButtonPrimary } from "../../components/Buttons"; 3 - import { TextBlock, ImageBlock, CardBlock, ExternalLinkBlock } from "./Blocks"; 4 - import { useIsMobile, useIsInitialRender } from "src/hooks/isMobile"; 5 - import { TextToolbar } from "./TextToolbar"; 6 - 7 - export const Card = (props: { 8 - first?: boolean; 9 - focused?: boolean; 10 - id: string; 11 - index: number; 12 - setFocusedCardIndex: (index: number) => void; 13 - setCards: ([]) => void; 14 - cards: number[]; 15 - card: number; 16 - cardWidth: number; 17 - cardHeight: number; 18 - }) => { 19 - let isMobile = useIsMobile(); 20 - let isInitialRender = useIsInitialRender(); 21 - 22 - if (isInitialRender) return null; 23 - return ( 24 - <> 25 - {/* if the card is the first one in the list, remove this div... can we do with :before? */} 26 - {!props.first && <div className="w-6 md:snap-center" />} 27 - <div 28 - id={props.id} 29 - className={` 30 - cardWrapper w-[calc(100vw-12px)] md:w-[calc(50vw-32px)] max-w-prose 31 - relative 32 - grow flex flex-col 33 - overflow-y-scroll no-scrollbar 34 - snap-center 35 - rounded-lg border 36 - ${props.focused ? "shadow-md border-border" : "border-border-light"} 37 - 38 - `} 39 - style={{ 40 - backgroundColor: "rgba(var(--bg-card), var(--bg-card-alpha))", 41 - }} 42 - > 43 - <CardContent cardHeight={props.cardHeight} /> 44 - 45 - {!isMobile && props.focused ? ( 46 - <div className="textToolbarWrapper sticky bottom-3 w-fit flex gap-[6px] items-center py-2 px-3 mx-auto bg-bg-card border border-border rounded-full shadow-md"> 47 - <TextToolbar /> 48 - </div> 49 - ) : null} 50 - </div> 51 - </> 52 - ); 53 - }; 54 - 55 - const CardContent = (props: { cardHeight: number }) => { 56 - return ( 57 - <div className=" p-3 sm:p-4 flex flex-col"> 58 - <h2>Chapter 1</h2> 59 - <TextBlock 60 - lines={6} 61 - defaultValue="It is a truth universally acknowledged, that a single man in possession of a good fortune must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered as the rightful property of some one or other of their daughters." 62 - /> 63 - <TextBlock 64 - lines={2} 65 - defaultValue="“My dear Mr. Bennet,” said his lady to him one day, “have you heard that Netherfield Park is let at last?”" 66 - /> 67 - <TextBlock lines={1} defaultValue="Mr. Bennet replied that he had not." /> 68 - <TextBlock 69 - lines={2} 70 - defaultValue="“But it is,” returned she; “for Mrs. Long has just been here, and she told me all about it.”" 71 - /> 72 - <TextBlock lines={1} defaultValue="Mr. Bennet made no answer." /> 73 - <TextBlock 74 - lines={2} 75 - defaultValue="“Do not you want to know who has taken it?” cried his wife, impatiently." 76 - /> 77 - <TextBlock 78 - lines={1} 79 - defaultValue="“You want to tell me, and I have no objection to hearing it.”" 80 - /> 81 - <TextBlock lines={1} defaultValue="" /> 82 - {/* <h4>Related Links</h4> 83 - <h3>Related Images</h3> 84 - <ImageBlock src="./test-image.jpg" cardHeight={props.cardHeight} /> 85 - <ImageBlock src="./test-image-2.jpg" cardHeight={props.cardHeight} /> */} 86 - <ExternalLinkBlock /> 87 - 88 - <CardBlock screenshot="./card1.png" title="Chapter 2" /> 89 - 90 - <CardBlock 91 - screenshot="./card2.png" 92 - title="Notes" 93 - body="This is me just sort of blabbing on and on and on so that i can make htis thing wrap enough lines and see what it looks like. indeed blah blah blah thats what i have to say about it big yada yada energy" 94 - /> 95 - <CardBlock 96 - screenshot="./card3.png" 97 - title="Footnote #3" 98 - body="what if first block very short?" 99 - /> 100 - <CardBlock 101 - screenshot="./card5.png" 102 - body="This was invitation enough. “Why, my dear, you must know, Mrs. Long says 103 - that Netherfield is taken by a young man of large fortune from the north 104 - of England; that he came down on Monday in a chaise and four to see the 105 - place, and was so much delighted with it that he agreed with Mr. Morris 106 - immediately; that he is to take possession before Michaelmas, and some 107 - of his servants are to be in the house by the end of next week.”" 108 - /> 109 - </div> 110 - ); 111 - }; 112 - 113 - const AddCardButton = (props: { 114 - setCards: ([]) => void; 115 - setFocusedCardIndex: (index: number) => void; 116 - cards: number[]; 117 - card: number; 118 - index: number; 119 - }) => { 120 - return ( 121 - <ButtonPrimary 122 - onClick={() => { 123 - //add a new card after this one 124 - props.setCards([...props.cards, props.card + 1]); 125 - 126 - // focus the new card 127 - props.setFocusedCardIndex(props.index + 1); 128 - 129 - //scroll the new card into view 130 - setTimeout(() => { 131 - let newCardID = document.getElementById((props.index + 1).toString()); 132 - newCardID?.scrollIntoView({ 133 - behavior: "smooth", 134 - inline: "nearest", 135 - }); 136 - }, 100); 137 - }} 138 - > 139 - add card 140 - </ButtonPrimary> 141 - ); 142 - }; 143 - 144 - const RemoveCardButton = (props: { 145 - setCards: ([]) => void; 146 - cards: number[]; 147 - index: number; 148 - }) => { 149 - return ( 150 - <ButtonPrimary 151 - onClick={() => { 152 - props.cards.splice(props.index, 1); 153 - props.setCards([...props.cards]); 154 - }} 155 - > 156 - remove card 157 - </ButtonPrimary> 158 - ); 159 - }; 160 - 161 - const FocusCardButton = (props: { 162 - setFocusedCardIndex: (index: number) => void; 163 - index: number; 164 - cardWidth: number; 165 - }) => { 166 - return ( 167 - <ButtonPrimary 168 - onClick={() => { 169 - //set the focused card to this one 170 - props.setFocusedCardIndex(props.index); 171 - 172 - // check if the card is off screen to the right or left 173 - let cardPosition = 174 - document 175 - .getElementById(props.index.toString()) 176 - ?.getBoundingClientRect().left || 0; 177 - let isOffScreenLeft = cardPosition < 0; 178 - let isOffScreenRight = 179 - cardPosition + props.cardWidth > window.innerWidth; 180 - 181 - //if card is off screen, scroll one card width to the left or right so that the card is in view 182 - setTimeout(() => { 183 - document.getElementById("card-carousel")?.scrollBy({ 184 - top: 0, 185 - left: isOffScreenLeft 186 - ? -props.cardWidth 187 - : isOffScreenRight 188 - ? props.cardWidth 189 - : 0, 190 - behavior: "smooth", 191 - }); 192 - }, 100); 193 - }} 194 - > 195 - focus this card 196 - </ButtonPrimary> 197 - ); 198 - };
-58
app/test/Header.tsx
··· 1 - import { ShareSmall } from "../../components/Icons"; 2 - import * as Popover from "@radix-ui/react-popover"; 3 - import { ThemePopover } from "./ThemeSetter"; 4 - import { imageArgs } from "./page"; 5 - 6 - export const PageHeader = (props: { 7 - pageBGImage: imageArgs; 8 - setPageBGImage: (imageArgs: Partial<imageArgs>) => void; 9 - }) => { 10 - return ( 11 - <> 12 - <InvitePopover /> 13 - 14 - <ThemePopover 15 - pageBGImage={props.pageBGImage} 16 - setPageBGImage={props.setPageBGImage} 17 - /> 18 - </> 19 - ); 20 - }; 21 - 22 - const InvitePopover = () => { 23 - return ( 24 - <Popover.Root> 25 - <Popover.Trigger className="p-1 bg-accent rounded-full font-bold text-accentText"> 26 - <ShareSmall /> 27 - </Popover.Trigger> 28 - <Popover.Portal> 29 - <Popover.Content 30 - className="bg-bg-card rounded-md border border-border py-1" 31 - align="end" 32 - sideOffset={4} 33 - collisionPadding={16} 34 - > 35 - <div className="px-3 py-1 flex items-stretch gap-2 hover:bg-bg-accent "> 36 - <div className="w-6 bg-test my-1" /> 37 - 38 - <div className="flex flex-col"> 39 - <strong>Publish</strong> 40 - <small>read only</small> 41 - </div> 42 - </div> 43 - 44 - <div className="py-1 px-3 flex items-stretch gap-2 hover:bg-bg-accent"> 45 - <div className="w-6 bg-test my-1" /> 46 - 47 - <div className="flex flex-col "> 48 - <strong>Collab</strong> 49 - <small>full edit access</small> 50 - </div> 51 - </div> 52 - 53 - <Popover.Arrow /> 54 - </Popover.Content> 55 - </Popover.Portal> 56 - </Popover.Root> 57 - ); 58 - };
-365
app/test/TextToolbar.tsx
··· 1 - "use client"; 2 - 3 - import { useState } from "react"; 4 - import { 5 - BoldSmall, 6 - CloseTiny, 7 - ItalicSmall, 8 - RedoSmall, 9 - UndoSmall, 10 - UnderlineSmall, 11 - LinkTextToolbarSmall, 12 - ParagraphSmall, 13 - ListUnorderedSmall, 14 - Header1Small, 15 - Header2Small, 16 - Header3Small, 17 - ListOrderedSmall, 18 - ListIndentDecreaseSmall, 19 - ListIndentIncreaseSmall, 20 - BlockImageSmall, 21 - BlockLinkSmall, 22 - BlockCardSmall, 23 - BlockSmall, 24 - CheckTiny, 25 - } from "components/Icons"; 26 - import { create } from "zustand"; 27 - import { combine } from "zustand/middleware"; 28 - import { Separator } from "components/Layout"; 29 - 30 - type textState = { 31 - bold: boolean; 32 - italic: boolean; 33 - underline: boolean; 34 - header: "h1" | "h2" | "h3" | "p"; 35 - list: "ordered" | "unordered" | "none"; 36 - link: string | undefined; 37 - }; 38 - 39 - let useTextState = create( 40 - combine( 41 - { 42 - bold: false, 43 - italic: false, 44 - underline: false, 45 - header: "p", 46 - list: "none", 47 - link: undefined as string | undefined, 48 - }, 49 - (set) => ({ 50 - toggleBold: () => set((state) => ({ bold: !state.bold })), 51 - toggleItalic: () => set((state) => ({ italic: !state.italic })), 52 - toggleUnderline: () => set((state) => ({ underline: !state.underline })), 53 - setHeader: (newHeader: "h1" | "h2" | "h3" | "p") => 54 - set(() => ({ header: newHeader })), 55 - setList: (newList: "ordered" | "unordered" | "none") => 56 - set(() => ({ list: newList })), 57 - setLink: (newLink: string | undefined) => set(() => ({ link: newLink })), 58 - }), 59 - ), 60 - ); 61 - 62 - export const TextToolbar = () => { 63 - let [toolbarState, setToolbarState] = useState< 64 - "default" | "link" | "header" | "list" | "block" 65 - >("default"); 66 - 67 - let state = useTextState(); 68 - 69 - return ( 70 - <> 71 - {toolbarState === "default" ? ( 72 - <> 73 - <ToolbarButton onClick={() => {}}> 74 - <UndoSmall /> 75 - </ToolbarButton> 76 - <ToolbarButton onClick={() => {}}> 77 - <RedoSmall /> 78 - </ToolbarButton> 79 - <Separator /> 80 - <ToolbarButton 81 - active={state.bold} 82 - onClick={() => { 83 - state.toggleBold(); 84 - }} 85 - > 86 - <BoldSmall /> 87 - </ToolbarButton> 88 - <ToolbarButton 89 - active={state.italic} 90 - onClick={() => { 91 - state.toggleItalic(); 92 - }} 93 - > 94 - <ItalicSmall /> 95 - </ToolbarButton> 96 - <ToolbarButton 97 - active={state.underline} 98 - onClick={() => { 99 - state.toggleUnderline(); 100 - }} 101 - > 102 - <UnderlineSmall /> 103 - </ToolbarButton> 104 - {/* possibly link is only available if text is actively selected */} 105 - <ToolbarButton 106 - active={state.link !== undefined && state.link !== ""} 107 - onClick={() => { 108 - setToolbarState("link"); 109 - }} 110 - > 111 - <LinkTextToolbarSmall /> 112 - </ToolbarButton> 113 - <Separator /> 114 - <ToolbarButton 115 - active 116 - onClick={() => { 117 - setToolbarState("header"); 118 - }} 119 - > 120 - {state.header === "h1" ? ( 121 - <Header1Small /> 122 - ) : state.header === "h2" ? ( 123 - <Header2Small /> 124 - ) : state.header === "h3" ? ( 125 - <Header3Small /> 126 - ) : ( 127 - <ParagraphSmall /> 128 - )} 129 - </ToolbarButton> 130 - <Separator /> 131 - <ToolbarButton 132 - active={state.list !== "none"} 133 - onClick={() => { 134 - setToolbarState("list"); 135 - }} 136 - > 137 - {state.list === "ordered" ? ( 138 - <ListOrderedSmall /> 139 - ) : ( 140 - <ListUnorderedSmall /> 141 - )} 142 - </ToolbarButton> 143 - <Separator /> 144 - <ToolbarButton 145 - onClick={() => { 146 - setToolbarState("block"); 147 - }} 148 - > 149 - <BlockSmall /> 150 - </ToolbarButton> 151 - </> 152 - ) : toolbarState === "link" ? ( 153 - <LinkToolbar onClose={() => setToolbarState("default")} /> 154 - ) : toolbarState === "header" ? ( 155 - <HeaderToolbar onClose={() => setToolbarState("default")} /> 156 - ) : toolbarState === "list" ? ( 157 - <ListToolbar onClose={() => setToolbarState("default")} /> 158 - ) : toolbarState === "block" ? ( 159 - <BlockToolbar onClose={() => setToolbarState("default")} /> 160 - ) : null} 161 - </> 162 - ); 163 - }; 164 - 165 - const LinkToolbar = (props: { onClose: () => void }) => { 166 - let state = useTextState(); 167 - let [linkValue, setLinkValue] = useState(state.link); 168 - return ( 169 - <div className=" w-full flex items-center gap-[6px]"> 170 - <ToolbarButton 171 - active={state.link !== undefined && state.link !== ""} 172 - onClick={() => props.onClose} 173 - > 174 - <LinkTextToolbarSmall /> 175 - </ToolbarButton> 176 - <Separator /> 177 - <input 178 - className="w-full grow bg-transparent border-none outline-none " 179 - placeholder="www.leaflet.pub" 180 - value={linkValue} 181 - onChange={(e) => setLinkValue(e.target.value)} 182 - /> 183 - <div className="flex items-center gap-3"> 184 - <button 185 - className="hover:text-accent" 186 - onClick={() => { 187 - state.setLink(linkValue); 188 - props.onClose(); 189 - }} 190 - > 191 - <CheckTiny /> 192 - </button> 193 - <button className="hover:text-accent" onClick={() => props.onClose()}> 194 - <CloseTiny /> 195 - </button> 196 - </div> 197 - </div> 198 - ); 199 - }; 200 - 201 - const HeaderToolbar = (props: { onClose: () => void }) => { 202 - let state = useTextState(); 203 - 204 - return ( 205 - // This Toolbar should close once the user starts typing again 206 - <div className="flex w-full justify-between items-center gap-4"> 207 - <div className="flex items-center gap-[6px]"> 208 - <ToolbarButton 209 - className="w-10 flex justify-center" 210 - active 211 - onClick={() => props.onClose()} 212 - > 213 - {state.header === "h1" ? ( 214 - <Header1Small /> 215 - ) : state.header === "h2" ? ( 216 - <Header2Small /> 217 - ) : state.header === "h3" ? ( 218 - <Header3Small /> 219 - ) : ( 220 - <ParagraphSmall /> 221 - )}{" "} 222 - </ToolbarButton> 223 - <Separator /> 224 - <ToolbarButton 225 - onClick={() => { 226 - state.setHeader("h1"); 227 - }} 228 - active={state.header === "h1"} 229 - > 230 - <Header1Small /> 231 - </ToolbarButton> 232 - <ToolbarButton 233 - onClick={() => { 234 - state.setHeader("h2"); 235 - }} 236 - active={state.header === "h2"} 237 - > 238 - <Header2Small /> 239 - </ToolbarButton> 240 - <ToolbarButton 241 - onClick={() => { 242 - state.setHeader("h3"); 243 - }} 244 - active={state.header === "h3"} 245 - > 246 - <Header3Small /> 247 - </ToolbarButton> 248 - <ToolbarButton 249 - onClick={() => { 250 - state.setHeader("p"); 251 - props.onClose(); 252 - }} 253 - active={state.header === "p"} 254 - className="px-[6px]" 255 - > 256 - Paragraph 257 - </ToolbarButton> 258 - </div> 259 - <CloseToolbarButton onClose={props.onClose} /> 260 - </div> 261 - ); 262 - }; 263 - 264 - const ListToolbar = (props: { onClose: () => void }) => { 265 - let state = useTextState(); 266 - 267 - // This Toolbar should close once the user starts typing again 268 - return ( 269 - <div className="flex w-full justify-between items-center gap-4"> 270 - <div className="flex items-center gap-[6px]"> 271 - <ToolbarButton 272 - onClick={() => props.onClose()} 273 - active={state.list !== "none"} 274 - > 275 - {state.list === "ordered" ? ( 276 - <ListOrderedSmall /> 277 - ) : ( 278 - <ListUnorderedSmall /> 279 - )} 280 - </ToolbarButton> 281 - <Separator /> 282 - <ToolbarButton 283 - onClick={() => { 284 - state.list === "unordered" 285 - ? state.setList("none") 286 - : state.setList("unordered"); 287 - }} 288 - active={state.list === "unordered"} 289 - > 290 - <ListUnorderedSmall /> 291 - </ToolbarButton> 292 - <ToolbarButton 293 - onClick={() => { 294 - state.list === "ordered" 295 - ? state.setList("none") 296 - : state.setList("ordered"); 297 - }} 298 - active={state.list === "ordered"} 299 - > 300 - <ListOrderedSmall /> 301 - </ToolbarButton> 302 - 303 - {/* if there is no list and you click and indent button, then it should create a list */} 304 - <ToolbarButton> 305 - <ListIndentIncreaseSmall /> 306 - </ToolbarButton> 307 - <ToolbarButton> 308 - <ListIndentDecreaseSmall /> 309 - </ToolbarButton> 310 - </div> 311 - <CloseToolbarButton onClose={props.onClose} /> 312 - </div> 313 - ); 314 - }; 315 - 316 - const BlockToolbar = (props: { onClose: () => void }) => { 317 - return ( 318 - <div className="flex w-full justify-between items-center gap-4"> 319 - <div className="flex items-center gap-[6px]"> 320 - <ToolbarButton onClick={() => props.onClose()}> 321 - <BlockSmall /> 322 - </ToolbarButton> 323 - <Separator /> 324 - <ToolbarButton> 325 - <BlockImageSmall /> 326 - </ToolbarButton> 327 - <ToolbarButton> 328 - <BlockLinkSmall /> 329 - </ToolbarButton> 330 - <ToolbarButton> 331 - <BlockCardSmall /> 332 - </ToolbarButton> 333 - </div> 334 - <CloseToolbarButton onClose={props.onClose} /> 335 - </div> 336 - ); 337 - }; 338 - 339 - const ToolbarButton = (props: { 340 - textState?: textState; 341 - setTextState?: (textState: textState) => void; 342 - className?: string; 343 - onClick?: () => void; 344 - children: React.ReactNode; 345 - active?: boolean; 346 - }) => { 347 - return ( 348 - <button 349 - className={`rounded-md text-secondary shrink-0 p-0.5 active:bg-border active:text-primary ${props.className} ${props.active ? "bg-border text-primary" : ""}`} 350 - onClick={() => { 351 - props.onClick && props.onClick(); 352 - }} 353 - > 354 - {props.children} 355 - </button> 356 - ); 357 - }; 358 - 359 - const CloseToolbarButton = (props: { onClose: () => void }) => { 360 - return ( 361 - <button className="hover:text-accent" onClick={() => props.onClose()}> 362 - <CloseTiny /> 363 - </button> 364 - ); 365 - };
-487
app/test/ThemeSetter.tsx
··· 1 - import * as Popover from "@radix-ui/react-popover"; 2 - import * as Slider from "@radix-ui/react-slider"; 3 - import { theme } from "../../tailwind.config"; 4 - 5 - import { 6 - ColorPicker as SpectrumColorPicker, 7 - parseColor, 8 - Color, 9 - ColorArea, 10 - ColorThumb, 11 - ColorSlider, 12 - Input, 13 - ColorField, 14 - SliderTrack, 15 - ColorSwatch, 16 - } from "react-aria-components"; 17 - 18 - import { 19 - Dispatch, 20 - SetStateAction, 21 - useEffect, 22 - useMemo, 23 - useRef, 24 - useState, 25 - } from "react"; 26 - import { imageArgs } from "./page"; 27 - import { CloseContrastSmall, BlockImageSmall } from "components/Icons"; 28 - 29 - function setCSSVariableToColor(name: string, value: Color) { 30 - let colorStringLength = value.toString("rgb").length; 31 - let root = document.querySelector(":root") as HTMLElement; 32 - root?.style.setProperty( 33 - name, 34 - value.toString("rgb").substring(4, colorStringLength - 1), 35 - ); 36 - } 37 - export type pickers = 38 - | "null" 39 - | "page" 40 - | "card" 41 - | "accent" 42 - | "accentText" 43 - | "text"; 44 - 45 - export const ThemePopover = (props: { 46 - pageBGImage: imageArgs; 47 - setPageBGImage: (imageArgs: Partial<imageArgs>) => void; 48 - }) => { 49 - let [pageValue, setPageValue] = useState(parseColor("#F0F7FA")); 50 - let [cardValue, setCardValue] = useState(parseColor("#FFFFFF")); 51 - let [textValue, setTextValue] = useState(parseColor("#272727")); 52 - let [accentValue, setAccentValue] = useState(parseColor("#0000FF")); 53 - let [accentTextValue, setAccentTextValue] = useState(parseColor("#FFFFFF")); 54 - let [openPicker, setOpenPicker] = useState<pickers>("page"); 55 - 56 - useEffect(() => { 57 - setCSSVariableToColor("--bg-page", pageValue); 58 - setCSSVariableToColor("--bg-card", cardValue); 59 - setCSSVariableToColor("--primary", textValue); 60 - setCSSVariableToColor("--accent", accentValue); 61 - setCSSVariableToColor("--accent-text", accentTextValue); 62 - }, [pageValue, cardValue, textValue, accentValue, accentTextValue]); 63 - 64 - let randomPositions = useMemo(() => { 65 - let values = [] as string[]; 66 - for (let i = 0; i < 3; i++) { 67 - values.push( 68 - `${Math.floor(Math.random() * 100)}% ${Math.floor(Math.random() * 100)}%`, 69 - ); 70 - } 71 - return values; 72 - }, []); 73 - 74 - let gradient = [ 75 - `radial-gradient(at ${randomPositions[0]}, ${accentValue.toString("hex")}80 2px, transparent 70%)`, 76 - `radial-gradient(at ${randomPositions[1]}, ${cardValue.toString("hex")}66 2px, transparent 60%)`, 77 - `radial-gradient(at ${randomPositions[2]}, ${textValue.toString("hex")}B3 2px, transparent 100%)`, 78 - ].join(", "); 79 - 80 - return ( 81 - <> 82 - <Popover.Root> 83 - <Popover.Trigger> 84 - <div 85 - className="rounded-full w-7 h-7 border border-border" 86 - style={{ 87 - backgroundColor: pageValue.toString("hex"), 88 - backgroundImage: gradient, 89 - }} 90 - /> 91 - 92 - <div className="relative z-10"></div> 93 - </Popover.Trigger> 94 - <Popover.Portal> 95 - <Popover.Content 96 - className="w-80 max-h-[800px] p-2 overflow-y-scroll bg-white rounded-md border border-border flex" 97 - align="center" 98 - sideOffset={4} 99 - collisionPadding={16} 100 - > 101 - <div className="flex flex-col w-full overflow-hidden "> 102 - <div className="themeBGPage flex pt-1 pr-2 pl-0 items-start"> 103 - <ColorPicker 104 - label="Background" 105 - value={pageValue} 106 - setValue={(newPageValue) => setPageValue(newPageValue)} 107 - thisPicker={"page"} 108 - openPicker={openPicker} 109 - setOpenPicker={(thisPicker: pickers) => 110 - setOpenPicker(thisPicker) 111 - } 112 - closePicker={() => setOpenPicker("null")} 113 - pageBGImage={props.pageBGImage} 114 - setPageBGImage={props.setPageBGImage} 115 - /> 116 - <div className="w-2 h-full border-t-2 border-r-2 border-border rounded-tr-md mt-[13px]" /> 117 - </div> 118 - <div 119 - className="bg-bg-page p-3 pb-0 flex flex-col rounded-md" 120 - style={{ 121 - backgroundImage: `url(${props.pageBGImage.url})`, 122 - backgroundRepeat: props.pageBGImage.repeat 123 - ? "repeat" 124 - : "no-repeat", 125 - backgroundSize: !props.pageBGImage.repeat 126 - ? "cover" 127 - : `calc(${props.pageBGImage.size}px/ 2 )`, 128 - }} 129 - > 130 - <div className="themeAccentControls flex flex-col h-full"> 131 - <div className="themeAccentColor flex w-full pr-2 items-start "> 132 - <ColorPicker 133 - label="Accent" 134 - value={accentValue} 135 - setValue={(newAccentValue) => 136 - setAccentValue(newAccentValue) 137 - } 138 - thisPicker={"accent"} 139 - openPicker={openPicker} 140 - setOpenPicker={(thisPicker: pickers) => 141 - setOpenPicker(thisPicker) 142 - } 143 - closePicker={() => setOpenPicker("null")} 144 - pageBGImage={props.pageBGImage} 145 - /> 146 - <div className="w-4 h-full border-t-2 border-r-2 border-accent rounded-tr-md mt-[13px] pb-[52px] -mb-[52px]" /> 147 - </div> 148 - <div className="themeTextAccentColor w-full flex pr-2 pb-1 items-start "> 149 - <div className="flex w-full gap-2 items-center place-self-end"> 150 - <ColorPicker 151 - label="Text on Accent" 152 - value={accentTextValue} 153 - setValue={(newAccentTextValue) => 154 - setAccentTextValue(newAccentTextValue) 155 - } 156 - thisPicker={"accentText"} 157 - openPicker={openPicker} 158 - setOpenPicker={(thisPicker: pickers) => 159 - setOpenPicker(thisPicker) 160 - } 161 - closePicker={() => setOpenPicker("null")} 162 - pageBGImage={props.pageBGImage} 163 - /> 164 - </div> 165 - <div className="w-2 h-full border-r-2 border-t-2 border-border rounded-tr-md mt-[13px] z-10" /> 166 - <div className="w-2 h-full border-r-2 border-accent " /> 167 - </div> 168 - <div className="font-bold relative text-center text-lg py-2 rounded-md bg-accent text-accentText shadow-md"> 169 - Button 170 - <div className="absolute h-[26px] w-[92px] top-0 right-[15.5px] border-b-2 border-r-2 rounded-br-md border-border" /> 171 - </div> 172 - </div> 173 - <hr className="my-3" /> 174 - 175 - <div className="themePageControls flex flex-col h-full pb-1"> 176 - <div className="themePageColor flex pr-2 items-start "> 177 - <ColorPicker 178 - label="Page" 179 - value={cardValue} 180 - alpha 181 - setValue={(newCardValue) => setCardValue(newCardValue)} 182 - thisPicker={"card"} 183 - openPicker={openPicker} 184 - setOpenPicker={(thisPicker: pickers) => 185 - setOpenPicker(thisPicker) 186 - } 187 - closePicker={() => setOpenPicker("null")} 188 - pageBGImage={props.pageBGImage} 189 - /> 190 - <div className="w-4 h-full border-t border-r border-border rounded-tr-md mt-3" /> 191 - </div> 192 - <div className="themePageTextColor w-full flex pr-2 items-start"> 193 - <div className="flex w-full gap-2 items-center place-self-end"> 194 - <ColorPicker 195 - label="Text" 196 - value={textValue} 197 - setValue={(newTextValue) => setTextValue(newTextValue)} 198 - thisPicker={"text"} 199 - openPicker={openPicker} 200 - setOpenPicker={(thisPicker: pickers) => 201 - setOpenPicker(thisPicker) 202 - } 203 - closePicker={() => setOpenPicker("null")} 204 - pageBGImage={props.pageBGImage} 205 - /> 206 - </div> 207 - <div className="w-2 h-full border-b border-r border-t border-border rounded-r-md mt-3 z-10 " /> 208 - <div className="w-2 h-full border-r border-border " /> 209 - </div> 210 - </div> 211 - 212 - <div 213 - className="rounded-t-lg p-2 border border-border border-b-transparent shadow-md" 214 - style={{ 215 - backgroundColor: 216 - "rgba(var(--bg-card), var(--bg-card-alpha))", 217 - }} 218 - > 219 - <p className="font-bold">Hello!</p> 220 - <small className=""> 221 - Welcome to Leaflet. It&apos;s a super easy and fun way to 222 - make, share, and collab on little bits of paper 223 - </small> 224 - </div> 225 - </div> 226 - </div> 227 - 228 - <Popover.Arrow /> 229 - </Popover.Content> 230 - </Popover.Portal> 231 - </Popover.Root> 232 - </> 233 - ); 234 - }; 235 - 236 - const ColorPicker = (props: { 237 - label?: string; 238 - value: Color; 239 - alpha?: boolean; 240 - 241 - setValue: Dispatch<SetStateAction<Color>>; 242 - openPicker: pickers; 243 - thisPicker: pickers; 244 - setOpenPicker: (thisPicker: pickers) => void; 245 - closePicker: () => void; 246 - 247 - pageBGImage: imageArgs; 248 - setPageBGImage?: (imageArgs: Partial<imageArgs>) => void; 249 - }) => { 250 - let bgImageExists = 251 - props.pageBGImage?.url !== undefined && props.pageBGImage?.url !== ""; 252 - let thumbStyle = 253 - "w-4 h-4 rounded-full border-2 border-white shadow-[0_0_0_1px_black,_inset_0_0_0_1px_black]"; 254 - return ( 255 - <SpectrumColorPicker value={props.value} onChange={props.setValue}> 256 - <div className="flex flex-col w-full"> 257 - <button 258 - onClick={() => { 259 - props.setOpenPicker(props.thisPicker); 260 - }} 261 - className="colorPickerLabel flex gap-1 items-center place-self-end py-[2px] pl-1 mb-1 rounded-md" 262 - style={{ 263 - backgroundColor: bgImageExists 264 - ? "rgba(var(--bg-card), .6)" 265 - : "transparent", 266 - }} 267 - > 268 - <strong className="text-primary">{props.label}</strong> 269 - <div className="flex"> 270 - {bgImageExists && props.thisPicker === "page" ? ( 271 - <div className="w-[72px] text-left"> Image </div> 272 - ) : ( 273 - <ColorField className="w-fit" defaultValue={props.value}> 274 - <Input 275 - onFocus={(e) => { 276 - e.currentTarget.setSelectionRange( 277 - 1, 278 - e.currentTarget.value.length, 279 - ); 280 - props.setOpenPicker(props.thisPicker); 281 - }} 282 - onKeyDown={(e) => { 283 - if (e.key === "Enter") { 284 - e.currentTarget.blur(); 285 - } else return; 286 - }} 287 - onBlur={(e) => { 288 - props.setValue(parseColor(e.currentTarget.value)); 289 - }} 290 - className="w-[72px] bg-transparent outline-none text-primary" 291 - /> 292 - </ColorField> 293 - )} 294 - </div> 295 - <div className="flex items-center"> 296 - <ColorSwatch 297 - color={props.value} 298 - className={`w-6 h-6 rounded-full border-2 border-border`} 299 - style={{ 300 - backgroundImage: 301 - props.thisPicker === "page" 302 - ? `url(${props.pageBGImage?.url})` 303 - : "none", 304 - backgroundSize: "cover", 305 - }} 306 - /> 307 - <div className="border border-border w-1" /> 308 - </div> 309 - </button> 310 - {props.openPicker === props.thisPicker && ( 311 - <div className="w-full flex flex-col gap-2 pb-3"> 312 - {props.thisPicker === "page" && bgImageExists ? ( 313 - <BGImagePicker 314 - pageBGImage={props.pageBGImage ? props.pageBGImage : undefined} 315 - setPageBGImage={ 316 - props.setPageBGImage ? props.setPageBGImage : () => {} 317 - } 318 - /> 319 - ) : ( 320 - <> 321 - <ColorArea 322 - className="w-full h-[128px] rounded-md" 323 - colorSpace="hsb" 324 - xChannel="saturation" 325 - yChannel="brightness" 326 - > 327 - <ColorThumb className={thumbStyle} /> 328 - </ColorArea> 329 - <ColorSlider 330 - colorSpace="hsb" 331 - className="w-full " 332 - channel="hue" 333 - > 334 - <SliderTrack className="h-2 w-full rounded-md"> 335 - <ColorThumb className={`${thumbStyle} mt-[4px]`} /> 336 - </SliderTrack> 337 - </ColorSlider> 338 - {props.alpha && ( 339 - <ColorSlider 340 - colorSpace="hsb" 341 - className="w-full pt-1" 342 - channel="alpha" 343 - onChange={(value) => { 344 - let valueAlpha = value.getChannelValue("alpha"); 345 - let root = document.querySelector(":root") as HTMLElement; 346 - root?.style.setProperty( 347 - "--bg-card-alpha", 348 - valueAlpha.toString(), 349 - ); 350 - }} 351 - > 352 - <SliderTrack className="h-2 w-full rounded-md"> 353 - <ColorThumb className={`${thumbStyle} mt-[4px]`} /> 354 - </SliderTrack> 355 - </ColorSlider> 356 - )} 357 - </> 358 - )} 359 - </div> 360 - )} 361 - </div> 362 - </SpectrumColorPicker> 363 - ); 364 - }; 365 - 366 - const BGImagePicker = (props: { 367 - pageBGImage: imageArgs | undefined; 368 - setPageBGImage: (imageArgs: Partial<imageArgs>) => void; 369 - }) => { 370 - let bgImageExists = props.pageBGImage && props.pageBGImage.url !== ""; 371 - 372 - return !bgImageExists ? ( 373 - //replace this with a file picker button 374 - <div className="flex gap-2 w-full text-border items-center"> 375 - <BlockImageSmall /> 376 - <input 377 - className="w-full text-primary bg-transparent border border-border rounded-md" 378 - type="text" 379 - id="url" 380 - name="url" 381 - value={props?.pageBGImage?.url} 382 - onChange={(e) => { 383 - props.setPageBGImage({ 384 - url: e.currentTarget.value, 385 - }); 386 - }} 387 - /> 388 - </div> 389 - ) : ( 390 - <div className="themeBGImagePicker flex flex-col border border-border rounded-md overflow-hidden"> 391 - <div 392 - className="themeBGImagePreview flex gap-2 place-items-center justify-center w-full h-[128px] bg-cover bg-center bg-no-repeat" 393 - style={{ 394 - backgroundImage: `url(${props.pageBGImage?.url})`, 395 - }} 396 - > 397 - <input 398 - className={` 399 - themeBGImageInput relative text-center rounded-md text-primary 400 - `} 401 - style={{ 402 - backgroundColor: "rgba(var(--bg-card), 0.7)", 403 - }} 404 - type="text" 405 - id="url" 406 - name="url" 407 - value={props?.pageBGImage?.url} 408 - onChange={(e) => { 409 - props.setPageBGImage({ 410 - url: e.currentTarget.value, 411 - }); 412 - }} 413 - /> 414 - <button onClick={() => props.setPageBGImage({ url: "" })}> 415 - <CloseContrastSmall 416 - fill={theme.colors.primary} 417 - stroke={theme.colors["bg-card"]} 418 - /> 419 - </button> 420 - </div> 421 - <div className="themeBGImageControls p-2 flex gap-2 items-center"> 422 - <label htmlFor="cover" className="flex shrink-0"> 423 - <input 424 - className="appearance-none" 425 - type="radio" 426 - id="cover" 427 - name="cover" 428 - value="cover" 429 - checked={props?.pageBGImage?.repeat === false} 430 - onChange={() => { 431 - props.setPageBGImage({ 432 - repeat: false, 433 - }); 434 - }} 435 - /> 436 - <div 437 - className={`border border-accent rounded-md px-1 py-0.5 cursor-pointer ${props?.pageBGImage?.repeat === false ? "bg-accent text-accentText" : "bg-transparent text-accent"}`} 438 - > 439 - cover 440 - </div> 441 - </label> 442 - <label htmlFor="repeat" className="flex shrink-0"> 443 - <input 444 - className={`appearance-none `} 445 - type="radio" 446 - id="repeat" 447 - name="repeat" 448 - value="repeat" 449 - checked={props?.pageBGImage?.repeat === true} 450 - onChange={() => { 451 - props.setPageBGImage({ 452 - repeat: true, 453 - }); 454 - }} 455 - /> 456 - <div 457 - className={`border border-accent rounded-md px-1 py-0.5 cursor-pointer ${props?.pageBGImage?.repeat === true ? "bg-accent text-accentText" : "bg-transparent text-accent"}`} 458 - > 459 - repeat 460 - </div> 461 - </label> 462 - {props.pageBGImage?.repeat && ( 463 - <Slider.Root 464 - className="relative grow flex items-center select-none touch-none w-full h-fit" 465 - defaultValue={[props.pageBGImage?.size]} 466 - max={3000} 467 - min={10} 468 - step={10} 469 - onValueChange={(value) => { 470 - props.setPageBGImage({ 471 - size: value[0], 472 - }); 473 - }} 474 - > 475 - <Slider.Track className="bg-accent relative grow rounded-full h-[3px]"> 476 - {/* <Slider.Range className="absolute bg-accentText rounded-full h-full" /> */} 477 - </Slider.Track> 478 - <Slider.Thumb 479 - className="flex w-4 h-4 rounded-full border-2 border-white bg-accent shadow-[0_0_0_1px_black,_inset_0_0_0_1px_black] cursor-pointer" 480 - aria-label="Volume" 481 - /> 482 - </Slider.Root> 483 - )} 484 - </div> 485 - </div> 486 - ); 487 - };
-134
app/test/page.tsx
··· 1 - "use client"; 2 - 3 - import { useEffect, useState } from "react"; 4 - import useMeasure from "react-use-measure"; 5 - import { PageHeader } from "./Header"; 6 - import { Card } from "./Card"; 7 - import { TextToolbar } from "./TextToolbar"; 8 - import { useIsMobile, useIsInitialRender } from "src/hooks/isMobile"; 9 - import { DeleteSmall, MoreOptionsTiny } from "components/Icons"; 10 - import * as Popover from "@radix-ui/react-popover"; 11 - 12 - export type imageArgs = { 13 - url: string; 14 - repeat: boolean; 15 - size: number; 16 - }; 17 - 18 - export default function Index() { 19 - let [cardRef, { width: cardWidth, height: cardHeight }] = useMeasure(); 20 - let [cards, setCards] = useState([0, 1]); 21 - let [focusedCardIndex, setFocusedCardIndex] = useState(0); 22 - let [pageBGImage, setPageBGImage] = useState({ 23 - url: "./test-image.jpg", 24 - repeat: true, 25 - size: 500, 26 - }); 27 - let isFirstRender = useIsInitialRender(); 28 - let isMobile = useIsMobile(); 29 - let isKeyboardUp = true; 30 - 31 - if (isFirstRender) return null; 32 - 33 - return ( 34 - <div 35 - className="pageWrapper h-screen flex flex-col bg-cover bg-center bg-no-repeat items-stretch" 36 - style={{ 37 - backgroundImage: `url(${pageBGImage.url})`, 38 - backgroundRepeat: pageBGImage.repeat ? "repeat" : "no-repeat", 39 - backgroundSize: !pageBGImage.repeat ? "cover" : pageBGImage.size, 40 - }} 41 - > 42 - <div 43 - className="pageContentWrapper w-full relative overflow-scroll snap-x snap-mandatory no-scrollbar grow items-stretch flex " 44 - id="card-carousel" 45 - > 46 - <div className="pageContent flex pt-2 pb-8 sm:py-6"> 47 - <div 48 - className="flex justify-end items-start" 49 - style={{ width: `calc((100vw - ${cardWidth}px)/2)` }} 50 - > 51 - {!isMobile && ( 52 - <div className="flex flex-col gap-2 mr-4 mt-2"> 53 - <PageHeader 54 - pageBGImage={pageBGImage} 55 - setPageBGImage={(imageArgs) => 56 - setPageBGImage((s) => ({ ...s, ...imageArgs })) 57 - } 58 - /> 59 - </div> 60 - )} 61 - </div> 62 - 63 - {cards.map((card, index) => ( 64 - <div 65 - className="flex items-stretch relative" 66 - key={index} 67 - ref={index === 0 ? cardRef : null} 68 - > 69 - <Card 70 - first={index === 0} 71 - focused={index === focusedCardIndex} 72 - id={index.toString()} 73 - index={index} 74 - setFocusedCardIndex={setFocusedCardIndex} 75 - setCards={setCards} 76 - cards={cards} 77 - card={card} 78 - cardWidth={cardWidth} 79 - cardHeight={cardHeight} 80 - /> 81 - {index === focusedCardIndex && <CardOptions />} 82 - </div> 83 - ))} 84 - <div style={{ width: `calc((100vw - ${cardWidth}px)/2)` }} /> 85 - </div> 86 - </div> 87 - {/* can we remove this if keyboard is up??? */} 88 - {isMobile && !isKeyboardUp && ( 89 - <div className="w-full pb-2 px-2 -mt-6 flex gap-2 flex-row-reverse items-center z-10"> 90 - <PageHeader 91 - pageBGImage={pageBGImage} 92 - setPageBGImage={(imageArgs) => 93 - setPageBGImage((s) => ({ ...s, ...imageArgs })) 94 - } 95 - /> 96 - </div> 97 - )} 98 - {isMobile && isKeyboardUp && ( 99 - <div className="-mt-6 z-10 bg-bg-card px-2 py-2 flex gap-[6px] items-center"> 100 - <TextToolbar /> 101 - </div> 102 - )} 103 - </div> 104 - ); 105 - } 106 - 107 - const CardOptions = () => { 108 - return ( 109 - <Popover.Root> 110 - <Popover.Trigger className="px-2 py-1 w-fit absolute top-0 right-3 bg-border text-bg-card rounded-b-md"> 111 - <MoreOptionsTiny /> 112 - </Popover.Trigger> 113 - <Popover.Portal> 114 - <Popover.Content 115 - align="end" 116 - className="bg-bg-card flex flex-col py-1 gap-0.5 border border-border rounded-md" 117 - > 118 - <CardMenuItem> 119 - Delete Page <DeleteSmall /> 120 - </CardMenuItem> 121 - <Popover.Arrow /> 122 - </Popover.Content> 123 - </Popover.Portal> 124 - </Popover.Root> 125 - ); 126 - }; 127 - 128 - const CardMenuItem = (props: { children: React.ReactNode }) => { 129 - return ( 130 - <div className="py-1 px-2 flex gap-2 font-bold hover:bg-accent hover:text-accentText "> 131 - {props.children} 132 - </div> 133 - ); 134 - };