bits-lab 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +57 -2
- package/dist/Accordion.d.ts +52 -0
- package/dist/Accordion.js +192 -0
- package/dist/AchievementToast.d.ts +36 -0
- package/dist/AchievementToast.js +162 -0
- package/dist/AgentCard.d.ts +41 -0
- package/dist/AgentCard.js +168 -0
- package/dist/ArcadeButtons.d.ts +23 -0
- package/dist/ArcadeButtons.js +169 -0
- package/dist/AuroraBands.d.ts +19 -0
- package/dist/AuroraBands.js +126 -0
- package/dist/AvatarTooltips.d.ts +32 -0
- package/dist/AvatarTooltips.js +121 -0
- package/dist/BarRace.d.ts +39 -0
- package/dist/BarRace.js +227 -0
- package/dist/BentoGrid.d.ts +50 -0
- package/dist/BentoGrid.js +145 -0
- package/dist/BootSequence.d.ts +48 -0
- package/dist/BootSequence.js +234 -0
- package/dist/BreadcrumbTrail.d.ts +31 -0
- package/dist/BreadcrumbTrail.js +181 -0
- package/dist/CRTPower.d.ts +30 -0
- package/dist/CRTPower.js +142 -0
- package/dist/CardDeck.d.ts +29 -0
- package/dist/CardDeck.js +154 -0
- package/dist/ChangelogTimeline.d.ts +35 -0
- package/dist/ChangelogTimeline.js +137 -0
- package/dist/ChapterProgress.d.ts +23 -0
- package/dist/ChapterProgress.js +211 -0
- package/dist/ChatBubbles.d.ts +42 -0
- package/dist/ChatBubbles.js +198 -0
- package/dist/ChiptuneKit.d.ts +158 -0
- package/dist/ChiptuneKit.js +207 -0
- package/dist/CipherCard.d.ts +22 -0
- package/dist/CipherCard.js +91 -0
- package/dist/CloudSky.d.ts +28 -0
- package/dist/CloudSky.js +222 -0
- package/dist/CodeRain.d.ts +24 -0
- package/dist/CodeRain.js +158 -0
- package/dist/CodeShowcase.d.ts +50 -0
- package/dist/CodeShowcase.js +307 -0
- package/dist/ColorDial.d.ts +42 -0
- package/dist/ColorDial.js +288 -0
- package/dist/CommandPalette.d.ts +59 -0
- package/dist/CommandPalette.js +349 -0
- package/dist/CompareSlider.d.ts +33 -0
- package/dist/CompareSlider.js +152 -0
- package/dist/Confetti.d.ts +61 -0
- package/dist/Confetti.js +176 -0
- package/dist/ConnectBeams.d.ts +39 -0
- package/dist/ConnectBeams.js +132 -0
- package/dist/ContextOrb.d.ts +34 -0
- package/dist/ContextOrb.js +269 -0
- package/dist/ContourLines.d.ts +20 -0
- package/dist/ContourLines.js +159 -0
- package/dist/Coverflow.d.ts +27 -0
- package/dist/Coverflow.js +167 -0
- package/dist/CursorAttractor.d.ts +21 -0
- package/dist/CursorAttractor.js +136 -0
- package/dist/CutReveal.d.ts +21 -0
- package/dist/CutReveal.js +116 -0
- package/dist/DateRange.d.ts +45 -0
- package/dist/DateRange.js +327 -0
- package/dist/DeviceFrame.d.ts +30 -0
- package/dist/DeviceFrame.js +145 -0
- package/dist/DiffReveal.d.ts +36 -0
- package/dist/DiffReveal.js +247 -0
- package/dist/DirectionHover.d.ts +15 -0
- package/dist/DirectionHover.js +90 -0
- package/dist/Discard.d.ts +18 -0
- package/dist/Discard.js +61 -0
- package/dist/DitherField.d.ts +15 -0
- package/dist/DitherField.js +157 -0
- package/dist/DotGlobe.d.ts +48 -0
- package/dist/DotGlobe.js +312 -0
- package/dist/DotMap.d.ts +47 -0
- package/dist/DotMap.js +172 -0
- package/dist/DropZone.d.ts +57 -0
- package/dist/DropZone.js +304 -0
- package/dist/DynamicIsland.d.ts +32 -0
- package/dist/DynamicIsland.js +92 -0
- package/dist/EdgeFX.d.ts +27 -0
- package/dist/EdgeFX.js +98 -0
- package/dist/ElasticString.d.ts +22 -0
- package/dist/ElasticString.js +127 -0
- package/dist/ExpandCard.d.ts +44 -0
- package/dist/ExpandCard.js +182 -0
- package/dist/FeatureTabs.d.ts +34 -0
- package/dist/FeatureTabs.js +169 -0
- package/dist/FeedList.d.ts +37 -0
- package/dist/FeedList.js +163 -0
- package/dist/FlickerGrid.d.ts +29 -0
- package/dist/FlickerGrid.js +156 -0
- package/dist/FocusGrid.d.ts +20 -0
- package/dist/FocusGrid.js +48 -0
- package/dist/FocusPull.d.ts +26 -0
- package/dist/FocusPull.js +106 -0
- package/dist/Footprints.d.ts +24 -0
- package/dist/Footprints.js +126 -0
- package/dist/FunnelFlow.d.ts +34 -0
- package/dist/FunnelFlow.js +267 -0
- package/dist/GameOver.d.ts +46 -0
- package/dist/GameOver.js +191 -0
- package/dist/GaugeMeter.d.ts +38 -0
- package/dist/GaugeMeter.js +195 -0
- package/dist/GlassPanel.d.ts +27 -0
- package/dist/GlassPanel.js +80 -0
- package/dist/GrainFX.d.ts +34 -0
- package/dist/GrainFX.js +122 -0
- package/dist/GravityBox.d.ts +30 -0
- package/dist/GravityBox.js +220 -0
- package/dist/HealthBar.d.ts +33 -0
- package/dist/HealthBar.js +140 -0
- package/dist/Heatmap.d.ts +42 -0
- package/dist/Heatmap.js +232 -0
- package/dist/HexField.d.ts +29 -0
- package/dist/HexField.js +188 -0
- package/dist/HoldToConfirm.d.ts +29 -0
- package/dist/HoldToConfirm.js +163 -0
- package/dist/HorizontalTrack.d.ts +28 -0
- package/dist/HorizontalTrack.js +195 -0
- package/dist/HotbarDock.d.ts +40 -0
- package/dist/HotbarDock.js +148 -0
- package/dist/InkText.d.ts +27 -0
- package/dist/InkText.js +65 -0
- package/dist/InlineEdit.d.ts +30 -0
- package/dist/InlineEdit.js +198 -0
- package/dist/InteractiveGrid.d.ts +25 -0
- package/dist/InteractiveGrid.js +194 -0
- package/dist/InventoryGrid.d.ts +45 -0
- package/dist/InventoryGrid.js +272 -0
- package/dist/JellyBlob.d.ts +30 -0
- package/dist/JellyBlob.js +209 -0
- package/dist/KineticHeadline.d.ts +30 -0
- package/dist/KineticHeadline.js +133 -0
- package/dist/LavaLamp.d.ts +21 -0
- package/dist/LavaLamp.js +133 -0
- package/dist/LetterSwap.d.ts +20 -0
- package/dist/LetterSwap.js +78 -0
- package/dist/LevelMap.d.ts +35 -0
- package/dist/LevelMap.js +189 -0
- package/dist/LightShafts.d.ts +22 -0
- package/dist/LightShafts.js +170 -0
- package/dist/LightboxGallery.d.ts +26 -0
- package/dist/LightboxGallery.js +327 -0
- package/dist/LinkPeek.d.ts +36 -0
- package/dist/LinkPeek.js +194 -0
- package/dist/LogoOrbit.d.ts +37 -0
- package/dist/LogoOrbit.js +117 -0
- package/dist/Marquee.d.ts +33 -0
- package/dist/Marquee.js +148 -0
- package/dist/MarqueeLine.d.ts +33 -0
- package/dist/MarqueeLine.js +167 -0
- package/dist/MasonryWall.d.ts +13 -0
- package/dist/MasonryWall.js +90 -0
- package/dist/MegaMenu.d.ts +32 -0
- package/dist/MegaMenu.js +280 -0
- package/dist/MeteorShower.d.ts +24 -0
- package/dist/MeteorShower.js +155 -0
- package/dist/Minimap.d.ts +21 -0
- package/dist/Minimap.js +236 -0
- package/dist/MobileTabBar.d.ts +38 -0
- package/dist/MobileTabBar.js +148 -0
- package/dist/MorphDialog.d.ts +34 -0
- package/dist/MorphDialog.js +199 -0
- package/dist/MorphPopover.d.ts +33 -0
- package/dist/MorphPopover.js +168 -0
- package/dist/MorphWords.d.ts +18 -0
- package/dist/MorphWords.js +136 -0
- package/dist/OTPInput.d.ts +43 -0
- package/dist/OTPInput.js +174 -0
- package/dist/OverlayKit.d.ts +97 -0
- package/dist/OverlayKit.js +466 -0
- package/dist/OverlayMenu.d.ts +32 -0
- package/dist/OverlayMenu.js +154 -0
- package/dist/ParallaxCollage.d.ts +41 -0
- package/dist/ParallaxCollage.js +143 -0
- package/dist/PasswordMeter.d.ts +44 -0
- package/dist/PasswordMeter.js +165 -0
- package/dist/PegBoard.d.ts +26 -0
- package/dist/PegBoard.js +184 -0
- package/dist/PinCard.d.ts +21 -0
- package/dist/PinCard.js +84 -0
- package/dist/PixelAvatar.d.ts +33 -0
- package/dist/PixelAvatar.js +144 -0
- package/dist/PixelButton.d.ts +20 -0
- package/dist/PixelButton.js +128 -0
- package/dist/PixelExplode.d.ts +31 -0
- package/dist/PixelExplode.js +164 -0
- package/dist/PixelLoader.d.ts +32 -0
- package/dist/PixelLoader.js +142 -0
- package/dist/PixelParallax.d.ts +41 -0
- package/dist/PixelParallax.js +151 -0
- package/dist/PixelSpinners.d.ts +21 -0
- package/dist/PixelSpinners.js +110 -0
- package/dist/PixelType.d.ts +26 -0
- package/dist/PixelType.js +220 -0
- package/dist/PointerKit.d.ts +23 -0
- package/dist/PointerKit.js +222 -0
- package/dist/Polaroids.d.ts +41 -0
- package/dist/Polaroids.js +293 -0
- package/dist/PressStart.d.ts +28 -0
- package/dist/PressStart.js +99 -0
- package/dist/PricingFlip.d.ts +52 -0
- package/dist/PricingFlip.js +190 -0
- package/dist/ProximityType.d.ts +26 -0
- package/dist/ProximityType.js +135 -0
- package/dist/QuestDialog.d.ts +50 -0
- package/dist/QuestDialog.js +204 -0
- package/dist/QuestLog.d.ts +39 -0
- package/dist/QuestLog.js +214 -0
- package/dist/RangeDual.d.ts +34 -0
- package/dist/RangeDual.js +238 -0
- package/dist/RatingPips.d.ts +25 -0
- package/dist/RatingPips.js +129 -0
- package/dist/RetroHorizon.d.ts +27 -0
- package/dist/RetroHorizon.js +169 -0
- package/dist/Reveal.d.ts +30 -0
- package/dist/Reveal.js +92 -0
- package/dist/RingProgress.d.ts +45 -0
- package/dist/RingProgress.js +180 -0
- package/dist/RollingNumber.d.ts +28 -0
- package/dist/RollingNumber.js +108 -0
- package/dist/ScanScope.d.ts +24 -0
- package/dist/ScanScope.js +164 -0
- package/dist/ScoreTicker.d.ts +32 -0
- package/dist/ScoreTicker.js +176 -0
- package/dist/ScreenScroller.d.ts +24 -0
- package/dist/ScreenScroller.js +138 -0
- package/dist/Screensaver.d.ts +31 -0
- package/dist/Screensaver.js +221 -0
- package/dist/ScrollTilt.d.ts +27 -0
- package/dist/ScrollTilt.js +100 -0
- package/dist/ScrollTypography.d.ts +26 -0
- package/dist/ScrollTypography.js +124 -0
- package/dist/ScrollZoom.d.ts +31 -0
- package/dist/ScrollZoom.js +123 -0
- package/dist/ScrubNumber.d.ts +29 -0
- package/dist/ScrubNumber.js +152 -0
- package/dist/ShadowStack.d.ts +28 -0
- package/dist/ShadowStack.js +116 -0
- package/dist/SideRail.d.ts +43 -0
- package/dist/SideRail.js +253 -0
- package/dist/SignalText.d.ts +25 -0
- package/dist/SignalText.js +178 -0
- package/dist/SkeletonShimmer.d.ts +18 -0
- package/dist/SkeletonShimmer.js +111 -0
- package/dist/SketchHighlight.d.ts +27 -0
- package/dist/SketchHighlight.js +180 -0
- package/dist/SlideTabs.d.ts +40 -0
- package/dist/SlideTabs.js +190 -0
- package/dist/SmartNavbar.d.ts +48 -0
- package/dist/SmartNavbar.js +188 -0
- package/dist/SparkChart.d.ts +41 -0
- package/dist/SparkChart.js +188 -0
- package/dist/Spoiler.d.ts +22 -0
- package/dist/Spoiler.js +97 -0
- package/dist/SpotlightTour.d.ts +38 -0
- package/dist/SpotlightTour.js +231 -0
- package/dist/SpringDrawer.d.ts +39 -0
- package/dist/SpringDrawer.js +360 -0
- package/dist/SpriteAnimator.d.ts +41 -0
- package/dist/SpriteAnimator.js +124 -0
- package/dist/StatCard.d.ts +43 -0
- package/dist/StatCard.js +168 -0
- package/dist/StepLoader.d.ts +41 -0
- package/dist/StepLoader.js +191 -0
- package/dist/StepperForm.d.ts +41 -0
- package/dist/StepperForm.js +206 -0
- package/dist/StickerBoard.d.ts +39 -0
- package/dist/StickerBoard.js +209 -0
- package/dist/StickyReveal.d.ts +42 -0
- package/dist/StickyReveal.js +181 -0
- package/dist/StoryViewer.d.ts +36 -0
- package/dist/StoryViewer.js +175 -0
- package/dist/StreamingText.d.ts +29 -0
- package/dist/StreamingText.js +271 -0
- package/dist/SuggestionChips.d.ts +32 -0
- package/dist/SuggestionChips.js +96 -0
- package/dist/SwipeActions.d.ts +30 -0
- package/dist/SwipeActions.js +141 -0
- package/dist/TagInput.d.ts +30 -0
- package/dist/TagInput.js +284 -0
- package/dist/TallyUp.d.ts +38 -0
- package/dist/TallyUp.js +157 -0
- package/dist/TerminalDemo.d.ts +63 -0
- package/dist/TerminalDemo.js +214 -0
- package/dist/TestimonialWall.d.ts +35 -0
- package/dist/TestimonialWall.js +131 -0
- package/dist/TextOnPath.d.ts +36 -0
- package/dist/TextOnPath.js +130 -0
- package/dist/ThinkingShimmer.d.ts +36 -0
- package/dist/ThinkingShimmer.js +132 -0
- package/dist/ToastStack.d.ts +62 -0
- package/dist/ToastStack.js +289 -0
- package/dist/ToggleGroup.d.ts +39 -0
- package/dist/ToggleGroup.js +190 -0
- package/dist/ToggleKit.d.ts +70 -0
- package/dist/ToggleKit.js +229 -0
- package/dist/TracingLine.d.ts +24 -0
- package/dist/TracingLine.js +139 -0
- package/dist/TradingCard.d.ts +30 -0
- package/dist/TradingCard.js +147 -0
- package/dist/VanishInput.d.ts +34 -0
- package/dist/VanishInput.js +190 -0
- package/dist/VideoHero.d.ts +32 -0
- package/dist/VideoHero.js +207 -0
- package/dist/VoiceOrb.d.ts +23 -0
- package/dist/VoiceOrb.js +182 -0
- package/dist/VoxelTiles.d.ts +38 -0
- package/dist/VoxelTiles.js +201 -0
- package/dist/WarpField.d.ts +18 -0
- package/dist/WarpField.js +160 -0
- package/dist/WebcamPixels.d.ts +38 -0
- package/dist/WebcamPixels.js +292 -0
- package/dist/WheelPicker.d.ts +29 -0
- package/dist/WheelPicker.js +199 -0
- package/dist/WobbleCard.d.ts +23 -0
- package/dist/WobbleCard.js +112 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +159 -0
- package/package.json +58 -4
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { cloneElement, isValidElement, useCallback, useEffect, useId, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
const MOTION = {
|
|
6
|
+
fast: 140,
|
|
7
|
+
base: 220,
|
|
8
|
+
slow: 320,
|
|
9
|
+
easeOut: "cubic-bezier(.2,.9,.25,1)",
|
|
10
|
+
easeIn: "cubic-bezier(.4,0,1,1)",
|
|
11
|
+
spring: "cubic-bezier(.3,1.35,.5,1)"
|
|
12
|
+
};
|
|
13
|
+
const STYLE_ID = "bl-overlay-kit";
|
|
14
|
+
const CSS = `
|
|
15
|
+
.bl-ok-layer { --ok-bg: var(--bl-ok-bg, #1a1c2c); --ok-ink: var(--bl-ok-ink, #f4f4f4); --ok-muted: var(--bl-ok-muted, #94b0c2); --ok-line: var(--bl-ok-line, rgba(255,255,255,0.12)); --ok-accent: var(--bl-ok-accent, #73eff7); --ok-hover: var(--bl-ok-hover, rgba(255,255,255,0.07)); --ok-radius: var(--bl-ok-radius, 14px); --ok-backdrop: var(--bl-ok-backdrop, rgba(8,8,16,0.6)); font-family: var(--bl-ok-font, ui-sans-serif, system-ui, sans-serif); color: var(--ok-ink); }
|
|
16
|
+
.bl-ok-surface { background: var(--ok-bg); color: var(--ok-ink); border-radius: var(--ok-radius); box-shadow: 0 0 0 1px var(--ok-line), 0 24px 60px -24px rgba(0,0,0,0.75); outline: none; }
|
|
17
|
+
.bl-ok-backdrop { position: fixed; inset: 0; z-index: 1500; background: var(--ok-backdrop); }
|
|
18
|
+
.bl-ok-backdrop[data-state="open"] { animation: bl-ok-fade ${MOTION.base}ms ${MOTION.easeOut} both; }
|
|
19
|
+
.bl-ok-backdrop[data-state="closed"] { animation: bl-ok-fade ${MOTION.base}ms ${MOTION.easeIn} reverse both; }
|
|
20
|
+
|
|
21
|
+
.bl-ok-dialog { position: fixed; z-index: 1501; left: 50%; top: 50%; width: min(var(--ok-w, 480px), calc(100vw - 32px)); max-height: calc(100vh - 48px); overflow: auto; padding: 22px; transform: translate(-50%, -50%); }
|
|
22
|
+
.bl-ok-dialog[data-state="open"] { animation: bl-ok-pop ${MOTION.slow}ms ${MOTION.spring} both; }
|
|
23
|
+
.bl-ok-dialog[data-state="closed"] { animation: bl-ok-pop ${MOTION.base}ms ${MOTION.easeIn} reverse both; }
|
|
24
|
+
|
|
25
|
+
.bl-ok-sheet { position: fixed; z-index: 1501; overflow: auto; padding: 22px; border-radius: 0; }
|
|
26
|
+
.bl-ok-sheet[data-side="right"] { top: 0; right: 0; bottom: 0; width: min(var(--ok-w, 380px), 92vw); border-radius: var(--ok-radius) 0 0 var(--ok-radius); }
|
|
27
|
+
.bl-ok-sheet[data-side="left"] { top: 0; left: 0; bottom: 0; width: min(var(--ok-w, 380px), 92vw); border-radius: 0 var(--ok-radius) var(--ok-radius) 0; }
|
|
28
|
+
.bl-ok-sheet[data-side="bottom"] { left: 0; right: 0; bottom: 0; max-height: 85vh; border-radius: var(--ok-radius) var(--ok-radius) 0 0; }
|
|
29
|
+
.bl-ok-sheet[data-side="top"] { left: 0; right: 0; top: 0; max-height: 85vh; border-radius: 0 0 var(--ok-radius) var(--ok-radius); }
|
|
30
|
+
.bl-ok-sheet[data-state="open"] { animation: bl-ok-slide-in ${MOTION.slow}ms ${MOTION.easeOut} both; }
|
|
31
|
+
.bl-ok-sheet[data-state="closed"] { animation: bl-ok-slide-in ${MOTION.base}ms ${MOTION.easeIn} reverse both; }
|
|
32
|
+
.bl-ok-sheet[data-side="right"] { --ok-from: translateX(100%); }
|
|
33
|
+
.bl-ok-sheet[data-side="left"] { --ok-from: translateX(-100%); }
|
|
34
|
+
.bl-ok-sheet[data-side="bottom"] { --ok-from: translateY(100%); }
|
|
35
|
+
.bl-ok-sheet[data-side="top"] { --ok-from: translateY(-100%); }
|
|
36
|
+
|
|
37
|
+
.bl-ok-title { margin: 0 32px 6px 0; font-size: 1.15em; font-weight: 700; }
|
|
38
|
+
.bl-ok-desc { margin: 0 0 16px; color: var(--ok-muted); font-size: 0.93em; line-height: 1.5; }
|
|
39
|
+
.bl-ok-close { position: absolute; top: 14px; right: 14px; display: grid; place-items: center; width: 30px; height: 30px; border: 0; border-radius: 50%; background: transparent; color: var(--ok-muted); cursor: pointer; }
|
|
40
|
+
.bl-ok-close:hover { background: var(--ok-hover); color: var(--ok-ink); }
|
|
41
|
+
.bl-ok-close:focus-visible, .bl-ok-item:focus-visible { outline: 2px solid var(--ok-accent); outline-offset: 1px; }
|
|
42
|
+
|
|
43
|
+
.bl-ok-float[data-state="open"] { animation: bl-ok-float-in ${MOTION.base}ms ${MOTION.spring} both; }
|
|
44
|
+
.bl-ok-float[data-state="closed"] { animation: bl-ok-float-in ${MOTION.fast}ms ${MOTION.easeIn} reverse both; }
|
|
45
|
+
.bl-ok-float[data-side="top"] { --ok-shift: 0, 6px; transform-origin: bottom center; }
|
|
46
|
+
.bl-ok-float[data-side="bottom"] { --ok-shift: 0, -6px; transform-origin: top center; }
|
|
47
|
+
.bl-ok-float[data-side="left"] { --ok-shift: 6px, 0; transform-origin: center right; }
|
|
48
|
+
.bl-ok-float[data-side="right"] { --ok-shift: -6px, 0; transform-origin: center left; }
|
|
49
|
+
|
|
50
|
+
.bl-ok-tooltip { max-width: 260px; padding: 6px 10px; border-radius: calc(var(--ok-radius) * 0.6); background: var(--bl-ok-tooltip-bg, #f4f4f4); color: var(--bl-ok-tooltip-ink, #1a1c2c); font-size: 13px; line-height: 1.4; box-shadow: 0 8px 24px -10px rgba(0,0,0,0.6); }
|
|
51
|
+
.bl-ok-popover { padding: 14px; width: var(--ok-w, auto); max-width: min(360px, calc(100vw - 16px)); }
|
|
52
|
+
.bl-ok-menu { min-width: 200px; max-width: calc(100vw - 16px); padding: 5px; }
|
|
53
|
+
.bl-ok-item { display: flex; align-items: center; gap: 10px; width: 100%; padding: 8px 10px; border: 0; border-radius: calc(var(--ok-radius) * 0.55); background: transparent; color: inherit; font: inherit; font-size: 14px; text-align: left; cursor: pointer; }
|
|
54
|
+
.bl-ok-item:hover, .bl-ok-item:focus { background: var(--ok-hover); outline: none; }
|
|
55
|
+
.bl-ok-item:focus-visible { box-shadow: inset 0 0 0 2px var(--ok-accent); outline: none; }
|
|
56
|
+
.bl-ok-item[data-danger] { color: var(--bl-ok-danger, #ef7d57); }
|
|
57
|
+
.bl-ok-item:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
58
|
+
.bl-ok-item__label { flex: 1; }
|
|
59
|
+
.bl-ok-item__kbd { color: var(--ok-muted); font-size: 12px; }
|
|
60
|
+
.bl-ok-sep { height: 1px; margin: 5px 4px; background: var(--ok-line); }
|
|
61
|
+
.bl-ok-menu-label { padding: 6px 10px 4px; color: var(--ok-muted); font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; }
|
|
62
|
+
|
|
63
|
+
@keyframes bl-ok-fade { from { opacity: 0; } }
|
|
64
|
+
@keyframes bl-ok-pop { from { opacity: 0; transform: translate(-50%, -48%) scale(0.95); } }
|
|
65
|
+
@keyframes bl-ok-slide-in { from { transform: var(--ok-from); } }
|
|
66
|
+
@keyframes bl-ok-float-in { from { opacity: 0; transform: translate(var(--ok-shift, 0, 0)) scale(0.96); } }
|
|
67
|
+
@media (prefers-reduced-motion: reduce) {
|
|
68
|
+
.bl-ok-dialog[data-state], .bl-ok-sheet[data-state], .bl-ok-float[data-state] { animation-name: bl-ok-fade; }
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
function useStyles() {
|
|
72
|
+
useInsertionEffect(() => {
|
|
73
|
+
if (typeof document === "undefined") return;
|
|
74
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
75
|
+
style.textContent = CSS;
|
|
76
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
77
|
+
}, []);
|
|
78
|
+
}
|
|
79
|
+
function useControllable(open, defaultOpen, onOpenChange) {
|
|
80
|
+
const [inner, setInner] = useState(defaultOpen ?? false);
|
|
81
|
+
const value = open ?? inner;
|
|
82
|
+
const set = useCallback(
|
|
83
|
+
(next) => {
|
|
84
|
+
if (open === void 0) setInner(next);
|
|
85
|
+
onOpenChange?.(next);
|
|
86
|
+
},
|
|
87
|
+
[open, onOpenChange]
|
|
88
|
+
);
|
|
89
|
+
return [value, set];
|
|
90
|
+
}
|
|
91
|
+
function usePresence(open, exitMs = MOTION.base) {
|
|
92
|
+
const [mounted, setMounted] = useState(open);
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (open) {
|
|
95
|
+
setMounted(true);
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
const timer = setTimeout(() => setMounted(false), exitMs);
|
|
99
|
+
return () => clearTimeout(timer);
|
|
100
|
+
}, [open, exitMs]);
|
|
101
|
+
return [mounted || open, open ? "open" : "closed"];
|
|
102
|
+
}
|
|
103
|
+
const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
104
|
+
function useFocusTrap(ref, active, { trap = true, restore = true } = {}) {
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!active) return void 0;
|
|
107
|
+
const node = ref.current;
|
|
108
|
+
const previous = document.activeElement;
|
|
109
|
+
const first = node?.querySelector("[data-autofocus]") ?? node?.querySelector(FOCUSABLE) ?? node;
|
|
110
|
+
first?.focus({ preventScroll: true });
|
|
111
|
+
const onKey = (event) => {
|
|
112
|
+
if (!trap || event.key !== "Tab" || !node) return;
|
|
113
|
+
const items = [...node.querySelectorAll(FOCUSABLE)];
|
|
114
|
+
if (!items.length) {
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const a = items[0];
|
|
119
|
+
const z = items[items.length - 1];
|
|
120
|
+
if (event.shiftKey && (document.activeElement === a || document.activeElement === node)) {
|
|
121
|
+
event.preventDefault();
|
|
122
|
+
z.focus();
|
|
123
|
+
} else if (!event.shiftKey && document.activeElement === z) {
|
|
124
|
+
event.preventDefault();
|
|
125
|
+
a.focus();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
node?.addEventListener("keydown", onKey);
|
|
129
|
+
return () => {
|
|
130
|
+
node?.removeEventListener("keydown", onKey);
|
|
131
|
+
if (restore && previous instanceof HTMLElement && (node?.contains(document.activeElement) || document.activeElement === document.body)) previous.focus({ preventScroll: true });
|
|
132
|
+
};
|
|
133
|
+
}, [active]);
|
|
134
|
+
}
|
|
135
|
+
function useScrollLock(active) {
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!active) return void 0;
|
|
138
|
+
const { body, documentElement } = document;
|
|
139
|
+
const previous = { overflow: body.style.overflow, paddingRight: body.style.paddingRight };
|
|
140
|
+
const gap = window.innerWidth - documentElement.clientWidth;
|
|
141
|
+
body.style.overflow = "hidden";
|
|
142
|
+
if (gap > 0) body.style.paddingRight = `${gap}px`;
|
|
143
|
+
return () => {
|
|
144
|
+
Object.assign(body.style, previous);
|
|
145
|
+
};
|
|
146
|
+
}, [active]);
|
|
147
|
+
}
|
|
148
|
+
function useDismiss(active, refs, onDismiss, { outside = true } = {}) {
|
|
149
|
+
const latest = useRef(onDismiss);
|
|
150
|
+
latest.current = onDismiss;
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (!active) return void 0;
|
|
153
|
+
const onKey = (event) => {
|
|
154
|
+
if (event.key === "Escape") {
|
|
155
|
+
event.stopPropagation();
|
|
156
|
+
latest.current();
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const onDown = (event) => {
|
|
160
|
+
if (!outside) return;
|
|
161
|
+
if (refs.every((ref) => !ref.current?.contains(event.target))) latest.current();
|
|
162
|
+
};
|
|
163
|
+
document.addEventListener("keydown", onKey);
|
|
164
|
+
document.addEventListener("pointerdown", onDown);
|
|
165
|
+
return () => {
|
|
166
|
+
document.removeEventListener("keydown", onKey);
|
|
167
|
+
document.removeEventListener("pointerdown", onDown);
|
|
168
|
+
};
|
|
169
|
+
}, [active, outside]);
|
|
170
|
+
}
|
|
171
|
+
function useFloating(anchorRef, floatingRef, active, { side = "bottom", align = "center", offset = 8 }) {
|
|
172
|
+
const [placed, setPlaced] = useState(side);
|
|
173
|
+
useLayoutEffect(() => {
|
|
174
|
+
if (!active) return void 0;
|
|
175
|
+
const update = () => {
|
|
176
|
+
const anchor = anchorRef.current;
|
|
177
|
+
const floating = floatingRef.current;
|
|
178
|
+
if (!anchor || !floating) return;
|
|
179
|
+
const a = anchor.getBoundingClientRect();
|
|
180
|
+
const f = { width: floating.offsetWidth, height: floating.offsetHeight };
|
|
181
|
+
const vw = window.innerWidth;
|
|
182
|
+
const vh = window.innerHeight;
|
|
183
|
+
const opposite = { top: "bottom", bottom: "top", left: "right", right: "left" };
|
|
184
|
+
const fits = (s2) => s2 === "top" ? a.top - offset - f.height >= 4 : s2 === "bottom" ? a.bottom + offset + f.height <= vh - 4 : s2 === "left" ? a.left - offset - f.width >= 4 : a.right + offset + f.width <= vw - 4;
|
|
185
|
+
const s = fits(side) || !fits(opposite[side]) ? side : opposite[side];
|
|
186
|
+
let top;
|
|
187
|
+
let left;
|
|
188
|
+
if (s === "top" || s === "bottom") {
|
|
189
|
+
top = s === "top" ? a.top - offset - f.height : a.bottom + offset;
|
|
190
|
+
left = align === "start" ? a.left : align === "end" ? a.right - f.width : a.left + a.width / 2 - f.width / 2;
|
|
191
|
+
} else {
|
|
192
|
+
left = s === "left" ? a.left - offset - f.width : a.right + offset;
|
|
193
|
+
top = align === "start" ? a.top : align === "end" ? a.bottom - f.height : a.top + a.height / 2 - f.height / 2;
|
|
194
|
+
}
|
|
195
|
+
left = Math.max(8, Math.min(vw - f.width - 8, left));
|
|
196
|
+
top = Math.max(8, Math.min(vh - f.height - 8, top));
|
|
197
|
+
floating.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
|
|
198
|
+
setPlaced(s);
|
|
199
|
+
};
|
|
200
|
+
update();
|
|
201
|
+
window.addEventListener("resize", update);
|
|
202
|
+
window.addEventListener("scroll", update, true);
|
|
203
|
+
return () => {
|
|
204
|
+
window.removeEventListener("resize", update);
|
|
205
|
+
window.removeEventListener("scroll", update, true);
|
|
206
|
+
};
|
|
207
|
+
}, [active, side, align, offset]);
|
|
208
|
+
return placed;
|
|
209
|
+
}
|
|
210
|
+
function mergeRefs(...refs) {
|
|
211
|
+
return (node) => {
|
|
212
|
+
for (const ref of refs) {
|
|
213
|
+
if (typeof ref === "function") ref(node);
|
|
214
|
+
else if (ref) ref.current = node;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const CloseIcon = () => /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2 2l10 10M12 2 2 12" }) });
|
|
219
|
+
const floatStyle = { position: "fixed", zIndex: 1600, top: 0, left: 0 };
|
|
220
|
+
function Modal({ variant, open, defaultOpen, onOpenChange, trigger, title, description, children, side = "right", width, closeLabel = "Close", className = "", style }) {
|
|
221
|
+
useStyles();
|
|
222
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
223
|
+
const [isOpen, setOpen] = useControllable(open, defaultOpen, onOpenChange);
|
|
224
|
+
const [mounted, state] = usePresence(isOpen);
|
|
225
|
+
const panelRef = useRef(null);
|
|
226
|
+
useFocusTrap(panelRef, isOpen && mounted);
|
|
227
|
+
useScrollLock(isOpen);
|
|
228
|
+
useDismiss(isOpen, [panelRef], () => setOpen(false), { outside: false });
|
|
229
|
+
const triggerEl = isValidElement(trigger) ? cloneElement(trigger, {
|
|
230
|
+
"aria-haspopup": "dialog",
|
|
231
|
+
"aria-expanded": isOpen,
|
|
232
|
+
onClick: (event) => {
|
|
233
|
+
trigger.props.onClick?.(event);
|
|
234
|
+
setOpen(true);
|
|
235
|
+
}
|
|
236
|
+
}) : null;
|
|
237
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
238
|
+
triggerEl,
|
|
239
|
+
mounted && createPortal(
|
|
240
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-ok-layer", style: { ...width ? { "--ok-w": `${width}px` } : null, ...style }, children: [
|
|
241
|
+
/* @__PURE__ */ jsx("div", { className: "bl-ok-backdrop", "data-state": state, onClick: () => setOpen(false), "aria-hidden": "true" }),
|
|
242
|
+
/* @__PURE__ */ jsxs(
|
|
243
|
+
"div",
|
|
244
|
+
{
|
|
245
|
+
ref: panelRef,
|
|
246
|
+
className: `bl-ok-surface ${variant === "sheet" ? "bl-ok-sheet" : "bl-ok-dialog"} ${className}`,
|
|
247
|
+
"data-state": state,
|
|
248
|
+
"data-side": variant === "sheet" ? side : void 0,
|
|
249
|
+
role: "dialog",
|
|
250
|
+
"aria-modal": "true",
|
|
251
|
+
"aria-labelledby": title ? `${uid}-title` : void 0,
|
|
252
|
+
"aria-describedby": description ? `${uid}-desc` : void 0,
|
|
253
|
+
tabIndex: -1,
|
|
254
|
+
children: [
|
|
255
|
+
title && /* @__PURE__ */ jsx("h2", { id: `${uid}-title`, className: "bl-ok-title", children: title }),
|
|
256
|
+
description && /* @__PURE__ */ jsx("p", { id: `${uid}-desc`, className: "bl-ok-desc", children: description }),
|
|
257
|
+
typeof children === "function" ? children({ close: () => setOpen(false) }) : children,
|
|
258
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "bl-ok-close", "aria-label": closeLabel, onClick: () => setOpen(false), children: /* @__PURE__ */ jsx(CloseIcon, {}) })
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
] }),
|
|
263
|
+
document.body
|
|
264
|
+
)
|
|
265
|
+
] });
|
|
266
|
+
}
|
|
267
|
+
function Dialog(props) {
|
|
268
|
+
return /* @__PURE__ */ jsx(Modal, { ...props, variant: "dialog" });
|
|
269
|
+
}
|
|
270
|
+
function Sheet(props) {
|
|
271
|
+
return /* @__PURE__ */ jsx(Modal, { ...props, variant: "sheet" });
|
|
272
|
+
}
|
|
273
|
+
function Tooltip({ content, children, side = "top", align = "center", delay = 400, closeDelay = 120, className = "", style }) {
|
|
274
|
+
useStyles();
|
|
275
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
276
|
+
const [open, setOpen] = useState(false);
|
|
277
|
+
const [mounted, state] = usePresence(open, MOTION.fast);
|
|
278
|
+
const anchorRef = useRef(null);
|
|
279
|
+
const floatRef = useRef(null);
|
|
280
|
+
const timer = useRef(0);
|
|
281
|
+
const placed = useFloating(anchorRef, floatRef, mounted, { side, align, offset: 8 });
|
|
282
|
+
const show = (wait) => {
|
|
283
|
+
clearTimeout(timer.current);
|
|
284
|
+
timer.current = setTimeout(() => setOpen(true), wait);
|
|
285
|
+
};
|
|
286
|
+
const hide = (wait) => {
|
|
287
|
+
clearTimeout(timer.current);
|
|
288
|
+
timer.current = setTimeout(() => setOpen(false), wait);
|
|
289
|
+
};
|
|
290
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
291
|
+
useDismiss(open, [], () => setOpen(false), { outside: false });
|
|
292
|
+
if (!isValidElement(children)) return children ?? null;
|
|
293
|
+
const child = cloneElement(children, {
|
|
294
|
+
ref: mergeRefs(anchorRef, children.props.ref),
|
|
295
|
+
"aria-describedby": open ? `${uid}-tip` : children.props["aria-describedby"],
|
|
296
|
+
onPointerEnter: (event) => {
|
|
297
|
+
children.props.onPointerEnter?.(event);
|
|
298
|
+
if (event.pointerType === "mouse") show(delay);
|
|
299
|
+
},
|
|
300
|
+
onPointerLeave: (event) => {
|
|
301
|
+
children.props.onPointerLeave?.(event);
|
|
302
|
+
hide(closeDelay);
|
|
303
|
+
},
|
|
304
|
+
onFocus: (event) => {
|
|
305
|
+
children.props.onFocus?.(event);
|
|
306
|
+
show(0);
|
|
307
|
+
},
|
|
308
|
+
onBlur: (event) => {
|
|
309
|
+
children.props.onBlur?.(event);
|
|
310
|
+
hide(0);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
314
|
+
child,
|
|
315
|
+
mounted && createPortal(
|
|
316
|
+
/* @__PURE__ */ jsx("div", { ref: floatRef, className: "bl-ok-layer", style: { ...floatStyle, ...style }, onPointerEnter: () => show(0), onPointerLeave: () => hide(closeDelay), children: /* @__PURE__ */ jsx("div", { id: `${uid}-tip`, role: "tooltip", className: `bl-ok-float bl-ok-tooltip ${className}`, "data-state": state, "data-side": placed, children: content }) }),
|
|
317
|
+
document.body
|
|
318
|
+
)
|
|
319
|
+
] });
|
|
320
|
+
}
|
|
321
|
+
function Popover({ trigger, children, label, open, defaultOpen, onOpenChange, side = "bottom", align = "center", width, className = "", style }) {
|
|
322
|
+
useStyles();
|
|
323
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
324
|
+
const [isOpen, setOpen] = useControllable(open, defaultOpen, onOpenChange);
|
|
325
|
+
const [mounted, state] = usePresence(isOpen, MOTION.fast);
|
|
326
|
+
const anchorRef = useRef(null);
|
|
327
|
+
const floatRef = useRef(null);
|
|
328
|
+
const panelRef = useRef(null);
|
|
329
|
+
const placed = useFloating(anchorRef, floatRef, mounted, { side, align, offset: 8 });
|
|
330
|
+
useFocusTrap(panelRef, isOpen && mounted, { trap: false });
|
|
331
|
+
useDismiss(isOpen, [anchorRef, floatRef], () => setOpen(false));
|
|
332
|
+
const triggerEl = isValidElement(trigger) ? cloneElement(trigger, {
|
|
333
|
+
ref: mergeRefs(anchorRef, trigger.props.ref),
|
|
334
|
+
"aria-haspopup": "dialog",
|
|
335
|
+
"aria-expanded": isOpen,
|
|
336
|
+
"aria-controls": mounted ? `${uid}-pop` : void 0,
|
|
337
|
+
onClick: (event) => {
|
|
338
|
+
trigger.props.onClick?.(event);
|
|
339
|
+
setOpen(!isOpen);
|
|
340
|
+
}
|
|
341
|
+
}) : null;
|
|
342
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
343
|
+
triggerEl,
|
|
344
|
+
mounted && createPortal(
|
|
345
|
+
/* @__PURE__ */ jsx("div", { ref: floatRef, className: "bl-ok-layer", style: { ...floatStyle, ...width ? { "--ok-w": `${width}px` } : null, ...style }, children: /* @__PURE__ */ jsx(
|
|
346
|
+
"div",
|
|
347
|
+
{
|
|
348
|
+
ref: panelRef,
|
|
349
|
+
id: `${uid}-pop`,
|
|
350
|
+
role: "dialog",
|
|
351
|
+
"aria-label": label,
|
|
352
|
+
tabIndex: -1,
|
|
353
|
+
className: `bl-ok-surface bl-ok-float bl-ok-popover ${className}`,
|
|
354
|
+
"data-state": state,
|
|
355
|
+
"data-side": placed,
|
|
356
|
+
children: typeof children === "function" ? children({ close: () => setOpen(false) }) : children
|
|
357
|
+
}
|
|
358
|
+
) }),
|
|
359
|
+
document.body
|
|
360
|
+
)
|
|
361
|
+
] });
|
|
362
|
+
}
|
|
363
|
+
function Dropdown({ trigger, items = [], label = "Menu", side = "bottom", align = "start", className = "", style }) {
|
|
364
|
+
useStyles();
|
|
365
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
366
|
+
const [open, setOpen] = useState(false);
|
|
367
|
+
const [mounted, state] = usePresence(open, MOTION.fast);
|
|
368
|
+
const anchorRef = useRef(null);
|
|
369
|
+
const floatRef = useRef(null);
|
|
370
|
+
const menuRef = useRef(null);
|
|
371
|
+
const startAt = useRef("first");
|
|
372
|
+
const typed = useRef({ text: "", at: 0 });
|
|
373
|
+
const placed = useFloating(anchorRef, floatRef, mounted, { side, align, offset: 6 });
|
|
374
|
+
const close = (refocus = true) => {
|
|
375
|
+
setOpen(false);
|
|
376
|
+
if (refocus) anchorRef.current?.focus({ preventScroll: true });
|
|
377
|
+
};
|
|
378
|
+
useDismiss(open, [anchorRef, floatRef], () => close());
|
|
379
|
+
const menuItems = () => [...menuRef.current?.querySelectorAll('[role="menuitem"]:not(:disabled)') ?? []];
|
|
380
|
+
useEffect(() => {
|
|
381
|
+
if (!open || !mounted) return;
|
|
382
|
+
const list = menuItems();
|
|
383
|
+
(startAt.current === "last" ? list[list.length - 1] : list[0])?.focus({ preventScroll: true });
|
|
384
|
+
}, [open, mounted]);
|
|
385
|
+
const onMenuKey = (event) => {
|
|
386
|
+
const list = menuItems();
|
|
387
|
+
const index = list.indexOf(document.activeElement);
|
|
388
|
+
const move = (to) => {
|
|
389
|
+
event.preventDefault();
|
|
390
|
+
list[(to + list.length) % list.length]?.focus();
|
|
391
|
+
};
|
|
392
|
+
if (event.key === "ArrowDown") move(index + 1);
|
|
393
|
+
else if (event.key === "ArrowUp") move(index - 1);
|
|
394
|
+
else if (event.key === "Home") move(0);
|
|
395
|
+
else if (event.key === "End") move(list.length - 1);
|
|
396
|
+
else if (event.key === "Tab") close(false);
|
|
397
|
+
else if (event.key.length === 1 && /\S/.test(event.key)) {
|
|
398
|
+
const now = Date.now();
|
|
399
|
+
typed.current = { text: (now - typed.current.at < 600 ? typed.current.text : "") + event.key.toLowerCase(), at: now };
|
|
400
|
+
const match = list.findIndex((el, i) => i > index - (typed.current.text.length > 1 ? 1 : 0) && el.textContent.trim().toLowerCase().startsWith(typed.current.text));
|
|
401
|
+
const fallback = list.findIndex((el) => el.textContent.trim().toLowerCase().startsWith(typed.current.text));
|
|
402
|
+
if (match >= 0 || fallback >= 0) move(match >= 0 ? match : fallback);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
const triggerEl = isValidElement(trigger) ? cloneElement(trigger, {
|
|
406
|
+
ref: mergeRefs(anchorRef, trigger.props.ref),
|
|
407
|
+
"aria-haspopup": "menu",
|
|
408
|
+
"aria-expanded": open,
|
|
409
|
+
"aria-controls": mounted ? `${uid}-menu` : void 0,
|
|
410
|
+
onClick: (event) => {
|
|
411
|
+
trigger.props.onClick?.(event);
|
|
412
|
+
startAt.current = "first";
|
|
413
|
+
setOpen(!open);
|
|
414
|
+
},
|
|
415
|
+
onKeyDown: (event) => {
|
|
416
|
+
trigger.props.onKeyDown?.(event);
|
|
417
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
418
|
+
event.preventDefault();
|
|
419
|
+
startAt.current = event.key === "ArrowUp" ? "last" : "first";
|
|
420
|
+
setOpen(true);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}) : null;
|
|
424
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
425
|
+
triggerEl,
|
|
426
|
+
mounted && createPortal(
|
|
427
|
+
/* @__PURE__ */ jsx("div", { ref: floatRef, className: "bl-ok-layer", style: { ...floatStyle, ...style }, children: /* @__PURE__ */ jsx("div", { ref: menuRef, id: `${uid}-menu`, role: "menu", "aria-label": label, className: `bl-ok-surface bl-ok-float bl-ok-menu ${className}`, "data-state": state, "data-side": placed, onKeyDown: onMenuKey, children: items.map((item, i) => {
|
|
428
|
+
if (item.type === "separator") return /* @__PURE__ */ jsx("div", { className: "bl-ok-sep", role: "separator" }, i);
|
|
429
|
+
if (item.type === "label")
|
|
430
|
+
return /* @__PURE__ */ jsx("div", { className: "bl-ok-menu-label", role: "presentation", children: item.label }, i);
|
|
431
|
+
return /* @__PURE__ */ jsxs(
|
|
432
|
+
"button",
|
|
433
|
+
{
|
|
434
|
+
type: "button",
|
|
435
|
+
role: "menuitem",
|
|
436
|
+
tabIndex: -1,
|
|
437
|
+
className: "bl-ok-item",
|
|
438
|
+
disabled: item.disabled,
|
|
439
|
+
"data-danger": item.danger || void 0,
|
|
440
|
+
onClick: () => {
|
|
441
|
+
item.onSelect?.();
|
|
442
|
+
close();
|
|
443
|
+
},
|
|
444
|
+
children: [
|
|
445
|
+
item.icon && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: item.icon }),
|
|
446
|
+
/* @__PURE__ */ jsx("span", { className: "bl-ok-item__label", children: item.label }),
|
|
447
|
+
item.shortcut && /* @__PURE__ */ jsx("span", { className: "bl-ok-item__kbd", children: item.shortcut })
|
|
448
|
+
]
|
|
449
|
+
},
|
|
450
|
+
i
|
|
451
|
+
);
|
|
452
|
+
}) }) }),
|
|
453
|
+
document.body
|
|
454
|
+
)
|
|
455
|
+
] });
|
|
456
|
+
}
|
|
457
|
+
var stdin_default = Dialog;
|
|
458
|
+
export {
|
|
459
|
+
Dialog,
|
|
460
|
+
Dropdown,
|
|
461
|
+
MOTION,
|
|
462
|
+
Popover,
|
|
463
|
+
Sheet,
|
|
464
|
+
Tooltip,
|
|
465
|
+
stdin_default as default
|
|
466
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type CSSProperties, type MouseEvent as ReactMouseEvent, type ReactNode } from 'react';
|
|
2
|
+
export interface MenuItem {
|
|
3
|
+
label: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
/** A small note at the side. */
|
|
6
|
+
meta?: ReactNode;
|
|
7
|
+
onSelect?: (event: ReactMouseEvent<HTMLElement>) => void;
|
|
8
|
+
}
|
|
9
|
+
export interface OverlayMenuProps {
|
|
10
|
+
items?: MenuItem[];
|
|
11
|
+
/** Anything under the links. */
|
|
12
|
+
footer?: ReactNode;
|
|
13
|
+
open?: boolean;
|
|
14
|
+
defaultOpen?: boolean;
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
onNavigate?: (item: MenuItem, event: ReactMouseEvent<HTMLElement>) => void;
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Number the links. */
|
|
19
|
+
numbered?: boolean;
|
|
20
|
+
/** Maximum link size in px. */
|
|
21
|
+
fontSize?: number;
|
|
22
|
+
backgroundColor?: string;
|
|
23
|
+
textColor?: string;
|
|
24
|
+
mutedColor?: string;
|
|
25
|
+
lineColor?: string;
|
|
26
|
+
chipColor?: string;
|
|
27
|
+
accentColor?: string;
|
|
28
|
+
fontFamily?: string;
|
|
29
|
+
className?: string;
|
|
30
|
+
style?: CSSProperties;
|
|
31
|
+
}
|
|
32
|
+
export default function OverlayMenu({ items, footer, open: controlled, defaultOpen, onOpenChange, onNavigate, label, numbered, fontSize, backgroundColor, textColor, mutedColor, lineColor, chipColor, accentColor, fontFamily, className, style }: OverlayMenuProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
const STYLE_ID = "bl-overlay-menu";
|
|
6
|
+
const CSS = `
|
|
7
|
+
.bl-omenu__toggle { position: relative; z-index: 1801; display: inline-grid; place-items: center; width: 46px; height: 46px; padding: 0; border: 0; border-radius: 10px; background: var(--bl-chip); color: var(--bl-ink); cursor: pointer; }
|
|
8
|
+
.bl-omenu__toggle:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
|
|
9
|
+
.bl-omenu__bars { position: relative; width: 20px; height: 14px; }
|
|
10
|
+
.bl-omenu__bars span { position: absolute; left: 0; width: 100%; height: 2px; background: currentColor; transition: transform 240ms steps(4), opacity 160ms steps(2), top 240ms steps(4); }
|
|
11
|
+
.bl-omenu__bars span:nth-child(1) { top: 0; }
|
|
12
|
+
.bl-omenu__bars span:nth-child(2) { top: 6px; }
|
|
13
|
+
.bl-omenu__bars span:nth-child(3) { top: 12px; }
|
|
14
|
+
.bl-omenu__toggle[aria-expanded="true"] .bl-omenu__bars span:nth-child(1) { top: 6px; transform: rotate(45deg); }
|
|
15
|
+
.bl-omenu__toggle[aria-expanded="true"] .bl-omenu__bars span:nth-child(2) { opacity: 0; }
|
|
16
|
+
.bl-omenu__toggle[aria-expanded="true"] .bl-omenu__bars span:nth-child(3) { top: 6px; transform: rotate(-45deg); }
|
|
17
|
+
.bl-omenu { position: fixed; inset: 0; z-index: 1800; display: grid; grid-template-rows: 1fr auto; overflow-y: auto; padding: 88px max(24px, 6vw) 32px; background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); clip-path: inset(0 0 0 0); animation: bl-omenu-wipe 420ms steps(8) both; }
|
|
18
|
+
.bl-omenu[data-closing] { animation: bl-omenu-wipe 300ms steps(6) reverse both; }
|
|
19
|
+
.bl-omenu__close { position: absolute; top: 20px; right: max(20px, 5vw); }
|
|
20
|
+
.bl-omenu__list { display: grid; align-content: center; gap: 4px; margin: 0; padding: 0; list-style: none; }
|
|
21
|
+
.bl-omenu__item { opacity: 0; transform: translateY(16px); animation: bl-omenu-drop 360ms cubic-bezier(.2,.9,.3,1.2) forwards; animation-delay: calc(160ms + var(--i) * 55ms); }
|
|
22
|
+
.bl-omenu__link { display: flex; align-items: baseline; gap: 16px; width: 100%; padding: 6px 0; border: 0; background: none; color: inherit; font: inherit; font-size: var(--bl-size); line-height: 1.15; text-align: left; text-decoration: none; cursor: pointer; transition: color 120ms steps(2), transform 160ms steps(3); }
|
|
23
|
+
.bl-omenu__link:hover, .bl-omenu__link:focus-visible { color: var(--bl-accent); transform: translateX(10px); outline: none; }
|
|
24
|
+
.bl-omenu__index { color: var(--bl-muted); font-size: 13px; font-variant-numeric: tabular-nums; }
|
|
25
|
+
.bl-omenu__meta { margin-left: auto; color: var(--bl-muted); font-size: 14px; }
|
|
26
|
+
.bl-omenu__foot { padding-top: 24px; border-top: 1px solid var(--bl-line); color: var(--bl-muted); font-size: 14px; opacity: 0; animation: bl-omenu-drop 360ms ease forwards; animation-delay: calc(200ms + var(--n) * 55ms); }
|
|
27
|
+
@keyframes bl-omenu-wipe { from { clip-path: inset(0 0 100% 0); } }
|
|
28
|
+
@keyframes bl-omenu-drop { to { opacity: 1; transform: none; } }
|
|
29
|
+
@media (prefers-reduced-motion: reduce) { .bl-omenu, .bl-omenu[data-closing], .bl-omenu__item, .bl-omenu__foot { animation: none; opacity: 1; transform: none; } .bl-omenu__bars span { transition: none; } }
|
|
30
|
+
`;
|
|
31
|
+
function useStyles() {
|
|
32
|
+
useInsertionEffect(() => {
|
|
33
|
+
if (typeof document === "undefined") return;
|
|
34
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
35
|
+
style.textContent = CSS;
|
|
36
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
37
|
+
}, []);
|
|
38
|
+
}
|
|
39
|
+
const Bars = () => /* @__PURE__ */ jsxs("span", { className: "bl-omenu__bars", "aria-hidden": "true", children: [
|
|
40
|
+
/* @__PURE__ */ jsx("span", {}),
|
|
41
|
+
/* @__PURE__ */ jsx("span", {}),
|
|
42
|
+
/* @__PURE__ */ jsx("span", {})
|
|
43
|
+
] });
|
|
44
|
+
function OverlayMenu({
|
|
45
|
+
items = [],
|
|
46
|
+
footer,
|
|
47
|
+
open: controlled,
|
|
48
|
+
defaultOpen = false,
|
|
49
|
+
onOpenChange,
|
|
50
|
+
onNavigate,
|
|
51
|
+
label = "Menu",
|
|
52
|
+
numbered = true,
|
|
53
|
+
fontSize = 44,
|
|
54
|
+
backgroundColor = "#1a1c2c",
|
|
55
|
+
textColor = "#f4f4f4",
|
|
56
|
+
mutedColor = "#94b0c2",
|
|
57
|
+
lineColor = "#333c57",
|
|
58
|
+
chipColor = "rgba(255,255,255,0.08)",
|
|
59
|
+
accentColor = "#ffcd75",
|
|
60
|
+
fontFamily = "inherit",
|
|
61
|
+
className = "",
|
|
62
|
+
style
|
|
63
|
+
}) {
|
|
64
|
+
useStyles();
|
|
65
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
66
|
+
const [inner, setInner] = useState(defaultOpen);
|
|
67
|
+
const open = controlled ?? inner;
|
|
68
|
+
const [mounted, setMounted] = useState(open);
|
|
69
|
+
const [closing, setClosing] = useState(false);
|
|
70
|
+
const toggleRef = useRef(null);
|
|
71
|
+
const panelRef = useRef(null);
|
|
72
|
+
const setOpen = (next) => {
|
|
73
|
+
if (controlled === void 0) setInner(next);
|
|
74
|
+
onOpenChange?.(next);
|
|
75
|
+
};
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (open) {
|
|
78
|
+
setMounted(true);
|
|
79
|
+
setClosing(false);
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
if (!mounted) return void 0;
|
|
83
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
84
|
+
setClosing(true);
|
|
85
|
+
const id = setTimeout(() => {
|
|
86
|
+
setMounted(false);
|
|
87
|
+
setClosing(false);
|
|
88
|
+
}, reduced ? 0 : 300);
|
|
89
|
+
return () => clearTimeout(id);
|
|
90
|
+
}, [open]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!open || !mounted) return void 0;
|
|
93
|
+
const panel = panelRef.current;
|
|
94
|
+
const toggle = toggleRef.current;
|
|
95
|
+
const focusables = () => [...panel.querySelectorAll('a[href], button, [tabindex]:not([tabindex="-1"])')];
|
|
96
|
+
focusables()[1]?.focus();
|
|
97
|
+
const onKey = (event) => {
|
|
98
|
+
if (event.key === "Escape") {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
setOpen(false);
|
|
101
|
+
toggle?.focus();
|
|
102
|
+
} else if (event.key === "Tab") {
|
|
103
|
+
const list = focusables();
|
|
104
|
+
const first = list[0];
|
|
105
|
+
const last = list[list.length - 1];
|
|
106
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
107
|
+
event.preventDefault();
|
|
108
|
+
last.focus();
|
|
109
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
first.focus();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
const overflow = document.documentElement.style.overflow;
|
|
116
|
+
document.documentElement.style.overflow = "hidden";
|
|
117
|
+
document.addEventListener("keydown", onKey);
|
|
118
|
+
return () => {
|
|
119
|
+
document.documentElement.style.overflow = overflow;
|
|
120
|
+
document.removeEventListener("keydown", onKey);
|
|
121
|
+
};
|
|
122
|
+
}, [open, mounted]);
|
|
123
|
+
const vars = { "--bl-size": `clamp(26px, 6vw, ${fontSize}px)`, "--bl-bg": backgroundColor, "--bl-ink": textColor, "--bl-muted": mutedColor, "--bl-line": lineColor, "--bl-chip": chipColor, "--bl-accent": accentColor, "--bl-font": fontFamily };
|
|
124
|
+
const choose = (item, event) => {
|
|
125
|
+
item.onSelect?.(event);
|
|
126
|
+
onNavigate?.(item, event);
|
|
127
|
+
setOpen(false);
|
|
128
|
+
toggleRef.current?.focus();
|
|
129
|
+
};
|
|
130
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
131
|
+
/* @__PURE__ */ jsx("button", { ref: toggleRef, type: "button", className: `bl-omenu__toggle ${className}`, "aria-expanded": open, "aria-controls": `${uid}-menu`, "aria-label": open ? `Close ${label}` : `Open ${label}`, onClick: () => setOpen(!open), style: { ...vars, ...style }, children: /* @__PURE__ */ jsx(Bars, {}) }),
|
|
132
|
+
mounted && typeof document !== "undefined" && createPortal(
|
|
133
|
+
/* @__PURE__ */ jsxs("div", { ref: panelRef, id: `${uid}-menu`, className: "bl-omenu", role: "dialog", "aria-modal": "true", "aria-label": label, "data-closing": closing || void 0, style: { ...vars, "--n": items.length }, children: [
|
|
134
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "bl-omenu__toggle bl-omenu__close", "aria-expanded": "true", "aria-label": `Close ${label}`, onClick: () => {
|
|
135
|
+
setOpen(false);
|
|
136
|
+
toggleRef.current?.focus();
|
|
137
|
+
}, children: /* @__PURE__ */ jsx(Bars, {}) }),
|
|
138
|
+
/* @__PURE__ */ jsx("nav", { "aria-label": label, children: /* @__PURE__ */ jsx("ul", { className: "bl-omenu__list", children: items.map((item, i) => {
|
|
139
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
140
|
+
numbered && /* @__PURE__ */ jsx("span", { className: "bl-omenu__index", children: String(i + 1).padStart(2, "0") }),
|
|
141
|
+
/* @__PURE__ */ jsx("span", { children: item.label }),
|
|
142
|
+
item.meta && /* @__PURE__ */ jsx("span", { className: "bl-omenu__meta", children: item.meta })
|
|
143
|
+
] });
|
|
144
|
+
return /* @__PURE__ */ jsx("li", { className: "bl-omenu__item", style: { "--i": i }, children: item.href ? /* @__PURE__ */ jsx("a", { className: "bl-omenu__link", href: item.href, onClick: (event) => choose(item, event), children: content }) : /* @__PURE__ */ jsx("button", { type: "button", className: "bl-omenu__link", onClick: (event) => choose(item, event), children: content }) }, item.label);
|
|
145
|
+
}) }) }),
|
|
146
|
+
footer && /* @__PURE__ */ jsx("div", { className: "bl-omenu__foot", children: footer })
|
|
147
|
+
] }),
|
|
148
|
+
document.body
|
|
149
|
+
)
|
|
150
|
+
] });
|
|
151
|
+
}
|
|
152
|
+
export {
|
|
153
|
+
OverlayMenu as default
|
|
154
|
+
};
|