Files
eranos/src/components/SortableList.tsx
T
Alex Gleason 29fd0c9a0f Remove unused exports and dead code
Aggressive cleanup of 359 exports across 153 files identified as
having zero importers outside their declaring module:

- 105 symbols deleted entirely (no internal uses either)
- 254 symbols un-exported (still referenced file-locally; dropped the
  `export` keyword to shrink the public surface)
- ~70 cascade cleanups of locals that became dead once their sole
  consumer was removed

Notable shrinkage:
- src/hooks/useShakespeare.ts: 626 \u2192 22 lines (unwired AI chat surface;
  only the ChatMessage type is consumed)
- src/hooks/useTrending.ts: only useEventStats survives; trending feed
  hooks were never wired up
- src/hooks/useTrustedCountryStats.ts: dead type re-exports removed
- src/lib/bitcoin.ts: PSBT helpers \u2014 unused wallet feature scaffolding
- src/lib/communityUtils.ts: unused NIP-72 moderation helpers
- src/lib/extraKinds.ts, src/lib/colorUtils.ts: unused helpers
- src/lib/logger.ts: bare debug/info/warn/error exports dropped;
  consumers use the `logger` object
- src/lib/aiChatSystemPrompt.ts: trimmed to the
  DEFAULT_SYSTEM_PROMPT_TEMPLATE constant
- src/components/music/MusicTrackRow.tsx: dead row component removed;
  only the skeleton is consumed

src/hooks/useNostr.ts (intentional decoy) and src/i18n.ts
(side-effect import) were preserved per their respective contracts.
2026-05-23 20:56:43 -05:00

115 lines
4.2 KiB
TypeScript

import { useCallback } from 'react';
import { GripVertical } from 'lucide-react';
import {
DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors,
type DragEndEvent,
} from '@dnd-kit/core';
import {
SortableContext, verticalListSortingStrategy, useSortable, arrayMove,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { cn } from '@/lib/utils';
// ── Generic sortable list container ──────────────────────────────────────────
interface SortableListProps<T> {
/** Items in current order. */
items: T[];
/** Extract a unique stable string id from each item. */
getItemId: (item: T, index: number) => string;
/** Called with the reordered items array after a drag completes. */
onReorder: (items: T[]) => void;
/** Render each item. The wrapper provides the sortable ref, transform, and drag handle. */
renderItem: (item: T, index: number) => React.ReactNode;
/** Additional classes on the outer container. */
className?: string;
}
/**
* Generic drag-and-drop sortable list.
*
* Reuses the same DnD-kit sensor configuration and vertical sort strategy
* used by the sidebar edit view. Wrap each child in `<SortableItem>` to
* get the grip handle and drag styling for free.
*/
export function SortableList<T>({ items, getItemId, onReorder, renderItem, className }: SortableListProps<T>) {
const sensors = useSensors(
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
useSensor(KeyboardSensor),
);
const sortableIds = items.map(getItemId);
const handleDragEnd = useCallback((event: DragEndEvent) => {
const { active, over } = event;
if (!over || active.id === over.id) return;
const oldIndex = sortableIds.indexOf(active.id as string);
const newIndex = sortableIds.indexOf(over.id as string);
if (oldIndex === -1 || newIndex === -1) return;
onReorder(arrayMove(items, oldIndex, newIndex));
}, [sortableIds, items, onReorder]);
return (
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={sortableIds} strategy={verticalListSortingStrategy}>
<div className={className}>
{items.map((item, i) => renderItem(item, i))}
</div>
</SortableContext>
</DndContext>
);
}
// ── Generic sortable item wrapper ────────────────────────────────────────────
interface SortableItemProps {
/** Must match the id returned by `getItemId` for this item. */
id: string;
/** When false the grip handle is hidden and dragging is disabled. */
enabled?: boolean;
/** Additional classes on the wrapper div. */
className?: string;
/** Classes applied while the item is being dragged. */
draggingClassName?: string;
/** Override the grip handle width class (default: "w-8"). */
gripClassName?: string;
children: React.ReactNode;
}
/**
* Wraps a single child with `useSortable` and renders a grip-vertical
* drag handle. Shares the same visual pattern as the sidebar edit view.
*/
export function SortableItem({ id, enabled = true, className, draggingClassName, gripClassName, children }: SortableItemProps) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id, disabled: !enabled });
const style = { transform: CSS.Transform.toString(transform), transition };
return (
<div
ref={setNodeRef}
style={style}
className={cn(
'flex transition-colors relative',
className,
isDragging && (draggingClassName ?? 'z-10 opacity-80 shadow-lg'),
)}
>
{enabled && (
<button
className={cn(
'flex items-center justify-center shrink-0 cursor-grab active:cursor-grabbing text-muted-foreground hover:text-foreground transition-colors',
gripClassName ?? 'w-8',
)}
{...attributes}
{...listeners}
>
<GripVertical className="size-4" />
</button>
)}
<div className="flex-1 min-w-0">
{children}
</div>
</div>
);
}