a tool for shared writing and social publishing

unify background color for publications

+181 -80
+1 -1
app/[leaflet_id]/page.tsx
··· 31 31 if (!rootEntity || !res.data || res.data.blocked_by_admin) 32 32 return ( 33 33 <div className="w-screen h-screen flex place-items-center bg-bg-leaflet"> 34 - <div className="bg-bg-page mx-auto p-4 border border-border rounded-md flex flex-col text-center justify-centergap-1 w-fit"> 34 + <div className="bg-bg-page mx-auto p-4 border border-border rounded-md flex flex-col text-center justify-center gap-1 w-fit"> 35 35 <div className="font-bold"> 36 36 Hmmm…we couldn&apos;t find that Leaflet. 37 37 </div>
+1 -2
app/lish/[handle]/[publication]/[rkey]/page.tsx
··· 1 1 import Link from "next/link"; 2 - import { Footer } from "../../../Footer"; 3 2 import { getPds, IdResolver } from "@atproto/identity"; 4 3 import { supabaseServerClient } from "supabase/serverClient"; 5 4 import { AtUri } from "@atproto/syntax"; ··· 59 58 blocks = firstPage.blocks || []; 60 59 } 61 60 return ( 62 - <div className="postPage w-full h-screen bg-bg-leaflet flex items-stretch"> 61 + <div className="postPage w-full h-screen bg-[#FDFCFA] flex items-stretch"> 63 62 <div className="pubWrapper flex flex-col w-full "> 64 63 <div className="pubContent flex flex-col px-3 sm:px-4 py-3 sm:py-9 mx-auto max-w-prose h-full w-full overflow-auto"> 65 64 <div className="flex flex-col pb-8">
+1 -1
app/lish/[handle]/[publication]/dashboard/PublicationDashboard.tsx
··· 32 32 function Tab(props: { name: string; selected: boolean; onSelect: () => void }) { 33 33 return ( 34 34 <div 35 - className={`pubTabs border bg-bg-page border-b-0 px-2 pt-1 pb-0.5 rounded-t-md border-border hover:cursor-pointer ${props.selected ? "text-accent-1 font-bold -mb-[1px]" : ""}`} 35 + className={`pubTabs border bg-[#FDFCFA] border-b-0 px-2 pt-1 pb-0.5 rounded-t-md border-border hover:cursor-pointer ${props.selected ? "text-accent-1 font-bold -mb-[1px]" : ""}`} 36 36 onClick={() => props.onSelect()} 37 37 > 38 38 {props.name}
+76 -70
app/lish/[handle]/[publication]/dashboard/page.tsx
··· 61 61 try { 62 62 return ( 63 63 <ThemeProvider entityID={null}> 64 - <div className="relative max-w-prose w-full h-full mx-auto flex sm:flex-row flex-col sm:items-stretch sm:px-6"> 65 - <div className="w-12 relative"> 66 - <Sidebar className="mt-6 p-2"> 67 - <Actions publication={publication.uri} /> 68 - </Sidebar> 69 - </div> 70 - <div 71 - className={`h-full overflow-y-scroll pt-4 sm:pl-5 sm:pt-9 w-full`} 72 - > 73 - <PublicationDashboard 74 - name={publication.name} 75 - tabs={{ 76 - Drafts: ( 77 - <DraftList 78 - publication={publication.uri} 79 - drafts={publication.leaflets_in_publications.filter( 80 - (p) => !p.doc, 81 - )} 82 - /> 83 - ), 84 - Published: 85 - publication.documents_in_publications.length === 0 ? ( 86 - <div className="italic text-tertiary w-full container text-center place-items-center flex flex-col gap-3 p-3"> 87 - Nothing's been published yet... 88 - </div> 89 - ) : ( 90 - <div className="publishedList w-full flex flex-col gap-4 pb-6"> 91 - {publication.documents_in_publications.map((doc) => { 92 - if (!doc.documents) return null; 93 - let leaflet = publication.leaflets_in_publications.find( 94 - (l) => doc.documents && l.doc === doc.documents.uri, 95 - ); 96 - let uri = new AtUri(doc.documents.uri); 97 - let record = doc.documents 98 - .data as PubLeafletDocument.Record; 64 + <div className="w-screen h-screen flex place-items-center bg-[#FDFCFA]"> 65 + <div className="relative max-w-prose w-full h-full mx-auto flex sm:flex-row flex-col sm:items-stretch sm:px-6"> 66 + <div className="w-12 relative"> 67 + <Sidebar className="mt-6 p-2"> 68 + <Actions publication={publication.uri} /> 69 + </Sidebar> 70 + </div> 71 + <div 72 + className={`h-full overflow-y-scroll pt-4 sm:pl-5 sm:pt-9 w-full`} 73 + > 74 + <PublicationDashboard 75 + name={publication.name} 76 + tabs={{ 77 + Drafts: ( 78 + <DraftList 79 + publication={publication.uri} 80 + drafts={publication.leaflets_in_publications.filter( 81 + (p) => !p.doc, 82 + )} 83 + /> 84 + ), 85 + Published: 86 + publication.documents_in_publications.length === 0 ? ( 87 + <div className="italic text-tertiary w-full container text-center place-items-center flex flex-col gap-3 p-3"> 88 + Nothing's been published yet... 89 + </div> 90 + ) : ( 91 + <div className="publishedList w-full flex flex-col gap-4 pb-6"> 92 + {publication.documents_in_publications.map((doc) => { 93 + if (!doc.documents) return null; 94 + let leaflet = 95 + publication.leaflets_in_publications.find( 96 + (l) => 97 + doc.documents && l.doc === doc.documents.uri, 98 + ); 99 + let uri = new AtUri(doc.documents.uri); 100 + let record = doc.documents 101 + .data as PubLeafletDocument.Record; 99 102 100 - return ( 101 - <React.Fragment key={doc.documents?.uri}> 102 - <div className="flex w-full "> 103 - <Link 104 - href={`/lish/${params.handle}/${params.publication}/${uri.rkey}`} 105 - className="publishedPost grow flex flex-col gap-2 hover:!no-underline" 106 - > 107 - <h3 className="text-primary">{record.title}</h3> 108 - <p className="italic text-secondary"> 109 - This is a placeholder for description 110 - </p> 111 - <p className="text-sm text-tertiary pt-2"> 112 - {record.publishedAt} PlaceholderDate 113 - </p> 114 - </Link> 115 - {leaflet && ( 103 + return ( 104 + <React.Fragment key={doc.documents?.uri}> 105 + <div className="flex w-full "> 116 106 <Link 117 - className="pt-[6px]" 118 - href={`/${leaflet.leaflet}`} 107 + href={`/lish/${params.handle}/${params.publication}/${uri.rkey}`} 108 + className="publishedPost grow flex flex-col hover:!no-underline" 119 109 > 120 - <EditTiny /> 110 + <h3 className="text-primary"> 111 + {record.title} 112 + </h3> 113 + <p className="italic text-secondary"> 114 + This is a placeholder for description 115 + </p> 116 + <p className="text-sm text-tertiary pt-2"> 117 + {record.publishedAt} PlaceholderDate 118 + </p> 121 119 </Link> 122 - )} 123 - </div> 124 - <hr className="last:hidden border-border-light" /> 125 - </React.Fragment> 126 - ); 127 - })} 128 - </div> 129 - ), 130 - }} 131 - defaultTab={"Drafts"} 132 - /> 120 + {leaflet && ( 121 + <Link 122 + className="pt-[6px]" 123 + href={`/${leaflet.leaflet}`} 124 + > 125 + <EditTiny /> 126 + </Link> 127 + )} 128 + </div> 129 + <hr className="last:hidden border-border-light" /> 130 + </React.Fragment> 131 + ); 132 + })} 133 + </div> 134 + ), 135 + }} 136 + defaultTab={"Drafts"} 137 + /> 138 + </div> 139 + <Media mobile> 140 + <Footer> 141 + <Actions publication={publication.uri} /> 142 + </Footer> 143 + </Media> 133 144 </div> 134 - <Media mobile> 135 - <Footer> 136 - <Actions publication={publication.uri} /> 137 - </Footer> 138 - </Media> 139 145 </div> 140 146 </ThemeProvider> 141 147 );
+102 -6
components/ThemeManager/ThemeProvider.tsx
··· 5 5 CSSProperties, 6 6 useContext, 7 7 useEffect, 8 + useMemo, 8 9 useState, 9 10 } from "react"; 10 11 import { colorToString, useColorAttribute } from "./useColorAttribute"; ··· 12 13 import { parse, contrastLstar, ColorSpace, sRGB } from "colorjs.io/fn"; 13 14 14 15 import { useEntity } from "src/replicache"; 16 + import { useLeafletPublicationData } from "components/PageSWRDataProvider"; 15 17 16 18 type CSSVariables = { 17 19 "--bg-leaflet": string; ··· 52 54 local?: boolean; 53 55 children: React.ReactNode; 54 56 }) { 57 + let { data } = useLeafletPublicationData(); 58 + if (!data[0]) return <LeafletThemeProvider {...props} />; 59 + return <PublicationThemeProvider {...props} />; 60 + } 61 + export function PublicationThemeProvider(props: { 62 + entityID: string | null; 63 + local?: boolean; 64 + children: React.ReactNode; 65 + }) { 66 + let bgLeaflet = useMemo(() => { 67 + return parseColor(`#FDFCFA`); 68 + }, []); 69 + let bgPage = useColorAttribute(props.entityID, "theme/card-background"); 70 + let primary = useColorAttribute(props.entityID, "theme/primary"); 71 + 72 + let highlight1 = useEntity(props.entityID, "theme/highlight-1"); 73 + let highlight2 = useColorAttribute(props.entityID, "theme/highlight-2"); 74 + let highlight3 = useColorAttribute(props.entityID, "theme/highlight-3"); 75 + 76 + let accent1 = useColorAttribute(props.entityID, "theme/accent-background"); 77 + let accent2 = useColorAttribute(props.entityID, "theme/accent-text"); 78 + // set accent contrast to the accent color that has the highest contrast with the page background 79 + let accentContrast = [accent1, accent2].sort((a, b) => { 80 + return ( 81 + getColorContrast(colorToString(b, "rgb"), colorToString(bgPage, "rgb")) - 82 + getColorContrast(colorToString(a, "rgb"), colorToString(bgPage, "rgb")) 83 + ); 84 + })[0]; 85 + 86 + return ( 87 + <BaseThemeProvider 88 + bgLeaflet={bgLeaflet} 89 + bgPage={bgPage} 90 + primary={primary} 91 + highlight2={highlight2} 92 + highlight3={highlight3} 93 + highlight1={highlight1?.data.value} 94 + accent1={accent1} 95 + accent2={accent2} 96 + accentContrast={accentContrast} 97 + > 98 + {props.children} 99 + </BaseThemeProvider> 100 + ); 101 + } 102 + 103 + export function LeafletThemeProvider(props: { 104 + entityID: string | null; 105 + local?: boolean; 106 + children: React.ReactNode; 107 + }) { 55 108 let bgLeaflet = useColorAttribute(props.entityID, "theme/page-background"); 56 109 let bgPage = useColorAttribute(props.entityID, "theme/card-background"); 57 110 let primary = useColorAttribute(props.entityID, "theme/primary"); ··· 70 123 ); 71 124 })[0]; 72 125 126 + return ( 127 + <BaseThemeProvider 128 + bgLeaflet={bgLeaflet} 129 + bgPage={bgPage} 130 + primary={primary} 131 + highlight2={highlight2} 132 + highlight3={highlight3} 133 + highlight1={highlight1?.data.value} 134 + accent1={accent1} 135 + accent2={accent2} 136 + accentContrast={accentContrast} 137 + > 138 + {props.children} 139 + </BaseThemeProvider> 140 + ); 141 + } 142 + 143 + let BaseThemeProvider = ({ 144 + local, 145 + bgLeaflet, 146 + bgPage, 147 + primary, 148 + accent1, 149 + accent2, 150 + accentContrast, 151 + highlight1, 152 + highlight2, 153 + highlight3, 154 + children, 155 + }: { 156 + local?: boolean; 157 + bgLeaflet: AriaColor; 158 + bgPage: AriaColor; 159 + primary: AriaColor; 160 + accent1: AriaColor; 161 + accent2: AriaColor; 162 + accentContrast: AriaColor; 163 + highlight1?: string; 164 + highlight2: AriaColor; 165 + highlight3: AriaColor; 166 + children: React.ReactNode; 167 + }) => { 73 168 useEffect(() => { 74 - if (props.local) return; 169 + if (local) return; 75 170 let el = document.querySelector(":root") as HTMLElement; 76 171 if (!el) return; 77 172 setCSSVariableToColor(el, "--bg-leaflet", bgLeaflet); ··· 90 185 91 186 //highlight 1 is special because its default value is a calculated value 92 187 if (highlight1) { 93 - let color = parseColor(`hsba(${highlight1.data.value})`); 188 + let color = parseColor(`hsba(${highlight1})`); 94 189 el?.style.setProperty( 95 190 "--highlight-1", 96 191 `rgb(${colorToString(color, "rgb")})`, ··· 112 207 accentContrast === accent1 ? "1" : "0", 113 208 ); 114 209 }, [ 115 - props.local, 210 + local, 116 211 bgLeaflet, 117 212 bgPage, 118 213 primary, ··· 137 232 "--accent-contrast": colorToString(accentContrast, "rgb"), 138 233 "--accent-1-is-contrast": accentContrast === accent1 ? 1 : 0, 139 234 "--highlight-1": highlight1 140 - ? `rgb(${colorToString(parseColor(`hsba(${highlight1.data.value})`), "rgb")})` 235 + ? `rgb(${colorToString(parseColor(`hsba(${highlight1})`), "rgb")})` 141 236 : "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)", 142 237 "--highlight-2": colorToString(highlight2, "rgb"), 143 238 "--highlight-3": colorToString(highlight3, "rgb"), 144 239 } as CSSProperties 145 240 } 146 241 > 147 - {props.children} 242 + {" "} 243 + {children}{" "} 148 244 </div> 149 245 ); 150 - } 246 + }; 151 247 152 248 let CardThemeProviderContext = createContext<null | string>(null); 153 249 export function NestedCardThemeProvider(props: { children: React.ReactNode }) {