From 8f2f1766bfd10f0e8060df32b63d06a64528cf03 Mon Sep 17 00:00:00 2001 From: Aly Raffauf Date: Fri, 24 Jul 2026 16:28:25 -0400 Subject: [PATCH] components: reorganize --- src/App.tsx | 2 +- src/components/Header.tsx | 148 ------------------ src/components/{ => layout}/Footer.tsx | 6 +- src/components/layout/Header.tsx | 148 ++++++++++++++++++ src/components/layout/PageContainer.tsx | 10 ++ src/components/{ => layout}/PageLayout.tsx | 0 src/components/profile/ProfileAvatar.tsx | 40 +++++ .../{ => profile}/ProfileByline.tsx | 25 +-- .../{ => profile}/ProfileHeader.tsx | 21 +-- .../{ => profile}/ProfileOverview.tsx | 0 src/components/{ => repo}/RepoCommit.tsx | 2 +- src/components/{ => repo}/RepoListItem.tsx | 6 +- src/components/{ => repo}/RepoLog.tsx | 16 +- src/components/{ => repo}/RepoReadme.tsx | 10 +- src/components/{ => repo}/RepoTree.tsx | 32 ++-- src/components/{ => repo}/RepoView.tsx | 6 +- src/components/{ => repo}/RepoWorkspace.tsx | 2 +- src/components/repo/WorkspacePaneHeader.tsx | 18 +++ src/components/{ => shared}/LoadingPanel.tsx | 0 src/components/shared/MarkdownContent.tsx | 16 ++ src/components/{ => shared}/PageSkeletons.tsx | 9 +- .../{ => string}/StringListItem.tsx | 2 +- src/components/{ => string}/StringView.tsx | 2 +- src/pages/ProfilePage.tsx | 12 +- src/pages/RepoPage.tsx | 10 +- src/pages/StringPage.tsx | 6 +- 26 files changed, 305 insertions(+), 244 deletions(-) delete mode 100644 src/components/Header.tsx rename src/components/{ => layout}/Footer.tsx (68%) create mode 100644 src/components/layout/Header.tsx create mode 100644 src/components/layout/PageContainer.tsx rename src/components/{ => layout}/PageLayout.tsx (100%) create mode 100644 src/components/profile/ProfileAvatar.tsx rename src/components/{ => profile}/ProfileByline.tsx (57%) rename src/components/{ => profile}/ProfileHeader.tsx (79%) rename src/components/{ => profile}/ProfileOverview.tsx (100%) rename src/components/{ => repo}/RepoCommit.tsx (93%) rename src/components/{ => repo}/RepoListItem.tsx (90%) rename src/components/{ => repo}/RepoLog.tsx (72%) rename src/components/{ => repo}/RepoReadme.tsx (62%) rename src/components/{ => repo}/RepoTree.tsx (86%) rename src/components/{ => repo}/RepoView.tsx (93%) rename src/components/{ => repo}/RepoWorkspace.tsx (94%) create mode 100644 src/components/repo/WorkspacePaneHeader.tsx rename src/components/{ => shared}/LoadingPanel.tsx (100%) create mode 100644 src/components/shared/MarkdownContent.tsx rename src/components/{ => shared}/PageSkeletons.tsx (84%) rename src/components/{ => string}/StringListItem.tsx (95%) rename src/components/{ => string}/StringView.tsx (90%) diff --git a/src/App.tsx b/src/App.tsx index c13926a..e755869 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { Route, Routes } from 'react-router-dom' -import { PageLayout } from './components/PageLayout' +import { PageLayout } from './components/layout/PageLayout' import { HomePage } from './pages/HomePage' import { ProfilePage } from './pages/ProfilePage' import { RepoPage } from './pages/RepoPage' diff --git a/src/components/Header.tsx b/src/components/Header.tsx deleted file mode 100644 index 95b03e8..0000000 --- a/src/components/Header.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { useEffect, useRef, useState } from 'react' -import { Link, useNavigate } from 'react-router-dom' -import { isHandle } from '@atcute/lexicons/syntax' -import { searchBlueskyActors, type BlueskyActorSearchResult } from '../lib/bsky/actorSearch' - -export function Header() { - const [handleInput, setHandleInput] = useState('') - const [isInvalid, setIsInvalid] = useState(false) - const [results, setResults] = useState([]) - const [isSearching, setIsSearching] = useState(false) - const [isSuggestionsOpen, setIsSuggestionsOpen] = useState(false) - const searchFormRef = useRef(null) - const navigate = useNavigate() - - useEffect(() => { - function closeSuggestions(event: PointerEvent) { - if (!searchFormRef.current?.contains(event.target as Node)) { - setResults([]) - setIsSearching(false) - setIsSuggestionsOpen(false) - } - } - - document.addEventListener('pointerdown', closeSuggestions) - return () => document.removeEventListener('pointerdown', closeSuggestions) - }, []) - - useEffect(() => { - const query = handleInput.trim() - if (query.length < 2) { - setResults([]) - setIsSearching(false) - setIsSuggestionsOpen(false) - return - } - - const controller = new AbortController() - const timeout = window.setTimeout(async () => { - setIsSearching(true) - - try { - const actors = await searchBlueskyActors(query, controller.signal) - setResults(actors) - } catch (error) { - if (!(error instanceof DOMException && error.name === 'AbortError')) { - setResults([]) - } - } finally { - if (!controller.signal.aborted) setIsSearching(false) - } - }, 180) - - return () => { - controller.abort() - window.clearTimeout(timeout) - } - }, [handleInput]) - - function selectActor(actor: BlueskyActorSearchResult) { - setHandleInput('') - setResults([]) - setIsSuggestionsOpen(false) - navigate(`/${actor.handle}`) - } - - return ( -
- -
- ) -} diff --git a/src/components/Footer.tsx b/src/components/layout/Footer.tsx similarity index 68% rename from src/components/Footer.tsx rename to src/components/layout/Footer.tsx index 9bcc32d..b2e00eb 100644 --- a/src/components/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -1,7 +1,9 @@ +import { PageContainer } from './PageContainer' + export function Footer() { return ( ) } diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx new file mode 100644 index 0000000..8c33bba --- /dev/null +++ b/src/components/layout/Header.tsx @@ -0,0 +1,148 @@ +import { useEffect, useRef, useState } from 'react' +import { Link, useNavigate } from 'react-router-dom' +import { isHandle } from '@atcute/lexicons/syntax' +import { searchBlueskyActors, type BlueskyActorSearchResult } from '../../lib/bsky/actorSearch' +import { PageContainer } from './PageContainer' + +export function Header() { + const [handleInput, setHandleInput] = useState('') + const [isInvalid, setIsInvalid] = useState(false) + const [results, setResults] = useState([]) + const [isSearching, setIsSearching] = useState(false) + const [isSuggestionsOpen, setIsSuggestionsOpen] = useState(false) + const searchFormRef = useRef(null) + const navigate = useNavigate() + + useEffect(() => { + function closeSuggestions(event: PointerEvent) { + if (!searchFormRef.current?.contains(event.target as Node)) { + setResults([]) + setIsSearching(false) + setIsSuggestionsOpen(false) + } + } + + document.addEventListener('pointerdown', closeSuggestions) + return () => document.removeEventListener('pointerdown', closeSuggestions) + }, []) + + useEffect(() => { + const query = handleInput.trim() + if (query.length < 2) { + setResults([]) + setIsSearching(false) + setIsSuggestionsOpen(false) + return + } + + const controller = new AbortController() + const timeout = window.setTimeout(async () => { + setIsSearching(true) + + try { + const actors = await searchBlueskyActors(query, controller.signal) + setResults(actors) + } catch (error) { + if (!(error instanceof DOMException && error.name === 'AbortError')) { + setResults([]) + } + } finally { + if (!controller.signal.aborted) setIsSearching(false) + } + }, 180) + + return () => { + controller.abort() + window.clearTimeout(timeout) + } + }, [handleInput]) + + function selectActor(actor: BlueskyActorSearchResult) { + setHandleInput('') + setResults([]) + setIsSuggestionsOpen(false) + navigate(`/${actor.handle}`) + } + + return ( +
+ +
+ ) +} diff --git a/src/components/layout/PageContainer.tsx b/src/components/layout/PageContainer.tsx new file mode 100644 index 0000000..7907721 --- /dev/null +++ b/src/components/layout/PageContainer.tsx @@ -0,0 +1,10 @@ +import type { ReactNode } from 'react' + +type PageContainerProps = { + children: ReactNode + className?: string +} + +export function PageContainer({ children, className = '' }: PageContainerProps) { + return
{children}
+} diff --git a/src/components/PageLayout.tsx b/src/components/layout/PageLayout.tsx similarity index 100% rename from src/components/PageLayout.tsx rename to src/components/layout/PageLayout.tsx diff --git a/src/components/profile/ProfileAvatar.tsx b/src/components/profile/ProfileAvatar.tsx new file mode 100644 index 0000000..27df945 --- /dev/null +++ b/src/components/profile/ProfileAvatar.tsx @@ -0,0 +1,40 @@ +import type { AppBskyActorProfile } from '@atcute/bluesky' +import type { $output as MiniDoc } from '@atcute/microcosm/types/blue/microcosm/identity/resolveMiniDoc' +import { getAvatarUrl } from '../../lib/atproto/media' +import type { Profile } from '../../lib/tangled' + +type ProfileAvatarProps = { + miniDoc: MiniDoc + profile: Profile + bskyProfile?: AppBskyActorProfile.Main | null + size?: 'small' | 'medium' +} + +export function ProfileAvatar({ + miniDoc, + profile, + bskyProfile, + size = 'medium', +}: ProfileAvatarProps) { + const avatar = profile.value.avatar ?? bskyProfile?.avatar + const avatarUrl = avatar ? getAvatarUrl(miniDoc.did, avatar) : null + const sizeClass = size === 'small' ? 'size-8 text-sm' : 'size-16 text-xl' + + if (avatarUrl) { + return ( + {`${miniDoc.handle} + ) + } + + return ( + + {miniDoc.handle[0].toUpperCase()} + + ) +} diff --git a/src/components/ProfileByline.tsx b/src/components/profile/ProfileByline.tsx similarity index 57% rename from src/components/ProfileByline.tsx rename to src/components/profile/ProfileByline.tsx index 87b0c7c..54e27e8 100644 --- a/src/components/ProfileByline.tsx +++ b/src/components/profile/ProfileByline.tsx @@ -1,8 +1,8 @@ import type { AppBskyActorProfile } from '@atcute/bluesky' import type { $output as MiniDoc } from '@atcute/microcosm/types/blue/microcosm/identity/resolveMiniDoc' import { Link } from 'react-router-dom' -import { getAvatarUrl } from '../lib/atproto/media' -import type { Profile } from '../lib/tangled' +import type { Profile } from '../../lib/tangled' +import { ProfileAvatar } from './ProfileAvatar' type ProfileBylineProps = { miniDoc: MiniDoc @@ -12,10 +12,6 @@ type ProfileBylineProps = { } export function ProfileByline({ miniDoc, profile, bskyProfile, label }: ProfileBylineProps) { - const { value } = profile - const avatar = value.avatar ?? bskyProfile?.avatar - const avatarUrl = avatar ? getAvatarUrl(miniDoc.did, avatar) : null - return (