Container Box Drawing
ns-ui/container-box-drawingFreeComponent
A container with real box-drawing chrome — an inline title breaking the top rule, upgrading to a double-line border character by character on hover or focus.
Live preview
live · rendered by the registry
Rendered by ns-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://design.helpmarq.com/r/container-box-drawing.jsonSource
1"use client";23import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";45type Role = "corner-tl" | "corner-tr" | "corner-bl" | "corner-br" | "h" | "v";6type Cell = { char: string } | { refIndex: number; role: Role };78const SINGLE: Record<Role, string> = {9 "corner-tl": "┌",10 "corner-tr": "┐",11 "corner-bl": "└",12 "corner-br": "┘",13 h: "─",14 v: "│",15};16const DOUBLE: Record<Role, string> = {17 "corner-tl": "╔",18 "corner-tr": "╗",19 "corner-bl": "╚",20 "corner-br": "╝",21 h: "═",22 v: "║",23};2425const LINE_HEIGHT = 1.4;2627export interface RuleFrameProps {28 /** sits inline in the top rule, like a fieldset legend */29 title?: string;30 children?: React.ReactNode;31 className?: string;32}3334// Builds every border position once, as plain data, in true clockwise order35// starting at the top-left corner: each rule-character position gets a36// stable `refIndex` (0..N-1) assigned by pure traversal order, never by a37// ref-callback side effect. That matters because React tears down and sets38// up a ref callback on every render whose identity changed (ours always39// does, since it closes over per-render data) — if the index came from an40// incrementing counter read inside the callback itself, an old callback's41// teardown firing after a new callback's setup would scramble the count.42// Indexing from precomputed data sidesteps that: every callback, old or43// new, only ever writes to the one slot it was built for.44function buildPerimeter(cols: number, rows: number, title?: string) {45 const role: Role[] = [];46 let n = 0;47 const alloc = (r: Role) => {48 role[n] = r;49 return n++;50 };5152 // `cols`/`rows` are the FULL measured box in character units, corners53 // included. Each straight edge holds two of those cells for its own54 // corners, so the run of dash/bar glyphs between them is cols-2 / rows-255 // long, not cols / rows — using the full count here was the bug: it made56 // every straight edge two characters longer than the box it was supposed57 // to tile, so the perimeter never closed.58 const innerCols = Math.max(0, cols - 2);59 const innerRows = Math.max(0, rows - 2);6061 const titleText = title ? ` ${title} ` : "";62 const titleCols = Math.min(titleText.length, innerCols);63 const leftDashes = Math.max(0, Math.floor((innerCols - titleCols) / 2));6465 const tl: Cell = { refIndex: alloc("corner-tl"), role: "corner-tl" };66 const top: Cell[] = [];67 for (let i = 0; i < innerCols; i++) {68// … truncated
Files it writes
More from ns-ui
All 228 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Accordion Latchaccordion-latch | ns-ui | Components | Free | no deps | |
| Approval Inline Diffapproval-inline-diff | ns-ui | Components | Free | no deps | |
| ASCII Engraving Contourascii-engraving-contour | ns-ui | Components | Free | no deps | |
| ASCII Globe Spinascii-globe-spin | ns-ui | Components | Free | no deps | |
| ASCII Torus Donutascii-torus-donut | ns-ui | Components | Free | no deps | |
| Autosave Ratchetautosave-ratchet | ns-ui | Components | Free | no deps | |
| Avatar Stack Flockavatar-stack-flock | ns-ui | Components | Free | no deps | |
| Background ASCII Ditherbackground-ascii-dither | ns-ui | Components | Free | no deps |
