@mova-ui/react 0.4.7 → 0.4.9
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/index.js +2561 -455
- package/dist/styles.css +1 -1
- package/dist/ui/cards/calendar-day-card.d.ts +6 -0
- package/dist/ui/cards/day-trading-order-ticket.d.ts +4 -2
- package/dist/ui/cards/imessage-chat.d.ts +3 -1
- package/dist/ui/cards/liquid-glass-sheet.d.ts +3 -1
- package/dist/ui/cards/voice-assistant-card.d.ts +3 -1
- package/package.json +3 -2
- 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/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 +2 -2
- 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-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 +3 -5
- package/registry/bar-cascade.json +1 -1
- package/registry/bar-sweep.json +1 -1
- 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 +4 -5
- 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/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 +3 -5
- 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/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/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 +2 -2
- 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 +3 -5
- 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 +659 -666
- 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/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 +3 -3
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "ai-sidebar-nav",
|
|
3
3
|
"tier": "free",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.9",
|
|
5
5
|
"file": "ai-sidebar-nav.tsx",
|
|
6
6
|
"sourcePath": "ui/ai/ai-sidebar-nav.tsx",
|
|
7
7
|
"registryFile": "ai-sidebar-nav.json",
|
|
@@ -13,5 +13,5 @@
|
|
|
13
13
|
],
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"previewMode": "client",
|
|
16
|
-
"content": "\"use client\";\n\"use client\";\n\n// SPDX-License-Identifier: MIT\n// Adapted from Beautiful UI, Copyright (c) 2026 Shane Levine.\n// Retains the original 224px / 52px rail and gliding navigation motion.\n\nimport { useEffect, useId, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { LogOut as IconArrowBoxLeft, Check as IconCheckmark1Small, ChevronDown as IconChevronDownSmall, X as IconCrossSmall, SquarePen as IconEditBig, House as IconHome, Search as IconMagnifyingGlass, Plus as IconPlusMedium, IceCream as IconPopsicle2, Settings as IconSettingsGear1, PanelLeftClose as IconSidebarLeftArrow, UserPlus as IconUserAdd } from \"lucide-react\";\n\n/* Shadow tokens from shadow-plugin 2.1.1.\nMIT License\n\nCopyright (c) 2026 Florian Kiem\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*/\nconst AI_THEME_STYLES = \"/* Shared public tokens for the standalone AI component family. */\\n.mova-ai-theme {\\n color-scheme: light;\\n color: var(--mova-ai-ink, #202020);\\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", sans-serif;\\n font-size: 13px;\\n line-height: 1.45;\\n letter-spacing: -0.01em;\\n\\n --mova-ai-page: #f7f7f7;\\n --mova-ai-canvas: #f4f4f4;\\n --mova-ai-surface: #ffffff;\\n --mova-ai-inset: #f8f8f8;\\n --mova-ai-hover: #f1f1f1;\\n --mova-ai-hover-strong: #e9e9e9;\\n\\n --mova-ai-ink: #202020;\\n --mova-ai-ink-secondary: #666666;\\n --mova-ai-ink-muted: #858585;\\n\\n --mova-ai-border: #dedede;\\n --mova-ai-border-soft: #ededed;\\n --mova-ai-border-strong: #c8c8c8;\\n --mova-ai-field: #f6f6f6;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(32 35 38 / 7%);\\n --mova-ai-stripe-bg: #242424;\\n\\n --mova-ai-accent: #454545;\\n --mova-ai-accent-ink: #333333;\\n --mova-ai-accent-foreground: #ffffff;\\n --mova-ai-accent-tint: #eeeeee;\\n --mova-ai-success: #28734d;\\n --mova-ai-success-tint: #e8f3ec;\\n --mova-ai-warning: #986517;\\n --mova-ai-warning-tint: #f8f0df;\\n --mova-ai-danger: #a33c3c;\\n --mova-ai-danger-tint: #f7eaea;\\n --mova-ai-info: #496b89;\\n --mova-ai-info-tint: #eaf0f5;\\n --mova-ai-neutral-status: #69737d;\\n --mova-ai-status-on: #ffffff;\\n\\n --mova-ai-tooltip-bg: #292929;\\n --mova-ai-tooltip-fg: #f6f6f6;\\n --mova-ai-tooltip-muted: #c3c3c3;\\n --mova-ai-tooltip-border: #484848;\\n\\n --mova-ai-radius-panel: 8px;\\n --mova-ai-radius-control: 8px;\\n --mova-ai-shadow-xs: 0 1px 2px rgb(22 28 33 / 5%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(22 28 33 / 7%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(22 28 33 / 11%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(22 28 33 / 15%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(22 28 33 / 12%);\\n}\\n\\n.mova-ai-theme[data-theme=\\\"dark\\\"] {\\n color-scheme: dark;\\n --mova-ai-page: #181818;\\n --mova-ai-canvas: #202020;\\n --mova-ai-surface: #272727;\\n --mova-ai-inset: #1d1d1d;\\n --mova-ai-hover: #2d2d2d;\\n --mova-ai-hover-strong: #393939;\\n\\n --mova-ai-ink: #eeeeee;\\n --mova-ai-ink-secondary: #b5b5b5;\\n --mova-ai-ink-muted: #929292;\\n\\n --mova-ai-border: #3b3b3b;\\n --mova-ai-border-soft: #303030;\\n --mova-ai-border-strong: #505050;\\n --mova-ai-field: #2c2c2c;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(255 255 255 / 6%);\\n --mova-ai-stripe-bg: #202528;\\n\\n --mova-ai-accent: #c2c2c2;\\n --mova-ai-accent-ink: #d5d5d5;\\n --mova-ai-accent-foreground: #202020;\\n --mova-ai-accent-tint: #353535;\\n --mova-ai-success: #88c7a0;\\n --mova-ai-success-tint: #293b30;\\n --mova-ai-warning: #d7b66f;\\n --mova-ai-warning-tint: #3c3525;\\n --mova-ai-danger: #e5928e;\\n --mova-ai-danger-tint: #3c2b2b;\\n --mova-ai-info: #9bbbd6;\\n --mova-ai-info-tint: #293541;\\n --mova-ai-neutral-status: #a0a0a0;\\n --mova-ai-status-on: #181818;\\n\\n --mova-ai-tooltip-bg: #121212;\\n --mova-ai-tooltip-fg: #eeeeee;\\n --mova-ai-tooltip-muted: #b5b5b5;\\n --mova-ai-tooltip-border: #3b3b3b;\\n\\n --mova-ai-shadow-xs: 0 1px 2px rgb(0 0 0 / 22%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(0 0 0 / 25%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(0 0 0 / 30%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(0 0 0 / 38%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(0 0 0 / 42%);\\n}\\n\";\nconst COMPONENT_STYLES = `[data-mova-sidebar] button:focus-visible { outline:2px solid var(--mova-ai-accent,#454545);outline-offset:2px;border-radius:8px; }\n[data-mova-sidebar] input:focus-visible { outline:none; }\n[data-mova-sidebar] .sidebar-search:focus-within { outline:2px solid var(--mova-ai-accent,#454545);outline-offset:2px;border-radius:8px; }\n[data-mova-sidebar] .sidebar-search button:focus-visible { outline:none; }\n[data-mova-sidebar] button { cursor:pointer; } [data-mova-sidebar] button:disabled { cursor:default; }\n[data-mova-sidebar] .sidebar-row{\n width: 208px;\n transition-duration: var(--sidebar-copy-duration);\n transition-timing-function: var(--sidebar-easing);\n}\n\n[data-mova-sidebar] .sidebar-copy{\n opacity: 1;\n transform: translateX(0);\n transition:\n opacity var(--sidebar-copy-duration) ease-out,\n transform var(--sidebar-copy-duration) var(--sidebar-easing);\n}\n\n[data-mova-sidebar] .sidebar-logo,\n[data-mova-sidebar] .sidebar-collapse-control,\n[data-mova-sidebar] .sidebar-expand-control{\n transition-duration: var(--sidebar-copy-duration);\n transition-timing-function: ease-out;\n}\n\n[data-mova-sidebar] .sidebar-expand-control{\n pointer-events: none;\n opacity: 0;\n}\n\n[data-mova-sidebar] .sidebar-glide-highlight{\n right: 8px;\n left: 8px;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-row{\n width: 36px;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-copy{\n pointer-events: none;\n opacity: 0;\n transform: translateX(calc(var(--sidebar-copy-offset) * -1));\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-workspace-control{\n pointer-events: none;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-logo,\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-collapse-control{\n pointer-events: none;\n opacity: 0;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-expand-control{\n pointer-events: auto;\n opacity: 1;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-glide-highlight{\n right: auto;\n width: 36px;\n}\n\n\n@keyframes mova-sidebar-pop {from {opacity:0;transform:scale(.95)} to {opacity:1;transform:scale(1)}}\n@keyframes mova-sidebar-eq {0%,100% {transform:scaleY(.35)} 50% {transform:scaleY(1)}}\n@media(prefers-reduced-motion:reduce) { [data-mova-sidebar], [data-mova-sidebar] * {animation:none!important;transition:none!important;scroll-behavior:auto!important;} }\n`;\n\ntype GlideMenuProps = {\n children: ReactNode;\n className?: string;\n highlightClassName?: string;\n rowSelector?: string;\n};\n\n/** A single hover layer that glides between interactive menu rows. */\nfunction GlideMenu({\n children,\n className = \"\",\n highlightClassName = \"inset-x-0 rounded-[8px] bg-[var(--mova-ai-hover,#f1f1f1)]\",\n rowSelector = \"[data-menu-row]\",\n}: GlideMenuProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [box, setBox] = useState<{ top: number; height: number } | null>(null);\n const [visible, setVisible] = useState(false);\n\n const moveTo = (target: EventTarget | null) => {\n const container = ref.current;\n if (!(target instanceof Element) || !container) return;\n const row = target.closest(rowSelector);\n if (!(row instanceof HTMLElement) || !container.contains(row)) return;\n const containerRect = container.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n setBox({ top: rowRect.top - containerRect.top, height: rowRect.height });\n setVisible(true);\n };\n\n return (\n <div\n ref={ref}\n onMouseOver={(event) => moveTo(event.target)}\n onMouseLeave={() => setVisible(false)}\n onFocusCapture={(event) => moveTo(event.target)}\n onBlurCapture={(event) => {\n if (!ref.current?.contains(event.relatedTarget as Node | null)) setVisible(false);\n }}\n className={`group/glide-menu relative ${className}`}\n >\n <span\n aria-hidden\n className={`pointer-events-none absolute ${highlightClassName}`}\n style={{\n top: box?.top ?? 0,\n height: box?.height ?? 0,\n opacity: box && visible ? 1 : 0,\n transition:\n \"top 220ms cubic-bezier(0.23,1,0.32,1), height 220ms cubic-bezier(0.23,1,0.32,1), opacity 150ms ease\",\n }}\n />\n {children}\n </div>\n );\n}\n\n/* ─────────────────────────────────────────────────────────\n * SIDEBAR NAV\n * Shared by the design-system preview and the harness shell:\n * compact workspace switcher, primary navigation, searchable\n * chat history, and a collapse that preserves icon alignment.\n * ───────────────────────────────────────────────────────── */\n\nconst WORKSPACE = { key: \"creamery\", name: \"Creamery Ops\", monogram: \"C\" };\n\nconst NAV_ITEMS = [\n { key: \"home\", label: \"Home\", icon: <IconHome size={18} /> },\n { key: \"invite\", label: \"Invite users\", icon: <IconUserAdd size={18} />, count: \"3/10\" },\n];\n\ntype SidebarRecent = {\n id: string;\n label: string;\n prompt?: string;\n};\n\nconst DEFAULT_RECENTS: SidebarRecent[] = [\n { id: \"suppliers\", label: \"Supplier records\" },\n { id: \"todos\", label: \"Urgent to-dos this morning\" },\n { id: \"flavor\", label: \"Flavor page ticket\" },\n { id: \"workload\", label: \"Workload summary\" },\n { id: \"offboarding\", label: \"Off-board a supplier\" },\n { id: \"restock\", label: \"Batch restock function\" },\n { id: \"edits\", label: \"Propose flavor edits\" },\n { id: \"subway\", label: \"Subway surfing\" },\n];\n\ntype SidebarNavProps = {\n theme?: \"dark\" | \"light\";\n onWorkspaceAction?: (action: string) => void;\n activeTitle?: string | null;\n className?: string;\n fill?: boolean;\n onNewChat?: () => void;\n onPick?: (id: string, label: string, prompt?: string) => void;\n /** controlled primary-nav selection (e.g. \"home\" | \"invite\") */\n activeNav?: string;\n onNavigate?: (key: string) => void;\n /** footer call-to-action — defaults to the demo \"Upgrade\" button */\n footerLabel?: string;\n footerIcon?: ReactNode;\n onFooterClick?: () => void;\n recents?: SidebarRecent[];\n variant?: string;\n};\n\nconst SIDEBAR_MOTION = {\n expandedWidth: 224,\n collapsedWidth: 52,\n duration: 280,\n copyDuration: 180,\n copyOffset: 8,\n easing: \"cubic-bezier(0.16, 1, 0.3, 1)\",\n};\n\n/* ─────────────────────────────────────────────────────────\n * CHAT SEARCH STORYBOARD\n *\n * 0ms search is triggered; Chats label begins fading\n * 0ms field grows right → left from the search control\n * 180ms field fills the row; cursor is focused and ready\n * ───────────────────────────────────────────────────────── */\nconst CHAT_SEARCH_MOTION = {\n duration: 180,\n closedWidth: 28,\n easing: \"cubic-bezier(0.16, 1, 0.3, 1)\",\n listDuration: 220,\n};\n\nfunction GlideGroup({ children }: { children: ReactNode }) {\n return (\n <GlideMenu\n rowSelector=\"[data-row]\"\n highlightClassName=\"sidebar-glide-highlight rounded-[7px] bg-[var(--mova-ai-hover-strong,#e9e9e9)]\"\n className=\"group/glide flex flex-col gap-px\"\n >\n {children}\n </GlideMenu>\n );\n}\n\nfunction RailButton({\n icon,\n label,\n active = false,\n count,\n onClick,\n}: {\n icon: ReactNode;\n label: string;\n active?: boolean;\n count?: string;\n onClick?: () => void;\n}) {\n return (\n <button\n data-row\n aria-label={label}\n aria-current={active ? \"page\" : undefined}\n type=\"button\"\n onClick={onClick}\n className={`sidebar-row relative z-10 mx-2 flex h-8 items-center rounded-[8px] px-2 text-left\n transition-[width,background-color,color,transform] duration-150 active:scale-[0.98]\n ${active ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] group-hover/glide:bg-transparent\" : \"\"}`}\n >\n <span className={`flex size-5 shrink-0 items-center justify-center ${active ? \"text-[var(--mova-ai-ink,#202020)]\" : \"text-[var(--mova-ai-ink-secondary,#666)]\"}`}>\n {icon}\n </span>\n <span className={`sidebar-copy ml-1.5 min-w-0 flex-1 truncate text-[14px] font-medium ${active ? \"text-[var(--mova-ai-ink,#202020)]\" : \"text-[var(--mova-ai-ink-secondary,#666)]\"}`}>\n {label}\n </span>\n {count && (\n <span className=\"sidebar-copy mr-2 shrink-0 text-[12px] font-medium tabular-nums text-[var(--mova-ai-ink-muted,#858585)]\">\n {count}\n </span>\n )}\n </button>\n );\n}\n\nfunction WorkspaceMenu({\n position,\n theme,\n onAction,\n onClose,\n}: {\n theme: \"dark\" | \"light\";\n onAction: (action: string) => void;\n position: { top: number; left: number };\n onClose: () => void;\n}) {\n const menuRef = useRef<HTMLDivElement>(null);\n useEffect(() => { menuRef.current?.querySelector<HTMLButtonElement>(\"button\")?.focus(); }, []);\n return createPortal(\n <div ref={menuRef}\n data-mova-sidebar data-theme={theme}\n data-workspace-menu\n role=\"group\" aria-label=\"Workspace menu\"\n onKeyDown={(event) => { if (event.key === \"Escape\") { event.preventDefault(); onClose(); } if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") { event.preventDefault(); const buttons = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>(\"button\")); const index = buttons.indexOf(document.activeElement as HTMLButtonElement); buttons[(index + (event.key === \"ArrowDown\" ? 1 : buttons.length - 1)) % buttons.length]?.focus(); } }}\n className=\"mova-ai-theme fixed z-50 w-64 rounded-lg border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] p-1.5 shadow-[var(--mova-ai-shadow-overlay,none)]\"\n style={{\n top: position.top,\n left: position.left,\n maxWidth: \"calc(100vw - 16px)\",\n maxHeight: \"calc(100dvh - 16px)\", overflowY: \"auto\",\n animation: \"mova-sidebar-pop 180ms cubic-bezier(0.23,1,0.32,1) both\",\n transformOrigin: \"top left\",\n }}\n >\n <style>{AI_THEME_STYLES}{COMPONENT_STYLES}</style>\n <GlideMenu className=\"flex flex-col gap-px\" highlightClassName=\"inset-x-0 rounded-[8px] bg-[var(--mova-ai-hover-strong,#e9e9e9)]\">\n <button\n data-menu-row\n type=\"button\"\n onClick={onClose}\n className=\"relative z-10 flex h-10 w-full items-center gap-1.5 rounded-[8px] px-2 text-left\"\n >\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-[7px] bg-[var(--mova-ai-ink,#202020)] text-[11px] font-semibold text-[var(--mova-ai-surface,#fff)]\">\n {WORKSPACE.monogram}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-[13.5px] font-medium text-[var(--mova-ai-ink,#202020)]\">{WORKSPACE.name}</span>\n <span className=\"shrink-0 text-[var(--mova-ai-ink,#202020)]\"><IconCheckmark1Small size={18} /></span>\n </button>\n <div className=\"my-1 h-px bg-[var(--mova-ai-border,#dedede)]\" />\n {[\n { label: \"New workspace\", icon: <IconPlusMedium size={16} /> },\n { label: \"Workspace settings\", icon: <IconSettingsGear1 size={16} /> },\n { label: \"Invite team members\", icon: <IconUserAdd size={16} /> },\n ].map((item) => (\n <button\n key={item.label}\n data-menu-row\n type=\"button\"\n onClick={() => { onAction(item.label); onClose(); }}\n className=\"relative z-10 flex h-9 w-full items-center gap-1.5 rounded-[8px] px-2 text-left\"\n >\n <span className=\"flex size-5 shrink-0 items-center justify-center text-[var(--mova-ai-ink-secondary,#666)]\">{item.icon}</span>\n <span className=\"min-w-0 flex-1 truncate text-[13.5px] text-[var(--mova-ai-ink,#202020)]\">{item.label}</span>\n </button>\n ))}\n <div className=\"my-1 h-px bg-[var(--mova-ai-border,#dedede)]\" />\n <button\n data-menu-row\n type=\"button\"\n onClick={() => { onAction(\"Sign out\"); onClose(); }}\n className=\"relative z-10 flex h-9 w-full items-center gap-1.5 rounded-[8px] px-2 text-left\"\n >\n <span className=\"flex size-5 shrink-0 items-center justify-center text-[var(--mova-ai-ink-secondary,#666)]\"><IconArrowBoxLeft size={16} /></span>\n <span className=\"min-w-0 flex-1 truncate text-[13.5px] text-[var(--mova-ai-ink,#202020)]\">Sign out</span>\n </button>\n </GlideMenu>\n </div>,\n document.body,\n );\n}\n\nexport function AISidebarNav({\n theme = \"dark\",\n onWorkspaceAction,\n activeTitle,\n className = \"\",\n fill = false,\n onNewChat,\n onPick,\n activeNav,\n onNavigate,\n footerLabel = \"Upgrade\",\n footerIcon,\n onFooterClick,\n recents = DEFAULT_RECENTS,\n}: SidebarNavProps) {\n const [feedback, setFeedback] = useState(\"\");\n const rootRef = useRef<HTMLElement>(null);\n const searchButtonRef = useRef<HTMLButtonElement>(null);\n const expandRef = useRef<HTMLButtonElement>(null);\n const collapseRef = useRef<HTMLButtonElement>(null);\n const [collapsed, setCollapsed] = useState(false);\n const [internalNav, setInternalNav] = useState(\"chats\");\n const currentNav = activeNav ?? internalNav;\n const selectNav = (key: string) => {\n setInternalNav(key);\n if (key === \"home\" || key === \"invite\") setFeedback(key === \"home\" ? \"Home selected\" : \"Invite users selected\");\n onNavigate?.(key);\n };\n const [demoActiveTitle, setDemoActiveTitle] = useState<string | null>(null);\n const [workspaceOpen, setWorkspaceOpen] = useState(false);\n const [workspacePosition, setWorkspacePosition] = useState({ top: 0, left: 0 });\n const [searchOpen, setSearchOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n const [chatsExpanded, setChatsExpanded] = useState(true);\n const historyId = useId();\n const searchId = useId();\n const workspaceButtonRef = useRef<HTMLButtonElement>(null);\n const searchRef = useRef<HTMLInputElement>(null);\n\n const selectedTitle = activeTitle === undefined ? demoActiveTitle : activeTitle;\n const visibleRecents = recents.filter((item) => item.label.toLowerCase().includes(query.trim().toLowerCase()));\n\n useEffect(() => {\n if (!workspaceOpen) return;\n const close = (event: PointerEvent) => {\n const target = event.target as Element;\n if (!workspaceButtonRef.current?.contains(target) && !target.closest(\"[data-workspace-menu]\")) {\n setWorkspaceOpen(false);\n }\n };\n document.addEventListener(\"pointerdown\", close);\n return () => document.removeEventListener(\"pointerdown\", close);\n }, [workspaceOpen]);\n\n useEffect(() => {\n if (!workspaceOpen) return;\n const close = () => { setWorkspaceOpen(false); workspaceButtonRef.current?.focus(); };\n window.addEventListener(\"resize\", close);\n return () => { window.removeEventListener(\"resize\", close); };\n }, [workspaceOpen]);\n\n useEffect(() => {\n if (searchOpen) searchRef.current?.focus();\n }, [searchOpen]);\n\n const collapse = () => {\n setCollapsed(true);\n requestAnimationFrame(() => expandRef.current?.focus());\n setWorkspaceOpen(false);\n setSearchOpen(false);\n setQuery(\"\");\n };\n\n const toggleChats = () => {\n if (chatsExpanded) {\n setSearchOpen(false);\n setQuery(\"\");\n }\n setChatsExpanded(!chatsExpanded);\n };\n\n return (\n <aside\n ref={rootRef}\n data-mova-sidebar data-theme={theme}\n data-sidebar-collapsed={collapsed}\n aria-label=\"Workspace navigation\"\n className={`mova-ai-theme relative flex shrink-0 overflow-hidden rounded-lg border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] text-[color:var(--mova-ai-ink,#202020)] transition-[width] ${fill ? \"h-full\" : \"h-[600px]\"} ${className}`}\n style={{\n width: collapsed ? SIDEBAR_MOTION.collapsedWidth : SIDEBAR_MOTION.expandedWidth,\n transitionDuration: `${SIDEBAR_MOTION.duration}ms`,\n transitionTimingFunction: SIDEBAR_MOTION.easing,\n \"--sidebar-copy-duration\": `${SIDEBAR_MOTION.copyDuration}ms`,\n \"--sidebar-copy-offset\": `${SIDEBAR_MOTION.copyOffset}px`,\n \"--sidebar-easing\": SIDEBAR_MOTION.easing,\n } as CSSProperties}\n >\n <style>{AI_THEME_STYLES}{COMPONENT_STYLES}</style>\n <div className=\"flex min-h-0 w-[224px] shrink-0 flex-col\">\n <div className=\"relative mb-2.5 h-10 shrink-0\">\n <button\n ref={workspaceButtonRef}\n data-workspace-trigger\n type=\"button\"\n aria-expanded={workspaceOpen}\n aria-hidden={collapsed}\n tabIndex={collapsed ? -1 : 0}\n onClick={() => {\n if (!workspaceOpen && workspaceButtonRef.current) {\n const rect = workspaceButtonRef.current.getBoundingClientRect();\n setWorkspacePosition({ top: Math.min(rect.bottom + 6, Math.max(8, window.innerHeight - 260)), left: Math.max(8, Math.min(rect.left, window.innerWidth - 264)) });\n }\n setWorkspaceOpen((open) => !open);\n }}\n className=\"sidebar-workspace-control absolute left-2 top-1 flex h-8 w-[164px] items-center rounded-[8px] px-2 text-left transition-[background-color,transform] duration-100 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] active:scale-[0.99]\"\n >\n <span className=\"sidebar-logo flex size-5 shrink-0 items-center justify-center text-[var(--mova-ai-ink,#202020)]\">\n <IconPopsicle2 size={18} />\n </span>\n <span className=\"sidebar-copy ml-1.5 min-w-0 flex-1 truncate text-[14px] font-medium text-[var(--mova-ai-ink-secondary,#666)]\">\n {WORKSPACE.name}\n </span>\n <span className=\"sidebar-copy ml-1 flex shrink-0 text-[var(--mova-ai-ink-muted,#858585)]\">\n <IconChevronDownSmall size={16} />\n </span>\n </button>\n\n {workspaceOpen && <WorkspaceMenu theme={theme} position={workspacePosition} onAction={(action) => { setFeedback(`${action} selected`); onWorkspaceAction?.(action); }} onClose={() => { setWorkspaceOpen(false); workspaceButtonRef.current?.focus(); }} />}\n\n <button\n type=\"button\"\n ref={collapseRef}\n aria-label=\"Collapse sidebar\"\n aria-hidden={collapsed}\n tabIndex={collapsed ? -1 : 0}\n onClick={collapse}\n className=\"sidebar-collapse-control absolute right-2 top-1 flex size-8 items-center justify-center rounded-[8px] text-[var(--mova-ai-ink-muted,#858585)] transition-[opacity,background-color,color] duration-150 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] hover:text-[var(--mova-ai-ink,#202020)]\"\n >\n <IconSidebarLeftArrow size={18} />\n </button>\n <button\n type=\"button\"\n ref={expandRef}\n aria-label=\"Expand sidebar\"\n aria-hidden={!collapsed}\n tabIndex={collapsed ? 0 : -1}\n onClick={() => { setCollapsed(false); requestAnimationFrame(() => collapseRef.current?.focus()); }}\n className=\"sidebar-expand-control absolute left-2 top-0.5 flex size-9 items-center justify-center rounded-[8px] text-[var(--mova-ai-ink-muted,#858585)] transition-[opacity,background-color,color] duration-150 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] hover:text-[var(--mova-ai-ink,#202020)]\"\n >\n <IconSidebarLeftArrow size={18} className=\"rotate-180\" />\n </button>\n </div>\n\n <GlideGroup>\n <RailButton\n icon={<IconEditBig size={18} />}\n label=\"New chat\"\n onClick={() => {\n if (activeTitle === undefined) setDemoActiveTitle(null);\n selectNav(\"chats\");\n setFeedback(\"New chat ready\");\n onNewChat?.();\n }}\n />\n {NAV_ITEMS.map((item) => (\n <RailButton\n key={item.key}\n icon={item.icon}\n label={item.label}\n count={item.count}\n active={currentNav === item.key}\n onClick={() => selectNav(item.key)}\n />\n ))}\n </GlideGroup>\n\n <div className=\"mt-3 flex min-h-0 flex-1 flex-col\" inert={collapsed}>\n <div className=\"sidebar-copy relative mx-2 mb-1 h-8 shrink-0\">\n <button\n type=\"button\"\n aria-label=\"Chats\"\n aria-expanded={chatsExpanded}\n aria-controls={historyId}\n aria-hidden={searchOpen}\n tabIndex={searchOpen ? -1 : 0}\n onClick={toggleChats}\n className={`absolute inset-0 flex items-center gap-1.5 rounded-[8px] px-2 text-left text-[12.5px] font-medium text-[var(--mova-ai-ink-muted,#858585)] transition-[opacity,transform,color] hover:text-[var(--mova-ai-ink,#202020)] ${searchOpen ? \"pointer-events-none -translate-x-1 opacity-0\" : \"translate-x-0 opacity-100\"}`}\n style={{ transitionDuration: `${CHAT_SEARCH_MOTION.duration}ms`, transitionTimingFunction: CHAT_SEARCH_MOTION.easing }}\n >\n <IconChevronDownSmall size={16} className={`transition-transform duration-200 ${chatsExpanded ? \"rotate-0\" : \"-rotate-90\"}`} />\n <span>Chats</span>\n </button>\n\n <button\n type=\"button\"\n ref={searchButtonRef}\n aria-label=\"Search chats\"\n tabIndex={searchOpen ? -1 : 0}\n aria-expanded={searchOpen}\n aria-controls={searchId}\n aria-hidden={searchOpen}\n onClick={() => { setChatsExpanded(true); setSearchOpen(true); }}\n className={`absolute right-0 top-0 z-10 flex size-8 items-center justify-center rounded-[8px] text-[var(--mova-ai-ink-muted,#858585)] transition-[opacity,background-color,color,transform] hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] hover:text-[var(--mova-ai-ink,#202020)] active:scale-[0.96] ${searchOpen ? \"pointer-events-none opacity-0\" : \"opacity-100\"}`}\n style={{ transitionDuration: `${CHAT_SEARCH_MOTION.duration}ms` }}\n >\n <IconMagnifyingGlass size={16} />\n </button>\n\n <div\n id={searchId}\n role=\"search\"\n aria-hidden={!searchOpen}\n inert={!searchOpen}\n className={`sidebar-search absolute right-0 top-0 z-20 flex h-8 items-center overflow-hidden rounded-[8px] bg-[var(--mova-ai-field,#f6f6f6)] text-[var(--mova-ai-ink-muted,#858585)] shadow-[var(--mova-ai-shadow-hairline,none)] transition-[width,opacity] focus-within:text-[var(--mova-ai-ink-secondary,#666)] ${searchOpen ? \"pointer-events-auto opacity-100\" : \"pointer-events-none opacity-0\"}`}\n style={{\n width: searchOpen ? \"100%\" : CHAT_SEARCH_MOTION.closedWidth,\n transitionDuration: `${CHAT_SEARCH_MOTION.duration}ms`,\n transitionTimingFunction: CHAT_SEARCH_MOTION.easing,\n }}\n >\n <span className=\"ml-2 flex shrink-0 items-center justify-center\">\n <IconMagnifyingGlass size={15} />\n </span>\n <input\n ref={searchRef}\n tabIndex={searchOpen ? 0 : -1}\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === \"Escape\") {\n setSearchOpen(false);\n setQuery(\"\");\n searchButtonRef.current?.focus();\n }\n }}\n placeholder=\"Search chats\"\n aria-label=\"Search chat history\"\n className=\"ml-1.5 min-w-0 flex-1 bg-transparent text-[13px] font-medium text-[var(--mova-ai-ink,#202020)] outline-none placeholder:text-[var(--mova-ai-ink-muted,#858585)]\"\n />\n <button\n type=\"button\"\n aria-label=\"Close chat search\"\n tabIndex={searchOpen ? 0 : -1}\n onClick={() => {\n setSearchOpen(false);\n setQuery(\"\");\n searchButtonRef.current?.focus();\n }}\n className=\"flex size-8 shrink-0 items-center justify-center rounded-[8px] text-[var(--mova-ai-ink-muted,#858585)] transition-[background-color,color,transform] duration-150 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] hover:text-[var(--mova-ai-ink,#202020)] active:scale-[0.96]\"\n >\n <IconCrossSmall size={16} />\n </button>\n </div>\n </div>\n\n <div\n id={historyId}\n aria-hidden={!chatsExpanded}\n inert={!chatsExpanded}\n className=\"grid min-h-0 flex-1 transition-[grid-template-rows,opacity]\"\n style={{\n gridTemplateRows: chatsExpanded ? \"minmax(0, 1fr)\" : \"minmax(0, 0fr)\",\n opacity: chatsExpanded ? 1 : 0,\n transitionDuration: `${CHAT_SEARCH_MOTION.listDuration}ms`,\n transitionTimingFunction: CHAT_SEARCH_MOTION.easing,\n }}\n >\n <div className=\"min-h-0 overflow-hidden\">\n <div className=\"h-full overflow-y-auto\">\n <GlideGroup>\n {visibleRecents.map((item) => {\n const active = item.label === selectedTitle;\n return (\n <button\n key={item.id}\n data-row\n type=\"button\"\n title={item.label}\n aria-current={active ? \"page\" : undefined}\n onClick={() => {\n setInternalNav(\"chats\");\n onNavigate?.(\"chats\");\n if (activeTitle === undefined) setDemoActiveTitle(item.label);\n onPick?.(item.id, item.label, item.prompt);\n }}\n className={`sidebar-row relative z-10 mx-2 flex h-8 items-center rounded-[8px] px-2 text-left transition-[width,background-color,color,transform] duration-150 active:scale-[0.98] ${\n active ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] group-hover/glide:bg-transparent\" : \"\"\n }`}\n >\n <span className={`sidebar-copy min-w-0 flex-1 truncate text-[14px] font-medium ${active ? \"text-[var(--mova-ai-ink,#202020)]\" : \"text-[var(--mova-ai-ink-secondary,#666)]\"}`}>\n {item.label}\n </span>\n </button>\n );\n })}\n {query && visibleRecents.length === 0 && (\n <div className=\"sidebar-copy mx-2 px-2 py-2 text-[12.5px] text-[var(--mova-ai-ink-muted,#858585)]\">No chats found</div>\n )}\n </GlideGroup>\n </div>\n </div>\n </div>\n </div>\n\n <div inert={collapsed} className=\"sidebar-copy mx-2 mt-3 w-[208px] border-t border-[var(--mova-ai-border,#dedede)] pt-3\">\n <button\n type=\"button\"\n onClick={() => { (onFooterClick ?? onNewChat)?.(); }}\n className=\"flex h-8 w-full items-center justify-center gap-1.5 rounded-[8px] bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[12.5px] font-medium text-[var(--mova-ai-ink,#202020)] transition-[background-color,transform] duration-150 hover:bg-[var(--mova-ai-border-strong,#c8c8c8)] active:scale-[0.98]\"\n >\n {footerIcon}\n {footerLabel}\n </button>\n <p role=\"status\" className=\"sr-only\">{feedback}</p>\n </div>\n </div>\n </aside>\n );\n}\n\nexport default AISidebarNav;\n"
|
|
16
|
+
"content": "\"use client\";\n\"use client\";\n\n// SPDX-License-Identifier: MIT\n// Adapted from Beautiful UI, Copyright (c) 2026 Shane Levine.\n// Retains the original 224px / 52px rail and gliding navigation motion.\n\nimport { useEffect, useId, useRef, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { LogOut as IconArrowBoxLeft, Check as IconCheckmark1Small, ChevronDown as IconChevronDownSmall, X as IconCrossSmall, SquarePen as IconEditBig, House as IconHome, Search as IconMagnifyingGlass, Plus as IconPlusMedium, IceCream as IconPopsicle2, Settings as IconSettingsGear1, PanelLeftClose as IconSidebarLeftArrow, UserPlus as IconUserAdd } from \"lucide-react\";\n\n/* Shadow tokens from shadow-plugin 2.1.1.\nMIT License\n\nCopyright (c) 2026 Florian Kiem\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*/\nconst AI_THEME_STYLES = \"/* Shared public tokens for the standalone AI component family. */\\n.mova-ai-theme {\\n color-scheme: light;\\n color: var(--mova-ai-ink, #202020);\\n font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \\\"Segoe UI\\\", sans-serif;\\n font-size: 13px;\\n line-height: 1.45;\\n letter-spacing: -0.01em;\\n\\n --mova-ai-page: #f7f7f7;\\n --mova-ai-canvas: #f4f4f4;\\n --mova-ai-surface: #ffffff;\\n --mova-ai-inset: #f8f8f8;\\n --mova-ai-hover: #f1f1f1;\\n --mova-ai-hover-strong: #e9e9e9;\\n\\n --mova-ai-ink: #202020;\\n --mova-ai-ink-secondary: #666666;\\n --mova-ai-ink-muted: #858585;\\n\\n --mova-ai-border: #dedede;\\n --mova-ai-border-soft: #ededed;\\n --mova-ai-border-strong: #c8c8c8;\\n --mova-ai-field: #f6f6f6;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(32 35 38 / 7%);\\n --mova-ai-stripe-bg: #242424;\\n\\n --mova-ai-accent: #454545;\\n --mova-ai-accent-ink: #333333;\\n --mova-ai-accent-foreground: #ffffff;\\n --mova-ai-accent-tint: #eeeeee;\\n --mova-ai-success: #28734d;\\n --mova-ai-success-tint: #e8f3ec;\\n --mova-ai-warning: #986517;\\n --mova-ai-warning-tint: #f8f0df;\\n --mova-ai-danger: #a33c3c;\\n --mova-ai-danger-tint: #f7eaea;\\n --mova-ai-info: #496b89;\\n --mova-ai-info-tint: #eaf0f5;\\n --mova-ai-neutral-status: #69737d;\\n --mova-ai-status-on: #ffffff;\\n\\n --mova-ai-tooltip-bg: #292929;\\n --mova-ai-tooltip-fg: #f6f6f6;\\n --mova-ai-tooltip-muted: #c3c3c3;\\n --mova-ai-tooltip-border: #484848;\\n\\n --mova-ai-radius-panel: 8px;\\n --mova-ai-radius-control: 8px;\\n --mova-ai-shadow-xs: 0 1px 2px rgb(22 28 33 / 5%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(22 28 33 / 7%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(22 28 33 / 11%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(22 28 33 / 15%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(22 28 33 / 12%);\\n}\\n\\n.mova-ai-theme[data-theme=\\\"dark\\\"] {\\n color-scheme: dark;\\n --mova-ai-page: #181818;\\n --mova-ai-canvas: #202020;\\n --mova-ai-surface: #272727;\\n --mova-ai-inset: #1d1d1d;\\n --mova-ai-hover: #2d2d2d;\\n --mova-ai-hover-strong: #393939;\\n\\n --mova-ai-ink: #eeeeee;\\n --mova-ai-ink-secondary: #b5b5b5;\\n --mova-ai-ink-muted: #929292;\\n\\n --mova-ai-border: #3b3b3b;\\n --mova-ai-border-soft: #303030;\\n --mova-ai-border-strong: #505050;\\n --mova-ai-field: #2c2c2c;\\n --mova-ai-grid: color-mix(in srgb, var(--mova-ai-border) 78%, transparent);\\n --mova-ai-stripe: rgb(255 255 255 / 6%);\\n --mova-ai-stripe-bg: #202528;\\n\\n --mova-ai-accent: #c2c2c2;\\n --mova-ai-accent-ink: #d5d5d5;\\n --mova-ai-accent-foreground: #202020;\\n --mova-ai-accent-tint: #353535;\\n --mova-ai-success: #88c7a0;\\n --mova-ai-success-tint: #293b30;\\n --mova-ai-warning: #d7b66f;\\n --mova-ai-warning-tint: #3c3525;\\n --mova-ai-danger: #e5928e;\\n --mova-ai-danger-tint: #3c2b2b;\\n --mova-ai-info: #9bbbd6;\\n --mova-ai-info-tint: #293541;\\n --mova-ai-neutral-status: #a0a0a0;\\n --mova-ai-status-on: #181818;\\n\\n --mova-ai-tooltip-bg: #121212;\\n --mova-ai-tooltip-fg: #eeeeee;\\n --mova-ai-tooltip-muted: #b5b5b5;\\n --mova-ai-tooltip-border: #3b3b3b;\\n\\n --mova-ai-shadow-xs: 0 1px 2px rgb(0 0 0 / 22%);\\n --mova-ai-shadow-sm: 0 2px 8px rgb(0 0 0 / 25%);\\n --mova-ai-shadow-md: 0 8px 20px rgb(0 0 0 / 30%);\\n --mova-ai-shadow-lg: 0 14px 32px rgb(0 0 0 / 38%);\\n --mova-ai-shadow-hairline: 0 0 0 1px var(--mova-ai-border);\\n --mova-ai-shadow-btn: 0 0 0 1px var(--mova-ai-border-strong), var(--mova-ai-shadow-xs);\\n --mova-ai-shadow-card: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-sm);\\n --mova-ai-shadow-raised: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-md);\\n --mova-ai-shadow-overlay: 0 0 0 1px var(--mova-ai-border), var(--mova-ai-shadow-lg);\\n --mova-ai-shadow-inset-field: inset 0 1px 2px rgb(0 0 0 / 42%);\\n}\\n\";\nconst COMPONENT_STYLES = `[data-mova-sidebar] button:focus-visible { outline:2px solid var(--mova-ai-accent,#454545);outline-offset:2px;border-radius:8px; }\n[data-mova-sidebar] input:focus-visible { outline:none; }\n[data-mova-sidebar] .sidebar-search:focus-within { outline:2px solid var(--mova-ai-accent,#454545);outline-offset:2px;border-radius:8px; }\n[data-mova-sidebar] .sidebar-search button:focus-visible { outline:none; }\n[data-mova-sidebar] button { cursor:pointer; } [data-mova-sidebar] button:disabled { cursor:default; }\n[data-mova-sidebar] .sidebar-row{\n width: 208px;\n transition-duration: var(--sidebar-copy-duration);\n transition-timing-function: var(--sidebar-easing);\n}\n\n[data-mova-sidebar] .sidebar-copy{\n opacity: 1;\n transform: translateX(0);\n transition:\n opacity var(--sidebar-copy-duration) ease-out,\n transform var(--sidebar-copy-duration) var(--sidebar-easing);\n}\n\n[data-mova-sidebar] .sidebar-logo,\n[data-mova-sidebar] .sidebar-collapse-control,\n[data-mova-sidebar] .sidebar-expand-control{\n transition-duration: var(--sidebar-copy-duration);\n transition-timing-function: ease-out;\n}\n\n[data-mova-sidebar] .sidebar-expand-control{\n pointer-events: none;\n opacity: 0;\n}\n\n[data-mova-sidebar] .sidebar-glide-highlight{\n right: 8px;\n left: 8px;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-row{\n width: 36px;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-copy{\n pointer-events: none;\n opacity: 0;\n transform: translateX(calc(var(--sidebar-copy-offset) * -1));\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-workspace-control{\n pointer-events: none;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-logo,\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-collapse-control{\n pointer-events: none;\n opacity: 0;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-expand-control{\n pointer-events: auto;\n opacity: 1;\n}\n\n[data-mova-sidebar][data-sidebar-collapsed=\"true\"] .sidebar-glide-highlight{\n right: auto;\n width: 36px;\n}\n\n\n@keyframes mova-sidebar-pop {from {opacity:0;transform:scale(.95)} to {opacity:1;transform:scale(1)}}\n@keyframes mova-sidebar-eq {0%,100% {transform:scaleY(.35)} 50% {transform:scaleY(1)}}\n@media(prefers-reduced-motion:reduce) { [data-mova-sidebar], [data-mova-sidebar] * {animation:none!important;transition:none!important;scroll-behavior:auto!important;} }\n`;\n\ntype GlideMenuProps = {\n children: ReactNode;\n className?: string;\n highlightClassName?: string;\n rowSelector?: string;\n};\n\n/** A single hover layer that glides between interactive menu rows. */\nfunction GlideMenu({\n children,\n className = \"\",\n highlightClassName = \"inset-x-0 rounded-[8px] bg-[var(--mova-ai-hover,#f1f1f1)]\",\n rowSelector = \"[data-menu-row]\",\n}: GlideMenuProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [box, setBox] = useState<{ top: number; height: number } | null>(null);\n const [visible, setVisible] = useState(false);\n\n const moveTo = (target: EventTarget | null) => {\n const container = ref.current;\n if (!(target instanceof Element) || !container) return;\n const row = target.closest(rowSelector);\n if (!(row instanceof HTMLElement) || !container.contains(row)) return;\n const containerRect = container.getBoundingClientRect();\n const rowRect = row.getBoundingClientRect();\n setBox({ top: rowRect.top - containerRect.top, height: rowRect.height });\n setVisible(true);\n };\n\n return (\n <div\n ref={ref}\n onMouseOver={(event) => moveTo(event.target)}\n onMouseLeave={() => setVisible(false)}\n onFocusCapture={(event) => moveTo(event.target)}\n onBlurCapture={(event) => {\n if (!ref.current?.contains(event.relatedTarget as Node | null)) setVisible(false);\n }}\n className={`group/glide-menu relative ${className}`}\n >\n <span\n aria-hidden\n className={`pointer-events-none absolute ${highlightClassName}`}\n style={{\n top: box?.top ?? 0,\n height: box?.height ?? 0,\n opacity: box && visible ? 1 : 0,\n transition:\n \"top 220ms cubic-bezier(0.23,1,0.32,1), height 220ms cubic-bezier(0.23,1,0.32,1), opacity 150ms ease\",\n }}\n />\n {children}\n </div>\n );\n}\n\n/* ─────────────────────────────────────────────────────────\n * SIDEBAR NAV\n * Shared by the design-system preview and the harness shell:\n * compact workspace switcher, primary navigation, searchable\n * chat history, and a collapse that preserves icon alignment.\n * ───────────────────────────────────────────────────────── */\n\nconst WORKSPACE = { key: \"creamery\", name: \"Creamery Ops\", monogram: \"C\" };\n\nconst NAV_ITEMS = [\n { key: \"home\", label: \"Home\", icon: <IconHome size={18} /> },\n { key: \"invite\", label: \"Invite users\", icon: <IconUserAdd size={18} />, count: \"3/10\" },\n];\n\ntype SidebarRecent = {\n id: string;\n label: string;\n prompt?: string;\n};\n\nconst DEFAULT_RECENTS: SidebarRecent[] = [\n { id: \"suppliers\", label: \"Supplier records\" },\n { id: \"todos\", label: \"Urgent to-dos this morning\" },\n { id: \"flavor\", label: \"Flavor page ticket\" },\n { id: \"workload\", label: \"Workload summary\" },\n { id: \"offboarding\", label: \"Off-board a supplier\" },\n { id: \"restock\", label: \"Batch restock function\" },\n { id: \"edits\", label: \"Propose flavor edits\" },\n { id: \"subway\", label: \"Subway surfing\" },\n];\n\ntype SidebarNavProps = {\n theme?: \"dark\" | \"light\";\n onWorkspaceAction?: (action: string) => void;\n activeTitle?: string | null;\n className?: string;\n fill?: boolean;\n onNewChat?: () => void;\n onPick?: (id: string, label: string, prompt?: string) => void;\n /** controlled primary-nav selection (e.g. \"home\" | \"invite\") */\n activeNav?: string;\n onNavigate?: (key: string) => void;\n /** footer call-to-action — defaults to the demo \"Upgrade\" button */\n footerLabel?: string;\n footerIcon?: ReactNode;\n onFooterClick?: () => void;\n recents?: SidebarRecent[];\n variant?: string;\n};\n\nconst SIDEBAR_MOTION = {\n expandedWidth: 224,\n collapsedWidth: 52,\n duration: 280,\n copyDuration: 180,\n copyOffset: 8,\n easing: \"cubic-bezier(0.16, 1, 0.3, 1)\",\n};\n\n/* ─────────────────────────────────────────────────────────\n * CHAT SEARCH STORYBOARD\n *\n * 0ms search is triggered; Chats label begins fading\n * 0ms field grows right → left from the search control\n * 180ms field fills the row; cursor is focused and ready\n * ───────────────────────────────────────────────────────── */\nconst CHAT_SEARCH_MOTION = {\n duration: 180,\n closedWidth: 28,\n easing: \"cubic-bezier(0.16, 1, 0.3, 1)\",\n listDuration: 220,\n};\n\nfunction GlideGroup({ children }: { children: ReactNode }) {\n return (\n <GlideMenu\n rowSelector=\"[data-row]\"\n highlightClassName=\"sidebar-glide-highlight rounded-[7px] bg-[var(--mova-ai-hover-strong,#e9e9e9)]\"\n className=\"group/glide flex flex-col gap-px\"\n >\n {children}\n </GlideMenu>\n );\n}\n\nfunction RailButton({\n icon,\n label,\n active = false,\n count,\n onClick,\n}: {\n icon: ReactNode;\n label: string;\n active?: boolean;\n count?: string;\n onClick?: () => void;\n}) {\n return (\n <button\n data-row\n aria-label={label}\n aria-current={active ? \"page\" : undefined}\n type=\"button\"\n onClick={onClick}\n className={`sidebar-row relative z-10 mx-2 flex h-8 items-center rounded-[8px] px-2 text-left\n transition-[width,background-color,color,transform] duration-150 active:scale-[0.98]\n ${active ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] group-hover/glide:bg-transparent\" : \"\"}`}\n >\n <span className={`flex size-5 shrink-0 items-center justify-center ${active ? \"text-[var(--mova-ai-ink,#202020)]\" : \"text-[var(--mova-ai-ink-secondary,#666)]\"}`}>\n {icon}\n </span>\n <span className={`sidebar-copy ml-1.5 min-w-0 flex-1 truncate text-[14px] font-medium ${active ? \"text-[var(--mova-ai-ink,#202020)]\" : \"text-[var(--mova-ai-ink-secondary,#666)]\"}`}>\n {label}\n </span>\n {count && (\n <span className=\"sidebar-copy mr-2 shrink-0 text-[12px] font-medium tabular-nums text-[var(--mova-ai-ink-muted,#858585)]\">\n {count}\n </span>\n )}\n </button>\n );\n}\n\nfunction WorkspaceMenu({\n position,\n theme,\n onAction,\n onClose,\n}: {\n theme: \"dark\" | \"light\";\n onAction: (action: string) => void;\n position: { top: number; left: number };\n onClose: () => void;\n}) {\n const menuRef = useRef<HTMLDivElement>(null);\n useEffect(() => { menuRef.current?.querySelector<HTMLButtonElement>(\"button\")?.focus(); }, []);\n return createPortal(\n <div ref={menuRef}\n data-mova-sidebar data-theme={theme}\n data-workspace-menu\n role=\"group\" aria-label=\"Workspace menu\"\n onKeyDown={(event) => { if (event.key === \"Escape\") { event.preventDefault(); onClose(); } if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") { event.preventDefault(); const buttons = Array.from(event.currentTarget.querySelectorAll<HTMLButtonElement>(\"button\")); const index = buttons.indexOf(document.activeElement as HTMLButtonElement); buttons[(index + (event.key === \"ArrowDown\" ? 1 : buttons.length - 1)) % buttons.length]?.focus(); } }}\n className=\"mova-ai-theme fixed z-50 w-64 rounded-lg border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] p-1.5 shadow-[var(--mova-ai-shadow-overlay,none)]\"\n style={{\n top: position.top,\n left: position.left,\n maxWidth: \"calc(100vw - 16px)\",\n maxHeight: \"calc(100dvh - 16px)\", overflowY: \"auto\",\n animation: \"mova-sidebar-pop 180ms cubic-bezier(0.23,1,0.32,1) both\",\n transformOrigin: \"top left\",\n }}\n >\n <style>{AI_THEME_STYLES}{COMPONENT_STYLES}</style>\n <GlideMenu className=\"flex flex-col gap-px\" highlightClassName=\"inset-x-0 rounded-[8px] bg-[var(--mova-ai-hover-strong,#e9e9e9)]\">\n <button\n data-menu-row\n type=\"button\"\n onClick={onClose}\n className=\"relative z-10 flex h-10 w-full items-center gap-1.5 rounded-[8px] px-2 text-left\"\n >\n <span className=\"flex size-6 shrink-0 items-center justify-center rounded-[7px] bg-[var(--mova-ai-ink,#202020)] text-[11px] font-semibold text-[var(--mova-ai-surface,#fff)]\">\n {WORKSPACE.monogram}\n </span>\n <span className=\"min-w-0 flex-1 truncate text-[13.5px] font-medium text-[var(--mova-ai-ink,#202020)]\">{WORKSPACE.name}</span>\n <span className=\"shrink-0 text-[var(--mova-ai-ink,#202020)]\"><IconCheckmark1Small size={18} /></span>\n </button>\n <div className=\"my-1 h-px bg-[var(--mova-ai-border,#dedede)]\" />\n {[\n { label: \"New workspace\", icon: <IconPlusMedium size={16} /> },\n { label: \"Workspace settings\", icon: <IconSettingsGear1 size={16} /> },\n { label: \"Invite team members\", icon: <IconUserAdd size={16} /> },\n ].map((item) => (\n <button\n key={item.label}\n data-menu-row\n type=\"button\"\n onClick={() => { onAction(item.label); onClose(); }}\n className=\"relative z-10 flex h-9 w-full items-center gap-1.5 rounded-[8px] px-2 text-left\"\n >\n <span className=\"flex size-5 shrink-0 items-center justify-center text-[var(--mova-ai-ink-secondary,#666)]\">{item.icon}</span>\n <span className=\"min-w-0 flex-1 truncate text-[13.5px] text-[var(--mova-ai-ink,#202020)]\">{item.label}</span>\n </button>\n ))}\n <div className=\"my-1 h-px bg-[var(--mova-ai-border,#dedede)]\" />\n <button\n data-menu-row\n type=\"button\"\n onClick={() => { onAction(\"Sign out\"); onClose(); }}\n className=\"relative z-10 flex h-9 w-full items-center gap-1.5 rounded-[8px] px-2 text-left\"\n >\n <span className=\"flex size-5 shrink-0 items-center justify-center text-[var(--mova-ai-ink-secondary,#666)]\"><IconArrowBoxLeft size={16} /></span>\n <span className=\"min-w-0 flex-1 truncate text-[13.5px] text-[var(--mova-ai-ink,#202020)]\">Sign out</span>\n </button>\n </GlideMenu>\n </div>,\n document.body,\n );\n}\n\nexport function AISidebarNav({\n theme = \"dark\",\n onWorkspaceAction,\n activeTitle,\n className = \"\",\n fill = false,\n onNewChat,\n onPick,\n activeNav,\n onNavigate,\n footerLabel = \"Upgrade\",\n footerIcon,\n onFooterClick,\n recents = DEFAULT_RECENTS,\n}: SidebarNavProps) {\n const [feedback, setFeedback] = useState(\"\");\n const rootRef = useRef<HTMLElement>(null);\n const searchButtonRef = useRef<HTMLButtonElement>(null);\n const expandRef = useRef<HTMLButtonElement>(null);\n const collapseRef = useRef<HTMLButtonElement>(null);\n const [collapsed, setCollapsed] = useState(false);\n const [internalNav, setInternalNav] = useState(\"chats\");\n const currentNav = activeNav ?? internalNav;\n const selectNav = (key: string) => {\n setInternalNav(key);\n if (key === \"home\" || key === \"invite\") setFeedback(key === \"home\" ? \"Home selected\" : \"Invite users selected\");\n onNavigate?.(key);\n };\n const [demoActiveTitle, setDemoActiveTitle] = useState<string | null>(null);\n const [workspaceOpen, setWorkspaceOpen] = useState(false);\n const [workspacePosition, setWorkspacePosition] = useState({ top: 0, left: 0 });\n const [searchOpen, setSearchOpen] = useState(false);\n const [query, setQuery] = useState(\"\");\n const [chatsExpanded, setChatsExpanded] = useState(true);\n const historyId = useId();\n const searchId = useId();\n const workspaceButtonRef = useRef<HTMLButtonElement>(null);\n const searchRef = useRef<HTMLInputElement>(null);\n\n const selectedTitle = activeTitle === undefined ? demoActiveTitle : activeTitle;\n const visibleRecents = recents.filter((item) => item.label.toLowerCase().includes(query.trim().toLowerCase()));\n\n useEffect(() => {\n if (!workspaceOpen) return;\n const close = (event: PointerEvent) => {\n const target = event.target as Element;\n if (!workspaceButtonRef.current?.contains(target) && !target.closest(\"[data-workspace-menu]\")) {\n setWorkspaceOpen(false);\n }\n };\n document.addEventListener(\"pointerdown\", close);\n return () => document.removeEventListener(\"pointerdown\", close);\n }, [workspaceOpen]);\n\n useEffect(() => {\n if (!workspaceOpen) return;\n const close = () => { setWorkspaceOpen(false); workspaceButtonRef.current?.focus(); };\n window.addEventListener(\"resize\", close);\n return () => { window.removeEventListener(\"resize\", close); };\n }, [workspaceOpen]);\n\n useEffect(() => {\n if (searchOpen) searchRef.current?.focus();\n }, [searchOpen]);\n\n const collapse = () => {\n setCollapsed(true);\n requestAnimationFrame(() => expandRef.current?.focus());\n setWorkspaceOpen(false);\n setSearchOpen(false);\n setQuery(\"\");\n };\n\n const toggleChats = () => {\n if (chatsExpanded) {\n setSearchOpen(false);\n setQuery(\"\");\n }\n setChatsExpanded(!chatsExpanded);\n };\n\n return (\n <aside\n ref={rootRef}\n data-mova-sidebar data-theme={theme}\n data-sidebar-collapsed={collapsed}\n aria-label=\"Workspace navigation\"\n className={`mova-ai-theme relative flex shrink-0 overflow-hidden rounded-lg border border-[var(--mova-ai-border,#dedede)] bg-[var(--mova-ai-surface,#fff)] text-[color:var(--mova-ai-ink,#202020)] transition-[width] ${fill ? \"h-full\" : \"h-[600px]\"} ${className}`}\n style={{\n width: collapsed ? SIDEBAR_MOTION.collapsedWidth : SIDEBAR_MOTION.expandedWidth,\n transitionDuration: `${SIDEBAR_MOTION.duration}ms`,\n transitionTimingFunction: SIDEBAR_MOTION.easing,\n \"--sidebar-copy-duration\": `${SIDEBAR_MOTION.copyDuration}ms`,\n \"--sidebar-copy-offset\": `${SIDEBAR_MOTION.copyOffset}px`,\n \"--sidebar-easing\": SIDEBAR_MOTION.easing,\n } as CSSProperties}\n >\n <style>{AI_THEME_STYLES}{COMPONENT_STYLES}</style>\n <div className=\"flex min-h-0 w-[224px] shrink-0 flex-col\">\n <div className=\"relative mb-2.5 h-10 shrink-0\">\n <button\n ref={workspaceButtonRef}\n data-workspace-trigger\n type=\"button\"\n aria-expanded={workspaceOpen}\n aria-hidden={collapsed}\n tabIndex={collapsed ? -1 : 0}\n onClick={() => {\n if (!workspaceOpen && workspaceButtonRef.current) {\n const rect = workspaceButtonRef.current.getBoundingClientRect();\n setWorkspacePosition({ top: Math.min(rect.bottom + 6, Math.max(8, window.innerHeight - 260)), left: Math.max(8, Math.min(rect.left, window.innerWidth - 264)) });\n }\n setWorkspaceOpen((open) => !open);\n }}\n className=\"sidebar-workspace-control absolute left-2 top-1 flex h-8 w-[164px] items-center rounded-[8px] px-2 text-left transition-[background-color,transform] duration-100 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] active:scale-[0.99]\"\n >\n <span className=\"sidebar-logo flex size-5 shrink-0 items-center justify-center text-[var(--mova-ai-ink,#202020)]\">\n <IconPopsicle2 size={18} />\n </span>\n <span className=\"sidebar-copy ml-1.5 min-w-0 flex-1 truncate text-[14px] font-medium text-[var(--mova-ai-ink-secondary,#666)]\">\n {WORKSPACE.name}\n </span>\n <span className=\"sidebar-copy ml-1 flex shrink-0 text-[var(--mova-ai-ink-muted,#858585)]\">\n <IconChevronDownSmall size={16} />\n </span>\n </button>\n\n {workspaceOpen && <WorkspaceMenu theme={theme} position={workspacePosition} onAction={(action) => { setFeedback(`${action} selected`); onWorkspaceAction?.(action); }} onClose={() => { setWorkspaceOpen(false); workspaceButtonRef.current?.focus(); }} />}\n\n <button\n type=\"button\"\n ref={collapseRef}\n aria-label=\"Collapse sidebar\"\n aria-hidden={collapsed}\n tabIndex={collapsed ? -1 : 0}\n onClick={collapse}\n className=\"sidebar-collapse-control absolute right-2 top-1 flex size-8 items-center justify-center rounded-[8px] text-[var(--mova-ai-ink-muted,#858585)] transition-[opacity,background-color,color] duration-150 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] hover:text-[var(--mova-ai-ink,#202020)]\"\n >\n <IconSidebarLeftArrow size={18} />\n </button>\n <button\n type=\"button\"\n ref={expandRef}\n aria-label=\"Expand sidebar\"\n aria-hidden={!collapsed}\n tabIndex={collapsed ? 0 : -1}\n onClick={() => { setCollapsed(false); requestAnimationFrame(() => collapseRef.current?.focus()); }}\n className=\"sidebar-expand-control absolute left-2 top-0.5 flex size-9 items-center justify-center rounded-[8px] text-[var(--mova-ai-ink-muted,#858585)] transition-[opacity,background-color,color] duration-150 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] hover:text-[var(--mova-ai-ink,#202020)]\"\n >\n <IconSidebarLeftArrow size={18} className=\"rotate-180\" />\n </button>\n </div>\n\n <GlideGroup>\n <RailButton\n icon={<IconEditBig size={18} />}\n label=\"New chat\"\n onClick={() => {\n if (activeTitle === undefined) setDemoActiveTitle(null);\n selectNav(\"chats\");\n setFeedback(\"New chat ready\");\n onNewChat?.();\n }}\n />\n {NAV_ITEMS.map((item) => (\n <RailButton\n key={item.key}\n icon={item.icon}\n label={item.label}\n count={item.count}\n active={currentNav === item.key}\n onClick={() => selectNav(item.key)}\n />\n ))}\n </GlideGroup>\n\n <div className=\"mt-3 flex min-h-0 flex-1 flex-col\" inert={collapsed}>\n <div className=\"sidebar-copy relative mx-2 mb-1 h-8 shrink-0\">\n <button\n type=\"button\"\n aria-label=\"Chats\"\n aria-expanded={chatsExpanded}\n aria-controls={historyId}\n aria-hidden={searchOpen}\n tabIndex={searchOpen ? -1 : 0}\n onClick={toggleChats}\n className={`absolute inset-0 flex items-center gap-1.5 rounded-[8px] px-2 text-left text-[12.5px] font-medium text-[var(--mova-ai-ink-muted,#858585)] transition-[opacity,transform,color] hover:text-[var(--mova-ai-ink,#202020)] ${searchOpen ? \"pointer-events-none -translate-x-1 opacity-0\" : \"translate-x-0 opacity-100\"}`}\n style={{ transitionDuration: `${CHAT_SEARCH_MOTION.duration}ms`, transitionTimingFunction: CHAT_SEARCH_MOTION.easing }}\n >\n <IconChevronDownSmall size={16} className={`transition-transform duration-200 ${chatsExpanded ? \"rotate-0\" : \"-rotate-90\"}`} />\n <span>Chats</span>\n </button>\n\n <button\n type=\"button\"\n ref={searchButtonRef}\n aria-label=\"Search chats\"\n tabIndex={searchOpen ? -1 : 0}\n aria-expanded={searchOpen}\n aria-controls={searchId}\n aria-hidden={searchOpen}\n onClick={() => { setChatsExpanded(true); setSearchOpen(true); }}\n className={`absolute right-0 top-0 z-10 flex size-8 items-center justify-center rounded-[8px] text-[var(--mova-ai-ink-muted,#858585)] transition-[opacity,background-color,color,transform] hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] hover:text-[var(--mova-ai-ink,#202020)] active:scale-[0.96] ${searchOpen ? \"pointer-events-none opacity-0\" : \"opacity-100\"}`}\n style={{ transitionDuration: `${CHAT_SEARCH_MOTION.duration}ms` }}\n >\n <IconMagnifyingGlass size={16} />\n </button>\n\n <div\n id={searchId}\n role=\"search\"\n aria-hidden={!searchOpen}\n inert={!searchOpen}\n className={`sidebar-search absolute right-0 top-0 z-20 flex h-8 items-center overflow-hidden rounded-[8px] bg-[var(--mova-ai-field,#f6f6f6)] text-[var(--mova-ai-ink-muted,#858585)] shadow-[var(--mova-ai-shadow-hairline,none)] transition-[width,opacity] focus-within:text-[var(--mova-ai-ink-secondary,#666)] ${searchOpen ? \"pointer-events-auto opacity-100\" : \"pointer-events-none opacity-0\"}`}\n style={{\n width: searchOpen ? \"100%\" : CHAT_SEARCH_MOTION.closedWidth,\n transitionDuration: `${CHAT_SEARCH_MOTION.duration}ms`,\n transitionTimingFunction: CHAT_SEARCH_MOTION.easing,\n }}\n >\n <span className=\"ml-2 flex shrink-0 items-center justify-center\">\n <IconMagnifyingGlass size={15} />\n </span>\n <input\n ref={searchRef}\n tabIndex={searchOpen ? 0 : -1}\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === \"Escape\") {\n setSearchOpen(false);\n setQuery(\"\");\n searchButtonRef.current?.focus();\n }\n }}\n placeholder=\"Search chats\"\n aria-label=\"Search chat history\"\n className=\"ml-1.5 min-w-0 flex-1 bg-transparent text-[13px] font-medium text-[var(--mova-ai-ink,#202020)] outline-none placeholder:text-[var(--mova-ai-ink-muted,#858585)]\"\n />\n <button\n type=\"button\"\n aria-label=\"Close chat search\"\n tabIndex={searchOpen ? 0 : -1}\n onClick={() => {\n setSearchOpen(false);\n setQuery(\"\");\n searchButtonRef.current?.focus();\n }}\n className=\"flex size-8 shrink-0 items-center justify-center rounded-[8px] text-[var(--mova-ai-ink-muted,#858585)] transition-[background-color,color,transform] duration-150 hover:bg-[var(--mova-ai-hover-strong,#e9e9e9)] hover:text-[var(--mova-ai-ink,#202020)] active:scale-[0.96]\"\n >\n <IconCrossSmall size={16} />\n </button>\n </div>\n </div>\n\n {/* Reveal the list without resizing its scroll viewport during the transition. */}\n <div\n id={historyId}\n aria-hidden={!chatsExpanded}\n inert={!chatsExpanded}\n className=\"min-h-0 flex-1 overflow-y-auto transition-[clip-path,opacity]\"\n style={{\n clipPath: chatsExpanded ? \"inset(0)\" : \"inset(0 0 100% 0)\",\n opacity: chatsExpanded ? 1 : 0,\n transitionDuration: `${CHAT_SEARCH_MOTION.listDuration}ms`,\n transitionTimingFunction: CHAT_SEARCH_MOTION.easing,\n }}\n >\n <GlideGroup>\n {visibleRecents.map((item) => {\n const active = item.label === selectedTitle;\n return (\n <button\n key={item.id}\n data-row\n type=\"button\"\n title={item.label}\n aria-current={active ? \"page\" : undefined}\n onClick={() => {\n setInternalNav(\"chats\");\n onNavigate?.(\"chats\");\n if (activeTitle === undefined) setDemoActiveTitle(item.label);\n onPick?.(item.id, item.label, item.prompt);\n }}\n className={`sidebar-row relative z-10 mx-2 flex h-8 max-w-[calc(100%-1rem)] items-center rounded-[8px] px-2 text-left transition-[width,background-color,color,transform] duration-150 active:scale-[0.98] ${\n active ? \"bg-[var(--mova-ai-hover-strong,#e9e9e9)] group-hover/glide:bg-transparent\" : \"\"\n }`}\n >\n <span className={`sidebar-copy min-w-0 flex-1 truncate text-[14px] font-medium ${active ? \"text-[var(--mova-ai-ink,#202020)]\" : \"text-[var(--mova-ai-ink-secondary,#666)]\"}`}>\n {item.label}\n </span>\n </button>\n );\n })}\n {query && visibleRecents.length === 0 && (\n <div className=\"sidebar-copy mx-2 px-2 py-2 text-[12.5px] text-[var(--mova-ai-ink-muted,#858585)]\">No chats found</div>\n )}\n </GlideGroup>\n </div>\n </div>\n\n <div inert={collapsed} className=\"sidebar-copy mx-2 mt-3 w-[208px] shrink-0 border-t border-[var(--mova-ai-border,#dedede)] pb-2 pt-3\">\n <button\n type=\"button\"\n onClick={() => { (onFooterClick ?? onNewChat)?.(); }}\n className=\"flex h-8 w-full items-center justify-center gap-1.5 rounded-[8px] bg-[var(--mova-ai-hover-strong,#e9e9e9)] text-[12.5px] font-medium text-[var(--mova-ai-ink,#202020)] transition-[background-color,transform] duration-150 hover:bg-[var(--mova-ai-border-strong,#c8c8c8)] active:scale-[0.98]\"\n >\n {footerIcon}\n {footerLabel}\n </button>\n <p role=\"status\" className=\"sr-only\">{feedback}</p>\n </div>\n </div>\n </aside>\n );\n}\n\nexport default AISidebarNav;\n"
|
|
17
17
|
}
|
package/registry/ai-sources.json
CHANGED
package/registry/arc-tracer.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "balance-bar-chart-card",
|
|
3
3
|
"tier": "free",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.9",
|
|
5
5
|
"file": "balance-bar-chart-card.tsx",
|
|
6
6
|
"sourcePath": "ui/cards/balance-bar-chart-card.tsx",
|
|
7
7
|
"registryFile": "balance-bar-chart-card.json",
|
|
@@ -11,10 +11,8 @@
|
|
|
11
11
|
"BalanceBarPeriod",
|
|
12
12
|
"BalanceBarPoint"
|
|
13
13
|
],
|
|
14
|
-
"dependencies": [
|
|
15
|
-
"lucide-react"
|
|
16
|
-
],
|
|
14
|
+
"dependencies": [],
|
|
17
15
|
"license": "MIT",
|
|
18
16
|
"previewMode": "client",
|
|
19
|
-
"content": "\"use client\";\n\"use client\";\n\n/** Original Mova implementation; visual reference documented in docs/provenance/interactive-cards.md. */\nimport * as React from \"react\";\nimport { ArrowDownRight, ArrowUpRight } from \"lucide-react\";\n\nexport interface BalanceBarPoint { label: string; value: number }\nexport interface BalanceBarPeriod { id: string; label: string; balance: number; change: string; detail: string; points: BalanceBarPoint[] }\nexport interface BalanceBarChartCardProps { periods?: BalanceBarPeriod[]; defaultPeriod?: string; currency?: string; locale?: string; onPeriodChange?: (period: BalanceBarPeriod) => void; theme?: \"light\" | \"dark\"; className?: string; style?: React.CSSProperties }\nconst periodsDefault: BalanceBarPeriod[] = [\n { id: \"week\", label: \"Week\", balance: 12480.50, change: \"+8.2%\", detail: \"+ $940.20 this week\", points: [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"].map((label, i) => ({ label, value: [640, 920, 760, 1180, 960, 1380, 1140][i] })) },\n { id: \"month\", label: \"Month\", balance: 12480.50, change: \"+12.8%\", detail: \"+ $1,416.23 this month\", points: [\"1–4\", \"5–8\", \"9–12\", \"13–16\", \"17–20\", \"21–24\", \"25–31\"].map((label, i) => ({ label, value: [2800, 3400, 2100, 3900, 3200, 4600, 5100][i] })) },\n { id: \"year\", label: \"Year\", balance: 12480.50, change: \"+24.6%\", detail: \"+ $2,464.05 this year\", points: [\"Jan\", \"Mar\", \"May\", \"Jul\", \"Sep\", \"Nov\", \"Dec\"].map((label, i) => ({ label, value: [5200, 6800, 6100, 8400, 9200, 11400, 12480][i] })) },\n];\nexport function BalanceBarChartCard({ periods = periodsDefault, defaultPeriod = \"week\", currency = \"USD\", locale = \"en-US\", onPeriodChange, theme = \"light\", className = \"\", style }: BalanceBarChartCardProps = {}) {\n const [selected, setSelected] = React.useState(defaultPeriod);\n const [active, setActive] = React.useState<number | null>(null);\n const period = periods.find(item => item.id === selected) ?? periods[0];\n const money = new Intl.NumberFormat(locale, { style: \"currency\", currency });\n const max = Math.max(1, ...(period?.points.map(point => Math.abs(point.value)) ?? []));\n const tipId = React.useId();\n const signed = period?.points.some(point => point.value < 0) ?? false;\n const negativeChange = period?.change.trim().startsWith(\"-\");\n return <section className={`mova-balance ${className}`} data-theme={theme} style={style} aria-label=\"Balance chart\">\n <style>{css}</style><div className=\"mbc-label\">Balance</div><div className=\"mbc-summary\"><strong>{money.format(period?.balance ?? 0)}</strong>{period && <span className=\"mbc-change\" data-negative={negativeChange}>{negativeChange ? <ArrowDownRight size={11} aria-hidden=\"true\" /> : <ArrowUpRight size={11} aria-hidden=\"true\" />}{period.change}</span>}</div><p className=\"mbc-detail\">{period?.detail ?? \"No balance data\"}</p>\n <div className=\"mbc-chart\" onMouseLeave={() => setActive(null)}>{period?.points.map((point, index) => <button key={`${period.id}-${index}`} type=\"button\" className=\"mbc-column\" aria-label={`${point.label}: ${money.format(point.value)}`} aria-describedby={active === index ? tipId : undefined} onFocus={() => setActive(index)} onBlur={() => setActive(null)} onMouseEnter={() => setActive(index)} onClick={() => setActive(index)} data-active={active === index || (active === null && index === period.points.length - 1)}><span className=\"mbc-track\" data-signed={signed}><span className=\"mbc-bar\" style={{ height: `${Math.max(1, Math.abs(point.value) / max * (signed ? 50 : 100))}%`, ...(signed ? point.value < 0 ? { top: \"50%\", bottom: \"auto\", borderRadius: \"0 0 3px 3px\" } : { bottom: \"50%\" } : {}) }} /></span><span className=\"mbc-axis\">{period.id === \"week\" ? point.label[0] : point.label}</span></button>)}{active !== null && period?.points[active] && <div id={tipId} role=\"tooltip\" className=\"mbc-tooltip\">{period.points[active].label} · {money.format(period.points[active].value)}</div>}</div>\n <div className=\"mbc-periods\" role=\"group\" aria-label=\"Chart period\">{periods.map(item => <button type=\"button\" key={item.id} aria-pressed={period?.id === item.id} onClick={() => { setSelected(item.id); setActive(null); onPeriodChange?.(item); }}>{item.label}</button>)}</div>\n </section>;\n}\nconst css = `\n.mova-balance{--mbc-bg:#fff;--mbc-fg:#202124;--mbc-muted:#737373;--mbc-bar:#e6e6e8;--mbc-chip:#f4f4f5;box-sizing:border-box;width:300px;max-width:100%;padding:24px;border-radius:24px;border:1px solid #88888818;background:var(--mbc-bg);color:var(--mbc-fg);font:400 13px/20px -apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;box-shadow:0 8px 32px -4px #0000000c}.mova-balance[data-theme=dark]{--mbc-bg:#1d1d1d;--mbc-fg:#f2f2f2;--mbc-muted:#aaa;--mbc-bar:#3b3b3e;--mbc-chip:#29292b;color-scheme:dark}.mova-balance *{box-sizing:border-box}.mbc-label{font-size:13px;color:var(--mbc-muted)}.mbc-summary{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:8px}.mbc-summary>strong{font-size:28px;line-height:34px;font-weight:500;letter-spacing:-1px;font-variant-numeric:tabular-nums}.mbc-change{display:inline-flex;align-items:center;background:#e5f6ed;color:#267151;font-size:10px;line-height:17px;padding:0 5px;border-radius:20px}.mova-balance[data-theme=dark] .mbc-change{background:#243d31;color:#8bd8b0}.mbc-detail{margin:4px 0 0;color:var(--mbc-muted);font-size:12px}.mbc-chart{display:flex;position:relative;gap:8px;margin-top:24px}.mova-balance button{border:0;font:inherit;cursor:pointer;color:inherit}.mova-balance button:focus-visible{outline:2px solid #7e9be9;outline-offset:3px}.mbc-column{background:transparent;min-width:0;flex:1;padding:0 0 4px;display:flex;flex-direction:column;gap:8px;align-items:center;border-radius:4px}.mbc-track{position:relative;height:112px;width:100%;display:flex;align-items:flex-end;justify-content:center}.mbc-track[data-signed=true]:after{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#8885}.mbc-track[data-signed=true] .mbc-bar{position:absolute}.mbc-change[data-negative=true]{background:#fbe9e9;color:#ad3c3c}.mova-balance[data-theme=dark] .mbc-change[data-negative=true]{background:#4c2b2b;color:#ffaaaa}.mbc-bar{width:100%;max-width:24px;border-radius:3px 3px 0 0;background:var(--mbc-bar);transform-origin:bottom;animation:mbc-rise 350ms ease-out;transition:height 250ms,background 150ms}.mbc-column[data-active=true] .mbc-bar{background:var(--mbc-fg)}.mbc-axis{font-size:10px;line-height:16px;color:var(--mbc-muted)}.mbc-tooltip{position:absolute;bottom:100%;left:50%;transform:translateX(-50%);background:var(--mbc-fg);color:var(--mbc-bg);padding:4px 8px;border-radius:7px;max-width:100%;width:max-content;overflow-wrap:anywhere;text-align:center;font-size:11px;pointer-events:none}.mbc-periods{display:flex;gap:4px;background:var(--mbc-chip);padding:4px;border-radius:24px;margin-top:20px}.mbc-periods button{background:transparent;flex:1;min-width:0;overflow-wrap:anywhere;padding:6px 4px;border-radius:20px;font-size:12px;color:var(--mbc-muted);transition:background 180ms,color 180ms}.mbc-periods button[aria-pressed=true]{color:var(--mbc-fg);background:var(--mbc-bg);box-shadow:0 1px 3px #00000012}@keyframes mbc-rise{from{transform:scaleY(.05);opacity:.4}to{transform:scaleY(1);opacity:1}}@media(prefers-reduced-motion:reduce){.mova-balance *{animation:none!important;transition:none!important}}\n`;\n"
|
|
17
|
+
"content": "\"use client\";\n\"use client\";\n\n/** Original Mova implementation; visual reference documented in docs/provenance/interactive-cards.md. */\nimport * as React from \"react\";\n\nexport interface BalanceBarPoint { label: string; value: number }\nexport interface BalanceBarPeriod { id: string; label: string; balance: number; change: string; detail: string; points: BalanceBarPoint[] }\nexport interface BalanceBarChartCardProps { periods?: BalanceBarPeriod[]; defaultPeriod?: string; currency?: string; locale?: string; onPeriodChange?: (period: BalanceBarPeriod) => void; theme?: \"light\" | \"dark\"; className?: string; style?: React.CSSProperties }\nconst periodsDefault: BalanceBarPeriod[] = [\n { id: \"week\", label: \"Week\", balance: 12480.50, change: \"+8.2%\", detail: \"+$940.20 this week\", points: [\"M\", \"T\", \"W\", \"T\", \"F\", \"S\", \"S\"].map((label, i) => ({ label, value: [96, 142, 88, 210, 175, 120, 109][i] })) },\n { id: \"month\", label: \"Month\", balance: 12480.50, change: \"+12.4%\", detail: \"+$4,220.60 this month\", points: [\"1\", \"5\", \"10\", \"15\", \"20\", \"25\", \"30\"].map((label, i) => ({ label, value: [420, 610, 380, 720, 560, 890, 640][i] })) },\n { id: \"year\", label: \"Year\", balance: 12480.50, change: \"+18.6%\", detail: \"+$32,520.00 this year\", points: [\"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"].map((label, i) => ({ label, value: [3120, 4210, 3680, 5020, 4460, 5890, 6140][i] })) },\n];\nconst useLayoutEffect = typeof window === \"undefined\" ? React.useEffect : React.useLayoutEffect;\n\n/** Keep the previous and next strings in the same grid cell during a soft swap. */\nfunction SoftText({ text, className = \"\" }: { text: string; className?: string }) {\n const element = React.useRef<HTMLSpanElement>(null);\n const initial = React.useRef(text);\n useLayoutEffect(() => {\n const el = element.current;\n if (!el) return;\n const previous = el.dataset.text ?? initial.current;\n if (previous === text) return;\n el.dataset.text = text;\n const width = el.getBoundingClientRect().width;\n const old = document.createElement(\"span\");\n old.className = \"mbc-soft-old\";\n old.setAttribute(\"aria-hidden\", \"true\");\n old.textContent = previous;\n const next = document.createElement(\"span\");\n next.className = \"mbc-soft-new\";\n next.textContent = text;\n el.classList.remove(\"is-swapping\");\n el.replaceChildren(old, next);\n const nextWidth = next.getBoundingClientRect().width;\n el.style.width = `${width}px`;\n void el.offsetWidth;\n el.classList.add(\"is-swapping\");\n el.style.width = `${nextWidth}px`;\n const computed = getComputedStyle(el);\n const duration = Math.max(parseFloat(computed.getPropertyValue(\"--mbc-text-dur\")) || 0, parseFloat(computed.getPropertyValue(\"--mbc-text-resize-dur\")) || 0);\n const timer = window.setTimeout(() => {\n el.classList.remove(\"is-swapping\");\n el.style.removeProperty(\"width\");\n el.textContent = text;\n }, duration + 40);\n return () => window.clearTimeout(timer);\n }, [text]);\n return <span ref={element} className={`mbc-soft ${className}`}>{initial.current}</span>;\n}\n\nexport function BalanceBarChartCard({ periods = periodsDefault, defaultPeriod = \"week\", currency = \"USD\", locale = \"en-US\", onPeriodChange, theme = \"light\", className = \"\", style }: BalanceBarChartCardProps = {}) {\n const [selected, setSelected] = React.useState(defaultPeriod);\n const [active, setActive] = React.useState<number | null>(null);\n const root = React.useRef<HTMLElement>(null);\n const chart = React.useRef<HTMLDivElement>(null);\n const tooltip = React.useRef<HTMLDivElement>(null);\n const tooltipText = React.useRef<HTMLSpanElement>(null);\n const tabs = React.useRef<HTMLDivElement>(null);\n const pill = React.useRef<HTMLSpanElement>(null);\n const measuredPill = React.useRef(false);\n const lastTooltip = React.useRef(\"\");\n const tipId = React.useId();\n const chartId = React.useId();\n const period = periods.find(item => item.id === selected) ?? periods[0];\n const periodIndex = period ? periods.indexOf(period) : -1;\n const money = new Intl.NumberFormat(locale, { style: \"currency\", currency });\n const pointMoney = new Intl.NumberFormat(locale, { style: \"currency\", currency, minimumFractionDigits: 0, maximumFractionDigits: 2 });\n const amountParts = money.formatToParts(period?.balance ?? 0);\n const max = Math.max(1, ...(period?.points.map(point => Math.abs(point.value)) ?? []));\n const signed = period?.points.some(point => point.value < 0) ?? false;\n const negativeChange = period?.change.trim().startsWith(\"-\");\n const detail = /^(.*?)(\\$[\\d,.]+)(\\s+this\\s+)(.+)$/.exec(period?.detail ?? \"\");\n if (active !== null && period?.points[active]) lastTooltip.current = pointMoney.format(period.points[active].value);\n\n useLayoutEffect(() => {\n const tip = tooltip.current;\n if (!tip) return;\n const trigger = chart.current?.querySelectorAll<HTMLButtonElement>(\".mbc-column\")[active ?? -1];\n if (active === null || !trigger || !chart.current || !tooltipText.current) {\n tip.dataset.show = \"false\";\n tip.setAttribute(\"aria-hidden\", \"true\");\n return;\n }\n const showing = tip.dataset.show === \"true\";\n const computed = getComputedStyle(tip);\n const width = Math.ceil(tooltipText.current.scrollWidth + parseFloat(computed.paddingLeft) + parseFloat(computed.paddingRight));\n const groupRect = chart.current.getBoundingClientRect();\n const rect = trigger.getBoundingClientRect();\n const x = Math.max(0, Math.min(rect.left - groupRect.left + rect.width / 2 - width / 2, groupRect.width - width));\n if (!showing) tip.style.transition = \"none\";\n tip.style.width = `${width}px`;\n tip.style.setProperty(\"--mbc-tip-x\", `${x}px`);\n if (!showing) { void tip.offsetWidth; tip.style.transition = \"\"; }\n tip.dataset.show = \"true\";\n tip.setAttribute(\"aria-hidden\", \"false\");\n }, [active, period, currency, locale]);\n\n const movePill = React.useCallback((animate: boolean) => {\n const tab = tabs.current?.querySelector<HTMLButtonElement>('[role=tab][aria-selected=\"true\"]');\n const el = pill.current;\n if (!tab || !el) return;\n if (!animate) el.style.transition = \"none\";\n el.style.transform = `translateX(${tab.offsetLeft}px)`;\n el.style.width = `${tab.offsetWidth}px`;\n if (!animate) { void el.offsetWidth; el.style.transition = \"\"; }\n }, []);\n\n useLayoutEffect(() => {\n movePill(measuredPill.current);\n measuredPill.current = true;\n }, [movePill, periodIndex, periods]);\n\n useLayoutEffect(() => {\n const resize = () => movePill(false);\n const observer = typeof ResizeObserver !== \"undefined\" ? new ResizeObserver(resize) : undefined;\n if (tabs.current) observer?.observe(tabs.current);\n window.addEventListener(\"resize\", resize);\n let cancelled = false;\n document.fonts?.ready.then(() => { if (!cancelled) movePill(false); });\n return () => { cancelled = true; observer?.disconnect(); window.removeEventListener(\"resize\", resize); };\n }, []);\n\n React.useEffect(() => {\n const el = root.current;\n if (!el) return;\n let pressed: HTMLButtonElement | null = null;\n let started = 0;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const clear = () => { if (pressed) delete pressed.dataset.pressed; pressed = null; };\n const down = (event: PointerEvent) => {\n if (event.pointerType === \"mouse\") return;\n const button = (event.target as Element).closest<HTMLButtonElement>(\"button\");\n if (!button || button.disabled) return;\n clearTimeout(timer); clear(); pressed = button; started = performance.now(); button.dataset.pressed = \"true\";\n };\n const up = () => {\n if (!pressed) return;\n const duration = parseFloat(getComputedStyle(pressed).getPropertyValue(\"--mbc-press-dur\")) || 150;\n timer = setTimeout(clear, Math.max(0, duration - (performance.now() - started)));\n };\n el.addEventListener(\"pointerdown\", down);\n window.addEventListener(\"pointerup\", up); window.addEventListener(\"pointercancel\", up);\n return () => { clearTimeout(timer); clear(); el.removeEventListener(\"pointerdown\", down); window.removeEventListener(\"pointerup\", up); window.removeEventListener(\"pointercancel\", up); };\n }, []);\n\n function selectPeriod(item: BalanceBarPeriod) {\n if (period?.id === item.id) return;\n setSelected(item.id); setActive(null); onPeriodChange?.(item);\n }\n\n return <section ref={root} className={`mova-balance ${className}`} data-theme={theme} style={style} aria-label=\"Balance chart\">\n <style>{css}</style>\n <div className=\"mbc-label\">Balance</div>\n <div className=\"mbc-summary\">{amountParts.map((part, index) => <span key={index} className={part.type === \"decimal\" || part.type === \"fraction\" ? \"mbc-cents\" : undefined}>{part.value}</span>)}</div>\n <div className=\"mbc-change-row\">\n {period && <span className=\"mbc-change\" data-negative={negativeChange}><SoftText text={period.change} /></span>}\n <span className=\"mbc-detail\">{detail ? <>{detail[1]}<SoftText text={detail[2]} />{detail[3]}<SoftText text={detail[4]} /></> : <SoftText text={period?.detail ?? \"No balance data\"} />}</span>\n </div>\n <div ref={chart} id={chartId} className=\"mbc-chart\" role=\"tabpanel\" aria-label={period?.label ?? \"Balance data\"} onPointerLeave={() => setActive(null)}>{period?.points.map((point, index) => <button key={index} type=\"button\" className=\"mbc-column\" aria-label={`${point.label}: ${money.format(point.value)}`} aria-describedby={active === index ? tipId : undefined} onFocus={() => setActive(index)} onBlur={() => setActive(null)} onPointerEnter={() => setActive(index)} onClick={() => setActive(index)} data-current={index === period.points.length - 1}>\n <span className=\"mbc-track\" data-signed={signed}><span className=\"mbc-bar\" style={{ height: `${Math.max(4, Math.round(Math.abs(point.value) / max * (signed ? 56 : 72)))}px`, ...(signed ? point.value < 0 ? { top: \"50%\", bottom: \"auto\", borderRadius: \"0 0 2px 2px\" } : { bottom: \"50%\" } : {}) }} /></span>\n <SoftText className=\"mbc-axis\" text={point.label} />\n </button>)}<div ref={tooltip} id={tipId} role=\"tooltip\" className=\"mbc-tooltip\" data-show=\"false\" aria-hidden=\"true\"><span ref={tooltipText}>{lastTooltip.current}</span></div></div>\n <div ref={tabs} className=\"mbc-periods\" role=\"tablist\" aria-label=\"Chart period\"><span ref={pill} className=\"mbc-period-pill\" aria-hidden=\"true\" />{periods.map((item, index) => <button type=\"button\" role=\"tab\" key={item.id} aria-selected={period?.id === item.id} aria-controls={chartId} tabIndex={period?.id === item.id ? 0 : -1} onClick={() => selectPeriod(item)} onKeyDown={event => {\n const next = event.key === \"ArrowRight\" ? (index + 1) % periods.length : event.key === \"ArrowLeft\" ? (index - 1 + periods.length) % periods.length : event.key === \"Home\" ? 0 : event.key === \"End\" ? periods.length - 1 : -1;\n if (next < 0) return;\n event.preventDefault(); selectPeriod(periods[next]); tabs.current?.querySelectorAll<HTMLButtonElement>(\"[role=tab]\")[next]?.focus();\n }}>{item.label}</button>)}</div>\n </section>;\n}\nconst css = `\n.mova-balance{--mbc-bg:#fff;--mbc-fg:#0d0d0d;--mbc-muted:#6c6c6c;--mbc-bar:rgba(28,95,251,.14);--mbc-bar-hover:rgba(28,95,251,.26);--mbc-bar-current:#1c5ffb;--mbc-chip:rgba(0,0,0,.04);--mbc-pill:#fff;--mbc-tip-bg:#fff;--mbc-tip-fg:#2f2f2f;--mbc-fill:#e7f9f3;--mbc-ink:#216e55;--mbc-ease:cubic-bezier(.22,1,.36,1);--mbc-press-dur:150ms;--mbc-chart-dur:400ms;--mbc-text-dur:200ms;--mbc-text-resize-dur:240ms;--mbc-tabs-dur:250ms;box-sizing:border-box;width:300px;max-width:100%;padding:24px;border-radius:24px;background:var(--mbc-bg);color:var(--mbc-fg);font:400 13px/20px 'Inter Variable',Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;letter-spacing:0;-webkit-font-smoothing:antialiased;box-shadow:0 0 0 1px #0000000a,0 8px 32px -4px #0000000f,0 1px 3px #0000000a}\n.mova-balance[data-theme=dark]{--mbc-bg:#1d1d1d;--mbc-fg:#f2f2f2;--mbc-muted:rgba(202,202,202,.7);--mbc-bar:rgba(92,140,255,.22);--mbc-bar-hover:rgba(92,140,255,.36);--mbc-bar-current:#5c8cff;--mbc-chip:rgba(255,255,255,.07);--mbc-pill:rgba(255,255,255,.12);--mbc-tip-bg:#262626;--mbc-tip-fg:#f2f2f2;--mbc-fill:#12291f;--mbc-ink:#6fd3a8;color-scheme:dark;box-shadow:0 0 0 1px #ffffff0f,0 8px 32px -4px #0000004d,0 1px 3px #0003}\n.mova-balance *{box-sizing:border-box}.mova-balance button{font:inherit;letter-spacing:inherit;margin:0;border:0;padding:0;background:none;color:inherit;-webkit-tap-highlight-color:transparent;touch-action:manipulation;user-select:none;-webkit-touch-callout:none;transition:transform var(--mbc-press-dur) var(--mbc-ease)}.mova-balance button:is(:active,[data-pressed=true]){transform:scale(.97)}.mova-balance button:focus{outline:none}.mova-balance button:focus-visible{box-shadow:0 0 0 2px var(--mbc-bg),0 0 0 4px var(--mbc-fg)}\n.mbc-label{font-size:13px;line-height:20px;color:var(--mbc-muted)}.mbc-summary{margin-top:16px;font-size:28px;line-height:34px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap}.mbc-cents{color:var(--mbc-muted)}.mbc-change-row{margin-top:8px;display:flex;align-items:center;gap:8px;font-size:12px;line-height:16px;color:var(--mbc-muted);min-width:0}.mbc-detail{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-variant-numeric:tabular-nums}.mbc-change{flex:none;display:inline-flex;align-items:center;height:18px;padding:0 6px;border-radius:50px;font-size:11px;line-height:16px;font-weight:500;background:var(--mbc-fill);color:var(--mbc-ink);font-variant-numeric:tabular-nums;box-shadow:0 1px 3px #0000000a,inset 0 0 0 1px color-mix(in srgb,var(--mbc-ink) 14%,transparent),inset 0 -1px 0 color-mix(in srgb,var(--mbc-ink) 10%,transparent)}.mbc-change[data-negative=true]{--mbc-fill:#fbe9e9;--mbc-ink:#ad3c3c}.mova-balance[data-theme=dark] .mbc-change[data-negative=true]{--mbc-fill:#4c2b2b;--mbc-ink:#ffaaaa}\n.mbc-soft{display:inline-grid;justify-items:var(--mbc-text-align,start);transition:width var(--mbc-text-resize-dur) var(--mbc-ease)}.mbc-soft>span{grid-area:1/1;white-space:pre;transition:filter var(--mbc-text-dur) ease-in-out,opacity var(--mbc-text-dur) ease-in-out}.mbc-soft-new{opacity:0;filter:blur(2px)}.mbc-soft.is-swapping .mbc-soft-old{opacity:0;filter:blur(2px)}.mbc-soft.is-swapping .mbc-soft-new{opacity:1;filter:blur(0)}\n.mbc-chart{display:flex;position:relative;width:100%;gap:4px;margin-top:16px}.mova-balance .mbc-column{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:8px;padding-bottom:4px;border-radius:8px;cursor:default}.mbc-track{position:relative;height:112px;width:100%;display:flex;align-items:flex-end;justify-content:center}.mbc-track[data-signed=true]:after{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#8885}.mbc-track[data-signed=true] .mbc-bar{position:absolute}.mbc-bar{display:block;width:100%;max-width:24px;border-radius:2px 2px 0 0;background:var(--mbc-bar);transition:height var(--mbc-chart-dur) var(--mbc-ease),background-color 150ms var(--mbc-ease)}.mbc-column:focus-visible .mbc-bar{background:var(--mbc-bar-hover)}@media(hover:hover) and (pointer:fine){.mbc-column:hover .mbc-bar{background:var(--mbc-bar-hover)}.mbc-periods button:not([aria-selected=true]):hover{color:var(--mbc-fg)}}.mbc-column[data-current=true] .mbc-bar{background:var(--mbc-bar-current)}.mbc-axis{font-size:12px;line-height:16px;color:var(--mbc-muted);--mbc-text-align:center}.mbc-column[data-current=true] .mbc-axis{color:var(--mbc-fg)}\n.mbc-tooltip{position:absolute;top:0;left:0;z-index:2;display:inline-flex;align-items:center;justify-content:center;width:0;max-width:100%;overflow:hidden;translate:var(--mbc-tip-x,0px) 0;scale:.98;transform-origin:50% 100%;padding:8px 12px;border-radius:8px;background:var(--mbc-tip-bg);color:var(--mbc-tip-fg);white-space:nowrap;box-shadow:0 0 0 1px #0000000f,0 2px 6px #0000000d,0 4px 42px #0000000f;opacity:0;pointer-events:none;font-size:12px;line-height:16px;font-variant-numeric:tabular-nums;transition:opacity 50ms ease-out,scale 50ms ease-out,translate 160ms var(--mbc-ease),width 160ms var(--mbc-ease)}.mbc-tooltip[data-show=true]{opacity:1;scale:1;transition:opacity 150ms ease-out 80ms,scale 150ms ease-out 80ms,translate 160ms var(--mbc-ease),width 160ms var(--mbc-ease)}.mova-balance[data-theme=dark] .mbc-tooltip{box-shadow:0 1px 3px #0000000a,inset 0 1px #ffffff0a,inset 0 0 0 1px #c4c4c414}\n.mbc-periods{position:relative;display:flex;align-items:center;width:100%;gap:3px;background:var(--mbc-chip);padding:3px;border-radius:48px;margin-top:16px}.mova-balance .mbc-periods button{position:relative;flex:1;min-width:0;height:30px;padding:4px 12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border-radius:48px;font-size:13px;line-height:20px;font-weight:500;color:var(--mbc-muted);cursor:pointer;z-index:1;transition:color var(--mbc-tabs-dur) var(--mbc-ease),transform var(--mbc-press-dur) var(--mbc-ease)}.mbc-periods button[aria-selected=true]{color:var(--mbc-fg)}.mbc-period-pill{position:absolute;top:3px;left:0;height:30px;width:0;background:var(--mbc-pill);border-radius:48px;box-shadow:0 1px 3px #00000014,0 0 0 1px #0000000a;transform:translateX(0);transition:transform var(--mbc-tabs-dur) var(--mbc-ease),width var(--mbc-tabs-dur) var(--mbc-ease);will-change:transform,width;z-index:0;pointer-events:none}\n@media(prefers-reduced-motion:reduce){.mova-balance *{animation:none!important;transition:none!important}.mova-balance button:is(:active,[data-pressed=true]){transform:none}}\n`;\n"
|
|
20
18
|
}
|
package/registry/bar-sweep.json
CHANGED
package/registry/blur-text.json
CHANGED