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,231 @@
1
+ 'use client';
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useId, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ const STYLE_ID = "bl-spotlight-tour";
6
+ const CSS = `
7
+ .bl-tour { position: fixed; inset: 0; z-index: 1950; font-family: var(--bl-font); }
8
+ .bl-tour__shade { position: fixed; inset: 0; }
9
+ .bl-tour__hole { position: fixed; top: 0; left: 0; border-radius: var(--bl-radius); box-shadow: 0 0 0 2px var(--bl-accent), 0 0 0 200vmax var(--bl-dim); pointer-events: none; transition: transform 420ms cubic-bezier(.3,1.2,.5,1), width 420ms cubic-bezier(.3,1.2,.5,1), height 420ms cubic-bezier(.3,1.2,.5,1); animation: bl-tour-in 260ms ease both; }
10
+ .bl-tour__hole[data-none] { width: 0 !important; height: 0 !important; box-shadow: 0 0 0 200vmax var(--bl-dim); }
11
+ .bl-tour__bubble { position: fixed; top: 0; left: 0; width: min(var(--bl-w), calc(100vw - 24px)); box-sizing: border-box; padding: 18px; border-radius: 14px; background: var(--bl-bg); color: var(--bl-ink); box-shadow: 0 0 0 1px var(--bl-line), 0 24px 60px -24px rgba(0,0,0,0.9); outline: none; transition: transform 420ms cubic-bezier(.3,1.2,.5,1); }
12
+ .bl-tour__card { animation: bl-tour-card 360ms cubic-bezier(.3,1.4,.5,1) both; }
13
+ .bl-tour__step { color: var(--bl-accent); font-size: 12px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
14
+ .bl-tour__title { margin: 6px 0 6px; font-size: 17px; font-weight: 700; line-height: 1.3; }
15
+ .bl-tour__body { margin: 0; color: var(--bl-muted); font-size: 14px; line-height: 1.55; }
16
+ .bl-tour__foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }
17
+ .bl-tour__dots { display: flex; gap: 6px; }
18
+ .bl-tour__dots i { width: 7px; height: 7px; border-radius: 50%; background: var(--bl-line); transition: width 240ms, background-color 240ms; }
19
+ .bl-tour__dots i[data-on] { width: 18px; border-radius: 4px; background: var(--bl-accent); }
20
+ .bl-tour__btns { display: flex; gap: 8px; }
21
+ .bl-tour__btn { padding: 8px 14px; border: 0; border-radius: 8px; background: transparent; color: var(--bl-muted); font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; }
22
+ .bl-tour__btn:hover { color: var(--bl-ink); }
23
+ .bl-tour__btn[data-primary] { background: var(--bl-accent); color: var(--bl-on-accent); }
24
+ .bl-tour__btn[data-primary]:hover { filter: brightness(1.12); }
25
+ .bl-tour__btn:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
26
+ .bl-tour__close { position: absolute; top: 10px; right: 10px; display: grid; place-items: center; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%; background: transparent; color: var(--bl-muted); cursor: pointer; }
27
+ .bl-tour__close:hover { color: var(--bl-ink); background: var(--bl-line); }
28
+ .bl-tour__close:focus-visible { outline: 2px solid var(--bl-accent); }
29
+ @keyframes bl-tour-in { from { opacity: 0; } }
30
+ @keyframes bl-tour-card { from { opacity: 0; transform: translateY(8px) scale(0.96); } }
31
+ @media (prefers-reduced-motion: reduce) { .bl-tour__hole, .bl-tour__bubble { transition: none; } .bl-tour__card { animation: none; } }
32
+ `;
33
+ function useStyles() {
34
+ useInsertionEffect(() => {
35
+ if (typeof document === "undefined") return;
36
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
37
+ style.textContent = CSS;
38
+ if (!style.isConnected) document.head.appendChild(style);
39
+ }, []);
40
+ }
41
+ const resolve = (target) => typeof target === "string" ? document.querySelector(target) : target?.current ?? null;
42
+ const FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex="-1"])';
43
+ function SpotlightTour({
44
+ steps = [],
45
+ open = false,
46
+ onClose,
47
+ onStepChange,
48
+ startAt = 0,
49
+ padding = 8,
50
+ radius = 12,
51
+ gap = 14,
52
+ width = 320,
53
+ closeOnBackdrop = false,
54
+ backLabel = "Back",
55
+ nextLabel = "Next",
56
+ doneLabel = "Done",
57
+ skipLabel = "Skip tour",
58
+ dimColor = "rgba(8,8,16,0.68)",
59
+ backgroundColor = "#1a1c2c",
60
+ textColor = "#f4f4f4",
61
+ mutedColor = "#94b0c2",
62
+ lineColor = "#333c57",
63
+ accentColor = "#73eff7",
64
+ onAccentColor = "#0b0b14",
65
+ fontFamily = "ui-sans-serif, system-ui, sans-serif"
66
+ }) {
67
+ useStyles();
68
+ const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
69
+ const [index, setIndex] = useState(startAt);
70
+ const [hole, setHole] = useState(null);
71
+ const [bubblePos, setBubblePos] = useState({ x: 0, y: 0 });
72
+ const bubbleRef = useRef(null);
73
+ const returnFocus = useRef(null);
74
+ const step = steps[index];
75
+ const last = index === steps.length - 1;
76
+ useEffect(() => {
77
+ if (open) {
78
+ returnFocus.current = document.activeElement;
79
+ setIndex(startAt);
80
+ }
81
+ }, [open, startAt]);
82
+ const finish = (completed) => {
83
+ onClose?.(completed);
84
+ const back = returnFocus.current;
85
+ if (back instanceof HTMLElement) setTimeout(() => back.focus({ preventScroll: true }), 0);
86
+ };
87
+ const go = (next) => {
88
+ if (next < 0 || next >= steps.length) return;
89
+ setIndex(next);
90
+ onStepChange?.(next);
91
+ };
92
+ useLayoutEffect(() => {
93
+ if (!open || !step) return void 0;
94
+ const el = resolve(step.target);
95
+ const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
96
+ el?.scrollIntoView?.({ block: "center", inline: "nearest", behavior: reduced ? "auto" : "smooth" });
97
+ let frame = 0;
98
+ const place = () => {
99
+ frame = 0;
100
+ const bubble = bubbleRef.current;
101
+ const bw = bubble?.offsetWidth ?? width;
102
+ const bh = bubble?.offsetHeight ?? 160;
103
+ const vw = window.innerWidth;
104
+ const vh = window.innerHeight;
105
+ if (!el) {
106
+ setHole(null);
107
+ setBubblePos({ x: (vw - bw) / 2, y: (vh - bh) / 2 });
108
+ return;
109
+ }
110
+ const r = el.getBoundingClientRect();
111
+ const box = { x: r.left - padding, y: r.top - padding, w: r.width + padding * 2, h: r.height + padding * 2 };
112
+ setHole(box);
113
+ const room = { bottom: vh - (box.y + box.h), top: box.y, right: vw - (box.x + box.w), left: box.x };
114
+ const fits = (side2) => side2 === "top" || side2 === "bottom" ? room[side2] >= bh + gap + 8 : room[side2] >= bw + gap + 8;
115
+ const side = step.side && fits(step.side) ? step.side : ["bottom", "top", "right", "left"].find(fits) ?? "bottom";
116
+ let x = side === "right" ? box.x + box.w + gap : side === "left" ? box.x - bw - gap : box.x + box.w / 2 - bw / 2;
117
+ let y = side === "bottom" ? box.y + box.h + gap : side === "top" ? box.y - bh - gap : box.y + box.h / 2 - bh / 2;
118
+ x = Math.max(12, Math.min(vw - bw - 12, x));
119
+ y = Math.max(12, Math.min(vh - bh - 12, y));
120
+ setBubblePos({ x, y });
121
+ };
122
+ const schedule = () => {
123
+ if (!frame) frame = requestAnimationFrame(place);
124
+ };
125
+ place();
126
+ window.addEventListener("scroll", schedule, true);
127
+ window.addEventListener("resize", schedule);
128
+ const resize = new ResizeObserver(schedule);
129
+ if (el) resize.observe(el);
130
+ if (bubbleRef.current) resize.observe(bubbleRef.current);
131
+ return () => {
132
+ cancelAnimationFrame(frame);
133
+ window.removeEventListener("scroll", schedule, true);
134
+ window.removeEventListener("resize", schedule);
135
+ resize.disconnect();
136
+ };
137
+ }, [open, step, padding, gap, width]);
138
+ useEffect(() => {
139
+ if (!open) return void 0;
140
+ bubbleRef.current?.focus({ preventScroll: true });
141
+ const onKey = (event) => {
142
+ if (event.key === "Escape") {
143
+ event.preventDefault();
144
+ finish(false);
145
+ } else if (event.key === "ArrowRight") {
146
+ if (last) finish(true);
147
+ else go(index + 1);
148
+ } else if (event.key === "ArrowLeft") {
149
+ go(index - 1);
150
+ } else if (event.key === "Tab") {
151
+ const items = [...bubbleRef.current?.querySelectorAll(FOCUSABLE) ?? []];
152
+ if (!items.length) return;
153
+ const first = items[0];
154
+ const end = items[items.length - 1];
155
+ if (event.shiftKey && (document.activeElement === first || document.activeElement === bubbleRef.current)) {
156
+ event.preventDefault();
157
+ end.focus();
158
+ } else if (!event.shiftKey && document.activeElement === end) {
159
+ event.preventDefault();
160
+ first.focus();
161
+ }
162
+ }
163
+ };
164
+ document.addEventListener("keydown", onKey);
165
+ return () => document.removeEventListener("keydown", onKey);
166
+ }, [open, index]);
167
+ if (!open || !step) return null;
168
+ const vars = {
169
+ "--bl-dim": dimColor,
170
+ "--bl-radius": `${radius}px`,
171
+ "--bl-w": `${width}px`,
172
+ "--bl-bg": backgroundColor,
173
+ "--bl-ink": textColor,
174
+ "--bl-muted": mutedColor,
175
+ "--bl-line": lineColor,
176
+ "--bl-accent": accentColor,
177
+ "--bl-on-accent": onAccentColor,
178
+ "--bl-font": fontFamily
179
+ };
180
+ return createPortal(
181
+ /* @__PURE__ */ jsxs("div", { className: "bl-tour", style: vars, children: [
182
+ /* @__PURE__ */ jsx("div", { className: "bl-tour__shade", onClick: () => closeOnBackdrop && finish(false) }),
183
+ /* @__PURE__ */ jsx(
184
+ "div",
185
+ {
186
+ className: "bl-tour__hole",
187
+ "data-none": !hole || void 0,
188
+ style: hole ? { width: hole.w, height: hole.h, transform: `translate(${hole.x}px, ${hole.y}px)` } : { transform: `translate(50vw, 50vh)` },
189
+ "aria-hidden": "true"
190
+ }
191
+ ),
192
+ /* @__PURE__ */ jsxs(
193
+ "div",
194
+ {
195
+ ref: bubbleRef,
196
+ className: "bl-tour__bubble",
197
+ role: "dialog",
198
+ "aria-modal": "true",
199
+ "aria-labelledby": `${uid}-title`,
200
+ "aria-describedby": `${uid}-body`,
201
+ tabIndex: -1,
202
+ style: { transform: `translate(${bubblePos.x}px, ${bubblePos.y}px)` },
203
+ children: [
204
+ /* @__PURE__ */ jsxs("div", { className: "bl-tour__card", children: [
205
+ /* @__PURE__ */ jsxs("div", { className: "bl-tour__step", children: [
206
+ "Step ",
207
+ index + 1,
208
+ " of ",
209
+ steps.length
210
+ ] }),
211
+ /* @__PURE__ */ jsx("h2", { id: `${uid}-title`, className: "bl-tour__title", children: step.title }),
212
+ /* @__PURE__ */ jsx("div", { id: `${uid}-body`, className: "bl-tour__body", children: step.body }),
213
+ /* @__PURE__ */ jsxs("div", { className: "bl-tour__foot", children: [
214
+ /* @__PURE__ */ jsx("span", { className: "bl-tour__dots", "aria-hidden": "true", children: steps.map((_, i) => /* @__PURE__ */ jsx("i", { "data-on": i === index || void 0 }, i)) }),
215
+ /* @__PURE__ */ jsxs("span", { className: "bl-tour__btns", children: [
216
+ index > 0 && /* @__PURE__ */ jsx("button", { type: "button", className: "bl-tour__btn", onClick: () => go(index - 1), children: backLabel }),
217
+ /* @__PURE__ */ jsx("button", { type: "button", className: "bl-tour__btn", "data-primary": "", onClick: () => last ? finish(true) : go(index + 1), children: last ? doneLabel : nextLabel })
218
+ ] })
219
+ ] })
220
+ ] }, index),
221
+ /* @__PURE__ */ jsx("button", { type: "button", className: "bl-tour__close", "aria-label": skipLabel, onClick: () => finish(false), children: /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2 2l8 8M10 2 2 10" }) }) })
222
+ ]
223
+ }
224
+ )
225
+ ] }),
226
+ document.body
227
+ );
228
+ }
229
+ export {
230
+ SpotlightTour as default
231
+ };
@@ -0,0 +1,39 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface SpringDrawerProps {
3
+ /** Controlled open state. */
4
+ open: boolean;
5
+ /** Drag-to-close, Esc and the backdrop call this with false. */
6
+ onOpenChange?: (open: boolean) => void;
7
+ children?: ReactNode;
8
+ /** Heading; also the dialog's accessible name. */
9
+ title?: ReactNode;
10
+ /** Wired up as the dialog's accessible description. */
11
+ description?: ReactNode;
12
+ side?: 'bottom' | 'right' | 'left';
13
+ /** Bottom only: open heights as fractions of the viewport (or container). */
14
+ snapPoints?: number[];
15
+ /** Index into the sorted snap points to open at. */
16
+ defaultSnap?: number;
17
+ /** Side drawers: width, in px. */
18
+ size?: number;
19
+ /** Bottom sheet max width, in px. */
20
+ maxWidth?: number;
21
+ /** Allow closing by drag, Esc and backdrop. */
22
+ dismissible?: boolean;
23
+ /** Render inside the parent element instead of the window. Nested drawers inherit it. */
24
+ contained?: boolean;
25
+ /** Spring stiffness. */
26
+ stiffness?: number;
27
+ /** Damping ratio: below 1 bounces, 1 is critically damped. */
28
+ damping?: number;
29
+ radius?: number;
30
+ backgroundColor?: string;
31
+ textColor?: string;
32
+ mutedColor?: string;
33
+ lineColor?: string;
34
+ accentColor?: string;
35
+ backdropColor?: string;
36
+ fontFamily?: string;
37
+ className?: string;
38
+ }
39
+ export default function SpringDrawer({ open, onOpenChange, children, title, description, side, snapPoints, defaultSnap, size, maxWidth, dismissible, contained, stiffness, damping, radius, backgroundColor, textColor, mutedColor, lineColor, accentColor, backdropColor, fontFamily, className }: SpringDrawerProps): import("react").JSX.Element;
@@ -0,0 +1,360 @@
1
+ 'use client';
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import { createPortal } from "react-dom";
5
+ const STYLE_ID = "bl-spring-drawer";
6
+ const CSS = `
7
+ .bl-drawer__backdrop { position: fixed; inset: 0; background: var(--bl-backdrop); opacity: 0; }
8
+ .bl-drawer__shell { position: fixed; display: flex; flex-direction: column; background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); box-shadow: 0 0 0 1px var(--bl-line), 0 -20px 60px -20px rgba(0,0,0,0.6); outline: none; touch-action: none; will-change: transform; transition: filter 300ms; }
9
+ .bl-drawer[data-contained] .bl-drawer__backdrop, .bl-drawer[data-contained] .bl-drawer__shell { position: absolute; }
10
+ .bl-drawer[data-side="bottom"] .bl-drawer__shell { left: 0; right: 0; bottom: 0; max-width: var(--bl-max-w); margin: 0 auto; height: var(--bl-size); border-radius: var(--bl-radius) var(--bl-radius) 0 0; }
11
+ .bl-drawer[data-side="right"] .bl-drawer__shell { top: 0; bottom: 0; right: 0; width: var(--bl-size); max-width: 92%; border-radius: var(--bl-radius) 0 0 var(--bl-radius); }
12
+ .bl-drawer[data-side="left"] .bl-drawer__shell { top: 0; bottom: 0; left: 0; width: var(--bl-size); max-width: 92%; border-radius: 0 var(--bl-radius) var(--bl-radius) 0; }
13
+ .bl-drawer__shell[data-nested] { filter: brightness(0.7); }
14
+ .bl-drawer__handle { flex: 0 0 auto; display: grid; place-items: center; height: 28px; margin: 0; padding: 0; border: 0; background: transparent; cursor: grab; }
15
+ .bl-drawer__handle::before { content: ""; width: 44px; height: 5px; border-radius: var(--bl-pill); background: var(--bl-muted); opacity: 0.5; }
16
+ .bl-drawer__handle:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: -4px; }
17
+ .bl-drawer[data-side="right"] .bl-drawer__handle, .bl-drawer[data-side="left"] .bl-drawer__handle { position: absolute; top: 50%; width: 28px; height: 60px; transform: translateY(-50%); }
18
+ .bl-drawer[data-side="right"] .bl-drawer__handle { left: 0; }
19
+ .bl-drawer[data-side="left"] .bl-drawer__handle { right: 0; }
20
+ .bl-drawer[data-side="right"] .bl-drawer__handle::before, .bl-drawer[data-side="left"] .bl-drawer__handle::before { width: 5px; height: 44px; }
21
+ .bl-drawer__header { flex: 0 0 auto; padding: 4px 22px 10px; }
22
+ .bl-drawer[data-side="right"] .bl-drawer__header, .bl-drawer[data-side="left"] .bl-drawer__header { padding-top: 22px; }
23
+ .bl-drawer__title { margin: 0; font-size: 1.15em; font-weight: 700; }
24
+ .bl-drawer__desc { margin: 4px 0 0; color: var(--bl-muted); font-size: 0.92em; }
25
+ .bl-drawer__body { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 8px 22px 22px; }
26
+ `;
27
+ function useStyles() {
28
+ useInsertionEffect(() => {
29
+ if (typeof document === "undefined") return;
30
+ const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
31
+ style.textContent = CSS;
32
+ if (!style.isConnected) document.head.appendChild(style);
33
+ }, []);
34
+ }
35
+ const DrawerContext = createContext(null);
36
+ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
37
+ function SpringDrawer({
38
+ open,
39
+ onOpenChange,
40
+ children,
41
+ title,
42
+ description,
43
+ side = "bottom",
44
+ snapPoints = [0.5, 0.92],
45
+ defaultSnap = 0,
46
+ size = 380,
47
+ maxWidth = 720,
48
+ dismissible = true,
49
+ contained = false,
50
+ stiffness = 520,
51
+ damping = 0.82,
52
+ radius = 20,
53
+ backgroundColor = "#1a1c2c",
54
+ textColor = "#f4f4f4",
55
+ mutedColor = "#94b0c2",
56
+ lineColor = "rgba(255,255,255,0.1)",
57
+ accentColor = "#73eff7",
58
+ backdropColor = "rgba(8,8,16,0.6)",
59
+ fontFamily = "ui-sans-serif, system-ui, sans-serif",
60
+ className = ""
61
+ }) {
62
+ useStyles();
63
+ const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
64
+ const parent = useContext(DrawerContext);
65
+ const anchorRef = useRef(null);
66
+ const shellRef = useRef(null);
67
+ const backdropRef = useRef(null);
68
+ const bodyRef = useRef(null);
69
+ const [mounted, setMounted] = useState(false);
70
+ const [host, setHost] = useState(null);
71
+ const [nested, setNested] = useState(false);
72
+ const physics = useRef({ x: 0, v: 0, target: 0, frame: 0, onRest: null });
73
+ const geometry = useRef({ snaps: [0], closed: 1, full: 1 });
74
+ const [snapIndex, setSnapIndex] = useState(defaultSnap);
75
+ const opener = useRef(null);
76
+ const isContained = parent ? parent.contained : contained;
77
+ const bottom = side === "bottom";
78
+ const sign = side === "left" ? -1 : 1;
79
+ const request = (next) => onOpenChange?.(next);
80
+ const childContext = useMemo(() => ({ host, contained: isContained, setNested }), [host, isContained]);
81
+ useLayoutEffect(() => {
82
+ setHost(parent?.host ?? (contained ? anchorRef.current?.parentElement : document.body));
83
+ }, [parent, contained]);
84
+ const measure = () => {
85
+ const box = isContained ? host : null;
86
+ const viewH = box ? box.clientHeight : window.innerHeight;
87
+ const viewW = box ? box.clientWidth : window.innerWidth;
88
+ if (bottom) {
89
+ const points = [...snapPoints].sort((a, b) => a - b);
90
+ const full = Math.max(...points) * viewH;
91
+ geometry.current = { full, closed: full, snaps: points.map((p) => full - p * viewH) };
92
+ } else {
93
+ const full = Math.min(size, viewW * 0.92);
94
+ geometry.current = { full, closed: full, snaps: [0] };
95
+ }
96
+ shellRef.current?.style.setProperty("--bl-size", `${geometry.current.full}px`);
97
+ };
98
+ const apply = () => {
99
+ const { x } = physics.current;
100
+ const { closed, snaps } = geometry.current;
101
+ const shell = shellRef.current;
102
+ if (!shell) return;
103
+ const pushed = nestedRef.current ? bottom ? " scale(0.94) translateY(-14px)" : " scale(0.96)" : "";
104
+ shell.style.transform = bottom ? `translate3d(0, ${x}px, 0)${pushed}` : `translate3d(${sign * x}px, 0, 0)${pushed}`;
105
+ const openAmount = 1 - Math.min(1, Math.max(0, (x - Math.min(...snaps)) / Math.max(1, closed - Math.min(...snaps))));
106
+ if (backdropRef.current) backdropRef.current.style.opacity = String(openAmount);
107
+ };
108
+ const nestedRef = useRef(false);
109
+ nestedRef.current = nested;
110
+ const springTo = (target, velocity = physics.current.v, onRest = null) => {
111
+ const p = physics.current;
112
+ p.target = target;
113
+ p.v = velocity;
114
+ p.onRest = onRest;
115
+ cancelAnimationFrame(p.frame);
116
+ if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
117
+ p.x = target;
118
+ p.v = 0;
119
+ apply();
120
+ onRest?.();
121
+ return;
122
+ }
123
+ const critical = 2 * Math.sqrt(stiffness);
124
+ let last = performance.now();
125
+ const step = (now) => {
126
+ const dt = Math.min(0.25, (now - last) / 1e3);
127
+ last = now;
128
+ const steps = Math.max(1, Math.ceil(dt / 4e-3));
129
+ for (let i = 0; i < steps; i++) {
130
+ const h = dt / steps;
131
+ const force = -stiffness * (p.x - p.target) - critical * damping * p.v;
132
+ p.v += force * h;
133
+ p.x += p.v * h;
134
+ }
135
+ apply();
136
+ if (Math.abs(p.x - p.target) < 0.4 && Math.abs(p.v) < 8) {
137
+ p.x = p.target;
138
+ p.v = 0;
139
+ apply();
140
+ p.frame = 0;
141
+ const done = p.onRest;
142
+ p.onRest = null;
143
+ done?.();
144
+ return;
145
+ }
146
+ p.frame = requestAnimationFrame(step);
147
+ };
148
+ p.frame = requestAnimationFrame(step);
149
+ };
150
+ useEffect(() => {
151
+ if (open) {
152
+ opener.current = document.activeElement;
153
+ setSnapIndex(Math.min(defaultSnap, (bottom ? snapPoints.length : 1) - 1));
154
+ setMounted(true);
155
+ } else if (mounted) {
156
+ springTo(geometry.current.closed, physics.current.v, () => {
157
+ setMounted(false);
158
+ opener.current?.focus?.({ preventScroll: true });
159
+ });
160
+ }
161
+ parent?.setNested(open);
162
+ }, [open]);
163
+ useEffect(() => () => parent?.setNested(false), [parent]);
164
+ useLayoutEffect(() => {
165
+ if (!mounted || !host) return;
166
+ measure();
167
+ physics.current.x = geometry.current.closed;
168
+ apply();
169
+ springTo(geometry.current.snaps[Math.min(snapIndex, geometry.current.snaps.length - 1)], 0);
170
+ shellRef.current.focus({ preventScroll: true });
171
+ let unlock = () => {
172
+ };
173
+ if (!isContained && !parent) {
174
+ const previous = document.body.style.overflow;
175
+ document.body.style.overflow = "hidden";
176
+ unlock = () => document.body.style.overflow = previous;
177
+ }
178
+ const onResize = () => {
179
+ measure();
180
+ springTo(geometry.current.snaps[Math.min(snapIndexRef.current, geometry.current.snaps.length - 1)]);
181
+ };
182
+ window.addEventListener("resize", onResize);
183
+ return () => {
184
+ unlock();
185
+ window.removeEventListener("resize", onResize);
186
+ cancelAnimationFrame(physics.current.frame);
187
+ };
188
+ }, [mounted, host]);
189
+ const snapIndexRef = useRef(snapIndex);
190
+ snapIndexRef.current = snapIndex;
191
+ useEffect(() => {
192
+ if (mounted) apply();
193
+ }, [nested]);
194
+ const goToSnap = (index) => {
195
+ const { snaps } = geometry.current;
196
+ if (index < 0) {
197
+ if (dismissible) request(false);
198
+ return;
199
+ }
200
+ const clamped = Math.min(snaps.length - 1, index);
201
+ setSnapIndex(clamped);
202
+ springTo(snaps[clamped]);
203
+ };
204
+ const drag = useRef(null);
205
+ const onPointerDown = (event) => {
206
+ if (event.button > 0 || event.target.closest("input, textarea, select, a, [data-no-drag]")) return;
207
+ const inBody = bodyRef.current?.contains(event.target);
208
+ if (inBody && bodyRef.current.scrollTop > 0) return;
209
+ if (event.target.closest("button") && !event.target.closest(".bl-drawer__handle")) return;
210
+ cancelAnimationFrame(physics.current.frame);
211
+ drag.current = { id: event.pointerId, start: bottom ? event.clientY : event.clientX, origin: physics.current.x, samples: [], moved: false, inBody };
212
+ };
213
+ const onPointerMove = (event) => {
214
+ const d = drag.current;
215
+ if (!d || event.pointerId !== d.id) return;
216
+ const pos = bottom ? event.clientY : event.clientX;
217
+ let delta = (pos - d.start) * (bottom ? 1 : sign);
218
+ if (!d.moved) {
219
+ if (Math.abs(delta) < 4) return;
220
+ if (d.inBody && delta < 0 && bodyRef.current.scrollHeight > bodyRef.current.clientHeight) {
221
+ drag.current = null;
222
+ return;
223
+ }
224
+ d.moved = true;
225
+ try {
226
+ shellRef.current.setPointerCapture(event.pointerId);
227
+ } catch {
228
+ }
229
+ }
230
+ let x = d.origin + delta;
231
+ const min = Math.min(...geometry.current.snaps);
232
+ if (x < min) x = min - Math.pow(min - x, 0.7);
233
+ if (!dismissible && x > geometry.current.snaps[0]) x = geometry.current.snaps[0] + Math.pow(x - geometry.current.snaps[0], 0.7);
234
+ physics.current.x = x;
235
+ d.samples.push([performance.now(), x]);
236
+ if (d.samples.length > 5) d.samples.shift();
237
+ apply();
238
+ };
239
+ const onPointerUp = (event) => {
240
+ const d = drag.current;
241
+ drag.current = null;
242
+ if (!d || !d.moved) return;
243
+ if (shellRef.current.hasPointerCapture?.(event.pointerId)) shellRef.current.releasePointerCapture(event.pointerId);
244
+ const [first, last] = [d.samples[0], d.samples[d.samples.length - 1]];
245
+ const velocity = first && last && last[0] > first[0] ? (last[1] - first[1]) / (last[0] - first[0]) * 1e3 : 0;
246
+ const projected = physics.current.x + velocity * 0.2;
247
+ const { snaps, closed } = geometry.current;
248
+ const candidates = snaps.map((x, i) => ({ x, i }));
249
+ if (dismissible) candidates.push({ x: closed, i: -1 });
250
+ const best = candidates.reduce((a, b) => Math.abs(b.x - projected) < Math.abs(a.x - projected) ? b : a);
251
+ physics.current.v = velocity;
252
+ if (best.i === -1) request(false);
253
+ else {
254
+ setSnapIndex(best.i);
255
+ springTo(best.x, velocity);
256
+ }
257
+ };
258
+ const onKeyDown = (event) => {
259
+ if (event.key === "Escape" && dismissible) {
260
+ event.stopPropagation();
261
+ request(false);
262
+ return;
263
+ }
264
+ if (event.key !== "Tab") return;
265
+ const items = [...shellRef.current.querySelectorAll(FOCUSABLE)].filter((el) => !el.closest("[inert]"));
266
+ if (!items.length) return event.preventDefault();
267
+ const first = items[0];
268
+ const last = items[items.length - 1];
269
+ if (event.shiftKey && (document.activeElement === first || document.activeElement === shellRef.current)) {
270
+ event.preventDefault();
271
+ last.focus();
272
+ } else if (!event.shiftKey && document.activeElement === last) {
273
+ event.preventDefault();
274
+ first.focus();
275
+ }
276
+ };
277
+ const onHandleKey = (event) => {
278
+ if (!bottom) return;
279
+ if (event.key === "ArrowUp") {
280
+ event.preventDefault();
281
+ goToSnap(snapIndex + 1);
282
+ } else if (event.key === "ArrowDown") {
283
+ event.preventDefault();
284
+ goToSnap(snapIndex - 1);
285
+ }
286
+ };
287
+ const snapCount = bottom ? snapPoints.length : 1;
288
+ const content = mounted && host && /* @__PURE__ */ jsxs(
289
+ "div",
290
+ {
291
+ className: `bl-drawer ${className}`,
292
+ "data-side": side,
293
+ "data-contained": isContained || void 0,
294
+ style: {
295
+ position: isContained ? "absolute" : "fixed",
296
+ inset: 0,
297
+ zIndex: 1200,
298
+ pointerEvents: "none",
299
+ "--bl-radius": `${radius}px`,
300
+ "--bl-pill": radius ? "999px" : "0",
301
+ "--bl-bg": backgroundColor,
302
+ "--bl-ink": textColor,
303
+ "--bl-muted": mutedColor,
304
+ "--bl-line": lineColor,
305
+ "--bl-accent": accentColor,
306
+ "--bl-backdrop": backdropColor,
307
+ "--bl-font": fontFamily,
308
+ "--bl-max-w": `${maxWidth}px`,
309
+ overflow: "hidden"
310
+ },
311
+ children: [
312
+ /* @__PURE__ */ jsx("div", { ref: backdropRef, className: "bl-drawer__backdrop", style: { pointerEvents: "auto" }, onClick: () => dismissible && request(false), "aria-hidden": "true" }),
313
+ /* @__PURE__ */ jsxs(
314
+ "div",
315
+ {
316
+ ref: shellRef,
317
+ className: "bl-drawer__shell",
318
+ role: "dialog",
319
+ "aria-modal": "true",
320
+ "aria-labelledby": title ? `${uid}-title` : void 0,
321
+ "aria-describedby": description ? `${uid}-desc` : void 0,
322
+ tabIndex: -1,
323
+ "data-nested": nested || void 0,
324
+ inert: nested ? true : void 0,
325
+ style: { pointerEvents: "auto" },
326
+ onPointerDown,
327
+ onPointerMove,
328
+ onPointerUp,
329
+ onPointerCancel: onPointerUp,
330
+ onKeyDown,
331
+ children: [
332
+ /* @__PURE__ */ jsx(
333
+ "button",
334
+ {
335
+ type: "button",
336
+ className: "bl-drawer__handle",
337
+ "aria-label": bottom && snapCount > 1 ? `Resize drawer (position ${snapIndex + 1} of ${snapCount}). Use up and down arrows.` : "Drag to close",
338
+ onKeyDown: onHandleKey,
339
+ onClick: () => bottom && snapCount > 1 && goToSnap((snapIndex + 1) % snapCount)
340
+ }
341
+ ),
342
+ (title || description) && /* @__PURE__ */ jsxs("div", { className: "bl-drawer__header", children: [
343
+ title && /* @__PURE__ */ jsx("h2", { id: `${uid}-title`, className: "bl-drawer__title", children: title }),
344
+ description && /* @__PURE__ */ jsx("p", { id: `${uid}-desc`, className: "bl-drawer__desc", children: description })
345
+ ] }),
346
+ /* @__PURE__ */ jsx("div", { ref: bodyRef, className: "bl-drawer__body", children: /* @__PURE__ */ jsx(DrawerContext.Provider, { value: childContext, children }) })
347
+ ]
348
+ }
349
+ )
350
+ ]
351
+ }
352
+ );
353
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
354
+ /* @__PURE__ */ jsx("span", { ref: anchorRef, hidden: true }),
355
+ content && createPortal(content, host)
356
+ ] });
357
+ }
358
+ export {
359
+ SpringDrawer as default
360
+ };
@@ -0,0 +1,41 @@
1
+ import { type CSSProperties } from 'react';
2
+ export interface SpriteAnimation {
3
+ /** Row of the sheet this animation lives on (0 is the top). */
4
+ row?: number;
5
+ /** Number of frames in the row. */
6
+ frames?: number;
7
+ /** Frames per second. */
8
+ fps?: number;
9
+ loop?: boolean;
10
+ /** Play forward then backward instead of jumping back to frame 0. */
11
+ pingPong?: boolean;
12
+ }
13
+ export interface SpriteAnimatorProps {
14
+ /** Sprite sheet image URL. */
15
+ src?: string;
16
+ /** Size of one frame on the sheet, in px. */
17
+ frameWidth?: number;
18
+ frameHeight?: number;
19
+ /** Named animations; pick one with `animation`. */
20
+ animations?: Record<string, SpriteAnimation>;
21
+ /** Key of the animation to play. Without it, `row`, `frames` and `fps` are used. */
22
+ animation?: string;
23
+ row?: number;
24
+ frames?: number;
25
+ fps?: number;
26
+ /** Integer upscaling factor (pixels stay crisp). */
27
+ scale?: number;
28
+ loop?: boolean;
29
+ pingPong?: boolean;
30
+ /** Pause on the current frame when false. */
31
+ playing?: boolean;
32
+ /** Mirror horizontally. */
33
+ flipX?: boolean;
34
+ /** Accessible description of the sprite. */
35
+ label?: string;
36
+ /** Called when a non-looping animation reaches its last frame. */
37
+ onEnd?: () => void;
38
+ className?: string;
39
+ style?: CSSProperties;
40
+ }
41
+ export default function SpriteAnimator({ src, frameWidth, frameHeight, animations, animation, row, frames, fps, scale, loop, pingPong, playing, flipX, label, onEnd, className, style }: SpriteAnimatorProps): import("react").JSX.Element;