({ list, hasDeleteButton = false, hasFavoriteButton = false, sorting = 'none', title })
| 78 | .trim(); |
| 79 | |
| 80 | const ComponentList = ({ list, hasDeleteButton = false, hasFavoriteButton = false, sorting = 'none', title }) => { |
| 81 | const scrollRef = useRef(null); |
| 82 | const GAP_PX = 16; |
| 83 | const [hoveredKey, setHoveredKey] = useState(null); |
| 84 | const clearSlotRef = useRef(null); |
| 85 | const clearBtnRef = useRef(null); |
| 86 | const CLEAR_APPEAR_DEBOUNCE_MS = 300; |
| 87 | |
| 88 | const setHoverToItemAtPoint = useCallback((x, y) => { |
| 89 | try { |
| 90 | const el = document.elementFromPoint(x, y); |
| 91 | let node = el; |
| 92 | while (node && node !== document.body) { |
| 93 | if (node.dataset && node.dataset.itemKey) { |
| 94 | setHoveredKey(node.dataset.itemKey); |
| 95 | return; |
| 96 | } |
| 97 | node = node.parentElement; |
| 98 | } |
| 99 | setHoveredKey(null); |
| 100 | } catch (e) { |
| 101 | // noop |
| 102 | } |
| 103 | }, []); |
| 104 | |
| 105 | const items = useMemo(() => { |
| 106 | if (!list) return []; |
| 107 | const entries = Array.isArray(list) ? list : Object.entries(list).map(([key, meta]) => ({ key, ...meta })); |
| 108 | |
| 109 | const mapToItem = entry => { |
| 110 | const key = entry.key ?? entry?.id ?? null; |
| 111 | const meta = entry.key ? entry : (componentMetadata?.[entry] ?? {}); |
| 112 | const fullKey = key || entry; |
| 113 | const [cat, comp] = (fullKey || '').split('/'); |
| 114 | const title = fromPascal(meta?.name ?? comp); |
| 115 | return { |
| 116 | key: fullKey, |
| 117 | categoryKey: cat, |
| 118 | componentKey: comp, |
| 119 | categoryLabel: fromPascal(meta?.category ?? cat), |
| 120 | title, |
| 121 | description: meta?.description ?? '', |
| 122 | videoUrl: meta?.videoUrl ?? '', |
| 123 | tags: Array.isArray(meta?.tags) ? meta.tags : [], |
| 124 | docsUrl: meta?.docsUrl, |
| 125 | isNew: NEW.includes(title) |
| 126 | }; |
| 127 | }; |
| 128 | |
| 129 | let arr = entries |
| 130 | .filter(e => { |
| 131 | const key = (e.key ?? e)?.toString?.() ?? ''; |
| 132 | return key.includes('/') && (e.key ? true : !!componentMetadata[key]); |
| 133 | }) |
| 134 | .map(mapToItem); |
| 135 | |
| 136 | if (sorting === 'alphabetical') { |
| 137 | // New components are sorted to the top (in NEW list order), then the rest alphabetically. |
nothing calls this directly
no test coverage detected