bits-lab 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +51 -2
  3. package/dist/Accordion.d.ts +52 -0
  4. package/dist/Accordion.js +192 -0
  5. package/dist/AchievementToast.d.ts +36 -0
  6. package/dist/AchievementToast.js +162 -0
  7. package/dist/AgentCard.d.ts +41 -0
  8. package/dist/AgentCard.js +168 -0
  9. package/dist/ArcadeButtons.d.ts +23 -0
  10. package/dist/ArcadeButtons.js +169 -0
  11. package/dist/AuroraBands.d.ts +19 -0
  12. package/dist/AuroraBands.js +126 -0
  13. package/dist/AvatarTooltips.d.ts +32 -0
  14. package/dist/AvatarTooltips.js +121 -0
  15. package/dist/BarRace.d.ts +39 -0
  16. package/dist/BarRace.js +227 -0
  17. package/dist/BentoGrid.d.ts +50 -0
  18. package/dist/BentoGrid.js +145 -0
  19. package/dist/BootSequence.d.ts +48 -0
  20. package/dist/BootSequence.js +234 -0
  21. package/dist/BreadcrumbTrail.d.ts +31 -0
  22. package/dist/BreadcrumbTrail.js +181 -0
  23. package/dist/CRTPower.d.ts +30 -0
  24. package/dist/CRTPower.js +142 -0
  25. package/dist/CardDeck.d.ts +29 -0
  26. package/dist/CardDeck.js +154 -0
  27. package/dist/ChangelogTimeline.d.ts +35 -0
  28. package/dist/ChangelogTimeline.js +137 -0
  29. package/dist/ChapterProgress.d.ts +23 -0
  30. package/dist/ChapterProgress.js +211 -0
  31. package/dist/ChatBubbles.d.ts +42 -0
  32. package/dist/ChatBubbles.js +198 -0
  33. package/dist/ChiptuneKit.d.ts +158 -0
  34. package/dist/ChiptuneKit.js +207 -0
  35. package/dist/CipherCard.d.ts +22 -0
  36. package/dist/CipherCard.js +91 -0
  37. package/dist/CloudSky.d.ts +28 -0
  38. package/dist/CloudSky.js +222 -0
  39. package/dist/CodeRain.d.ts +24 -0
  40. package/dist/CodeRain.js +158 -0
  41. package/dist/CodeShowcase.d.ts +50 -0
  42. package/dist/CodeShowcase.js +307 -0
  43. package/dist/ColorDial.d.ts +42 -0
  44. package/dist/ColorDial.js +288 -0
  45. package/dist/CommandPalette.d.ts +59 -0
  46. package/dist/CommandPalette.js +349 -0
  47. package/dist/CompareSlider.d.ts +33 -0
  48. package/dist/CompareSlider.js +152 -0
  49. package/dist/Confetti.d.ts +61 -0
  50. package/dist/Confetti.js +176 -0
  51. package/dist/ConnectBeams.d.ts +39 -0
  52. package/dist/ConnectBeams.js +132 -0
  53. package/dist/ContextOrb.d.ts +34 -0
  54. package/dist/ContextOrb.js +269 -0
  55. package/dist/ContourLines.d.ts +20 -0
  56. package/dist/ContourLines.js +159 -0
  57. package/dist/Coverflow.d.ts +27 -0
  58. package/dist/Coverflow.js +167 -0
  59. package/dist/CursorAttractor.d.ts +21 -0
  60. package/dist/CursorAttractor.js +136 -0
  61. package/dist/CutReveal.d.ts +21 -0
  62. package/dist/CutReveal.js +116 -0
  63. package/dist/DateRange.d.ts +45 -0
  64. package/dist/DateRange.js +327 -0
  65. package/dist/DeviceFrame.d.ts +30 -0
  66. package/dist/DeviceFrame.js +145 -0
  67. package/dist/DiffReveal.d.ts +36 -0
  68. package/dist/DiffReveal.js +247 -0
  69. package/dist/DirectionHover.d.ts +15 -0
  70. package/dist/DirectionHover.js +90 -0
  71. package/dist/Discard.d.ts +18 -0
  72. package/dist/Discard.js +61 -0
  73. package/dist/DitherField.d.ts +15 -0
  74. package/dist/DitherField.js +157 -0
  75. package/dist/DotGlobe.d.ts +48 -0
  76. package/dist/DotGlobe.js +312 -0
  77. package/dist/DotMap.d.ts +47 -0
  78. package/dist/DotMap.js +172 -0
  79. package/dist/DropZone.d.ts +57 -0
  80. package/dist/DropZone.js +304 -0
  81. package/dist/DynamicIsland.d.ts +32 -0
  82. package/dist/DynamicIsland.js +92 -0
  83. package/dist/EdgeFX.d.ts +27 -0
  84. package/dist/EdgeFX.js +98 -0
  85. package/dist/ElasticString.d.ts +22 -0
  86. package/dist/ElasticString.js +127 -0
  87. package/dist/ExpandCard.d.ts +44 -0
  88. package/dist/ExpandCard.js +182 -0
  89. package/dist/FeatureTabs.d.ts +34 -0
  90. package/dist/FeatureTabs.js +169 -0
  91. package/dist/FeedList.d.ts +37 -0
  92. package/dist/FeedList.js +163 -0
  93. package/dist/FlickerGrid.d.ts +29 -0
  94. package/dist/FlickerGrid.js +156 -0
  95. package/dist/FocusGrid.d.ts +20 -0
  96. package/dist/FocusGrid.js +48 -0
  97. package/dist/FocusPull.d.ts +26 -0
  98. package/dist/FocusPull.js +106 -0
  99. package/dist/Footprints.d.ts +24 -0
  100. package/dist/Footprints.js +126 -0
  101. package/dist/FunnelFlow.d.ts +34 -0
  102. package/dist/FunnelFlow.js +267 -0
  103. package/dist/GameOver.d.ts +46 -0
  104. package/dist/GameOver.js +191 -0
  105. package/dist/GaugeMeter.d.ts +38 -0
  106. package/dist/GaugeMeter.js +195 -0
  107. package/dist/GlassPanel.d.ts +27 -0
  108. package/dist/GlassPanel.js +80 -0
  109. package/dist/GrainFX.d.ts +34 -0
  110. package/dist/GrainFX.js +122 -0
  111. package/dist/GravityBox.d.ts +30 -0
  112. package/dist/GravityBox.js +220 -0
  113. package/dist/HealthBar.d.ts +33 -0
  114. package/dist/HealthBar.js +140 -0
  115. package/dist/Heatmap.d.ts +42 -0
  116. package/dist/Heatmap.js +232 -0
  117. package/dist/HexField.d.ts +29 -0
  118. package/dist/HexField.js +188 -0
  119. package/dist/HoldToConfirm.d.ts +29 -0
  120. package/dist/HoldToConfirm.js +163 -0
  121. package/dist/HorizontalTrack.d.ts +28 -0
  122. package/dist/HorizontalTrack.js +195 -0
  123. package/dist/HotbarDock.d.ts +40 -0
  124. package/dist/HotbarDock.js +148 -0
  125. package/dist/InkText.d.ts +27 -0
  126. package/dist/InkText.js +65 -0
  127. package/dist/InlineEdit.d.ts +30 -0
  128. package/dist/InlineEdit.js +198 -0
  129. package/dist/InteractiveGrid.d.ts +25 -0
  130. package/dist/InteractiveGrid.js +194 -0
  131. package/dist/InventoryGrid.d.ts +45 -0
  132. package/dist/InventoryGrid.js +272 -0
  133. package/dist/JellyBlob.d.ts +30 -0
  134. package/dist/JellyBlob.js +209 -0
  135. package/dist/KineticHeadline.d.ts +30 -0
  136. package/dist/KineticHeadline.js +133 -0
  137. package/dist/LavaLamp.d.ts +21 -0
  138. package/dist/LavaLamp.js +133 -0
  139. package/dist/LetterSwap.d.ts +20 -0
  140. package/dist/LetterSwap.js +78 -0
  141. package/dist/LevelMap.d.ts +35 -0
  142. package/dist/LevelMap.js +189 -0
  143. package/dist/LightShafts.d.ts +22 -0
  144. package/dist/LightShafts.js +170 -0
  145. package/dist/LightboxGallery.d.ts +26 -0
  146. package/dist/LightboxGallery.js +327 -0
  147. package/dist/LinkPeek.d.ts +36 -0
  148. package/dist/LinkPeek.js +194 -0
  149. package/dist/LogoOrbit.d.ts +37 -0
  150. package/dist/LogoOrbit.js +117 -0
  151. package/dist/Marquee.d.ts +33 -0
  152. package/dist/Marquee.js +148 -0
  153. package/dist/MarqueeLine.d.ts +33 -0
  154. package/dist/MarqueeLine.js +167 -0
  155. package/dist/MasonryWall.d.ts +13 -0
  156. package/dist/MasonryWall.js +90 -0
  157. package/dist/MegaMenu.d.ts +32 -0
  158. package/dist/MegaMenu.js +280 -0
  159. package/dist/MeteorShower.d.ts +24 -0
  160. package/dist/MeteorShower.js +155 -0
  161. package/dist/Minimap.d.ts +21 -0
  162. package/dist/Minimap.js +236 -0
  163. package/dist/MobileTabBar.d.ts +38 -0
  164. package/dist/MobileTabBar.js +148 -0
  165. package/dist/MorphDialog.d.ts +34 -0
  166. package/dist/MorphDialog.js +199 -0
  167. package/dist/MorphPopover.d.ts +33 -0
  168. package/dist/MorphPopover.js +168 -0
  169. package/dist/MorphWords.d.ts +18 -0
  170. package/dist/MorphWords.js +136 -0
  171. package/dist/OTPInput.d.ts +43 -0
  172. package/dist/OTPInput.js +174 -0
  173. package/dist/OverlayKit.d.ts +97 -0
  174. package/dist/OverlayKit.js +466 -0
  175. package/dist/OverlayMenu.d.ts +32 -0
  176. package/dist/OverlayMenu.js +154 -0
  177. package/dist/ParallaxCollage.d.ts +41 -0
  178. package/dist/ParallaxCollage.js +143 -0
  179. package/dist/PasswordMeter.d.ts +44 -0
  180. package/dist/PasswordMeter.js +165 -0
  181. package/dist/PegBoard.d.ts +26 -0
  182. package/dist/PegBoard.js +184 -0
  183. package/dist/PinCard.d.ts +21 -0
  184. package/dist/PinCard.js +84 -0
  185. package/dist/PixelAvatar.d.ts +33 -0
  186. package/dist/PixelAvatar.js +144 -0
  187. package/dist/PixelButton.d.ts +20 -0
  188. package/dist/PixelButton.js +128 -0
  189. package/dist/PixelExplode.d.ts +31 -0
  190. package/dist/PixelExplode.js +164 -0
  191. package/dist/PixelLoader.d.ts +32 -0
  192. package/dist/PixelLoader.js +142 -0
  193. package/dist/PixelParallax.d.ts +41 -0
  194. package/dist/PixelParallax.js +151 -0
  195. package/dist/PixelSpinners.d.ts +21 -0
  196. package/dist/PixelSpinners.js +110 -0
  197. package/dist/PixelType.d.ts +26 -0
  198. package/dist/PixelType.js +220 -0
  199. package/dist/PointerKit.d.ts +23 -0
  200. package/dist/PointerKit.js +222 -0
  201. package/dist/Polaroids.d.ts +41 -0
  202. package/dist/Polaroids.js +293 -0
  203. package/dist/PressStart.d.ts +28 -0
  204. package/dist/PressStart.js +99 -0
  205. package/dist/PricingFlip.d.ts +52 -0
  206. package/dist/PricingFlip.js +190 -0
  207. package/dist/ProximityType.d.ts +26 -0
  208. package/dist/ProximityType.js +135 -0
  209. package/dist/QuestDialog.d.ts +50 -0
  210. package/dist/QuestDialog.js +204 -0
  211. package/dist/QuestLog.d.ts +39 -0
  212. package/dist/QuestLog.js +214 -0
  213. package/dist/RangeDual.d.ts +34 -0
  214. package/dist/RangeDual.js +238 -0
  215. package/dist/RatingPips.d.ts +25 -0
  216. package/dist/RatingPips.js +129 -0
  217. package/dist/RetroHorizon.d.ts +27 -0
  218. package/dist/RetroHorizon.js +169 -0
  219. package/dist/Reveal.d.ts +30 -0
  220. package/dist/Reveal.js +92 -0
  221. package/dist/RingProgress.d.ts +45 -0
  222. package/dist/RingProgress.js +180 -0
  223. package/dist/RollingNumber.d.ts +28 -0
  224. package/dist/RollingNumber.js +108 -0
  225. package/dist/ScanScope.d.ts +24 -0
  226. package/dist/ScanScope.js +164 -0
  227. package/dist/ScoreTicker.d.ts +32 -0
  228. package/dist/ScoreTicker.js +176 -0
  229. package/dist/ScreenScroller.d.ts +24 -0
  230. package/dist/ScreenScroller.js +138 -0
  231. package/dist/Screensaver.d.ts +31 -0
  232. package/dist/Screensaver.js +221 -0
  233. package/dist/ScrollTilt.d.ts +27 -0
  234. package/dist/ScrollTilt.js +100 -0
  235. package/dist/ScrollTypography.d.ts +26 -0
  236. package/dist/ScrollTypography.js +124 -0
  237. package/dist/ScrollZoom.d.ts +31 -0
  238. package/dist/ScrollZoom.js +123 -0
  239. package/dist/ScrubNumber.d.ts +29 -0
  240. package/dist/ScrubNumber.js +152 -0
  241. package/dist/ShadowStack.d.ts +28 -0
  242. package/dist/ShadowStack.js +116 -0
  243. package/dist/SideRail.d.ts +43 -0
  244. package/dist/SideRail.js +253 -0
  245. package/dist/SignalText.d.ts +25 -0
  246. package/dist/SignalText.js +178 -0
  247. package/dist/SkeletonShimmer.d.ts +18 -0
  248. package/dist/SkeletonShimmer.js +111 -0
  249. package/dist/SketchHighlight.d.ts +27 -0
  250. package/dist/SketchHighlight.js +180 -0
  251. package/dist/SlideTabs.d.ts +40 -0
  252. package/dist/SlideTabs.js +190 -0
  253. package/dist/SmartNavbar.d.ts +48 -0
  254. package/dist/SmartNavbar.js +188 -0
  255. package/dist/SparkChart.d.ts +41 -0
  256. package/dist/SparkChart.js +188 -0
  257. package/dist/Spoiler.d.ts +22 -0
  258. package/dist/Spoiler.js +97 -0
  259. package/dist/SpotlightTour.d.ts +38 -0
  260. package/dist/SpotlightTour.js +231 -0
  261. package/dist/SpringDrawer.d.ts +39 -0
  262. package/dist/SpringDrawer.js +360 -0
  263. package/dist/SpriteAnimator.d.ts +41 -0
  264. package/dist/SpriteAnimator.js +124 -0
  265. package/dist/StatCard.d.ts +43 -0
  266. package/dist/StatCard.js +168 -0
  267. package/dist/StepLoader.d.ts +41 -0
  268. package/dist/StepLoader.js +191 -0
  269. package/dist/StepperForm.d.ts +41 -0
  270. package/dist/StepperForm.js +206 -0
  271. package/dist/StickerBoard.d.ts +39 -0
  272. package/dist/StickerBoard.js +209 -0
  273. package/dist/StickyReveal.d.ts +42 -0
  274. package/dist/StickyReveal.js +181 -0
  275. package/dist/StoryViewer.d.ts +36 -0
  276. package/dist/StoryViewer.js +175 -0
  277. package/dist/StreamingText.d.ts +29 -0
  278. package/dist/StreamingText.js +271 -0
  279. package/dist/SuggestionChips.d.ts +32 -0
  280. package/dist/SuggestionChips.js +96 -0
  281. package/dist/SwipeActions.d.ts +30 -0
  282. package/dist/SwipeActions.js +141 -0
  283. package/dist/TagInput.d.ts +30 -0
  284. package/dist/TagInput.js +284 -0
  285. package/dist/TallyUp.d.ts +38 -0
  286. package/dist/TallyUp.js +157 -0
  287. package/dist/TerminalDemo.d.ts +63 -0
  288. package/dist/TerminalDemo.js +214 -0
  289. package/dist/TestimonialWall.d.ts +35 -0
  290. package/dist/TestimonialWall.js +131 -0
  291. package/dist/TextOnPath.d.ts +36 -0
  292. package/dist/TextOnPath.js +130 -0
  293. package/dist/ThinkingShimmer.d.ts +36 -0
  294. package/dist/ThinkingShimmer.js +132 -0
  295. package/dist/ToastStack.d.ts +62 -0
  296. package/dist/ToastStack.js +289 -0
  297. package/dist/ToggleGroup.d.ts +39 -0
  298. package/dist/ToggleGroup.js +190 -0
  299. package/dist/ToggleKit.d.ts +70 -0
  300. package/dist/ToggleKit.js +229 -0
  301. package/dist/TracingLine.d.ts +24 -0
  302. package/dist/TracingLine.js +139 -0
  303. package/dist/TradingCard.d.ts +30 -0
  304. package/dist/TradingCard.js +147 -0
  305. package/dist/VanishInput.d.ts +34 -0
  306. package/dist/VanishInput.js +190 -0
  307. package/dist/VideoHero.d.ts +32 -0
  308. package/dist/VideoHero.js +207 -0
  309. package/dist/VoiceOrb.d.ts +23 -0
  310. package/dist/VoiceOrb.js +182 -0
  311. package/dist/VoxelTiles.d.ts +38 -0
  312. package/dist/VoxelTiles.js +201 -0
  313. package/dist/WarpField.d.ts +18 -0
  314. package/dist/WarpField.js +160 -0
  315. package/dist/WebcamPixels.d.ts +38 -0
  316. package/dist/WebcamPixels.js +292 -0
  317. package/dist/WheelPicker.d.ts +29 -0
  318. package/dist/WheelPicker.js +199 -0
  319. package/dist/WobbleCard.d.ts +23 -0
  320. package/dist/WobbleCard.js +112 -0
  321. package/dist/index.d.ts +158 -0
  322. package/dist/index.js +159 -0
  323. package/package.json +34 -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
+ };