{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "notebook", "title": "Notebook", "description": "A hand-drawn lined paper container with ruled lines and a red margin.", "dependencies": [ "roughjs" ], "registryDependencies": [ "https://www.bydefaulthuman.fun/r/use-rough.json", "utils", "https://www.bydefaulthuman.fun/r/crumble-theme.json", "https://www.bydefaulthuman.fun/r/rough-lib.json" ], "files": [ { "path": "registry/new-york/ui/notebook.tsx", "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, type HTMLAttributes } from \"react\";\nimport { useRough } from \"@/hooks/use-rough\";\nimport { cn } from \"@/lib/utils\";\nimport {\n resolveRoughVars,\n stableSeed,\n type CrumbleColorProps,\n type CrumbleTheme,\n} from \"@/lib/rough\";\n\nexport interface NotebookProps\n extends HTMLAttributes, CrumbleColorProps {\n id?: string;\n lineColor?: string;\n lineSpacing?: number;\n marginColor?: string;\n showMargin?: boolean;\n theme?: CrumbleTheme;\n}\n\nexport function Notebook({\n children,\n className,\n fill,\n id,\n lineColor,\n lineSpacing = 24,\n marginColor,\n showMargin = true,\n stroke,\n strokeMuted,\n style,\n theme: themeProp,\n ...props\n}: NotebookProps) {\n const containerRef = useRef(null);\n const linesCanvasRef = useRef(null);\n const borderSvgRef = useRef(null);\n const notebookId = id ?? \"notebook\";\n const { drawRect: drawBorder, theme } = useRough({\n variant: \"border\",\n stableId: notebookId,\n svgRef: borderSvgRef,\n theme: themeProp,\n });\n const { drawLine: drawRuledLine } = useRough({\n variant: \"border\",\n svgRef: linesCanvasRef,\n theme: themeProp,\n });\n const roughStyle = resolveRoughVars({ stroke, strokeMuted, fill });\n\n const MARGIN_X = showMargin ? 40 : 0;\n\n const draw = useCallback(() => {\n const container = containerRef.current;\n const linesSvg = linesCanvasRef.current;\n const borderSvg = borderSvgRef.current;\n if (!container || !linesSvg || !borderSvg) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n\n const lc =\n lineColor ??\n (theme === \"ink\"\n ? \"oklch(0.75 0.06 240 / 40%)\"\n : \"oklch(0.72 0.06 240 / 35%)\");\n const mc = marginColor ?? \"oklch(0.70 0.14 20 / 50%)\";\n\n // Border\n borderSvg.replaceChildren();\n borderSvg.setAttribute(\"width\", String(w));\n borderSvg.setAttribute(\"height\", String(h));\n borderSvg.setAttribute(\"viewBox\", `0 0 ${w} ${h}`);\n const borderEl = drawBorder(1, 1, w - 2, h - 2, {\n fill: \"none\",\n stroke: \"var(--cr-stroke-muted)\",\n strokeWidth: theme === \"crayon\" ? 2 : 1,\n });\n if (borderEl) borderSvg.appendChild(borderEl);\n\n // Ruled lines + margin\n linesSvg.replaceChildren();\n linesSvg.setAttribute(\"width\", String(w));\n linesSvg.setAttribute(\"height\", String(h));\n linesSvg.setAttribute(\"viewBox\", `0 0 ${w} ${h}`);\n // Draw horizontal ruled lines starting after header area\n const startY = lineSpacing + 8;\n for (let y = startY; y < h - 8; y += lineSpacing) {\n const lineEl = drawRuledLine(8, y, w - 8, y, {\n seed: stableSeed(`${notebookId}-line-${Math.round(y)}`),\n roughness: theme === \"pencil\" ? 0.3 : theme === \"ink\" ? 0.1 : 0.5,\n stroke: lc,\n strokeWidth: 0.6,\n });\n if (lineEl) linesSvg.appendChild(lineEl);\n }\n\n // Margin vertical line\n if (showMargin) {\n const marginLine = drawRuledLine(MARGIN_X, 8, MARGIN_X, h - 8, {\n seed: stableSeed(`${notebookId}-margin`),\n roughness: theme === \"pencil\" ? 0.4 : 0.2,\n stroke: mc,\n strokeWidth: theme === \"crayon\" ? 1.5 : 0.8,\n });\n if (marginLine) linesSvg.appendChild(marginLine);\n }\n }, [\n MARGIN_X,\n drawBorder,\n drawRuledLine,\n lineColor,\n lineSpacing,\n marginColor,\n notebookId,\n showMargin,\n theme,\n ]);\n\n useEffect(() => {\n const rid = requestAnimationFrame(() => draw());\n return () => cancelAnimationFrame(rid);\n }, [draw]);\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n const ro = new ResizeObserver(() => draw());\n ro.observe(container);\n return () => ro.disconnect();\n }, [draw]);\n\n return (\n \n {/* Ruled lines layer (behind content) */}\n \n {/* Border layer */}\n \n {/* Content — padded to respect margin and line spacing */}\n \n {children}\n \n \n );\n}\n", "type": "registry:ui", "target": "components/crumble/ui/notebook.tsx" } ], "type": "registry:ui" }