Peter Steinbergerfeat: add bounded web auto-synca9478564 months agoHistory
151 lines · 3.1 KB · TypeScript
RawDownload
import { Search } from "lucide-react";import type { ReactNode } from "react";import {	FeedEmpty,	FeedError,	FeedLoading,	TweetSkeletonRows,} from "./FeedState";import { ConversationSurfaceScope } from "#/lib/conversation-surface";import {	feedClass,	pageHeaderClass,	pageHeaderRowClass,	pageSubtitleClass,	pageTitleClass,	searchFieldIconClass,	searchFieldInputClass,	searchFieldShellClass,} from "#/lib/ui"; export function TimelineFeedHeader({	title,	subtitles,	action,	controls,}: {	title: string;	subtitles: ReactNode;	action?: ReactNode;	controls?: ReactNode;}) {	return (		<header className={pageHeaderClass}>			<div className={`${pageHeaderRowClass} flex-wrap`}>				<div className="flex min-w-0 flex-col">					<h1 className={pageTitleClass}>{title}</h1>					{subtitles}				</div>				{action}			</div>			{controls}		</header>	);} export function TimelineHeaderSubtitle({ children }: { children: ReactNode }) {	return <p className={pageSubtitleClass}>{children}</p>;} export function TimelineSearchField({	value,	onChange,	placeholder,}: {	value: string;	onChange: (value: string) => void;	placeholder: string;}) {	return (		<div className="px-4 pb-3">			<label className={searchFieldShellClass}>				<Search className={searchFieldIconClass} strokeWidth={2} />				<input					className={searchFieldInputClass}					onChange={(event) => onChange(event.target.value)}					placeholder={placeholder}					value={value}				/>			</label>		</div>	);} export function TimelineFeedShell({	header,	notice,	loading,	loadingLabel,	loadingDetail,	error,	errorTitle,	onRetry,	empty,	emptyLabel,	emptyDetail,	children,	hasMore,	loadingMore,	onLoadMore,}: {	header: ReactNode;	notice?: ReactNode;	loading: boolean;	loadingLabel: string;	loadingDetail: string;	error: string | null;	errorTitle: string;	onRetry: () => void;	empty: boolean;	emptyLabel: string;	emptyDetail: string;	children: ReactNode;	hasMore: boolean;	loadingMore: boolean;	onLoadMore: () => void;}) {	return (		<>			{header}			{notice}			<ConversationSurfaceScope>				<section className={feedClass}>					{loading ? (						<FeedLoading detail={loadingDetail} label={loadingLabel}>							<TweetSkeletonRows />						</FeedLoading>					) : error ? (						<FeedError							action={								<button									className="rounded-full bg-[var(--accent)] px-4 py-1.5 text-[14px] font-bold text-white"									onClick={onRetry}									type="button"								>									Retry								</button>							}							message={error}							title={errorTitle}						/>					) : empty ? (						<FeedEmpty detail={emptyDetail} label={emptyLabel} />					) : null}					{children}					{!loading && !error && hasMore ? (						<div className="flex justify-center py-4">							<button								className="rounded-full bg-[var(--accent)] px-5 py-1.5 text-[14px] font-bold text-white disabled:opacity-60"								disabled={loadingMore}								onClick={onLoadMore}								type="button"							>								{loadingMore ? "Loading…" : "Load more"}							</button>						</div>					) : null}				</section>			</ConversationSurfaceScope>		</>	);}