a tool for shared writing and social publishing

fixes to styling of tag selector popover in draft post header

+208 -169
+2 -6
app/lish/[did]/[publication]/[rkey]/Interactions/Interactions.tsx
··· 147 <button 148 className={`flex gap-1 items-center ${!props.compact && "px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"}`} 149 onClick={() => { 150 - if ( 151 - !drawerOpen || 152 - drawer !== "comments" || 153 - pageId !== props.pageId 154 - ) 155 openInteractionDrawer("comments", document_uri, props.pageId); 156 else setInteractionState(document_uri, { drawerOpen: false }); 157 }} ··· 185 <Popover 186 className="p-2! max-w-xs" 187 trigger={ 188 - <div className="flex gap-1 items-center "> 189 <TagTiny /> {tagCount} 190 </div> 191 }
··· 147 <button 148 className={`flex gap-1 items-center ${!props.compact && "px-1 py-0.5 border border-border-light rounded-lg trasparent-outline selected-outline"}`} 149 onClick={() => { 150 + if (!drawerOpen || drawer !== "comments" || pageId !== props.pageId) 151 openInteractionDrawer("comments", document_uri, props.pageId); 152 else setInteractionState(document_uri, { drawerOpen: false }); 153 }} ··· 181 <Popover 182 className="p-2! max-w-xs" 183 trigger={ 184 + <div className="tags flex gap-1 items-center "> 185 <TagTiny /> {tagCount} 186 </div> 187 }
+62 -30
app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader.tsx
··· 16 import { EditTiny } from "components/Icons/EditTiny"; 17 import { SpeedyLink } from "components/SpeedyLink"; 18 import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 19 20 export function PostHeader(props: { 21 data: PostPageData; ··· 40 41 if (!document?.data) return; 42 return ( 43 - <div 44 - className="max-w-prose w-full mx-auto px-3 sm:px-4 sm:pt-3 pt-2" 45 - id="post-header" 46 - > 47 - <div className="pubHeader flex flex-col pb-5"> 48 - <div className="flex justify-between w-full"> 49 {pub && ( 50 <SpeedyLink 51 className="font-bold hover:no-underline text-accent-contrast" ··· 65 <EditTiny className="shrink-0" /> 66 </a> 67 )} 68 - </div> 69 - <h2 className="leading-tight">{record.title}</h2> 70 - {record.description ? ( 71 - <p className="italic text-secondary pt-1">{record.description}</p> 72 - ) : null} 73 - <div className="text-sm text-tertiary pt-3 flex gap-1 flex-wrap"> 74 - {profile ? ( 75 - <> 76 - <a 77 - className="text-tertiary" 78 - href={`https://bsky.app/profile/${profile.handle}`} 79 - > 80 - by {profile.displayName || profile.handle} 81 - </a> 82 - </> 83 - ) : null} 84 - {record.publishedAt ? ( 85 - <> 86 - |<p>{formattedDate}</p> 87 - </> 88 - ) : null} 89 - |{" "} 90 <Interactions 91 showComments={props.preferences.showComments} 92 compact 93 quotesCount={getQuoteCount(document) || 0} 94 commentsCount={getCommentCount(document) || 0} 95 /> 96 - </div> 97 </div> 98 </div> 99 ); 100 - }
··· 16 import { EditTiny } from "components/Icons/EditTiny"; 17 import { SpeedyLink } from "components/SpeedyLink"; 18 import { useLocalizedDate } from "src/hooks/useLocalizedDate"; 19 + import Post from "app/p/[didOrHandle]/[rkey]/l-quote/[quote]/page"; 20 + import { Separator } from "components/Layout"; 21 22 export function PostHeader(props: { 23 data: PostPageData; ··· 42 43 if (!document?.data) return; 44 return ( 45 + <PostHeaderLayout 46 + pubLink={ 47 + <> 48 {pub && ( 49 <SpeedyLink 50 className="font-bold hover:no-underline text-accent-contrast" ··· 64 <EditTiny className="shrink-0" /> 65 </a> 66 )} 67 + </> 68 + } 69 + postTitle={record.title} 70 + postDescription={record.description} 71 + postInfo={ 72 + <> 73 + <div className="flex flex-row gap-2 items-center"> 74 + {profile ? ( 75 + <> 76 + <a 77 + className="text-tertiary" 78 + href={`https://bsky.app/profile/${profile.handle}`} 79 + > 80 + {profile.displayName || profile.handle} 81 + </a> 82 + </> 83 + ) : null} 84 + {record.publishedAt ? ( 85 + <> 86 + <Separator classname="h-4!" /> 87 + <p>{formattedDate}</p> 88 + </> 89 + ) : null} 90 + </div> 91 <Interactions 92 showComments={props.preferences.showComments} 93 compact 94 quotesCount={getQuoteCount(document) || 0} 95 commentsCount={getCommentCount(document) || 0} 96 /> 97 + </> 98 + } 99 + /> 100 + ); 101 + } 102 + 103 + export const PostHeaderLayout = (props: { 104 + pubLink: React.ReactNode; 105 + postTitle: React.ReactNode | undefined; 106 + postDescription: React.ReactNode | undefined; 107 + postInfo: React.ReactNode; 108 + }) => { 109 + return ( 110 + <div 111 + className="postHeader max-w-prose w-full flex flex-col px-3 sm:px-4 sm:pt-3 pt-2 pb-5" 112 + id="post-header" 113 + > 114 + <div className="pubInfo flex text-accent-contrast font-bold justify-between w-full"> 115 + {props.pubLink} 116 + </div> 117 + <h2 118 + className={`postTitle text-xl leading-tight pt-0.5 font-bold outline-hidden bg-transparent ${!props.postTitle && "text-tertiary italic"}`} 119 + > 120 + {props.postTitle ? props.postTitle : "Untitled"} 121 + </h2> 122 + {props.postDescription ? ( 123 + <p className="postDescription italic text-secondary outline-hidden bg-transparent pt-1"> 124 + {props.postDescription} 125 + </p> 126 + ) : null} 127 + <div className="postInfo text-sm text-tertiary pt-3 flex gap-1 flex-wrap justify-between"> 128 + {props.postInfo} 129 </div> 130 </div> 131 ); 132 + };
+84 -83
components/Pages/PublicationMetadata.tsx
··· 19 import { Popover } from "components/Popover"; 20 import { TagSelector } from "components/Tags"; 21 import { useIdentityData } from "components/IdentityProvider"; 22 export const PublicationMetadata = () => { 23 let { rep } = useReplicache(); 24 let { data: pub } = useLeafletPublicationData(); ··· 44 let tags = true; 45 46 return ( 47 - <div className={`flex flex-col px-3 sm:px-4 pb-5 sm:pt-3 pt-2`}> 48 - <div className="flex gap-2"> 49 - {pub.publications && ( 50 - <Link 51 - href={ 52 - identity?.atp_did === pub.publications?.identity_did 53 - ? `${getBasePublicationURL(pub.publications)}/dashboard` 54 - : getPublicationURL(pub.publications) 55 - } 56 - className="leafletMetadata text-accent-contrast font-bold hover:no-underline" 57 - > 58 - {pub.publications?.name} 59 - </Link> 60 - )} 61 - <div className="font-bold text-tertiary px-1 text-sm flex place-items-center bg-border-light rounded-md "> 62 - Editor 63 - </div> 64 - </div> 65 - <TextField 66 - className="text-xl font-bold outline-hidden bg-transparent" 67 - value={title} 68 - onChange={async (newTitle) => { 69 - await rep?.mutate.updatePublicationDraft({ 70 - title: newTitle, 71 - description, 72 - }); 73 - }} 74 - placeholder="Untitled" 75 - /> 76 - <TextField 77 - placeholder="add an optional description..." 78 - className="italic text-secondary outline-hidden bg-transparent" 79 - value={description} 80 - onChange={async (newDescription) => { 81 - await rep?.mutate.updatePublicationDraft({ 82 - title, 83 - description: newDescription, 84 - }); 85 - }} 86 - /> 87 - {pub.doc ? ( 88 - <div className="flex flex-row items-center justify-between gap-2 pt-3"> 89 - <div className="flex gap-2 items-center"> 90 - <p className="text-sm text-tertiary"> 91 - Published {publishedAt && timeAgo(publishedAt)} 92 - </p> 93 - <Separator classname="h-4" /> 94 <Link 95 - target="_blank" 96 - className="text-sm" 97 href={ 98 - pub.publications 99 - ? `${getPublicationURL(pub.publications)}/${new AtUri(pub.doc).rkey}` 100 - : `/p/${new AtUri(pub.doc).host}/${new AtUri(pub.doc).rkey}` 101 } 102 > 103 - View Post 104 </Link> 105 </div> 106 <div className="flex gap-2 text-border items-center"> 107 {tags && ( 108 <> 109 <AddTags /> 110 - <Separator classname="h-4" /> 111 </> 112 )} 113 <div className="flex gap-1 items-center"> ··· 119 </div> 120 )} 121 </div> 122 - </div> 123 - ) : ( 124 - <p className="text-sm text-tertiary pt-2">Draft</p> 125 - )} 126 - </div> 127 ); 128 }; 129 ··· 206 if (!pub) return null; 207 208 return ( 209 - <div className={`flex flex-col px-3 sm:px-4 pb-5 sm:pt-3 pt-2`}> 210 - <div className="text-accent-contrast font-bold hover:no-underline"> 211 - {pub.publications?.name} 212 - </div> 213 - 214 - <div 215 - className={`text-xl font-bold outline-hidden bg-transparent ${!pub.title && "text-tertiary italic"}`} 216 - > 217 - {pub.title ? pub.title : "Untitled"} 218 - </div> 219 - <div className="italic text-secondary outline-hidden bg-transparent"> 220 - {pub.description} 221 - </div> 222 - 223 - {pub.doc ? ( 224 - <div className="flex flex-row items-center gap-2 pt-3"> 225 - <p className="text-sm text-tertiary"> 226 - Published {publishedAt && timeAgo(publishedAt)} 227 - </p> 228 </div> 229 - ) : ( 230 - <p className="text-sm text-tertiary pt-2">Draft</p> 231 - )} 232 - </div> 233 ); 234 }; 235 ··· 263 <Popover 264 className="p-2! w-[1000px] max-w-sm" 265 trigger={ 266 - <div className="flex gap-1 hover:underline text-sm items-center text-tertiary"> 267 <TagTiny />{" "} 268 {tags.length > 0 269 ? `${tags.length} Tag${tags.length === 1 ? "" : "s"}`
··· 19 import { Popover } from "components/Popover"; 20 import { TagSelector } from "components/Tags"; 21 import { useIdentityData } from "components/IdentityProvider"; 22 + import { PostHeaderLayout } from "app/lish/[did]/[publication]/[rkey]/PostHeader/PostHeader"; 23 export const PublicationMetadata = () => { 24 let { rep } = useReplicache(); 25 let { data: pub } = useLeafletPublicationData(); ··· 45 let tags = true; 46 47 return ( 48 + <PostHeaderLayout 49 + pubLink={ 50 + <div className="flex gap-2 items-center"> 51 + {pub.publications && ( 52 <Link 53 href={ 54 + identity?.atp_did === pub.publications?.identity_did 55 + ? `${getBasePublicationURL(pub.publications)}/dashboard` 56 + : getPublicationURL(pub.publications) 57 } 58 + className="leafletMetadata text-accent-contrast font-bold hover:no-underline" 59 > 60 + {pub.publications?.name} 61 </Link> 62 + )} 63 + <div className="font-bold text-tertiary px-1 h-[20px] text-sm flex place-items-center bg-border-light rounded-md "> 64 + DRAFT 65 </div> 66 + </div> 67 + } 68 + postTitle={ 69 + <TextField 70 + className="leading-tight pt-0.5 text-xl font-bold outline-hidden bg-transparent" 71 + value={title} 72 + onChange={async (newTitle) => { 73 + await rep?.mutate.updatePublicationDraft({ 74 + title: newTitle, 75 + description, 76 + }); 77 + }} 78 + placeholder="Untitled" 79 + /> 80 + } 81 + postDescription={ 82 + <TextField 83 + placeholder="add an optional description..." 84 + className="pt-1 italic text-secondary outline-hidden bg-transparent" 85 + value={description} 86 + onChange={async (newDescription) => { 87 + await rep?.mutate.updatePublicationDraft({ 88 + title, 89 + description: newDescription, 90 + }); 91 + }} 92 + /> 93 + } 94 + postInfo={ 95 + <> 96 + {pub.doc ? ( 97 + <div className="flex gap-2 items-center"> 98 + <p className="text-sm text-tertiary"> 99 + Published {publishedAt && timeAgo(publishedAt)} 100 + </p> 101 + 102 + <Link 103 + target="_blank" 104 + className="text-sm" 105 + href={ 106 + pub.publications 107 + ? `${getPublicationURL(pub.publications)}/${new AtUri(pub.doc).rkey}` 108 + : `/p/${new AtUri(pub.doc).host}/${new AtUri(pub.doc).rkey}` 109 + } 110 + > 111 + View 112 + </Link> 113 + </div> 114 + ) : ( 115 + <p>Draft</p> 116 + )} 117 <div className="flex gap-2 text-border items-center"> 118 {tags && ( 119 <> 120 <AddTags /> 121 + <Separator classname="h-4!" /> 122 </> 123 )} 124 <div className="flex gap-1 items-center"> ··· 130 </div> 131 )} 132 </div> 133 + </> 134 + } 135 + /> 136 ); 137 }; 138 ··· 215 if (!pub) return null; 216 217 return ( 218 + <PostHeaderLayout 219 + pubLink={ 220 + <div className="text-accent-contrast font-bold hover:no-underline"> 221 + {pub.publications?.name} 222 </div> 223 + } 224 + postTitle={pub.title} 225 + postDescription={pub.description} 226 + postInfo={ 227 + pub.doc ? ( 228 + <p>Published {publishedAt && timeAgo(publishedAt)}</p> 229 + ) : ( 230 + <p>Draft</p> 231 + ) 232 + } 233 + /> 234 ); 235 }; 236 ··· 264 <Popover 265 className="p-2! w-[1000px] max-w-sm" 266 trigger={ 267 + <div className="addTagTrigger flex gap-1 hover:underline text-sm items-center text-tertiary"> 268 <TagTiny />{" "} 269 {tags.length > 0 270 ? `${tags.length} Tag${tags.length === 1 ? "" : "s"}`
+60 -50
components/Tags.tsx
··· 14 }) => { 15 return ( 16 <div 17 - className={`tag flex items-center text-xs rounded-md border ${props.selected ? "bg-accent-1 text-accent-2 border-accent-1 font-bold" : "bg-bg-page text-tertiary border-border"} ${props.className}`} 18 > 19 <Link 20 href={`/tag/${encodeURIComponent(props.name)}`} 21 - className={`px-1 py-0.5 text-tertiary hover:no-underline!`} 22 > 23 {props.name}{" "} 24 </Link> ··· 27 type="button" 28 onClick={() => (props.onDelete ? props.onDelete(props.name) : null)} 29 > 30 - <CloseTiny className="scale-75 pr-1" /> 31 </button> 32 ) : null} 33 </div> ··· 52 name={tag} 53 selected 54 onDelete={() => { 55 - props.setSelectedTags(props.selectedTags.filter((t) => t !== tag)); 56 }} 57 /> 58 ))} ··· 148 return ( 149 <div className="relative"> 150 <Input 151 - className="input-with-border grow w-full" 152 id="placeholder-tag-search-input" 153 value={tagInputValue} 154 placeholder="search tags..." ··· 172 document.getElementById("tag-search-input")?.focus(); 173 }, 100); 174 }} 175 - className="!w-full px-[8px] py-2!" 176 sideOffset={-39} 177 onOpenAutoFocus={(e) => e.preventDefault()} 178 asChild 179 trigger={ 180 <button 181 ref={placeholderInputRef} 182 - className="hello absolute left-0 top-0 right-0 h-[30px]" 183 ></button> 184 } 185 noArrow ··· 199 setIsOpen(true); 200 }} 201 /> 202 - {props.selectedTags.length > 0 && ( 203 - <> 204 - <div className="flex flex-wrap gap-2 "> 205 - {props.selectedTags.map((tag) => ( 206 - <Tag 207 - name={tag} 208 - selected 209 - onDelete={() => { 210 - props.setSelectedTags( 211 - props.selectedTags.filter((t) => t !== tag), 212 - ); 213 - }} 214 - /> 215 - ))} 216 - </div> 217 - <hr className="mt-[6px] mb-[2px] border-border-light" /> 218 - </> 219 )} 220 {userInputResult && ( 221 - <TagResult 222 - key={"userInput"} 223 - index={0} 224 - name={tagInputValue} 225 - tagged={0} 226 - highlighted={0 === highlightedIndex} 227 - setHighlightedIndex={setHighlightedIndex} 228 - onSelect={() => { 229 - selectTag(tagInputValue); 230 - }} 231 - /> 232 )} 233 {filteredTags.map((tag, i) => ( 234 <TagResult ··· 258 setHighlightedIndex: (i: number) => void; 259 }) => { 260 return ( 261 - <button 262 - className={`w-full flex justify-between items-center text-left px-[6px] py-0.5 rounded-md ${props.highlighted ? "bg-border-light" : ""}`} 263 - onSelect={(e) => { 264 - e.preventDefault(); 265 - props.onSelect(); 266 - }} 267 - onClick={(e) => { 268 - e.preventDefault(); 269 - props.onSelect(); 270 - }} 271 - onMouseEnter={(e) => props.setHighlightedIndex(props.index)} 272 - > 273 - {props.name} 274 - <div className="text-tertiary text-sm"> {props.tagged}</div> 275 - </button> 276 ); 277 };
··· 14 }) => { 15 return ( 16 <div 17 + className={`tag flex items-center text-xs rounded-md border ${props.selected ? "bg-accent-1 border-accent-1 font-bold" : "bg-bg-page border-border"} ${props.className}`} 18 > 19 <Link 20 href={`/tag/${encodeURIComponent(props.name)}`} 21 + className={`px-1 py-0.5 hover:no-underline! ${props.selected ? "text-accent-2" : "text-tertiary"}`} 22 > 23 {props.name}{" "} 24 </Link> ··· 27 type="button" 28 onClick={() => (props.onDelete ? props.onDelete(props.name) : null)} 29 > 30 + <CloseTiny className="scale-75 pr-1 text-accent-2" /> 31 </button> 32 ) : null} 33 </div> ··· 52 name={tag} 53 selected 54 onDelete={() => { 55 + props.setSelectedTags( 56 + props.selectedTags.filter((t) => t !== tag), 57 + ); 58 }} 59 /> 60 ))} ··· 150 return ( 151 <div className="relative"> 152 <Input 153 + className="input-with-border grow w-full bg-test" 154 id="placeholder-tag-search-input" 155 value={tagInputValue} 156 placeholder="search tags..." ··· 174 document.getElementById("tag-search-input")?.focus(); 175 }, 100); 176 }} 177 + className="!w-full px-2! py-2!" 178 sideOffset={-39} 179 onOpenAutoFocus={(e) => e.preventDefault()} 180 asChild 181 trigger={ 182 <button 183 ref={placeholderInputRef} 184 + className="absolute left-0 top-0 right-0 h-[30px]" 185 ></button> 186 } 187 noArrow ··· 201 setIsOpen(true); 202 }} 203 /> 204 + {props.selectedTags.length > 0 ? ( 205 + <div className="flex flex-wrap gap-2 pb-[6px]"> 206 + {props.selectedTags.map((tag) => ( 207 + <Tag 208 + key={tag} 209 + name={tag} 210 + selected 211 + onDelete={() => { 212 + props.setSelectedTags( 213 + props.selectedTags.filter((t) => t !== tag), 214 + ); 215 + }} 216 + /> 217 + ))} 218 + </div> 219 + ) : ( 220 + <div className="text-tertiary italic text-sm h-6"> 221 + no tags selected 222 + </div> 223 )} 224 + <hr className=" mb-[2px] border-border-light" /> 225 + 226 {userInputResult && ( 227 + <> 228 + <TagResult 229 + key={"userInput"} 230 + index={0} 231 + name={tagInputValue} 232 + tagged={0} 233 + highlighted={0 === highlightedIndex} 234 + setHighlightedIndex={setHighlightedIndex} 235 + onSelect={() => { 236 + selectTag(tagInputValue); 237 + }} 238 + /> 239 + </> 240 )} 241 {filteredTags.map((tag, i) => ( 242 <TagResult ··· 266 setHighlightedIndex: (i: number) => void; 267 }) => { 268 return ( 269 + <div className="-mx-1"> 270 + <button 271 + className={`w-full flex justify-between items-center text-left pr-1 pl-[6px] py-0.5 rounded-md ${props.highlighted ? "bg-border-light" : ""}`} 272 + onSelect={(e) => { 273 + e.preventDefault(); 274 + props.onSelect(); 275 + }} 276 + onClick={(e) => { 277 + e.preventDefault(); 278 + props.onSelect(); 279 + }} 280 + onMouseEnter={(e) => props.setHighlightedIndex(props.index)} 281 + > 282 + {props.name} 283 + <div className="text-tertiary text-sm"> {props.tagged}</div> 284 + </button> 285 + </div> 286 ); 287 };