@hunterchen/canvas 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +67 -8
  2. package/dist/components/canvas/canvas.d.ts +3 -4
  3. package/dist/components/canvas/canvas.d.ts.map +1 -1
  4. package/dist/components/canvas/canvas.js +3 -6
  5. package/dist/components/canvas/canvas.js.map +1 -1
  6. package/dist/components/canvas/component.js +2 -2
  7. package/dist/components/canvas/component.js.map +1 -1
  8. package/dist/components/canvas/navbar/index.d.ts +4 -10
  9. package/dist/components/canvas/navbar/index.d.ts.map +1 -1
  10. package/dist/components/canvas/navbar/index.js +16 -18
  11. package/dist/components/canvas/navbar/index.js.map +1 -1
  12. package/dist/components/canvas/navbar/single-button.d.ts +3 -6
  13. package/dist/components/canvas/navbar/single-button.d.ts.map +1 -1
  14. package/dist/components/canvas/navbar/single-button.js +9 -8
  15. package/dist/components/canvas/navbar/single-button.js.map +1 -1
  16. package/dist/components/canvas/wrapper.d.ts.map +1 -1
  17. package/dist/components/canvas/wrapper.js +1 -2
  18. package/dist/components/canvas/wrapper.js.map +1 -1
  19. package/dist/index.d.ts +1 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +0 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/styles.css +1 -1
  24. package/dist/types/index.d.ts +28 -7
  25. package/dist/types/index.d.ts.map +1 -1
  26. package/dist/types/index.js +1 -9
  27. package/dist/types/index.js.map +1 -1
  28. package/package.json +1 -2
  29. package/src/components/canvas/canvas.tsx +7 -14
  30. package/src/components/canvas/component.tsx +2 -2
  31. package/src/components/canvas/navbar/index.tsx +28 -64
  32. package/src/components/canvas/navbar/single-button.tsx +18 -33
  33. package/src/components/canvas/wrapper.tsx +1 -2
  34. package/src/index.ts +1 -2
  35. package/src/types/index.ts +27 -7
  36. package/dist/components/canvas/reset.d.ts +0 -5
  37. package/dist/components/canvas/reset.d.ts.map +0 -1
  38. package/dist/components/canvas/reset.js +0 -7
  39. package/dist/components/canvas/reset.js.map +0 -1
  40. package/src/components/canvas/reset.tsx +0 -21
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC3F,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACxG,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAGxF,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAG5F,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AAGpC,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAGrE,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC3F,YAAY,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AACxG,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAGxF,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAG5F,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AAGpC,YAAY,EAAE,kBAAkB,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -15,5 +15,4 @@ export { usePerformanceMode as usePerformanceModeLegacy } from './hooks/usePerfo
15
15
  export * from './lib/canvas';
16
16
  export * from './lib/constants';
17
17
  export * from './utils/performance';
18
- export { CanvasSection } from './types';
19
18
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAErE,WAAW;AACX,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAGxG,QAAQ;AACR,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAE5F,YAAY;AACZ,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AAIpC,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAErE,WAAW;AACX,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE3F,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAGxG,QAAQ;AACR,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,kBAAkB,IAAI,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAE5F,YAAY;AACZ,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--canvas-beige:#f7f1e5;--canvas-coral:#ffb5a7;--canvas-lilac:#d9c8e6;--canvas-salmon:#ffa585;--canvas-heavy:#3c204c;--canvas-emphasis:#513b7a;--canvas-active:#8f57ad;--canvas-tinted:#c9a7db;--canvas-medium:#776780;--canvas-light:#c3b8cb;--canvas-faint-lilac:#f5f2f7;--canvas-offwhite:#fdfcfd;--canvas-highlight:#f5f2f7;--canvas-border-light:0 0% 89%}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.-top-10{top:-2.5rem}.bottom-12{bottom:3rem}.bottom-24{bottom:6rem}.bottom-4{bottom:1rem}.left-1\/2{left:50%}.left-4{left:1rem}.top-1\/2{top:50%}.top-24{top:6rem}.top-6{top:1.5rem}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.z-\[1000\]{z-index:1000}.m-auto{margin:auto}.mb-4{margin-bottom:1rem}.block{display:block}.flex{display:flex}.hidden{display:none}.h-5{height:1.25rem}.h-auto{height:auto}.h-full{height:100%}.w-5{width:1.25rem}.w-full{width:100%}.flex-shrink-0{flex-shrink:0}.grow{flex-grow:1}.origin-center{transform-origin:center}.origin-top-left{transform-origin:top left}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-\[url\(\'\/customcursor\.svg\'\)\2c auto\]{cursor:url(/customcursor.svg),auto}.cursor-default{cursor:default}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.overflow-hidden{overflow:hidden}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-\[10px\]{border-radius:10px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border,.border-\[1px\]{border-width:1px}.border-border{border-color:hsl(var(--border))}.bg-\[\#EEE2FB\]{--tw-bg-opacity:1;background-color:rgb(238 226 251/var(--tw-bg-opacity,1))}.bg-canvas-highlight{background-color:var(--canvas-highlight)}.bg-canvas-offwhite{background-color:var(--canvas-offwhite)}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.bg-\[radial-gradient\(\#776780_1\.5px\2c transparent_1px\)\]{background-image:radial-gradient(#776780 1.5px,transparent 0)}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.bg-none{background-image:none}.from-black\/10{--tw-gradient-from:rgba(0,0,0,.1) var(--tw-gradient-from-position);--tw-gradient-to:transparent var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-\[1px\]{padding-left:1px;padding-right:1px}.py-1{padding-top:.25rem;padding-bottom:.25rem}.pb-\[2\.5px\]{padding-bottom:2.5px}.pt-\[1px\]{padding-top:1px}.text-center{text-align:center}.font-canvas-figtree{font-family:var(--font-figtree)}.font-canvas-jetbrains-mono{font-family:var(--font-jetbrainsmono)}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-\[\#543C5AB2\]{color:#543c5ab2}.text-canvas-emphasis{color:var(--canvas-emphasis)}.text-canvas-heavy{color:var(--canvas-heavy)}.text-canvas-light{color:var(--canvas-light)}.text-canvas-medium{color:var(--canvas-medium)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.opacity-100{opacity:1}.opacity-35{opacity:.35}.opacity-60{opacity:.6}.shadow-\[0_20px_40px_rgba\(103\2c 86\2c 86\2c 0\.15\)\]{--tw-shadow:0 20px 40px hsla(0,9%,37%,.15);--tw-shadow-colored:0 20px 40px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_6px_12px_rgba\(0\2c 0\2c 0\2c 0\.10\)\]{--tw-shadow:0 6px 12px rgba(0,0,0,.1);--tw-shadow-colored:0 6px 12px var(--tw-shadow-color)}.shadow-\[0_6px_12px_rgba\(0\2c 0\2c 0\2c 0\.10\)\],.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.blur{--tw-blur:blur(8px)}.blur,.drop-shadow{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.duration-200{animation-duration:.2s}.running{animation-play-state:running}.\[background-size\:22px_22px\]{background-size:22px 22px}.hover\:bg-gray-600:hover{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity,1))}@media (min-width:640px){.sm\:top-4{top:1rem}}@media (min-width:768px){.md\:bottom-4{bottom:1rem}.md\:inline{display:inline}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--canvas-beige:#f7f1e5;--canvas-coral:#ffb5a7;--canvas-lilac:#d9c8e6;--canvas-salmon:#ffa585;--canvas-heavy:#3c204c;--canvas-emphasis:#513b7a;--canvas-active:#8f57ad;--canvas-tinted:#c9a7db;--canvas-medium:#776780;--canvas-light:#c3b8cb;--canvas-faint-lilac:#f5f2f7;--canvas-offwhite:#fdfcfd;--canvas-highlight:#f5f2f7;--canvas-border-light:0 0% 89%}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.-top-10{top:-2.5rem}.bottom-12{bottom:3rem}.bottom-24{bottom:6rem}.left-1\/2{left:50%}.left-4{left:1rem}.top-1\/2{top:50%}.top-24{top:6rem}.top-6{top:1.5rem}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.z-\[1000\]{z-index:1000}.m-auto{margin:auto}.mb-4{margin-bottom:1rem}.block{display:block}.flex{display:flex}.hidden{display:none}.h-5{height:1.25rem}.h-auto{height:auto}.h-full{height:100%}.w-5{width:1.25rem}.w-full{width:100%}.flex-shrink-0{flex-shrink:0}.grow{flex-grow:1}.origin-center{transform-origin:center}.origin-top-left{transform-origin:top left}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.overflow-hidden{overflow:hidden}.whitespace-nowrap{white-space:nowrap}.rounded-\[10px\]{border-radius:10px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border,.border-\[1px\]{border-width:1px}.border-border{border-color:hsl(var(--border))}.bg-\[\#EEE2FB\]{--tw-bg-opacity:1;background-color:rgb(238 226 251/var(--tw-bg-opacity,1))}.bg-canvas-highlight{background-color:var(--canvas-highlight)}.bg-canvas-offwhite{background-color:var(--canvas-offwhite)}.bg-\[radial-gradient\(\#776780_1\.5px\2c transparent_1px\)\]{background-image:radial-gradient(#776780 1.5px,transparent 0)}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.bg-none{background-image:none}.from-black\/10{--tw-gradient-from:rgba(0,0,0,.1) var(--tw-gradient-from-position);--tw-gradient-to:transparent var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.object-contain{-o-object-fit:contain;object-fit:contain}.p-1{padding:.25rem}.p-2{padding:.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.px-\[1px\]{padding-left:1px;padding-right:1px}.py-1{padding-top:.25rem;padding-bottom:.25rem}.pb-\[2\.5px\]{padding-bottom:2.5px}.pt-\[1px\]{padding-top:1px}.text-center{text-align:center}.font-canvas-figtree{font-family:var(--font-figtree)}.font-canvas-jetbrains-mono{font-family:var(--font-jetbrainsmono)}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-\[\#543C5AB2\]{color:#543c5ab2}.text-canvas-emphasis{color:var(--canvas-emphasis)}.text-canvas-heavy{color:var(--canvas-heavy)}.text-canvas-light{color:var(--canvas-light)}.text-canvas-medium{color:var(--canvas-medium)}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.opacity-100{opacity:1}.opacity-35{opacity:.35}.opacity-60{opacity:.6}.shadow-\[0_20px_40px_rgba\(103\2c 86\2c 86\2c 0\.15\)\]{--tw-shadow:0 20px 40px hsla(0,9%,37%,.15);--tw-shadow-colored:0 20px 40px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_6px_12px_rgba\(0\2c 0\2c 0\2c 0\.10\)\]{--tw-shadow:0 6px 12px rgba(0,0,0,.1);--tw-shadow-colored:0 6px 12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.blur{--tw-blur:blur(8px)}.blur,.drop-shadow{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.duration-200{animation-duration:.2s}.running{animation-play-state:running}.\[background-size\:22px_22px\]{background-size:22px 22px}@media (min-width:640px){.sm\:top-4{top:1rem}}@media (min-width:768px){.md\:bottom-4{bottom:1rem}.md\:inline{display:inline}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}
@@ -8,12 +8,33 @@ export interface SectionCoordinates {
8
8
  width: number;
9
9
  height: number;
10
10
  }
11
- export declare enum CanvasSection {
12
- Home = "home",
13
- About = "about",
14
- Projects = "projects",
15
- Sponsors = "sponsors",
16
- FAQ = "faq",
17
- Team = "team"
11
+ /**
12
+ * CanvasSection is now a generic string type.
13
+ * Apps define their own section IDs as strings.
14
+ */
15
+ export type CanvasSection = string;
16
+ /**
17
+ * Configuration for a single navigation item.
18
+ * Combines section identity, display info, and coordinates.
19
+ */
20
+ export interface NavItem {
21
+ /** Unique identifier for this section */
22
+ id: string;
23
+ /** Display label shown in the navbar */
24
+ label: string;
25
+ /** Lucide icon name or a custom icon component */
26
+ icon: string | React.ComponentType<{
27
+ className?: string;
28
+ }>;
29
+ /** X coordinate on the canvas */
30
+ x: number;
31
+ /** Y coordinate on the canvas */
32
+ y: number;
33
+ /** Width of the section viewport */
34
+ width: number;
35
+ /** Height of the section viewport */
36
+ height: number;
37
+ /** If true, clicking this section triggers the reset/home behavior */
38
+ isHome?: boolean;
18
39
  }
19
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oBAAY,aAAa;IACvB,IAAI,SAAS;IACb,KAAK,UAAU;IACf,QAAQ,aAAa;IACrB,QAAQ,aAAa;IACrB,GAAG,QAAQ;IACX,IAAI,SAAS;CACd"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,kBAAkB;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC;AAEnC;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,IAAI,EAAE,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,iCAAiC;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,iCAAiC;IACjC,CAAC,EAAE,MAAM,CAAC;IACV,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB"}
@@ -2,13 +2,5 @@
2
2
  * Generic types for the canvas library
3
3
  * Apps should extend these types with their specific enums and constants
4
4
  */
5
- export var CanvasSection;
6
- (function (CanvasSection) {
7
- CanvasSection["Home"] = "home";
8
- CanvasSection["About"] = "about";
9
- CanvasSection["Projects"] = "projects";
10
- CanvasSection["Sponsors"] = "sponsors";
11
- CanvasSection["FAQ"] = "faq";
12
- CanvasSection["Team"] = "team";
13
- })(CanvasSection || (CanvasSection = {}));
5
+ export {};
14
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,MAAM,CAAN,IAAY,aAOX;AAPD,WAAY,aAAa;IACvB,8BAAa,CAAA;IACb,gCAAe,CAAA;IACf,sCAAqB,CAAA;IACrB,sCAAqB,CAAA;IACrB,4BAAW,CAAA;IACX,8BAAa,CAAA;AACf,CAAC,EAPW,aAAa,KAAb,aAAa,QAOxB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hunterchen/canvas",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "A React-based canvas library for creating pannable, zoomable, and interactive canvas experiences.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -56,7 +56,6 @@
56
56
  "@types/node": "^25.0.6",
57
57
  "@types/react": "19.0.12",
58
58
  "@types/react-dom": "19.0.4",
59
- "next": "^16.1.1",
60
59
  "tailwindcss": "^3.4.19",
61
60
  "tailwindcss-animate": "^1.0.7",
62
61
  "typescript": "^5.8.2"
@@ -39,7 +39,7 @@ import {
39
39
  import useWindowDimensions from "../../hooks/useWindowDimensions";
40
40
  import Navbar from "./navbar";
41
41
  import Toolbar from "./toolbar";
42
- import { CanvasSection, type SectionCoordinates } from "../../types";
42
+ import type { CanvasSection, NavItem, SectionCoordinates } from "../../types";
43
43
  import { CanvasWrapper } from "./wrapper";
44
44
  import { usePerformanceMode } from "../../hooks/usePerformanceMode";
45
45
  import type { ReactNode } from "react";
@@ -49,9 +49,8 @@ interface Props {
49
49
  children: React.ReactNode;
50
50
 
51
51
  // Navbar data (optional). If omitted, navbar is hidden.
52
- navbarCoordinates?: Record<CanvasSection, SectionCoordinates>;
53
- navbarSections?: CanvasSection[];
54
- navbarHomeSection?: CanvasSection;
52
+ /** Array of navigation items for the navbar. If omitted, navbar is hidden. */
53
+ navItems?: NavItem[];
55
54
 
56
55
  // ============== Intro Animation Customization ==============
57
56
  /** Disable intro animation entirely */
@@ -83,9 +82,7 @@ const stopAllMotion = (
83
82
  const Canvas: FC<Props> = ({
84
83
  children,
85
84
  homeCoordinates,
86
- navbarCoordinates,
87
- navbarSections,
88
- navbarHomeSection,
85
+ navItems,
89
86
  skipIntro = false,
90
87
  introContent,
91
88
  loadingText,
@@ -98,9 +95,7 @@ const Canvas: FC<Props> = ({
98
95
 
99
96
  const { mode } = usePerformanceMode();
100
97
 
101
- const navSections = navbarSections ?? Object.values(CanvasSection);
102
- const navHomeSection = navbarHomeSection ?? CanvasSection.Home;
103
- const hasNavbarData = Boolean(navbarCoordinates);
98
+ const hasNavbar = Boolean(navItems && navItems.length > 0);
104
99
 
105
100
  const sceneWidth = canvasWidth;
106
101
  const sceneHeight = canvasHeight;
@@ -616,13 +611,11 @@ const Canvas: FC<Props> = ({
616
611
  {animationStage >= 2 && (
617
612
  <>
618
613
  <Toolbar homeCoordinates={offsetHomeCoordinates} />
619
- {hasNavbarData && navbarCoordinates ? (
614
+ {hasNavbar && navItems ? (
620
615
  <Navbar
621
616
  panToOffset={handlePanToOffset}
622
617
  onReset={onResetViewAndItems}
623
- coordinates={navbarCoordinates}
624
- sections={navSections as CanvasSection[]}
625
- homeSection={navHomeSection}
618
+ items={navItems}
626
619
  />
627
620
  ) : null}
628
621
  </>
@@ -1,7 +1,7 @@
1
1
  import { type FC, useEffect, useState } from "react";
2
2
  import type { SectionCoordinates } from "../../types";
3
3
  import { useCanvasContext } from "../../contexts/CanvasContext";
4
- import Image from "next/image";
4
+ import { motion } from "framer-motion";
5
5
  import useWindowDimensions from "../../hooks/useWindowDimensions";
6
6
  import { usePerformanceMode } from "../../hooks/usePerformanceMode";
7
7
  import {
@@ -210,7 +210,7 @@ export const CanvasComponent: FC<CanvasProps> = ({
210
210
  }}
211
211
  >
212
212
  {shouldShowFallback ? (
213
- <Image
213
+ <motion.img
214
214
  src={imageFallback}
215
215
  alt="Canvas Fallback"
216
216
  width={offset?.width ?? 1920}
@@ -1,9 +1,7 @@
1
1
  import { motion, useMotionValueEvent } from "framer-motion";
2
- import { useState, useRef, useEffect, useCallback } from "react";
2
+ import { useState, useRef, useEffect, useCallback, useMemo } from "react";
3
3
  import SingleButton from "./single-button";
4
- // TODO: These should be passed as props or provided via context from the app
5
- // For now, apps will need to re-export from this library
6
- import { CanvasSection } from "../../../types";
4
+ import type { NavItem } from "../../../types";
7
5
  import { useCanvasContext } from "../../../contexts/CanvasContext";
8
6
  import useWindowDimensions from "../../../hooks/useWindowDimensions";
9
7
  import { usePerformanceMode } from "../../../hooks/usePerformanceMode";
@@ -23,18 +21,14 @@ interface NavbarProps {
23
21
  zoom?: number,
24
22
  ) => void;
25
23
  onReset: () => void;
26
- // App must provide section coordinates mapping
27
- coordinates: Record<string, { x: number; y: number; width: number; height: number }>;
28
- sections: CanvasSection[];
29
- homeSection: CanvasSection;
24
+ /** Array of navigation items defining sections, their icons, and coordinates */
25
+ items: NavItem[];
30
26
  }
31
27
 
32
28
  export default function Navbar({
33
29
  panToOffset,
34
30
  onReset,
35
- coordinates,
36
- sections,
37
- homeSection,
31
+ items,
38
32
  }: NavbarProps) {
39
33
  const { x, y, scale, animationStage, setNextTargetSection } =
40
34
  useCanvasContext();
@@ -56,6 +50,9 @@ export default function Navbar({
56
50
  // Derive debounce duration from performance mode
57
51
  const debounceMs = NAVBAR_DEBOUNCE_MS[mode] ?? 0;
58
52
 
53
+ // Find the home section from items
54
+ const homeItem = useMemo(() => items.find((item) => item.isHome), [items]);
55
+
59
56
  // Leading-edge debounce handler
60
57
  const handleDebouncedClick = useCallback(
61
58
  (callback: () => void) => {
@@ -100,25 +97,22 @@ export default function Navbar({
100
97
  useMotionValueEvent(scale, "change", updateExpandedButton);
101
98
 
102
99
  const handlePan = useCallback(
103
- function handlePan(section: CanvasSection) {
104
- setExpandedButton(section);
100
+ function handlePan(item: NavItem) {
101
+ setExpandedButton(item.id);
105
102
  activePans.current++;
106
103
 
107
104
  // Predictive pre-render hint: mark the target section so its CanvasComponent can
108
105
  // render even before it comes fully into view.
109
- setNextTargetSection(section);
106
+ setNextTargetSection(item.id);
110
107
 
111
- if (section === homeSection) {
108
+ if (item.isHome) {
112
109
  onReset();
113
110
  return;
114
111
  }
115
112
 
116
- const sectionCoords = coordinates[section];
117
- if (!sectionCoords) return;
118
-
119
113
  const panCoords = getSectionPanCoordinates({
120
114
  windowDimensions: { width, height },
121
- coords: sectionCoords,
115
+ coords: { x: item.x, y: item.y, width: item.width, height: item.height },
122
116
  targetZoom: defaultZoom,
123
117
  negative: true,
124
118
  });
@@ -134,16 +128,18 @@ export default function Navbar({
134
128
  [panToOffset, onReset, width, height, defaultZoom, setNextTargetSection],
135
129
  );
136
130
 
137
- // Clean up timers on unmount
131
+ // Clean up timers on unmount and pan to home on animation complete
138
132
  useEffect(() => {
139
133
  if (animationStage < 2) return;
140
- handlePan(CanvasSection.Home);
134
+ if (homeItem) {
135
+ handlePan(homeItem);
136
+ }
141
137
  return () => {
142
138
  if (panTimeout.current) clearTimeout(panTimeout.current);
143
139
  if (debounceCooldownTimeout.current)
144
140
  clearTimeout(debounceCooldownTimeout.current);
145
141
  };
146
- }, [handlePan, animationStage]);
142
+ }, [handlePan, animationStage, homeItem]);
147
143
 
148
144
  return (
149
145
  <div
@@ -163,48 +159,16 @@ export default function Navbar({
163
159
  <div className="px-4 md:px-8">
164
160
  <motion.div className="flex select-none items-center justify-center gap-1 rounded-[10px] border-[1px] border-border bg-canvas-offwhite p-1 shadow-[0_6px_12px_rgba(0,0,0,0.10)]">
165
161
  <div className="flex items-center gap-1">
166
- <SingleButton
167
- label="Home"
168
- icon="Home"
169
- onClick={() => handlePan(CanvasSection.Home)}
170
- isPushed={expandedButton === CanvasSection.Home}
171
- onDebouncedClick={handleDebouncedClick}
172
- />
173
- <SingleButton
174
- label="About"
175
- icon="Info"
176
- onClick={() => handlePan(CanvasSection.About)}
177
- isPushed={expandedButton === CanvasSection.About}
178
- onDebouncedClick={handleDebouncedClick}
179
- />
180
- <SingleButton
181
- label="Projects"
182
- icon="LayoutDashboard"
183
- onClick={() => handlePan(CanvasSection.Projects)}
184
- isPushed={expandedButton === CanvasSection.Projects}
185
- onDebouncedClick={handleDebouncedClick}
186
- />
187
- <SingleButton
188
- label="Sponsors"
189
- icon="Handshake"
190
- onClick={() => handlePan(CanvasSection.Sponsors)}
191
- isPushed={expandedButton === CanvasSection.Sponsors}
192
- onDebouncedClick={handleDebouncedClick}
193
- />
194
- <SingleButton
195
- label="FAQ"
196
- icon="HelpCircle"
197
- onClick={() => handlePan(CanvasSection.FAQ)}
198
- isPushed={expandedButton === CanvasSection.FAQ}
199
- onDebouncedClick={handleDebouncedClick}
200
- />
201
- <SingleButton
202
- label="Team"
203
- icon="Users"
204
- onClick={() => handlePan(CanvasSection.Team)}
205
- isPushed={expandedButton === CanvasSection.Team}
206
- onDebouncedClick={handleDebouncedClick}
207
- />
162
+ {items.map((item) => (
163
+ <SingleButton
164
+ key={item.id}
165
+ label={item.label}
166
+ icon={item.icon}
167
+ onClick={() => handlePan(item)}
168
+ isPushed={expandedButton === item.id}
169
+ onDebouncedClick={handleDebouncedClick}
170
+ />
171
+ ))}
208
172
  </div>
209
173
  </motion.div>
210
174
  </div>
@@ -2,39 +2,37 @@ import { useState, useEffect } from "react";
2
2
  import * as LucideIcons from "lucide-react";
3
3
  import { AnimatePresence, motion } from "framer-motion";
4
4
 
5
- type IconName = keyof typeof LucideIcons;
6
-
7
5
  interface SingleButtonProps {
8
6
  label: string;
9
- icon?: IconName;
10
- customIcon?: React.ComponentType<{ className?: string }>;
7
+ /** Lucide icon name or a custom icon component */
8
+ icon: string | React.ComponentType<{ className?: string }>;
11
9
  onClick?: () => void;
12
10
  isPushed: boolean;
13
11
  link?: string;
14
- emailAddress?: string;
15
12
  onDebouncedClick?: (callback: () => void) => void;
16
13
  }
17
14
 
18
15
  export default function SingleButton({
19
16
  label,
20
17
  icon,
21
- customIcon,
22
18
  onClick,
23
19
  isPushed,
24
20
  link,
25
- emailAddress,
26
21
  onDebouncedClick,
27
22
  }: SingleButtonProps) {
28
23
  const [isHovered, setIsHovered] = useState(false);
29
24
  const [showTag, setShowTag] = useState(false);
30
25
  const [copiedEmail, setCopiedEmail] = useState(false);
31
- const Icon = icon ? (LucideIcons[icon] as LucideIcons.LucideIcon) : null;
32
- const CustomIcon = customIcon;
26
+ const isLucideIconName = typeof icon === "string";
27
+ const IconComponent = isLucideIconName
28
+ ? (LucideIcons[icon as keyof typeof LucideIcons] as LucideIcons.LucideIcon | undefined)
29
+ : icon;
33
30
  const TagDelay = 100;
34
31
 
35
- // Ensure either icon or customIcon is provided
36
- if (!Icon && !CustomIcon) {
37
- throw new Error("Either 'icon' or 'customIcon' prop must be provided");
32
+ if (!IconComponent) {
33
+ throw new Error(
34
+ "A valid 'icon' prop is required (Lucide icon name or custom icon component).",
35
+ );
38
36
  }
39
37
 
40
38
  useEffect(() => {
@@ -105,16 +103,10 @@ export default function SingleButton({
105
103
  {isPushed ? (
106
104
  <div className="flex items-center gap-2">
107
105
  <div>
108
- {Icon ? (
109
- <Icon
110
- className={`h-5 w-5 flex-shrink-0 ${isPushed ? "text-canvas-emphasis" : "text-canvas-medium"}`}
111
- />
112
- ) : CustomIcon ? (
113
- <CustomIcon
114
- className={`h-5 w-5 flex-shrink-0 ${isPushed ? "text-white" : "text-canvas-medium"
115
- }`}
116
- />
117
- ) : null}
106
+ <IconComponent
107
+ className={`h-5 w-5 flex-shrink-0 ${isPushed ? (isLucideIconName ? "text-canvas-emphasis" : "text-white") : "text-canvas-medium"
108
+ }`}
109
+ />
118
110
  </div>
119
111
  <motion.span
120
112
  initial={{ opacity: 0, width: 0 }}
@@ -131,17 +123,10 @@ export default function SingleButton({
131
123
  </div>
132
124
  ) : (
133
125
  <div>
134
- {Icon ? (
135
- <Icon
136
- className={`h-5 w-5 flex-shrink-0 ${isPushed ? "text-white" : "text-canvas-medium"
137
- }`}
138
- />
139
- ) : CustomIcon ? (
140
- <CustomIcon
141
- className={`h-5 w-5 flex-shrink-0 ${isPushed ? "text-white" : "text-canvas-medium"
142
- }`}
143
- />
144
- ) : null}
126
+ <IconComponent
127
+ className={`h-5 w-5 flex-shrink-0 ${isPushed ? (isLucideIconName ? "text-canvas-emphasis" : "text-white") : "text-canvas-medium"
128
+ }`}
129
+ />
145
130
  <AnimatePresence>
146
131
  {showTag && !isPushed && (
147
132
  <motion.div
@@ -1,6 +1,5 @@
1
1
  import { motion, type MotionValue, type Transition, useMotionValue } from "framer-motion";
2
2
  import { useState, useEffect, useRef, type ReactNode } from "react";
3
- import Image from "next/image";
4
3
  import {
5
4
  MAX_DIM_RATIO,
6
5
  GROW_TRANSITION,
@@ -41,7 +40,7 @@ interface CanvasWrapperProps {
41
40
  */
42
41
  const DefaultIntroContent = () => (
43
42
  <div className="absolute left-1/2 top-24 flex -translate-x-1/2 flex-col items-center text-center">
44
- <Image
43
+ <motion.img
45
44
  src="/horse.svg"
46
45
  alt="Hack Western Logo"
47
46
  width={64}
package/src/index.ts CHANGED
@@ -22,5 +22,4 @@ export * from './lib/constants';
22
22
  export * from './utils/performance';
23
23
 
24
24
  // Types
25
- export type { SectionCoordinates } from './types';
26
- export { CanvasSection } from './types';
25
+ export type { SectionCoordinates, NavItem, CanvasSection } from './types';
@@ -10,11 +10,31 @@ export interface SectionCoordinates {
10
10
  height: number;
11
11
  }
12
12
 
13
- export enum CanvasSection {
14
- Home = "home",
15
- About = "about",
16
- Projects = "projects",
17
- Sponsors = "sponsors",
18
- FAQ = "faq",
19
- Team = "team",
13
+ /**
14
+ * CanvasSection is now a generic string type.
15
+ * Apps define their own section IDs as strings.
16
+ */
17
+ export type CanvasSection = string;
18
+
19
+ /**
20
+ * Configuration for a single navigation item.
21
+ * Combines section identity, display info, and coordinates.
22
+ */
23
+ export interface NavItem {
24
+ /** Unique identifier for this section */
25
+ id: string;
26
+ /** Display label shown in the navbar */
27
+ label: string;
28
+ /** Lucide icon name or a custom icon component */
29
+ icon: string | React.ComponentType<{ className?: string }>;
30
+ /** X coordinate on the canvas */
31
+ x: number;
32
+ /** Y coordinate on the canvas */
33
+ y: number;
34
+ /** Width of the section viewport */
35
+ width: number;
36
+ /** Height of the section viewport */
37
+ height: number;
38
+ /** If true, clicking this section triggers the reset/home behavior */
39
+ isHome?: boolean;
20
40
  }
@@ -1,5 +0,0 @@
1
- declare const Reset: ({ onResetViewAndItems, }: {
2
- onResetViewAndItems: () => void;
3
- }) => import("react/jsx-runtime").JSX.Element;
4
- export default Reset;
5
- //# sourceMappingURL=reset.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../../src/components/canvas/reset.tsx"],"names":[],"mappings":"AAEA,QAAA,MAAM,KAAK,GAAI,0BAEZ;IACD,mBAAmB,EAAE,MAAM,IAAI,CAAC;CACjC,4CAYA,CAAC;AAEF,eAAe,KAAK,CAAC"}
@@ -1,7 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import Image from "next/image";
3
- const Reset = ({ onResetViewAndItems, }) => {
4
- return (_jsx("div", { className: "absolute bottom-4 left-4 z-[1000] flex cursor-[url('/customcursor.svg'),auto] select-none", children: _jsx("button", { className: "rounded bg-gray-700 p-1.5 font-mono text-xs text-white shadow-md transition-colors hover:bg-gray-600 md:text-sm", onClick: onResetViewAndItems, onPointerDown: (e) => e.stopPropagation(), children: _jsx(Image, { src: "/images/reset.svg", alt: "Reset", width: 18, height: 18 }) }) }));
5
- };
6
- export default Reset;
7
- //# sourceMappingURL=reset.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"reset.js","sourceRoot":"","sources":["../../../src/components/canvas/reset.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,MAAM,YAAY,CAAC;AAE/B,MAAM,KAAK,GAAG,CAAC,EACb,mBAAmB,GAGpB,EAAE,EAAE;IACH,OAAO,CACL,cAAK,SAAS,EAAC,2FAA2F,YACxG,iBACE,SAAS,EAAC,iHAAiH,EAC3H,OAAO,EAAE,mBAAmB,EAC5B,aAAa,EAAE,CAAC,CAAqB,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE,YAE7D,KAAC,KAAK,IAAC,GAAG,EAAC,mBAAmB,EAAC,GAAG,EAAC,OAAO,EAAC,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,GAAI,GAC7D,GACL,CACP,CAAC;AACJ,CAAC,CAAC;AAEF,eAAe,KAAK,CAAC"}
@@ -1,21 +0,0 @@
1
- import Image from "next/image";
2
-
3
- const Reset = ({
4
- onResetViewAndItems,
5
- }: {
6
- onResetViewAndItems: () => void;
7
- }) => {
8
- return (
9
- <div className="absolute bottom-4 left-4 z-[1000] flex cursor-[url('/customcursor.svg'),auto] select-none">
10
- <button
11
- className="rounded bg-gray-700 p-1.5 font-mono text-xs text-white shadow-md transition-colors hover:bg-gray-600 md:text-sm"
12
- onClick={onResetViewAndItems}
13
- onPointerDown={(e: React.PointerEvent) => e.stopPropagation()}
14
- >
15
- <Image src="/images/reset.svg" alt="Reset" width={18} height={18} />
16
- </button>
17
- </div>
18
- );
19
- };
20
-
21
- export default Reset;