@mova-ui/react 0.4.10 → 0.4.11
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.
- package/dist/catalog/components/cards/CardTimeMachine.d.ts +7 -0
- package/dist/catalog/components/cards/CardTimeMachine.js +14 -11
- package/dist/catalog/components/dither-charts/DitherBook.d.ts +5 -2
- package/dist/catalog/components/dither-charts/DitherBook.js +35 -27
- package/dist/catalog/components/dither-charts/DitherChartsGrid.js +3 -3
- package/dist/catalog/components/simple-comp/DitherChartsGrid.js +3 -3
- package/dist/catalog/data/ditherCharts.js +3 -3
- package/dist/catalog/data/formElements.js +2 -2
- package/dist/catalog/data/installCommand.js +2 -2
- package/dist/catalog/data/metrics.js +2 -2
- package/dist/catalog/data/supportedRegistryIds.js +1 -1
- package/dist/catalog/data/textAnimations.js +2 -2
- package/dist/catalog/data/toggles.js +2 -2
- package/dist/catalog/data/transitions.js +2 -2
- package/dist/catalog-chunks/{chunk-YPZZOKH4.js → chunk-CQ7VYA46.js} +7 -0
- package/dist/catalog-chunks/{chunk-6NP66VN5.js → chunk-EYGZ2KK7.js} +1 -1
- package/dist/catalog-chunks/{chunk-WKFJSUM3.js → chunk-PMFHIVMP.js} +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +2149 -143
- package/dist/styles.css +1 -1
- package/dist/ui/patterns/animated-mega-menu.d.ts +23 -0
- package/dist/ui/patterns/blur-carousel.d.ts +44 -0
- package/dist/ui/patterns/compare-slider.d.ts +15 -0
- package/dist/ui/patterns/fold-panel.d.ts +15 -0
- package/dist/ui/patterns/gesture-range-slider.d.ts +21 -0
- package/dist/ui/patterns/long-press-context-menu.d.ts +48 -0
- package/dist/ui/patterns/split-view.d.ts +17 -0
- package/package.json +1 -1
- package/registry/1.json +1 -1
- package/registry/10.json +1 -1
- package/registry/11.json +1 -1
- package/registry/12.json +1 -1
- package/registry/13.json +1 -1
- package/registry/14.json +1 -1
- package/registry/15.json +1 -1
- package/registry/16.json +1 -1
- package/registry/17.json +1 -1
- package/registry/18.json +1 -1
- package/registry/19.json +1 -1
- package/registry/2.json +1 -1
- package/registry/20.json +1 -1
- package/registry/21.json +1 -1
- package/registry/22.json +1 -1
- package/registry/23.json +1 -1
- package/registry/24.json +1 -1
- package/registry/25.json +1 -1
- package/registry/26.json +1 -1
- package/registry/27.json +1 -1
- package/registry/28.json +1 -1
- package/registry/29.json +1 -1
- package/registry/3.json +1 -1
- package/registry/30.json +1 -1
- package/registry/31.json +1 -1
- package/registry/32.json +1 -1
- package/registry/33.json +1 -1
- package/registry/34.json +1 -1
- package/registry/35.json +1 -1
- package/registry/36.json +1 -1
- package/registry/37.json +1 -1
- package/registry/4.json +1 -1
- package/registry/5.json +1 -1
- package/registry/6.json +1 -1
- package/registry/7.json +1 -1
- package/registry/8.json +1 -1
- package/registry/9.json +1 -1
- package/registry/LICENSE.third-party +52 -0
- package/registry/ab-segmented-tabs.json +1 -1
- package/registry/accordion-blind.json +1 -1
- package/registry/accordion-loader.json +1 -1
- package/registry/ai-approval-card.json +1 -1
- package/registry/ai-code-block.json +1 -1
- package/registry/ai-context-meter.json +1 -1
- package/registry/ai-fine-tune-card.json +1 -1
- package/registry/ai-prompt-bar.json +1 -1
- package/registry/ai-sidebar-nav.json +1 -1
- package/registry/ai-sources.json +1 -1
- package/registry/ai-suggestions.json +1 -1
- package/registry/ai-task-list.json +1 -1
- package/registry/ai-tool-call.json +1 -1
- package/registry/analyzing-image.json +1 -1
- package/registry/animated-mega-menu.json +21 -0
- package/registry/animated-sidebar.json +1 -1
- package/registry/animated-tags.json +1 -1
- package/registry/app-icon-load.json +1 -1
- package/registry/apple-breathe.json +1 -1
- package/registry/apple-equalizer.json +1 -1
- package/registry/apple-icon-morph.json +1 -1
- package/registry/apple-pulse-dots.json +1 -1
- package/registry/apple-scale-pulse.json +1 -1
- package/registry/apple-sound-wave.json +1 -1
- package/registry/apple-text-reveal.json +1 -1
- package/registry/apple-unlock.json +1 -1
- package/registry/arc-tracer.json +1 -1
- package/registry/balance-bar-chart-card.json +1 -1
- package/registry/bar-cascade.json +1 -1
- package/registry/bar-sweep.json +1 -1
- package/registry/blur-carousel.json +18 -0
- package/registry/blur-text.json +1 -1
- package/registry/blur-up-char.json +1 -1
- package/registry/blur-up-word.json +1 -1
- package/registry/bobbing-dots.json +1 -1
- package/registry/bookmark-corner-peel.json +1 -1
- package/registry/bookmark-save-pill.json +1 -1
- package/registry/bookmark-toggle.json +1 -1
- package/registry/border-trail-loading.json +1 -1
- package/registry/bounce-dots.json +1 -1
- package/registry/bounce-in-char.json +1 -1
- package/registry/bouncing-bars.json +1 -1
- package/registry/bouncing-dots.json +1 -1
- package/registry/bouncing-lines.json +1 -1
- package/registry/bouncing-square.json +1 -1
- package/registry/breathe-ring.json +1 -1
- package/registry/breathing-glow.json +1 -1
- package/registry/breathing-square.json +1 -1
- package/registry/btn-1.json +1 -1
- package/registry/btn-10.json +1 -1
- package/registry/btn-11.json +1 -1
- package/registry/btn-12.json +1 -1
- package/registry/btn-13.json +1 -1
- package/registry/btn-14.json +1 -1
- package/registry/btn-15.json +1 -1
- package/registry/btn-16.json +1 -1
- package/registry/btn-17.json +1 -1
- package/registry/btn-18.json +1 -1
- package/registry/btn-19.json +1 -1
- package/registry/btn-2.json +1 -1
- package/registry/btn-20.json +1 -1
- package/registry/btn-21.json +1 -1
- package/registry/btn-22.json +1 -1
- package/registry/btn-23.json +1 -1
- package/registry/btn-24.json +1 -1
- package/registry/btn-25.json +1 -1
- package/registry/btn-26.json +1 -1
- package/registry/btn-27.json +1 -1
- package/registry/btn-28.json +1 -1
- package/registry/btn-29.json +1 -1
- package/registry/btn-3.json +1 -1
- package/registry/btn-30.json +1 -1
- package/registry/btn-31.json +1 -1
- package/registry/btn-32.json +1 -1
- package/registry/btn-33.json +1 -1
- package/registry/btn-34.json +1 -1
- package/registry/btn-35.json +1 -1
- package/registry/btn-36.json +1 -1
- package/registry/btn-37.json +1 -1
- package/registry/btn-4.json +1 -1
- package/registry/btn-5.json +1 -1
- package/registry/btn-6.json +1 -1
- package/registry/btn-7.json +1 -1
- package/registry/btn-8.json +1 -1
- package/registry/btn-9.json +1 -1
- package/registry/button-slide-arrow.json +1 -1
- package/registry/cacheable-bandwidth-cost.json +1 -1
- package/registry/calendar-day-card.json +1 -1
- package/registry/card-arc-5.json +1 -1
- package/registry/card-arc-7.json +1 -1
- package/registry/card-carousel-mono.json +1 -1
- package/registry/card-carousel.json +1 -1
- package/registry/card-cascade-stagger.json +1 -1
- package/registry/card-corner-fan.json +1 -1
- package/registry/card-cover-flow-mono.json +1 -1
- package/registry/card-cover-flow.json +1 -1
- package/registry/card-deck-cascade.json +1 -1
- package/registry/card-glance-preview.json +1 -1
- package/registry/card-hover.json +1 -1
- package/registry/card-linear-spread.json +1 -1
- package/registry/card-long-arc-5.json +1 -1
- package/registry/card-scatter-spread.json +1 -1
- package/registry/card-stack-peel.json +1 -1
- package/registry/card-stamp-arc.json +1 -1
- package/registry/card-time-machine-mono.json +1 -1
- package/registry/card-time-machine.json +1 -1
- package/registry/card-wheel-fan.json +1 -1
- package/registry/category-select.json +1 -1
- package/registry/character-stagger.json +1 -1
- package/registry/checkbox-draw.json +1 -1
- package/registry/checkmark-draw.json +1 -1
- package/registry/circle-toggle.json +1 -1
- package/registry/circuit-trace-draw.json +1 -1
- package/registry/circular-bars.json +1 -1
- package/registry/circular-create-button.json +1 -1
- package/registry/classic-spinner.json +1 -1
- package/registry/classic-toggle.json +1 -1
- package/registry/clock-spinner.json +1 -1
- package/registry/comet-spinner.json +1 -1
- package/registry/compare-slider.json +19 -0
- package/registry/compass-needle-deflect.json +1 -1
- package/registry/concentric-pulse.json +1 -1
- package/registry/concentric-ring.json +1 -1
- package/registry/concentric-squares.json +1 -1
- package/registry/context-menu.json +1 -1
- package/registry/conveyor-loop.json +1 -1
- package/registry/credit-score-barcode-meter.json +1 -1
- package/registry/cross-spinner.json +1 -1
- package/registry/cube-flip-spring.json +1 -1
- package/registry/cursor-trail.json +1 -1
- package/registry/dash-ring.json +1 -1
- package/registry/dashed-spiral.json +1 -1
- package/registry/date-position-selector.json +1 -1
- package/registry/day-trading-order-ticket.json +1 -1
- package/registry/dia-text-reveal.json +1 -1
- package/registry/diamond-grid.json +1 -1
- package/registry/diamond-rotate-spring.json +1 -1
- package/registry/dislike-toggle.json +1 -1
- package/registry/dither-book.json +1 -1
- package/registry/domino-cascade.json +1 -1
- package/registry/dot-spinner.json +1 -1
- package/registry/dots-ring.json +1 -1
- package/registry/double-bounce-toggle.json +1 -1
- package/registry/double-ring.json +1 -1
- package/registry/download-icons.json +1 -1
- package/registry/drop-dot.json +1 -1
- package/registry/drop-in-char.json +1 -1
- package/registry/droplet-squish.json +1 -1
- package/registry/dual-arc.json +1 -1
- package/registry/dual-magnet-dipole.json +1 -1
- package/registry/dynamic-island.json +1 -1
- package/registry/elastic-bars.json +1 -1
- package/registry/elastic-square.json +1 -1
- package/registry/elastic-tag-snap.json +1 -1
- package/registry/error-shake.json +1 -1
- package/registry/expanding-cross.json +1 -1
- package/registry/expanding-icon-tabs.json +1 -1
- package/registry/face-id-scan.json +1 -1
- package/registry/fade-arc.json +1 -1
- package/registry/fade-dots.json +1 -1
- package/registry/fade-down.json +1 -1
- package/registry/fade-in-char.json +1 -1
- package/registry/fade-in-text.json +1 -1
- package/registry/fade-in-word.json +1 -1
- package/registry/fade-in.json +1 -1
- package/registry/fade-up.json +1 -1
- package/registry/file-upload-dropzone.json +1 -1
- package/registry/filter-tag-pill.json +1 -1
- package/registry/flip-square.json +1 -1
- package/registry/flip-x-char.json +1 -1
- package/registry/flip-y-char.json +1 -1
- package/registry/float-char.json +1 -1
- package/registry/float-word.json +1 -1
- package/registry/floating-diamonds.json +1 -1
- package/registry/floating-label-input.json +1 -1
- package/registry/fluid-bars.json +1 -1
- package/registry/fluid-diamond.json +1 -1
- package/registry/fluid-dot-orbit.json +1 -1
- package/registry/fluid-skeleton.json +1 -1
- package/registry/focus-blur.json +1 -1
- package/registry/fold-panel.json +19 -0
- package/registry/form-submit-button.json +1 -1
- package/registry/french-doors-3d.json +1 -1
- package/registry/gear-tooth-step.json +1 -1
- package/registry/gears.json +1 -1
- package/registry/gelatin-wobble.json +1 -1
- package/registry/geometric-iris-shutter.json +1 -1
- package/registry/gesture-range-slider.json +18 -0
- package/registry/glassmorphic-card.json +1 -1
- package/registry/glow-button.json +1 -1
- package/registry/glow-text.json +1 -1
- package/registry/gooey-popover.json +1 -1
- package/registry/gradient-arc.json +1 -1
- package/registry/gradient-shimmer.json +1 -1
- package/registry/grid-dots.json +1 -1
- package/registry/haptic-ring.json +1 -1
- package/registry/heartbeat.json +1 -1
- package/registry/hex-lattice-draw.json +1 -1
- package/registry/hexagon-spinner.json +1 -1
- package/registry/hourglass.json +1 -1
- package/registry/hover-lift-char.json +1 -1
- package/registry/hover-lift-word.json +1 -1
- package/registry/hover-link-card.json +1 -1
- package/registry/hover-scale-char.json +1 -1
- package/registry/hover-scale-word.json +1 -1
- package/registry/imessage-chat.json +1 -1
- package/registry/inertia-skid-stop.json +1 -1
- package/registry/infinity-path.json +1 -1
- package/registry/input-focus-glow.json +1 -1
- package/registry/inset-action-button.json +1 -1
- package/registry/inset-button.json +1 -1
- package/registry/intersecting-rings.json +1 -1
- package/registry/ios-spinner.json +1 -1
- package/registry/kinetic-metronome-tick.json +1 -1
- package/registry/kinetic-tension-capsule.json +1 -1
- package/registry/light-dark-morph.json +1 -1
- package/registry/like-toggle.json +1 -1
- package/registry/line-spinner.json +1 -1
- package/registry/liquid-dots.json +1 -1
- package/registry/liquid-glass-sheet.json +1 -1
- package/registry/long-press-context-menu.json +19 -0
- package/registry/mac-terminal.json +1 -1
- package/registry/magnetic-button.json +1 -1
- package/registry/magnetic-disks.json +1 -1
- package/registry/magnetic-dots.json +1 -1
- package/registry/magnetic-slide-btn.json +1 -1
- package/registry/manifest.json +785 -652
- package/registry/matrix-grid-loader.json +1 -1
- package/registry/minimal-triangle.json +1 -1
- package/registry/modular-tile-snap.json +1 -1
- package/registry/mono-activity-blue.json +1 -1
- package/registry/mono-activity-green.json +1 -1
- package/registry/mono-activity-purple.json +1 -1
- package/registry/mono-heatmap.json +1 -1
- package/registry/mono-rounded-area.json +1 -1
- package/registry/mono-rounded-bar.json +1 -1
- package/registry/mono-rounded-bubble.json +1 -1
- package/registry/mono-rounded-bullet.json +1 -1
- package/registry/mono-rounded-candlestick.json +1 -1
- package/registry/mono-rounded-composed.json +1 -1
- package/registry/mono-rounded-donut.json +1 -1
- package/registry/mono-rounded-funnel.json +1 -1
- package/registry/mono-rounded-gauge-arc.json +1 -1
- package/registry/mono-rounded-heatmap.json +1 -1
- package/registry/mono-rounded-kpi.json +1 -1
- package/registry/mono-rounded-line.json +1 -1
- package/registry/mono-rounded-meter.json +1 -1
- package/registry/mono-rounded-polar.json +1 -1
- package/registry/mono-rounded-pyramid.json +1 -1
- package/registry/mono-rounded-radar.json +1 -1
- package/registry/mono-rounded-radial-gauge.json +1 -1
- package/registry/mono-rounded-radial-group.json +1 -1
- package/registry/mono-rounded-range.json +1 -1
- package/registry/mono-rounded-sankey.json +1 -1
- package/registry/mono-rounded-scatter.json +1 -1
- package/registry/mono-rounded-sparkline.json +1 -1
- package/registry/mono-rounded-stacked-bar.json +1 -1
- package/registry/mono-rounded-step.json +1 -1
- package/registry/mono-rounded-stream.json +1 -1
- package/registry/mono-rounded-treemap.json +1 -1
- package/registry/mono-rounded-waterfall.json +1 -1
- package/registry/mono-stock.json +1 -1
- package/registry/morph-action-pill.json +1 -1
- package/registry/morph-dot-ring.json +1 -1
- package/registry/morph-loader.json +1 -1
- package/registry/morph-toggle.json +1 -1
- package/registry/morphing-bars.json +1 -1
- package/registry/morphing-infinity.json +1 -1
- package/registry/morphing-ring.json +1 -1
- package/registry/morphing-shape.json +1 -1
- package/registry/mouse-follow.json +1 -1
- package/registry/multi-select-chips.json +1 -1
- package/registry/multi-tab-bar.json +1 -1
- package/registry/neon-sign-draw.json +1 -1
- package/registry/nested-orbital-gimbal.json +1 -1
- package/registry/network-telemetry-matrix.json +1 -1
- package/registry/newtons-cradle.json +1 -1
- package/registry/noise-decibel-level.json +1 -1
- package/registry/obsidian-liquid-wave.json +1 -1
- package/registry/offset-rings.json +1 -1
- package/registry/orbiting-circles.json +1 -1
- package/registry/orbiting-dot.json +1 -1
- package/registry/origami-envelope-unfold.json +1 -1
- package/registry/origami-shape.json +1 -1
- package/registry/otp-input.json +1 -1
- package/registry/overview-bar-scrubber-card.json +1 -1
- package/registry/page-transition.json +1 -1
- package/registry/page-turn-curl.json +1 -1
- package/registry/password-toggle.json +1 -1
- package/registry/pendulum-bubble-level.json +1 -1
- package/registry/pendulum.json +1 -1
- package/registry/perspective-flip-stage.json +1 -1
- package/registry/perspective-flip.json +1 -1
- package/registry/perspective-layout-switcher.json +1 -1
- package/registry/physics-dock.json +1 -1
- package/registry/pill-tabs.json +1 -1
- package/registry/plus-minus-toggle.json +1 -1
- package/registry/presets.json +1 -1
- package/registry/prism-block-stack.json +1 -1
- package/registry/progress-indicator-piano.json +1 -1
- package/registry/progress-indicator.json +1 -1
- package/registry/progress-stepper.json +1 -1
- package/registry/pulsating-dots.json +1 -1
- package/registry/pulse-char.json +1 -1
- package/registry/pulse-dot.json +1 -1
- package/registry/pulse-dots.json +1 -1
- package/registry/pulse-square.json +1 -1
- package/registry/pulse-word.json +1 -1
- package/registry/pulse.json +1 -1
- package/registry/pumping-heart.json +1 -1
- package/registry/pyramid-block-build.json +1 -1
- package/registry/radar-sweep.json +1 -1
- package/registry/radial-iris-mask.json +1 -1
- package/registry/radial-spinner.json +1 -1
- package/registry/radio-scale.json +1 -1
- package/registry/range-slider.json +1 -1
- package/registry/receipt-tape-print.json +1 -1
- package/registry/rectangle-toggle.json +1 -1
- package/registry/repost-toggle.json +1 -1
- package/registry/reveal-text.json +1 -1
- package/registry/ribbon-banner-slide.json +1 -1
- package/registry/ring-sweep.json +1 -1
- package/registry/ripple-effect.json +1 -1
- package/registry/rise-up-word.json +1 -1
- package/registry/roller-blind-drop.json +1 -1
- package/registry/rolling-tumble.json +1 -1
- package/registry/rotate-in-char.json +1 -1
- package/registry/rotating-cross.json +1 -1
- package/registry/rotating-louvers.json +1 -1
- package/registry/rotating-triangle.json +1 -1
- package/registry/rudder-issue-board.json +1 -1
- package/registry/sales-analytics-dual-bars.json +1 -1
- package/registry/sales-overview-radial-dashboard.json +1 -1
- package/registry/sales-target-segmented-arc.json +1 -1
- package/registry/scale-in-char.json +1 -1
- package/registry/scale-in-text.json +1 -1
- package/registry/scale-in-word.json +1 -1
- package/registry/scale-in.json +1 -1
- package/registry/scramble-text.json +1 -1
- package/registry/scroll-canvas-unroll.json +1 -1
- package/registry/scroll-reveal.json +1 -1
- package/registry/search-expand.json +1 -1
- package/registry/segmented-arc-meter.json +1 -1
- package/registry/segmented-link-stretch.json +1 -1
- package/registry/segmented-step-bar.json +1 -1
- package/registry/segmented-stepper-dots.json +1 -1
- package/registry/select-dropdown.json +1 -1
- package/registry/server-performance-step-bars.json +1 -1
- package/registry/shape-shift-grid.json +1 -1
- package/registry/shimmer-line.json +1 -1
- package/registry/shimmer-text.json +1 -1
- package/registry/shutter-slide.json +1 -1
- package/registry/shutter-step-slices.json +1 -1
- package/registry/siri-wave.json +1 -1
- package/registry/skeleton-loader.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/skew-x-char.json +1 -1
- package/registry/slide-down-char.json +1 -1
- package/registry/slide-down-word.json +1 -1
- package/registry/slide-left-char.json +1 -1
- package/registry/slide-left.json +1 -1
- package/registry/slide-right-char.json +1 -1
- package/registry/slide-right.json +1 -1
- package/registry/slide-up-char.json +1 -1
- package/registry/slide-up-text.json +1 -1
- package/registry/slide-up-word.json +1 -1
- package/registry/sliding-bars.json +1 -1
- package/registry/slinky-coil.json +1 -1
- package/registry/smart-card-dispenser.json +1 -1
- package/registry/smooth-dot-shift.json +1 -1
- package/registry/smooth-ring.json +1 -1
- package/registry/smooth-rounded-square.json +1 -1
- package/registry/solid-switch.json +1 -1
- package/registry/spatial-door-portal.json +1 -1
- package/registry/spinning-squares.json +1 -1
- package/registry/spiral-spinner.json +1 -1
- package/registry/split-gate-reveal.json +1 -1
- package/registry/split-view.json +18 -0
- package/registry/spotlight.json +1 -1
- package/registry/spring-bars.json +1 -1
- package/registry/spring-dot-matrix.json +1 -1
- package/registry/spring-hexagon.json +1 -1
- package/registry/spring-ring-expand.json +1 -1
- package/registry/spring-text-pop.json +1 -1
- package/registry/spring-text.json +1 -1
- package/registry/square-accordion.json +1 -1
- package/registry/square-grid.json +1 -1
- package/registry/square-snake.json +1 -1
- package/registry/square-spinner.json +1 -1
- package/registry/squash-sphere.json +1 -1
- package/registry/stacked-bar-pulse.json +1 -1
- package/registry/stagger-text.json +1 -1
- package/registry/staggered-glass-curtain.json +1 -1
- package/registry/sticky-note-peel.json +1 -1
- package/registry/sticky-reveal.json +1 -1
- package/registry/stretch-x-char.json +1 -1
- package/registry/stretch-y-char.json +1 -1
- package/registry/stroke-waveform.json +1 -1
- package/registry/submenu-flyout.json +1 -1
- package/registry/success-check.json +1 -1
- package/registry/sudden-brake.json +1 -1
- package/registry/swapping-dots.json +1 -1
- package/registry/swing-word.json +1 -1
- package/registry/swirling-spinner.json +1 -1
- package/registry/symmetric-wave.json +1 -1
- package/registry/terminal-loader.json +1 -1
- package/registry/text-blink.json +1 -1
- package/registry/text-dots.json +1 -1
- package/registry/text-morph.json +1 -1
- package/registry/text-reveal.json +1 -1
- package/registry/text-shimmer-wave.json +1 -1
- package/registry/text-shimmer.json +1 -1
- package/registry/textarea-auto-grow.json +1 -1
- package/registry/theme-toggle.json +1 -1
- package/registry/tilt-card.json +1 -1
- package/registry/timer-preparation-segmented.json +1 -1
- package/registry/tracking-in-text.json +1 -1
- package/registry/tracking-out-text.json +1 -1
- package/registry/trailing-dots.json +1 -1
- package/registry/triple-dot-spinner.json +1 -1
- package/registry/twin-orbit.json +1 -1
- package/registry/typewriter-text.json +1 -1
- package/registry/typing-indicator.json +1 -1
- package/registry/typing.json +1 -1
- package/registry/use-mouse-position.json +1 -1
- package/registry/use-reduced-motion.json +1 -1
- package/registry/use-scroll-progress.json +1 -1
- package/registry/use-stagger.json +1 -1
- package/registry/use-web-haptics.json +1 -1
- package/registry/users-growth-pill-progress.json +1 -1
- package/registry/utils.json +1 -1
- package/registry/vertical-wheel-counter.json +1 -1
- package/registry/views-hourly-wave-chart.json +1 -1
- package/registry/voice-assistant-card.json +1 -1
- package/registry/wandering-cube.json +1 -1
- package/registry/wandering-eyes.json +1 -1
- package/registry/watch-spinner.json +1 -1
- package/registry/wave-dots.json +1 -1
- package/registry/wave-physics-loader.json +1 -1
- package/registry/wave-reveal.json +1 -1
- package/registry/waveform-loader.json +1 -1
- package/registry/word-reveal.json +1 -1
- package/registry/zig-zag-pulse.json +1 -1
- package/registry/zoom-in-text.json +1 -1
- package/registry/zoom-in.json +1 -1
- package/registry/zoom-out-text.json +1 -1
package/registry/fluid-bars.json
CHANGED
package/registry/focus-blur.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "fold-panel",
|
|
3
|
+
"tier": "free",
|
|
4
|
+
"version": "0.4.11",
|
|
5
|
+
"file": "fold-panel.tsx",
|
|
6
|
+
"sourcePath": "ui/patterns/fold-panel.tsx",
|
|
7
|
+
"registryFile": "fold-panel.json",
|
|
8
|
+
"exports": [
|
|
9
|
+
"FoldPanel",
|
|
10
|
+
"FoldPanelProps"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": [
|
|
13
|
+
"framer-motion",
|
|
14
|
+
"lucide-react"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"previewMode": "client",
|
|
18
|
+
"content": "\"use client\";\n/*\nReact web adaptation of https://kinetik-ui.dev/components/fold-panel\n\nMIT License\n\nCopyright (c) 2026 Nurullah Turgut\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n*/\nimport { useCallback, useEffect, useId, useRef, useState, type CSSProperties, type ReactNode } from 'react';\nimport { motion, useReducedMotion, useSpring, useTransform } from 'framer-motion';\nimport { ChevronDown } from 'lucide-react';\n\nexport interface FoldPanelProps {\n title: string;\n subtitle?: string;\n leading?: ReactNode;\n children: ReactNode;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n theme?: 'dark' | 'light';\n className?: string;\n style?: CSSProperties;\n}\n\nconst foldSpring = { stiffness: 210, damping: 27, mass: 1 };\nconst focusStyle = 'focus-visible:outline-2 focus-visible:outline-offset-[-3px] focus-visible:outline-current';\n\nexport function FoldPanel({\n title, subtitle, leading, children, open, defaultOpen = false, onOpenChange,\n disabled = false, theme = 'dark', className, style,\n}: FoldPanelProps) {\n const [innerOpen, setInnerOpen] = useState(defaultOpen);\n const expanded = open ?? innerOpen;\n const reduced = useReducedMotion();\n const contentId = useId();\n const trigger = useRef<HTMLButtonElement>(null);\n const progress = useSpring(expanded ? 1 : 0, foldSpring);\n const rows = useTransform(progress, [0, 1], ['0fr', '1fr']);\n const opacity = useTransform(progress, [0.3, 0.8], [0, 1]);\n const offset = useTransform(progress, [0, 1], [-10, 0]);\n const rotate = useTransform(progress, [0, 1], [0, 180]);\n const dark = theme === 'dark';\n const backgroundColor = useTransform(progress, [0, 1], dark ? ['#0a0a0a', '#1c1c1c'] : ['#fafafa', '#ffffff']);\n const borderColor = useTransform(progress, [0, 1], dark\n ? ['rgba(255,255,255,.08)', 'rgba(255,255,255,.16)']\n : ['rgba(0,0,0,.08)', 'rgba(0,0,0,.16)']);\n\n useEffect(() => {\n const value = expanded ? 1 : 0;\n if (reduced) progress.jump(value);\n else progress.set(value);\n }, [expanded, progress, reduced]);\n\n const setContent = useCallback((element: HTMLDivElement | null) => {\n if (!element) return;\n if (!expanded && element.contains(document.activeElement)) trigger.current?.focus();\n element.inert = !expanded;\n }, [expanded]);\n\n function toggle() {\n if (open === undefined) setInnerOpen(!expanded);\n onOpenChange?.(!expanded);\n }\n\n return (\n <motion.div\n className={className}\n whileTap={disabled || reduced ? undefined : { scale: 0.99 }}\n transition={{ type: 'spring', stiffness: 520, damping: 38, mass: 1 }}\n style={{\n width: '100%', border: '1px solid', borderRadius: 20, overflow: 'hidden',\n opacity: disabled ? 0.5 : 1, color: dark ? '#ededed' : '#0a0a0a',\n backgroundColor, borderColor, ...style,\n }}\n >\n <button\n ref={trigger}\n type=\"button\"\n className={focusStyle}\n aria-controls={contentId}\n aria-expanded={expanded}\n disabled={disabled}\n onClick={toggle}\n style={{\n appearance: 'none', display: 'flex', alignItems: 'center', width: '100%',\n minHeight: 64, gap: 12, padding: '12px 16px', border: 0, background: 'none',\n color: 'inherit', font: 'inherit', cursor: disabled ? 'default' : 'pointer',\n textAlign: 'start',\n }}\n >\n {leading != null && (\n <span aria-hidden=\"true\" style={{\n display: 'grid', placeItems: 'center', width: 36, height: 36, flexShrink: 0,\n borderRadius: 10, color: dark ? '#7ab2ff' : '#1f5fe0',\n background: dark ? 'rgba(59,130,246,.16)' : 'rgba(31,95,224,.1)',\n }}>\n {leading}\n </span>\n )}\n <span style={{ display: 'grid', flex: 1, minWidth: 0, gap: 2 }}>\n <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 16, lineHeight: 1.2, fontWeight: 600 }}>{title}</span>\n {subtitle && (\n <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontSize: 14, lineHeight: 1.3, color: dark ? '#a1a1a1' : '#555' }}>{subtitle}</span>\n )}\n </span>\n <motion.span aria-hidden=\"true\" style={{ display: 'grid', placeItems: 'center', width: 20, height: 20, flexShrink: 0, rotate, color: dark ? '#a1a1a1' : '#555' }}>\n <ChevronDown size={16} strokeWidth={1.75} />\n </motion.span>\n </button>\n <motion.div ref={setContent} id={contentId} aria-hidden={!expanded} style={{ display: 'grid', gridTemplateRows: rows }}>\n <div style={{ minHeight: 0, overflow: 'hidden' }}>\n <motion.div style={{ padding: '0 16px 16px', opacity, y: offset }}>\n {children}\n </motion.div>\n </div>\n </motion.div>\n </motion.div>\n );\n}\n"
|
|
19
|
+
}
|
package/registry/gears.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "geometric-iris-shutter",
|
|
3
3
|
"tier": "free",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.11",
|
|
5
5
|
"file": "geometric-iris-shutter.tsx",
|
|
6
6
|
"sourcePath": "components/css-animations/whimsical/RedesignedPhysicsTrios.tsx",
|
|
7
7
|
"registryFile": "geometric-iris-shutter.json",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "gesture-range-slider",
|
|
3
|
+
"tier": "free",
|
|
4
|
+
"version": "0.4.11",
|
|
5
|
+
"file": "gesture-range-slider.tsx",
|
|
6
|
+
"sourcePath": "ui/patterns/gesture-range-slider.tsx",
|
|
7
|
+
"registryFile": "gesture-range-slider.json",
|
|
8
|
+
"exports": [
|
|
9
|
+
"GestureRangeSlider",
|
|
10
|
+
"GestureRangeSliderProps"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": [
|
|
13
|
+
"framer-motion"
|
|
14
|
+
],
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"previewMode": "client",
|
|
17
|
+
"content": "\"use client\";\n/*!\n * Adapted for the web from Reacticx Range Slider.\n * https://github.com/rit3zh/reacticx/tree/main/src/components/molecules/range-slider\n *\n * MIT License\n * Copyright (c) 2026 rit3zh\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\n\"use client\";\n\nimport { useEffect, useRef, useState, type CSSProperties, type KeyboardEvent, type PointerEvent } from \"react\";\nimport { motion, useReducedMotion, useSpring, useTransform } from \"framer-motion\";\n\nexport interface GestureRangeSliderProps {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n min?: number;\n max?: number;\n step?: number;\n showTicks?: boolean;\n disabled?: boolean;\n trackColor?: string;\n fillColor?: string;\n thumbColor?: string;\n tickColor?: string;\n style?: CSSProperties;\n accessibilityLabel?: string;\n className?: string;\n theme?: \"dark\" | \"light\";\n}\n\nconst TRACK_HEIGHT = 40;\nconst THUMB_WIDTH = 6;\nconst THUMB_HEIGHT = 20;\nconst TICK_SIZE = 4;\nconst TICK_INSET = 18;\nconst SPRING_GLIDE = { stiffness: 700, damping: 50, mass: 0.5 };\nconst SPRING_BOUNCY = { type: \"spring\", stiffness: 500, damping: 14, mass: 0.7 } as const;\nconst PALETTE = {\n dark: { track: \"rgba(120,120,128,0.22)\", fill: \"rgba(255,255,255,0.16)\", thumb: \"#ffffff\", tick: \"rgba(255,255,255,0.25)\", focus: \"#ffffff\" },\n light: { track: \"rgba(120,120,128,0.20)\", fill: \"rgba(0,0,0,0.14)\", thumb: \"#171717\", tick: \"rgba(0,0,0,0.32)\", focus: \"#171717\" },\n};\n\nfunction clamp(value: number, min: number, max: number) {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction decimalPlaces(value: number) {\n const [mantissa, exponent = \"0\"] = String(value).split(\"e\");\n return Math.max(0, (mantissa.split(\".\")[1]?.length ?? 0) - Number(exponent));\n}\n\nfunction thumbPosition(fraction: number, extra = 0) {\n const ratio = clamp(fraction, 0, 1);\n return `max(${extra}px, calc(${ratio * 100}% + ${extra - ratio * THUMB_WIDTH}px))`;\n}\n\nconst centerVertically = {\n position: \"absolute\",\n pointerEvents: \"none\",\n top: \"50%\",\n} as const;\n\nexport function GestureRangeSlider({\n value,\n defaultValue = 0,\n onValueChange,\n min = 0,\n max = 100,\n step = 1,\n showTicks = true,\n disabled = false,\n trackColor,\n fillColor,\n thumbColor,\n tickColor,\n style,\n accessibilityLabel = \"Value\",\n className,\n theme = \"dark\",\n}: GestureRangeSliderProps) {\n const minimum = Number.isFinite(min) ? min : 0;\n const maximum = Number.isFinite(max) ? Math.max(minimum, max) : Math.max(minimum, 100);\n const range = maximum - minimum;\n const increment = Number.isFinite(step) && step > 0 ? step : 1;\n const precision = Math.max(decimalPlaces(minimum), decimalPlaces(increment));\n const canAdjust = !disabled && range > 0 && Number.isFinite(range);\n const controlled = value !== undefined;\n const [internal, setInternal] = useState(defaultValue);\n const [dragging, setDragging] = useState(false);\n const pointerId = useRef<number | null>(null);\n const raw = value ?? internal;\n const current = clamp(Number.isFinite(raw) ? raw : minimum, minimum, maximum);\n const lastValue = useRef(current);\n const fraction = range > 0 && Number.isFinite(range) ? (current - minimum) / range : 0;\n const palette = PALETTE[theme];\n const reducedMotion = useReducedMotion();\n const position = useSpring(fraction, SPRING_GLIDE);\n const thumbLeft = useTransform(position, (ratio) => thumbPosition(ratio));\n const fillWidth = useTransform(position, (ratio) => thumbPosition(ratio, THUMB_WIDTH / 2));\n\n useEffect(() => {\n if (controlled || pointerId.current === null) lastValue.current = current;\n if (reducedMotion) position.jump(fraction);\n else position.set(fraction);\n }, [controlled, current, fraction, position, reducedMotion]);\n\n function commit(next: number) {\n if (lastValue.current === next) return;\n lastValue.current = next;\n if (!controlled) setInternal(next);\n onValueChange?.(next);\n }\n\n function valueFromStep(index: number) {\n const stepped = minimum + index * increment;\n const rounded = precision <= 100 ? Number(stepped.toFixed(precision)) : stepped;\n return clamp(rounded, minimum, maximum);\n }\n\n function updateFromPointer(event: PointerEvent<HTMLDivElement>) {\n const rect = event.currentTarget.getBoundingClientRect();\n if (rect.width <= 0) return;\n const ratio = clamp((event.clientX - rect.left) / rect.width, 0, 1);\n commit(valueFromStep(Math.round((ratio * range) / increment)));\n }\n\n function startDrag(event: PointerEvent<HTMLDivElement>) {\n if (!canAdjust || event.button !== 0 || pointerId.current !== null) return;\n pointerId.current = event.pointerId;\n lastValue.current = current;\n event.currentTarget.setPointerCapture(event.pointerId);\n setDragging(true);\n updateFromPointer(event);\n }\n\n function moveDrag(event: PointerEvent<HTMLDivElement>) {\n if (canAdjust && pointerId.current === event.pointerId) updateFromPointer(event);\n }\n\n function finishDrag(event: PointerEvent<HTMLDivElement>) {\n if (pointerId.current !== event.pointerId) return;\n if (canAdjust && event.type === \"pointerup\") updateFromPointer(event);\n pointerId.current = null;\n setDragging(false);\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLDivElement>) {\n if (!canAdjust || pointerId.current !== null) return;\n const index = (current - minimum) / increment;\n const nearestIndex = Math.round(index);\n const onStep = valueFromStep(nearestIndex) === current;\n const nextIndex = onStep ? nearestIndex + 1 : Math.ceil(index);\n const previousIndex = onStep ? nearestIndex - 1 : Math.floor(index);\n let target: number;\n\n switch (event.key) {\n case \"ArrowRight\":\n case \"ArrowUp\": target = valueFromStep(nextIndex); break;\n case \"ArrowLeft\":\n case \"ArrowDown\": target = valueFromStep(previousIndex); break;\n case \"PageUp\": target = valueFromStep(nextIndex + 9); break;\n case \"PageDown\": target = valueFromStep(previousIndex - 9); break;\n case \"Home\": target = minimum; break;\n case \"End\": target = maximum; break;\n default: return;\n }\n\n event.preventDefault();\n lastValue.current = current;\n commit(target);\n }\n\n const tickSteps = Math.floor(range / increment);\n const showMarkers = showTicks && tickSteps > 0 && tickSteps <= 50;\n\n return (\n <div\n role=\"slider\"\n aria-label={accessibilityLabel}\n aria-orientation=\"horizontal\"\n aria-valuemin={minimum}\n aria-valuemax={maximum}\n aria-valuenow={current}\n aria-disabled={!canAdjust || undefined}\n tabIndex={canAdjust ? 0 : -1}\n data-mova-gesture-range=\"\"\n className={className}\n onKeyDown={handleKeyDown}\n onPointerDown={startDrag}\n onPointerMove={moveDrag}\n onPointerUp={finishDrag}\n onPointerCancel={finishDrag}\n onLostPointerCapture={finishDrag}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: TRACK_HEIGHT,\n boxSizing: \"border-box\",\n overflow: \"hidden\",\n borderRadius: 10,\n cursor: canAdjust ? \"pointer\" : \"default\",\n touchAction: canAdjust ? \"none\" : \"auto\",\n userSelect: \"none\",\n color: palette.focus,\n backgroundColor: trackColor ?? palette.track,\n opacity: disabled ? 0.5 : 1,\n ...style,\n }}\n >\n <style>{`[data-mova-gesture-range]:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; }`}</style>\n <motion.span\n aria-hidden=\"true\"\n style={{\n position: \"absolute\",\n pointerEvents: \"none\",\n top: 0,\n bottom: 0,\n left: 0,\n width: fillWidth,\n backgroundColor: fillColor ?? palette.fill,\n }}\n />\n {showMarkers && Array.from({ length: tickSteps + 1 }, (_, index) => {\n const ratio = index / tickSteps;\n return (\n <span\n key={index}\n aria-hidden=\"true\"\n style={{\n ...centerVertically,\n width: TICK_SIZE,\n height: TICK_SIZE,\n marginTop: -TICK_SIZE / 2,\n left: `clamp(0px, calc(${ratio * 100}% + ${TICK_INSET * (1 - 2 * ratio) - TICK_SIZE / 2}px), 100%)`,\n borderRadius: \"50%\",\n backgroundColor: tickColor ?? palette.tick,\n }}\n />\n );\n })}\n <motion.span\n aria-hidden=\"true\"\n initial={false}\n animate={{ scaleY: dragging && canAdjust && !reducedMotion ? 1.35 : 1 }}\n transition={reducedMotion ? { duration: 0 } : SPRING_BOUNCY}\n style={{\n ...centerVertically,\n left: thumbLeft,\n width: THUMB_WIDTH,\n height: THUMB_HEIGHT,\n marginTop: -THUMB_HEIGHT / 2,\n borderRadius: 3,\n backgroundColor: thumbColor ?? palette.thumb,\n }}\n />\n </div>\n );\n}\n\nexport default GestureRangeSlider;\n"
|
|
18
|
+
}
|
package/registry/glow-text.json
CHANGED
package/registry/grid-dots.json
CHANGED
package/registry/heartbeat.json
CHANGED
package/registry/hourglass.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "kinetic-metronome-tick",
|
|
3
3
|
"tier": "free",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.11",
|
|
5
5
|
"file": "kinetic-metronome-tick.tsx",
|
|
6
6
|
"sourcePath": "components/css-animations/whimsical/RedesignedPhysicsTrios.tsx",
|
|
7
7
|
"registryFile": "kinetic-metronome-tick.json",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "kinetic-tension-capsule",
|
|
3
3
|
"tier": "free",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.11",
|
|
5
5
|
"file": "kinetic-tension-capsule.tsx",
|
|
6
6
|
"sourcePath": "components/css-animations/whimsical/KineticTensionCapsule.tsx",
|
|
7
7
|
"registryFile": "kinetic-tension-capsule.json",
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "long-press-context-menu",
|
|
3
|
+
"tier": "free",
|
|
4
|
+
"version": "0.4.11",
|
|
5
|
+
"file": "long-press-context-menu.tsx",
|
|
6
|
+
"sourcePath": "ui/patterns/long-press-context-menu.tsx",
|
|
7
|
+
"registryFile": "long-press-context-menu.json",
|
|
8
|
+
"exports": [
|
|
9
|
+
"ContextMenu",
|
|
10
|
+
"ContextMenuAction",
|
|
11
|
+
"ContextMenuProps"
|
|
12
|
+
],
|
|
13
|
+
"dependencies": [
|
|
14
|
+
"framer-motion"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"previewMode": "client",
|
|
18
|
+
"content": "\"use client\";\n\"use client\";\n\n/**\n * Adapted from Reacticx's Context Menu.\n * https://reacticx.com/components/context-menu\n * https://github.com/rit3zh/reacticx/blob/main/src/components/molecules/context-menu/index.tsx\n *\n * MIT License\n *\n * Copyright (c) 2026 rit3zh\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n * of this software and associated documentation files (the \"Software\"), to deal\n * in the Software without restriction, including without limitation the rights\n * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n * copies of the Software, and to permit persons to whom the Software is\n * furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n * copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n * SOFTWARE.\n */\n\nimport React, { Fragment, useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\nimport type { CSSProperties, KeyboardEvent, PointerEvent, ReactNode, RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { AnimatePresence, motion, useIsPresent, useReducedMotion } from \"framer-motion\";\n\nexport type ContextMenuAction = {\n id: string;\n label: string;\n subtitle?: string;\n icon?: ReactNode;\n separatorBefore?: boolean;\n destructive?: boolean;\n disabled?: boolean;\n onSelect?: () => void;\n};\n\nexport type ContextMenuProps = {\n children: ReactNode;\n items: readonly ContextMenuAction[];\n label?: string;\n theme?: \"light\" | \"dark\";\n disabled?: boolean;\n className?: string;\n};\n\nconst LONG_PRESS_DURATION = 250;\nconst PRESS_SCALE = 0.97;\nconst PREVIEW_SCALE = 1.04;\nconst MENU_WIDTH = 250;\nconst MENU_GAP = 30;\nconst SCREEN_PADDING = 22;\nconst OPEN_SPRING = { type: \"spring\", damping: 12, mass: 0.5, stiffness: 120 } as const;\nconst CLOSE_TRANSITION = { duration: 0.18, ease: \"easeOut\" } as const;\nconst useClientLayoutEffect = typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nconst palette = {\n light: {\n background: \"rgb(250 250 250 / 90%)\",\n text: \"#111111\",\n subtitle: \"#54545a\",\n destructive: \"#b51d13\",\n backdrop: \"rgb(0 0 0 / 8%)\",\n separator: \"rgb(0 0 0 / 9%)\",\n highlight: \"rgb(0 0 0 / 8%)\",\n },\n dark: {\n background: \"rgb(37 37 37 / 84%)\",\n text: \"#ffffff\",\n subtitle: \"#b0b0b5\",\n destructive: \"#ff5d54\",\n backdrop: \"rgb(0 0 0 / 35%)\",\n separator: \"rgb(255 255 255 / 10%)\",\n highlight: \"rgb(255 255 255 / 12%)\",\n },\n};\n\nconst contextMenuCss = `\n [data-mova-context-trigger]:focus-visible {\n outline: 2px solid Highlight;\n outline-offset: 4px;\n }\n [data-mova-context-action] { background: transparent; }\n [data-mova-context-action]:enabled:focus-visible {\n outline: 2px solid Highlight;\n outline-offset: -2px;\n background: var(--mova-context-highlight);\n }\n @media (hover: hover) {\n [data-mova-context-action]:enabled:hover { background: var(--mova-context-highlight); }\n }\n [data-mova-context-action]:enabled:active { background: var(--mova-context-highlight); }\n`;\n\ntype MenuPlacement = {\n left: number;\n top: number;\n width: number;\n maxHeight: number;\n origin: string;\n shiftX: number;\n shiftY: number;\n scale: number;\n};\n\nfunction placeMenu(rect: DOMRectReadOnly, height: number, reducedMotion: boolean): MenuPlacement {\n const viewport = window.visualViewport;\n const width = viewport?.width ?? document.documentElement.clientWidth;\n const viewportHeight = viewport?.height ?? document.documentElement.clientHeight;\n const inset = Math.min(SCREEN_PADDING, width / 10, viewportHeight / 10);\n const left = (viewport?.offsetLeft ?? 0) + inset;\n const top = (viewport?.offsetTop ?? 0) + inset;\n const availableWidth = width - inset * 2;\n const availableHeight = viewportHeight - inset * 2;\n const bottom = top + availableHeight;\n const gap = Math.min(MENU_GAP, availableHeight / 8);\n const menuWidth = Math.min(MENU_WIDTH, availableWidth);\n const scale = Math.min(\n reducedMotion ? 1 : PREVIEW_SCALE,\n availableWidth / Math.max(1, rect.width),\n availableHeight * 0.55 / Math.max(1, rect.height),\n );\n const previewWidth = rect.width * scale;\n const previewHeight = rect.height * scale;\n const maxHeight = availableHeight - gap - previewHeight;\n const menuHeight = Math.min(height, maxHeight);\n const centeredPreviewLeft = rect.left + (rect.width - previewWidth) / 2;\n const centeredPreviewTop = rect.top + (rect.height - previewHeight) / 2;\n const previewLeft = Math.max(left, Math.min(left + availableWidth - previewWidth, centeredPreviewLeft));\n const previewCenter = previewLeft + previewWidth / 2;\n const menuLeft = Math.max(left, Math.min(left + availableWidth - menuWidth, previewCenter - menuWidth / 2));\n const spaceAbove = centeredPreviewTop - top;\n const spaceBelow = bottom - (centeredPreviewTop + previewHeight);\n const above = spaceBelow < gap + menuHeight && spaceAbove > spaceBelow;\n const totalHeight = previewHeight + gap + menuHeight;\n const proposedTop = above ? centeredPreviewTop - gap - menuHeight : centeredPreviewTop;\n const groupTop = Math.max(top, Math.min(bottom - totalHeight, proposedTop));\n const previewTop = above ? groupTop + menuHeight + gap : groupTop;\n\n return {\n left: menuLeft,\n top: above ? groupTop : previewTop + previewHeight + gap,\n width: menuWidth,\n maxHeight,\n origin: `${Math.max(0, Math.min(menuWidth, previewCenter - menuLeft))}px ${above ? \"bottom\" : \"top\"}`,\n shiftX: previewLeft - centeredPreviewLeft,\n shiftY: previewTop - centeredPreviewTop,\n scale,\n };\n}\n\ntype Anchor = {\n rect: DOMRectReadOnly;\n pressed: boolean;\n font: string;\n color: string;\n};\n\ntype ContextMenuOverlayProps = {\n anchor: Anchor;\n triggerRef: RefObject<HTMLButtonElement | null>;\n id: string;\n label: string;\n children: ReactNode;\n items: readonly ContextMenuAction[];\n theme: NonNullable<ContextMenuProps[\"theme\"]>;\n reducedMotion: boolean;\n close: (restoreFocus?: boolean) => void;\n};\n\nfunction ContextMenuOverlay({\n anchor, triggerRef, id, label, children, items, theme, reducedMotion, close,\n}: ContextMenuOverlayProps) {\n const menuRef = useRef<HTMLDivElement>(null);\n const [placement, setPlacement] = useState<MenuPlacement | null>(null);\n const colors = palette[theme];\n const isPresent = useIsPresent();\n const closeRef = useRef(close);\n\n useClientLayoutEffect(() => { closeRef.current = close; }, [close]);\n\n useClientLayoutEffect(() => {\n const menu = menuRef.current;\n const trigger = triggerRef.current;\n if (!menu || !trigger) return;\n const update = () => setPlacement(placeMenu(anchor.rect, menu.scrollHeight, reducedMotion));\n const observer = new ResizeObserver(update);\n observer.observe(menu);\n if (menu.firstElementChild) observer.observe(menu.firstElementChild);\n window.addEventListener(\"resize\", update);\n window.visualViewport?.addEventListener(\"resize\", update);\n window.visualViewport?.addEventListener(\"scroll\", update);\n update();\n return () => {\n observer.disconnect();\n window.removeEventListener(\"resize\", update);\n window.visualViewport?.removeEventListener(\"resize\", update);\n window.visualViewport?.removeEventListener(\"scroll\", update);\n };\n }, [anchor, triggerRef, reducedMotion]);\n\n const ready = placement !== null;\n\n useClientLayoutEffect(() => {\n const menu = menuRef.current;\n if (!menu || !ready || !isPresent) return;\n const first = menu.querySelector<HTMLButtonElement>(\"[data-mova-context-action]:enabled\");\n (first ?? menu).focus({ preventScroll: true });\n\n const handleFocus = (event: FocusEvent) => {\n if (event.target instanceof Node && !menu.contains(event.target)) closeRef.current(false);\n };\n document.addEventListener(\"focusin\", handleFocus);\n return () => document.removeEventListener(\"focusin\", handleFocus);\n }, [isPresent, ready]);\n\n const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (event.key === \"Tab\") {\n close();\n return;\n }\n if (event.key === \"Escape\") {\n event.preventDefault();\n event.stopPropagation();\n close();\n return;\n }\n if (event.altKey || event.ctrlKey || event.metaKey) return;\n const buttons = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>(\"[data-mova-context-action]:enabled\"));\n if (!buttons.length) return;\n const focused = buttons.indexOf(document.activeElement as HTMLButtonElement);\n let next: number;\n switch (event.key) {\n case \"ArrowDown\": next = (focused + 1) % buttons.length; break;\n case \"ArrowUp\": next = (focused < 0 ? buttons.length - 1 : (focused - 1 + buttons.length) % buttons.length); break;\n case \"Home\": next = 0; break;\n case \"End\": next = buttons.length - 1; break;\n default: {\n if (event.key.length !== 1 || event.key === \" \") return;\n const key = event.key.toLocaleLowerCase();\n const ordered = [...buttons.slice(focused + 1), ...buttons.slice(0, focused + 1)];\n const match = ordered.find((button) => button.dataset.movaContextAction?.toLocaleLowerCase().startsWith(key));\n if (!match) return;\n next = buttons.indexOf(match);\n }\n }\n event.preventDefault();\n event.stopPropagation();\n buttons[next].focus();\n };\n\n const enter = reducedMotion ? { duration: 0 } : OPEN_SPRING;\n const exit = reducedMotion ? { duration: 0 } : CLOSE_TRANSITION;\n const menuRestScale = reducedMotion ? 1 : 0.8;\n\n return (\n <motion.div\n style={{ position: \"fixed\", inset: 0, zIndex: 2147483647, pointerEvents: \"none\" }}\n onContextMenu={(event) => event.preventDefault()}\n >\n <motion.div\n aria-hidden=\"true\"\n initial={{ opacity: 0 }}\n animate={{ opacity: 1, transition: enter }}\n exit={{ opacity: 0, transition: exit }}\n onClick={() => close()}\n style={{\n position: \"absolute\",\n inset: 0,\n pointerEvents: isPresent ? \"auto\" : \"none\",\n touchAction: \"none\",\n background: colors.backdrop,\n backdropFilter: \"blur(9px)\",\n WebkitBackdropFilter: \"blur(9px)\",\n }}\n />\n <motion.div\n aria-hidden=\"true\"\n initial={{ x: 0, y: 0, scale: anchor.pressed && !reducedMotion ? PRESS_SCALE : 1 }}\n animate={placement ? { x: placement.shiftX, y: placement.shiftY, scale: placement.scale, transition: enter } : undefined}\n exit={{ x: 0, y: 0, scale: 1, transition: exit }}\n style={{\n position: \"absolute\",\n left: anchor.rect.left,\n top: anchor.rect.top,\n width: anchor.rect.width,\n height: anchor.rect.height,\n font: anchor.font,\n color: anchor.color,\n }}\n >\n {children}\n </motion.div>\n <motion.div\n ref={menuRef}\n id={id}\n role=\"menu\"\n tabIndex={-1}\n aria-label={label}\n aria-hidden={!isPresent || undefined}\n onKeyDown={handleKeyDown}\n initial={{ opacity: 0, scale: menuRestScale }}\n animate={placement ? { opacity: 1, scale: 1, transition: enter } : undefined}\n exit={{ opacity: 0, scale: menuRestScale, transition: exit }}\n style={{\n \"--mova-context-highlight\": colors.highlight,\n boxSizing: \"border-box\",\n position: \"absolute\",\n left: placement?.left ?? 0,\n top: placement?.top ?? 0,\n width: placement?.width ?? `min(${MENU_WIDTH}px, 80vw)`,\n maxHeight: placement?.maxHeight,\n visibility: ready ? \"visible\" : \"hidden\",\n pointerEvents: isPresent ? \"auto\" : \"none\",\n transformOrigin: placement?.origin,\n background: colors.background,\n color: colors.text,\n borderRadius: 36,\n paddingBlock: 12,\n overflowY: \"auto\",\n overscrollBehavior: \"contain\",\n scrollbarWidth: \"thin\",\n boxShadow: \"0 12px 24px rgb(0 0 0 / 20%)\",\n backdropFilter: \"blur(24px)\",\n WebkitBackdropFilter: \"blur(24px)\",\n font: anchor.font,\n } as CSSProperties}\n >\n <div role=\"presentation\" style={{ paddingInline: 8 }}>\n {items.map((item) => (\n <Fragment key={item.id}>\n {item.separatorBefore && (\n <div role=\"separator\" style={{ height: 1, margin: \"4px 4px\", background: colors.separator }} />\n )}\n <button\n type=\"button\"\n role=\"menuitem\"\n data-mova-context-action={item.label}\n tabIndex={-1}\n disabled={item.disabled}\n onClick={() => { close(); item.onSelect?.(); }}\n style={{\n appearance: \"none\",\n border: 0,\n borderRadius: 18,\n width: \"100%\",\n minHeight: 50,\n display: \"flex\",\n alignItems: \"center\",\n padding: \"8px 10px\",\n gap: 16,\n color: item.destructive ? colors.destructive : \"inherit\",\n opacity: item.disabled ? 0.45 : 1,\n font: \"inherit\",\n textAlign: \"start\",\n cursor: item.disabled ? \"default\" : \"pointer\",\n }}\n >\n {item.icon !== undefined && (\n <span aria-hidden=\"true\" style={{ display: \"grid\", placeItems: \"center\", flex: \"0 0 26px\" }}>\n {item.icon}\n </span>\n )}\n <span style={{ display: \"grid\", gap: 1, minWidth: 0 }}>\n <span style={{ fontSize: 17, lineHeight: 1.2, overflowWrap: \"anywhere\" }}>{item.label}</span>\n {item.subtitle && (\n <span style={{ fontSize: 13, lineHeight: 1.25, color: colors.subtitle, overflowWrap: \"anywhere\" }}>\n {item.subtitle}\n </span>\n )}\n </span>\n </button>\n </Fragment>\n ))}\n </div>\n </motion.div>\n </motion.div>\n );\n}\n\ntype Press = { pointerId: number; x: number; y: number; timer: ReturnType<typeof setTimeout> };\n\nexport function ContextMenu({\n children,\n items,\n label = \"Actions\",\n theme = \"light\",\n disabled = false,\n className,\n}: ContextMenuProps) {\n const triggerRef = useRef<HTMLButtonElement>(null);\n const pressRef = useRef<Press | null>(null);\n const ignoreClick = useRef(false);\n const id = useId();\n const reducedMotion = useReducedMotion() ?? false;\n const [pressing, setPressing] = useState(false);\n const [anchor, setAnchor] = useState<Anchor | null>(null);\n const [showPreview, setShowPreview] = useState(false);\n const unavailable = disabled || items.length === 0;\n\n useEffect(() => {\n const trigger = triggerRef.current;\n const preventLongPressClick = (event: TouchEvent) => {\n // The browser can retarget the compatibility click to the backdrop on release.\n if (ignoreClick.current && event.cancelable) event.preventDefault();\n };\n trigger?.addEventListener(\"touchend\", preventLongPressClick, { passive: false });\n\n return () => {\n trigger?.removeEventListener(\"touchend\", preventLongPressClick);\n if (pressRef.current) clearTimeout(pressRef.current.timer);\n };\n }, []);\n\n const cancelPress = () => {\n if (pressRef.current) clearTimeout(pressRef.current.timer);\n pressRef.current = null;\n setPressing(false);\n };\n\n const open = (pressed = false) => {\n const trigger = triggerRef.current;\n if (!trigger || trigger.disabled || anchor) return;\n const { font, color } = getComputedStyle(trigger);\n setAnchor({ rect: trigger.getBoundingClientRect(), font, color, pressed });\n setShowPreview(true);\n };\n\n const close = (restoreFocus = true) => {\n setAnchor(null);\n if (restoreFocus) triggerRef.current?.focus({ preventScroll: true });\n };\n\n useEffect(() => {\n if (unavailable && anchor) {\n setAnchor(null);\n setShowPreview(false);\n }\n }, [anchor, unavailable]);\n\n const handlePointerDown = (event: PointerEvent<HTMLButtonElement>) => {\n if (!event.isPrimary || event.button !== 0 || anchor) return;\n ignoreClick.current = false;\n const timer = setTimeout(() => {\n ignoreClick.current = true;\n cancelPress();\n open(true);\n }, LONG_PRESS_DURATION);\n pressRef.current = { pointerId: event.pointerId, x: event.clientX, y: event.clientY, timer };\n setPressing(true);\n };\n\n const handlePointerMove = (event: PointerEvent<HTMLButtonElement>) => {\n const press = pressRef.current;\n if (!press || press.pointerId !== event.pointerId) return;\n if (Math.hypot(event.clientX - press.x, event.clientY - press.y) < 10) return;\n ignoreClick.current = true;\n cancelPress();\n };\n\n return (\n <div className={className} style={{ position: \"relative\", display: \"inline-grid\", maxWidth: \"100%\" }}>\n <style>{contextMenuCss}</style>\n <motion.div\n animate={{ scale: !reducedMotion && pressing ? PRESS_SCALE : 1 }}\n transition={{ duration: reducedMotion ? 0 : pressing ? 0.25 : 0.16, ease: \"easeOut\" }}\n style={{ minWidth: 0, visibility: showPreview ? \"hidden\" : undefined }}\n >\n {children}\n </motion.div>\n <button\n ref={triggerRef}\n type=\"button\"\n data-mova-context-trigger=\"\"\n aria-label={label}\n aria-haspopup=\"menu\"\n aria-expanded={anchor !== null}\n aria-controls={anchor ? id : undefined}\n aria-keyshortcuts=\"Shift+F10 ContextMenu\"\n disabled={unavailable}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={cancelPress}\n onPointerCancel={cancelPress}\n onPointerLeave={cancelPress}\n onContextMenu={(event) => {\n if (unavailable) return;\n event.preventDefault();\n event.stopPropagation();\n cancelPress();\n open();\n }}\n onKeyDown={(event) => {\n if (event.key !== \"ContextMenu\" && !(event.key === \"F10\" && event.shiftKey)) return;\n event.preventDefault();\n event.stopPropagation();\n open();\n }}\n onClick={(event) => {\n if (ignoreClick.current && event.detail !== 0) {\n ignoreClick.current = false;\n return;\n }\n open();\n }}\n style={{\n position: \"absolute\",\n inset: 0,\n width: \"100%\",\n height: \"100%\",\n appearance: \"none\",\n border: 0,\n padding: 0,\n borderRadius: \"inherit\",\n background: \"transparent\",\n color: \"inherit\",\n font: \"inherit\",\n cursor: unavailable ? undefined : \"context-menu\",\n touchAction: \"manipulation\",\n WebkitTouchCallout: \"none\",\n }}\n />\n {typeof document !== \"undefined\" && createPortal(\n <AnimatePresence onExitComplete={() => setShowPreview(false)}>\n {anchor && (\n <ContextMenuOverlay\n key=\"menu\"\n anchor={anchor}\n triggerRef={triggerRef}\n id={id}\n label={label}\n items={items}\n theme={theme}\n reducedMotion={reducedMotion}\n close={close}\n >\n {children}\n </ContextMenuOverlay>\n )}\n </AnimatePresence>,\n document.body,\n )}\n </div>\n );\n}\n"
|
|
19
|
+
}
|