{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "sticky-note", "title": "Sticky Note", "description": "A hand-drawn sticky note with a folded corner and five color variants.", "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/sticky-note.tsx", "content": "\"use client\";\n\nimport {\n useCallback,\n useEffect,\n useRef,\n type CSSProperties,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { useRough } from \"@/hooks/use-rough\";\nimport { cn } from \"@/lib/utils\";\nimport {\n randomSeed,\n resolveRoughVars,\n stableSeed,\n type CrumbleColorProps,\n type CrumbleTheme,\n} from \"@/lib/rough\";\n\nexport type StickyNoteColor = \"yellow\" | \"pink\" | \"blue\" | \"green\" | \"orange\";\n\nexport interface StickyNoteProps\n extends Omit, \"title\">, CrumbleColorProps {\n animateOnHover?: boolean;\n color?: StickyNoteColor;\n id?: string;\n rotate?: number;\n theme?: CrumbleTheme;\n title?: ReactNode;\n}\n\nconst noteColors: Record = {\n blue: { bg: \"oklch(0.95 0.05 240)\", border: \"oklch(0.65 0.12 240)\" },\n green: { bg: \"oklch(0.95 0.06 145)\", border: \"oklch(0.60 0.14 145)\" },\n orange: { bg: \"oklch(0.95 0.08 55)\", border: \"oklch(0.65 0.16 55)\" },\n pink: { bg: \"oklch(0.95 0.06 340)\", border: \"oklch(0.65 0.14 340)\" },\n yellow: { bg: \"oklch(0.97 0.09 90)\", border: \"oklch(0.75 0.16 90)\" },\n};\n\nexport function StickyNote({\n animateOnHover = true,\n children,\n className,\n color = \"yellow\",\n fill,\n id,\n rotate = 0,\n stroke,\n strokeMuted,\n style,\n theme: themeProp,\n title,\n ...props\n}: StickyNoteProps) {\n const containerRef = useRef(null);\n const svgRef = useRef(null);\n const foldSvgRef = useRef(null);\n const noteId = id ?? \"sticky-note\";\n const { drawRect, theme } = useRough({\n variant: \"border\",\n stableId: noteId,\n svgRef,\n theme: themeProp,\n });\n const {\n drawLine: drawFoldLine,\n getOptions: getFoldOptions,\n rc: foldRc,\n } = useRough({\n variant: \"fill\",\n stableId: `${noteId}-fold`,\n svgRef: foldSvgRef,\n theme: themeProp,\n });\n const roughStyle = resolveRoughVars({ stroke, strokeMuted, fill });\n\n const { bg, border: borderColor } = noteColors[color];\n const FOLD = 20; // fold triangle size\n\n const draw = useCallback(\n (reseed = false) => {\n const container = containerRef.current;\n const svg = svgRef.current;\n const foldSvg = foldSvgRef.current;\n if (!container || !svg || !foldSvg) return;\n\n const w = container.offsetWidth;\n const h = container.offsetHeight;\n\n // Main border — rough rectangle with bg fill\n svg.replaceChildren();\n svg.setAttribute(\"width\", String(w));\n svg.setAttribute(\"height\", String(h));\n svg.setAttribute(\"viewBox\", `0 0 ${w} ${h}`);\n\n const extraSeed = reseed ? { seed: randomSeed() } : {};\n const borderEl = drawRect(1, 1, w - 2, h - 2, {\n fill: bg,\n fillStyle: \"solid\",\n stroke: borderColor,\n strokeWidth: theme === \"crayon\" ? 2.5 : theme === \"ink\" ? 1.5 : 1,\n ...extraSeed,\n });\n if (borderEl) svg.appendChild(borderEl);\n\n // Fold triangle — bottom-right corner\n const foldSize =\n FOLD + (theme === \"crayon\" ? 4 : theme === \"ink\" ? 2 : 0);\n foldSvg.replaceChildren();\n foldSvg.setAttribute(\"width\", String(foldSize + 4));\n foldSvg.setAttribute(\"height\", String(foldSize + 4));\n foldSvg.setAttribute(\"viewBox\", `0 0 ${foldSize + 4} ${foldSize + 4}`);\n\n // Folded corner triangle (the turned-back part — lighter shade)\n const foldRenderer = foldRc.current;\n if (foldRenderer) {\n const foldPoly = foldRenderer.polygon(\n [\n [2, foldSize],\n [foldSize, 2],\n [foldSize, foldSize],\n ],\n getFoldOptions({\n fill: \"oklch(0.88 0.08 90 / 60%)\",\n fillStyle: \"solid\",\n stroke: borderColor,\n strokeWidth: theme === \"crayon\" ? 2 : 1,\n }),\n );\n foldSvg.appendChild(foldPoly);\n }\n\n // Shadow hint on the fold (a rough line)\n const foldLine = drawFoldLine(2, foldSize, foldSize, 2, {\n seed: stableSeed(`${noteId}-fold-line`),\n stroke: borderColor,\n strokeWidth: theme === \"crayon\" ? 2 : 1,\n });\n if (foldLine) foldSvg.appendChild(foldLine);\n },\n [\n bg,\n borderColor,\n drawFoldLine,\n drawRect,\n foldRc,\n getFoldOptions,\n noteId,\n theme,\n ],\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 const rotateStyle: CSSProperties =\n rotate !== 0\n ? { transform: `rotate(${rotate}deg)`, transformOrigin: \"center center\" }\n : {};\n\n return (\n {\n if (animateOnHover) draw(true);\n }}\n onMouseLeave={() => {\n if (animateOnHover) draw(false);\n }}\n {...props}\n >\n \n \n\n {/* Fold svg — bottom right */}\n \n\n
\n {title ? (\n \n {title}\n

\n ) : null}\n
{children}
\n
\n \n \n );\n}\n", "type": "registry:ui", "target": "components/crumble/ui/sticky-note.tsx" } ], "type": "registry:ui" }