UNPKG

react-rounded-border

Version:

React rounded border is a simple package that can add a round border to elements in React

162 lines (161 loc) 5.68 kB
import { useEffect, useRef } from 'react'; import union from '@turf/union'; import { featureCollection, polygon } from '@turf/helpers'; function getRecursiveNodes(node, nodes) { if (node.getAttribute('rounded-border') !== null) { nodes.push(node); } for (const n of node.children){ getRecursiveNodes(n, nodes); } } function getNodes(node) { const nodes = []; getRecursiveNodes(node, nodes); return nodes; } function getPolygons(nodes, paddingLeft, paddingTop, paddingRight, paddingBottom) { const polygons = []; for (const node of nodes){ if (node.getAttribute('text-rounded-border') !== null) { for (const rect of node.getClientRects()){ polygons.push(polygon([ [ [ rect.left - paddingLeft, rect.top - paddingTop ], [ rect.right + paddingRight, rect.top - paddingTop ], [ rect.right + paddingRight, rect.bottom + paddingBottom ], [ rect.left - paddingLeft, rect.bottom + paddingBottom ], [ rect.left - paddingLeft, rect.top - paddingTop ] ] ])); } } else { const rect = node.getBoundingClientRect(); polygons.push(polygon([ [ [ rect.left - paddingLeft, rect.top - paddingTop ], [ rect.right + paddingRight, rect.top - paddingTop ], [ rect.right + paddingRight, rect.bottom + paddingBottom ], [ rect.left - paddingLeft, rect.bottom + paddingBottom ], [ rect.left - paddingLeft, rect.top - paddingTop ] ] ])); } } return polygons; } function getPoints(polygons) { if (polygons.length === 0) { return []; } if (polygons.length === 1) { return polygons[0].geometry.coordinates; } const uni = union(featureCollection(polygons)); if (uni.geometry.type === 'Polygon') { return uni.geometry.coordinates; } return uni.geometry.coordinates.flat(); } function fastDist(A, B) { if (A[0] === B[0]) { return Math.abs(A[1] - B[1]); } return Math.abs(A[0] - B[0]); } function orientation(A, B, C) { return (B[0] - A[0]) * (C[1] - A[1]) - (B[1] - A[1]) * (C[0] - A[0]) > 0; } export default function({ minBorderRadius, borderRadius, paddingLeft, paddingTop, paddingRight, paddingBottom, fill, stroke }) { const containerRef = useRef(null); const svgRef = useRef(null); useEffect(()=>{ const container = containerRef.current; const svg = svgRef.current; const nodes = getNodes(container); const polygons = getPolygons(nodes, paddingLeft, paddingTop, paddingRight, paddingBottom); const result = getPoints(polygons); let content = ''; let endX, endY; for (const pol of result){ let path = ''; pol.push(pol[1]); for(let i = 1; i < pol.length - 1; i++){ const prev = pol[i - 1], cur = pol[i], next = pol[i + 1]; const radius = Math.min(fastDist(prev, cur) / 2, fastDist(cur, next) / 2, borderRadius); if (radius < minBorderRadius) { // Merge lines } if (prev[0] === cur[0]) { // equal X, vertical if (cur[1] > prev[1]) { path += `L${cur[0]} ${cur[1] - radius}`; } else { path += `L${cur[0]} ${cur[1] + radius}`; } } else { // equal Y, horizontal if (cur[0] > prev[0]) { path += `L${cur[0] - radius} ${cur[1]}`; } else { path += `L${cur[0] + radius} ${cur[1]}`; } } if (next[0] === cur[0]) { // equal X, vertical endX = cur[0]; if (cur[1] > next[1]) { endY = cur[1] - radius; } else { endY = cur[1] + radius; } } else { // equal Y, horizontal endY = cur[1]; if (cur[0] > next[0]) { endX = cur[0] - radius; } else { endX = cur[0] + radius; } } path += `A${radius} ${radius} 0 0 ${+orientation(prev, cur, next)} ${endX} ${endY}`; } content += `<path d="M ${endX} ${endY} ${path}" stroke="${stroke}" fill="${fill}" />`; } svg.innerHTML += content; }, []); return { containerRef, svgRef }; }