pixalynx/edge-connector
Configures USB-A, Micro-USB, and USB-C PCB-edge connectors with exposed copper contacts and automatically shaped, positioned board tongues.
- Version
- 1.0.0
- License
- GPL-3.0-only
- Stars
- 0
UsbEdgeConnectorBoard.tsx
import type { ComponentProps } from "react"
import { UsbAEdgeConnector, usbAEdgeConnectorGeometry, type UsbAEdgeConnectorProps } from "./UsbAEdgeConnector"
import { UsbCEdgeConnector, usbCEdgeConnectorGeometry, type UsbCEdgeConnectorProps } from "./UsbCEdgeConnector"
import { MicroUsbEdgeConnector, microUsbEdgeConnectorGeometry, type MicroUsbEdgeConnectorProps } from "./MicroUsbEdgeConnector"
export type UsbEdgeConnectorKind = "usb-a" | "usb-c" | "micro-usb"
export type UsbBoardEdge = "top" | "right" | "bottom" | "left"
type PositionedProps = "pcbX" | "pcbY" | "pcbRotation" | "pcbPositionAnchor" | "pcbRelativeTo" | "pcbLayout"
type BoardOptions = Omit<
ComponentProps<"board">,
PositionedProps | "width" | "height" | "outline" | "thickness" |
"outlineOffsetX" | "outlineOffsetY" | "boardAnchorPosition" |
"boardAnchorAlignment" | "anchorAlignment" | "borderRadius"
> & {
/** Overall board bounds in millimetres, including the tongue. */
width: number
height: number
edge?: UsbBoardEdge
/** Millimetres along the edge: positive is +X on top/bottom, +Y on left/right. */
offset?: number
}
export type UsbEdgeConnectorBoardProps = BoardOptions & (
| { connector: "usb-a"; connectorProps: Omit<UsbAEdgeConnectorProps, PositionedProps> }
| { connector: "usb-c"; connectorProps: Omit<UsbCEdgeConnectorProps, PositionedProps> }
| { connector: "micro-usb"; connectorProps: Omit<MicroUsbEdgeConnectorProps, PositionedProps> }
)
const geometries = {
"usb-a": usbAEdgeConnectorGeometry,
"usb-c": usbCEdgeConnectorGeometry,
"micro-usb": microUsbEdgeConnectorGeometry,
}
/** Internal layout calculation shared by the wrapper and its geometry tests. */
export function getUsbEdgeBoardLayout({ connector, width, height, edge = "top", offset = 0 }: {
connector: UsbEdgeConnectorKind
width: number
height: number
edge?: UsbBoardEdge
offset?: number
}) {
if (![width, height, offset].every(Number.isFinite) || width <= 0 || height <= 0) {
throw new Error("USB edge board width and height must be positive millimetres; offset must be finite.")
}
const geometry = geometries[connector]
if (!geometry) throw new Error(`Unknown USB edge connector: ${connector}`)
const rotations = { top: 0, left: 90, bottom: 180, right: 270 } as const
const rotation = rotations[edge]
if (rotation === undefined) throw new Error(`Unknown board edge: ${edge}`)
const sideways = edge === "left" || edge === "right"
const localWidth = sideways ? height : width
const localHeight = sideways ? width : height
const localOffset = edge === "bottom" || edge === "right" ? -offset : offset
const connectorY = localHeight / 2 - geometry.tipY
const profile = geometry.edgeProfile.map(({ x, y }) => ({ x: x + localOffset, y: y + connectorY }))
if (profile.some(p => Math.abs(p.x) > localWidth / 2 - 0.2)) {
throw new Error("Connector profile does not fit this edge. Increase the board size or reduce offset (0.2 mm shoulder margin required).")
}
if (Math.min(...profile.map(p => p.y)) <= -localHeight / 2 + 0.2) {
throw new Error("Board is too short for the connector tongue and reliefs. Increase the board size.")
}
const rotate = ({ x, y }: { x: number; y: number }) => {
switch (edge) {
case "top": return { x, y }
case "left": return { x: -y, y: x }
case "bottom": return { x: -x, y: -y }
case "right": return { x: y, y: -x }
}
}
const position = rotate({ x: localOffset, y: connectorY })
return {
outline: [
{ x: -localWidth / 2, y: -localHeight / 2 },
{ x: -localWidth / 2, y: profile[0].y },
...profile,
{ x: localWidth / 2, y: profile[profile.length - 1].y },
{ x: localWidth / 2, y: -localHeight / 2 },
].map(rotate),
thickness: geometry.boardThickness,
minBoardEdgeClearance: connector === "usb-c" ? 0.15 : 0.2,
placement: { pcbX: position.x, pcbY: position.y, pcbRotation: rotation },
}
}
/** A rectangular carrier with one automatically shaped and placed USB edge connector. */
export function UsbEdgeConnectorBoard(props: UsbEdgeConnectorBoardProps) {
const { connector, connectorProps, width, height, edge, offset, children, ...boardProps } = props
const layout = getUsbEdgeBoardLayout({ connector, width, height, edge, offset })
const contact = connector === "usb-a"
? <UsbAEdgeConnector {...connectorProps} {...layout.placement} />
: connector === "usb-c"
? <UsbCEdgeConnector {...connectorProps} {...layout.placement} />
: <MicroUsbEdgeConnector {...connectorProps} {...layout.placement} />
return (
<board
minBoardEdgeClearance={layout.minBoardEdgeClearance}
{...boardProps}
width={width}
height={height}
outline={layout.outline}
thickness={layout.thickness}
>
{contact}
{children}
</board>
)
}