T-HUB/apps/web/src/components/mobile/source-chips.tsx
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

24 lines
1.7 KiB
TypeScript

"use client";
import { useRef, type TouchEvent } from "react";
export interface SourceChip { id: string; label: string; dot?: string; count?: number; }
export function SourceChips({ sources, value, onChange }: { sources: SourceChip[]; value: string; onChange: (value: string) => void }): React.ReactElement {
const start = useRef<{ x: number; y: number } | null>(null);
function end(event: TouchEvent<HTMLDivElement>): void {
if (start.current === null) return;
const dx = (event.changedTouches[0]?.clientX ?? start.current.x) - start.current.x;
const dy = (event.changedTouches[0]?.clientY ?? start.current.y) - start.current.y;
start.current = null;
if (Math.abs(dx) < 50 || Math.abs(dx) < Math.abs(dy) * 1.5) return;
const index = sources.findIndex((source) => source.id === value);
const next = sources[Math.max(0, Math.min(sources.length - 1, index + (dx < 0 ? 1 : -1)))];
if (next) onChange(next.id);
}
return <div className="flex min-w-0 gap-2 overflow-x-auto px-4 py-2 scrollbar-thin" role="tablist" aria-label="Feed sources" onTouchStart={(event) => { start.current = { x: event.touches[0]?.clientX ?? 0, y: event.touches[0]?.clientY ?? 0 }; }} onTouchEnd={end}>
{sources.map((source) => <button key={source.id} type="button" role="tab" aria-selected={value === source.id} onClick={() => onChange(source.id)} className={`flex min-h-11 shrink-0 items-center gap-2 rounded-full border px-4 text-xs font-semibold ${value === source.id ? "border-accent bg-accent-dim text-accent" : "border-border text-text-dim"}`}>
{source.dot && <span className="h-2 w-2 rounded-full bg-accent" aria-hidden="true" />}{source.label}{source.count !== undefined && <span className="text-text-faint">{source.count}</span>}
</button>)}
</div>;
}