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.
- package/LICENSE +21 -0
- package/README.md +51 -2
- package/dist/Accordion.d.ts +52 -0
- package/dist/Accordion.js +192 -0
- package/dist/AchievementToast.d.ts +36 -0
- package/dist/AchievementToast.js +162 -0
- package/dist/AgentCard.d.ts +41 -0
- package/dist/AgentCard.js +168 -0
- package/dist/ArcadeButtons.d.ts +23 -0
- package/dist/ArcadeButtons.js +169 -0
- package/dist/AuroraBands.d.ts +19 -0
- package/dist/AuroraBands.js +126 -0
- package/dist/AvatarTooltips.d.ts +32 -0
- package/dist/AvatarTooltips.js +121 -0
- package/dist/BarRace.d.ts +39 -0
- package/dist/BarRace.js +227 -0
- package/dist/BentoGrid.d.ts +50 -0
- package/dist/BentoGrid.js +145 -0
- package/dist/BootSequence.d.ts +48 -0
- package/dist/BootSequence.js +234 -0
- package/dist/BreadcrumbTrail.d.ts +31 -0
- package/dist/BreadcrumbTrail.js +181 -0
- package/dist/CRTPower.d.ts +30 -0
- package/dist/CRTPower.js +142 -0
- package/dist/CardDeck.d.ts +29 -0
- package/dist/CardDeck.js +154 -0
- package/dist/ChangelogTimeline.d.ts +35 -0
- package/dist/ChangelogTimeline.js +137 -0
- package/dist/ChapterProgress.d.ts +23 -0
- package/dist/ChapterProgress.js +211 -0
- package/dist/ChatBubbles.d.ts +42 -0
- package/dist/ChatBubbles.js +198 -0
- package/dist/ChiptuneKit.d.ts +158 -0
- package/dist/ChiptuneKit.js +207 -0
- package/dist/CipherCard.d.ts +22 -0
- package/dist/CipherCard.js +91 -0
- package/dist/CloudSky.d.ts +28 -0
- package/dist/CloudSky.js +222 -0
- package/dist/CodeRain.d.ts +24 -0
- package/dist/CodeRain.js +158 -0
- package/dist/CodeShowcase.d.ts +50 -0
- package/dist/CodeShowcase.js +307 -0
- package/dist/ColorDial.d.ts +42 -0
- package/dist/ColorDial.js +288 -0
- package/dist/CommandPalette.d.ts +59 -0
- package/dist/CommandPalette.js +349 -0
- package/dist/CompareSlider.d.ts +33 -0
- package/dist/CompareSlider.js +152 -0
- package/dist/Confetti.d.ts +61 -0
- package/dist/Confetti.js +176 -0
- package/dist/ConnectBeams.d.ts +39 -0
- package/dist/ConnectBeams.js +132 -0
- package/dist/ContextOrb.d.ts +34 -0
- package/dist/ContextOrb.js +269 -0
- package/dist/ContourLines.d.ts +20 -0
- package/dist/ContourLines.js +159 -0
- package/dist/Coverflow.d.ts +27 -0
- package/dist/Coverflow.js +167 -0
- package/dist/CursorAttractor.d.ts +21 -0
- package/dist/CursorAttractor.js +136 -0
- package/dist/CutReveal.d.ts +21 -0
- package/dist/CutReveal.js +116 -0
- package/dist/DateRange.d.ts +45 -0
- package/dist/DateRange.js +327 -0
- package/dist/DeviceFrame.d.ts +30 -0
- package/dist/DeviceFrame.js +145 -0
- package/dist/DiffReveal.d.ts +36 -0
- package/dist/DiffReveal.js +247 -0
- package/dist/DirectionHover.d.ts +15 -0
- package/dist/DirectionHover.js +90 -0
- package/dist/Discard.d.ts +18 -0
- package/dist/Discard.js +61 -0
- package/dist/DitherField.d.ts +15 -0
- package/dist/DitherField.js +157 -0
- package/dist/DotGlobe.d.ts +48 -0
- package/dist/DotGlobe.js +312 -0
- package/dist/DotMap.d.ts +47 -0
- package/dist/DotMap.js +172 -0
- package/dist/DropZone.d.ts +57 -0
- package/dist/DropZone.js +304 -0
- package/dist/DynamicIsland.d.ts +32 -0
- package/dist/DynamicIsland.js +92 -0
- package/dist/EdgeFX.d.ts +27 -0
- package/dist/EdgeFX.js +98 -0
- package/dist/ElasticString.d.ts +22 -0
- package/dist/ElasticString.js +127 -0
- package/dist/ExpandCard.d.ts +44 -0
- package/dist/ExpandCard.js +182 -0
- package/dist/FeatureTabs.d.ts +34 -0
- package/dist/FeatureTabs.js +169 -0
- package/dist/FeedList.d.ts +37 -0
- package/dist/FeedList.js +163 -0
- package/dist/FlickerGrid.d.ts +29 -0
- package/dist/FlickerGrid.js +156 -0
- package/dist/FocusGrid.d.ts +20 -0
- package/dist/FocusGrid.js +48 -0
- package/dist/FocusPull.d.ts +26 -0
- package/dist/FocusPull.js +106 -0
- package/dist/Footprints.d.ts +24 -0
- package/dist/Footprints.js +126 -0
- package/dist/FunnelFlow.d.ts +34 -0
- package/dist/FunnelFlow.js +267 -0
- package/dist/GameOver.d.ts +46 -0
- package/dist/GameOver.js +191 -0
- package/dist/GaugeMeter.d.ts +38 -0
- package/dist/GaugeMeter.js +195 -0
- package/dist/GlassPanel.d.ts +27 -0
- package/dist/GlassPanel.js +80 -0
- package/dist/GrainFX.d.ts +34 -0
- package/dist/GrainFX.js +122 -0
- package/dist/GravityBox.d.ts +30 -0
- package/dist/GravityBox.js +220 -0
- package/dist/HealthBar.d.ts +33 -0
- package/dist/HealthBar.js +140 -0
- package/dist/Heatmap.d.ts +42 -0
- package/dist/Heatmap.js +232 -0
- package/dist/HexField.d.ts +29 -0
- package/dist/HexField.js +188 -0
- package/dist/HoldToConfirm.d.ts +29 -0
- package/dist/HoldToConfirm.js +163 -0
- package/dist/HorizontalTrack.d.ts +28 -0
- package/dist/HorizontalTrack.js +195 -0
- package/dist/HotbarDock.d.ts +40 -0
- package/dist/HotbarDock.js +148 -0
- package/dist/InkText.d.ts +27 -0
- package/dist/InkText.js +65 -0
- package/dist/InlineEdit.d.ts +30 -0
- package/dist/InlineEdit.js +198 -0
- package/dist/InteractiveGrid.d.ts +25 -0
- package/dist/InteractiveGrid.js +194 -0
- package/dist/InventoryGrid.d.ts +45 -0
- package/dist/InventoryGrid.js +272 -0
- package/dist/JellyBlob.d.ts +30 -0
- package/dist/JellyBlob.js +209 -0
- package/dist/KineticHeadline.d.ts +30 -0
- package/dist/KineticHeadline.js +133 -0
- package/dist/LavaLamp.d.ts +21 -0
- package/dist/LavaLamp.js +133 -0
- package/dist/LetterSwap.d.ts +20 -0
- package/dist/LetterSwap.js +78 -0
- package/dist/LevelMap.d.ts +35 -0
- package/dist/LevelMap.js +189 -0
- package/dist/LightShafts.d.ts +22 -0
- package/dist/LightShafts.js +170 -0
- package/dist/LightboxGallery.d.ts +26 -0
- package/dist/LightboxGallery.js +327 -0
- package/dist/LinkPeek.d.ts +36 -0
- package/dist/LinkPeek.js +194 -0
- package/dist/LogoOrbit.d.ts +37 -0
- package/dist/LogoOrbit.js +117 -0
- package/dist/Marquee.d.ts +33 -0
- package/dist/Marquee.js +148 -0
- package/dist/MarqueeLine.d.ts +33 -0
- package/dist/MarqueeLine.js +167 -0
- package/dist/MasonryWall.d.ts +13 -0
- package/dist/MasonryWall.js +90 -0
- package/dist/MegaMenu.d.ts +32 -0
- package/dist/MegaMenu.js +280 -0
- package/dist/MeteorShower.d.ts +24 -0
- package/dist/MeteorShower.js +155 -0
- package/dist/Minimap.d.ts +21 -0
- package/dist/Minimap.js +236 -0
- package/dist/MobileTabBar.d.ts +38 -0
- package/dist/MobileTabBar.js +148 -0
- package/dist/MorphDialog.d.ts +34 -0
- package/dist/MorphDialog.js +199 -0
- package/dist/MorphPopover.d.ts +33 -0
- package/dist/MorphPopover.js +168 -0
- package/dist/MorphWords.d.ts +18 -0
- package/dist/MorphWords.js +136 -0
- package/dist/OTPInput.d.ts +43 -0
- package/dist/OTPInput.js +174 -0
- package/dist/OverlayKit.d.ts +97 -0
- package/dist/OverlayKit.js +466 -0
- package/dist/OverlayMenu.d.ts +32 -0
- package/dist/OverlayMenu.js +154 -0
- package/dist/ParallaxCollage.d.ts +41 -0
- package/dist/ParallaxCollage.js +143 -0
- package/dist/PasswordMeter.d.ts +44 -0
- package/dist/PasswordMeter.js +165 -0
- package/dist/PegBoard.d.ts +26 -0
- package/dist/PegBoard.js +184 -0
- package/dist/PinCard.d.ts +21 -0
- package/dist/PinCard.js +84 -0
- package/dist/PixelAvatar.d.ts +33 -0
- package/dist/PixelAvatar.js +144 -0
- package/dist/PixelButton.d.ts +20 -0
- package/dist/PixelButton.js +128 -0
- package/dist/PixelExplode.d.ts +31 -0
- package/dist/PixelExplode.js +164 -0
- package/dist/PixelLoader.d.ts +32 -0
- package/dist/PixelLoader.js +142 -0
- package/dist/PixelParallax.d.ts +41 -0
- package/dist/PixelParallax.js +151 -0
- package/dist/PixelSpinners.d.ts +21 -0
- package/dist/PixelSpinners.js +110 -0
- package/dist/PixelType.d.ts +26 -0
- package/dist/PixelType.js +220 -0
- package/dist/PointerKit.d.ts +23 -0
- package/dist/PointerKit.js +222 -0
- package/dist/Polaroids.d.ts +41 -0
- package/dist/Polaroids.js +293 -0
- package/dist/PressStart.d.ts +28 -0
- package/dist/PressStart.js +99 -0
- package/dist/PricingFlip.d.ts +52 -0
- package/dist/PricingFlip.js +190 -0
- package/dist/ProximityType.d.ts +26 -0
- package/dist/ProximityType.js +135 -0
- package/dist/QuestDialog.d.ts +50 -0
- package/dist/QuestDialog.js +204 -0
- package/dist/QuestLog.d.ts +39 -0
- package/dist/QuestLog.js +214 -0
- package/dist/RangeDual.d.ts +34 -0
- package/dist/RangeDual.js +238 -0
- package/dist/RatingPips.d.ts +25 -0
- package/dist/RatingPips.js +129 -0
- package/dist/RetroHorizon.d.ts +27 -0
- package/dist/RetroHorizon.js +169 -0
- package/dist/Reveal.d.ts +30 -0
- package/dist/Reveal.js +92 -0
- package/dist/RingProgress.d.ts +45 -0
- package/dist/RingProgress.js +180 -0
- package/dist/RollingNumber.d.ts +28 -0
- package/dist/RollingNumber.js +108 -0
- package/dist/ScanScope.d.ts +24 -0
- package/dist/ScanScope.js +164 -0
- package/dist/ScoreTicker.d.ts +32 -0
- package/dist/ScoreTicker.js +176 -0
- package/dist/ScreenScroller.d.ts +24 -0
- package/dist/ScreenScroller.js +138 -0
- package/dist/Screensaver.d.ts +31 -0
- package/dist/Screensaver.js +221 -0
- package/dist/ScrollTilt.d.ts +27 -0
- package/dist/ScrollTilt.js +100 -0
- package/dist/ScrollTypography.d.ts +26 -0
- package/dist/ScrollTypography.js +124 -0
- package/dist/ScrollZoom.d.ts +31 -0
- package/dist/ScrollZoom.js +123 -0
- package/dist/ScrubNumber.d.ts +29 -0
- package/dist/ScrubNumber.js +152 -0
- package/dist/ShadowStack.d.ts +28 -0
- package/dist/ShadowStack.js +116 -0
- package/dist/SideRail.d.ts +43 -0
- package/dist/SideRail.js +253 -0
- package/dist/SignalText.d.ts +25 -0
- package/dist/SignalText.js +178 -0
- package/dist/SkeletonShimmer.d.ts +18 -0
- package/dist/SkeletonShimmer.js +111 -0
- package/dist/SketchHighlight.d.ts +27 -0
- package/dist/SketchHighlight.js +180 -0
- package/dist/SlideTabs.d.ts +40 -0
- package/dist/SlideTabs.js +190 -0
- package/dist/SmartNavbar.d.ts +48 -0
- package/dist/SmartNavbar.js +188 -0
- package/dist/SparkChart.d.ts +41 -0
- package/dist/SparkChart.js +188 -0
- package/dist/Spoiler.d.ts +22 -0
- package/dist/Spoiler.js +97 -0
- package/dist/SpotlightTour.d.ts +38 -0
- package/dist/SpotlightTour.js +231 -0
- package/dist/SpringDrawer.d.ts +39 -0
- package/dist/SpringDrawer.js +360 -0
- package/dist/SpriteAnimator.d.ts +41 -0
- package/dist/SpriteAnimator.js +124 -0
- package/dist/StatCard.d.ts +43 -0
- package/dist/StatCard.js +168 -0
- package/dist/StepLoader.d.ts +41 -0
- package/dist/StepLoader.js +191 -0
- package/dist/StepperForm.d.ts +41 -0
- package/dist/StepperForm.js +206 -0
- package/dist/StickerBoard.d.ts +39 -0
- package/dist/StickerBoard.js +209 -0
- package/dist/StickyReveal.d.ts +42 -0
- package/dist/StickyReveal.js +181 -0
- package/dist/StoryViewer.d.ts +36 -0
- package/dist/StoryViewer.js +175 -0
- package/dist/StreamingText.d.ts +29 -0
- package/dist/StreamingText.js +271 -0
- package/dist/SuggestionChips.d.ts +32 -0
- package/dist/SuggestionChips.js +96 -0
- package/dist/SwipeActions.d.ts +30 -0
- package/dist/SwipeActions.js +141 -0
- package/dist/TagInput.d.ts +30 -0
- package/dist/TagInput.js +284 -0
- package/dist/TallyUp.d.ts +38 -0
- package/dist/TallyUp.js +157 -0
- package/dist/TerminalDemo.d.ts +63 -0
- package/dist/TerminalDemo.js +214 -0
- package/dist/TestimonialWall.d.ts +35 -0
- package/dist/TestimonialWall.js +131 -0
- package/dist/TextOnPath.d.ts +36 -0
- package/dist/TextOnPath.js +130 -0
- package/dist/ThinkingShimmer.d.ts +36 -0
- package/dist/ThinkingShimmer.js +132 -0
- package/dist/ToastStack.d.ts +62 -0
- package/dist/ToastStack.js +289 -0
- package/dist/ToggleGroup.d.ts +39 -0
- package/dist/ToggleGroup.js +190 -0
- package/dist/ToggleKit.d.ts +70 -0
- package/dist/ToggleKit.js +229 -0
- package/dist/TracingLine.d.ts +24 -0
- package/dist/TracingLine.js +139 -0
- package/dist/TradingCard.d.ts +30 -0
- package/dist/TradingCard.js +147 -0
- package/dist/VanishInput.d.ts +34 -0
- package/dist/VanishInput.js +190 -0
- package/dist/VideoHero.d.ts +32 -0
- package/dist/VideoHero.js +207 -0
- package/dist/VoiceOrb.d.ts +23 -0
- package/dist/VoiceOrb.js +182 -0
- package/dist/VoxelTiles.d.ts +38 -0
- package/dist/VoxelTiles.js +201 -0
- package/dist/WarpField.d.ts +18 -0
- package/dist/WarpField.js +160 -0
- package/dist/WebcamPixels.d.ts +38 -0
- package/dist/WebcamPixels.js +292 -0
- package/dist/WheelPicker.d.ts +29 -0
- package/dist/WheelPicker.js +199 -0
- package/dist/WobbleCard.d.ts +23 -0
- package/dist/WobbleCard.js +112 -0
- package/dist/index.d.ts +158 -0
- package/dist/index.js +159 -0
- package/package.json +34 -4
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef } from "react";
|
|
4
|
+
const STYLE_ID = "bl-scroll-tilt";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-tilt { --bl-p: 0; position: relative; perspective: var(--bl-perspective); }
|
|
7
|
+
.bl-tilt__card { position: relative; overflow: hidden; border-radius: var(--bl-radius); box-shadow: 0 0 0 var(--bl-border) var(--bl-edge), 0 calc(10px + var(--bl-p) * 40px) calc(30px + var(--bl-p) * 60px) -20px var(--bl-shadow); transform-origin: 50% 0; transform: rotateX(calc((1 - var(--bl-p)) * var(--bl-tilt))) scale(calc(var(--bl-scale) + (1 - var(--bl-scale)) * var(--bl-p))) translateY(calc((1 - var(--bl-p)) * var(--bl-lift))); will-change: transform; }
|
|
8
|
+
.bl-tilt__card > img, .bl-tilt__card > video { display: block; width: 100%; height: auto; }
|
|
9
|
+
.bl-tilt__shine { position: absolute; inset: 0; pointer-events: none; background: linear-gradient(115deg, transparent 30%, var(--bl-shine) 48%, transparent 66%); background-size: 250% 100%; background-position: calc(100% - var(--bl-p) * 100%) 0; opacity: calc(1 - var(--bl-p) * 0.7); mix-blend-mode: screen; }
|
|
10
|
+
@media (prefers-reduced-motion: reduce) {
|
|
11
|
+
.bl-tilt__card { transform: none; }
|
|
12
|
+
.bl-tilt__shine { display: none; }
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
function useStyles() {
|
|
16
|
+
useInsertionEffect(() => {
|
|
17
|
+
if (typeof document === "undefined") return;
|
|
18
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
19
|
+
style.textContent = CSS;
|
|
20
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
21
|
+
}, []);
|
|
22
|
+
}
|
|
23
|
+
function scrollParent(node) {
|
|
24
|
+
for (let el = node?.parentElement; el; el = el.parentElement) {
|
|
25
|
+
if (/(auto|scroll|overlay)/.test(getComputedStyle(el).overflowY) && el.scrollHeight > el.clientHeight) return el;
|
|
26
|
+
}
|
|
27
|
+
return window;
|
|
28
|
+
}
|
|
29
|
+
const ease = (t) => t * t * (3 - 2 * t);
|
|
30
|
+
function ScrollTilt({
|
|
31
|
+
children,
|
|
32
|
+
tilt = 32,
|
|
33
|
+
scale = 0.88,
|
|
34
|
+
lift = 40,
|
|
35
|
+
start = 1,
|
|
36
|
+
end = 0.35,
|
|
37
|
+
perspective = 1200,
|
|
38
|
+
shine = true,
|
|
39
|
+
radius = 16,
|
|
40
|
+
borderWidth = 1,
|
|
41
|
+
borderColor = "rgba(255,255,255,0.12)",
|
|
42
|
+
shineColor = "rgba(255,255,255,0.35)",
|
|
43
|
+
shadowColor = "rgba(0,0,0,0.6)",
|
|
44
|
+
className = "",
|
|
45
|
+
style
|
|
46
|
+
}) {
|
|
47
|
+
useStyles();
|
|
48
|
+
const ref = useRef(null);
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
const root = ref.current;
|
|
51
|
+
const scroller = scrollParent(root);
|
|
52
|
+
let frame = 0;
|
|
53
|
+
const update = () => {
|
|
54
|
+
frame = 0;
|
|
55
|
+
const rect = root.getBoundingClientRect();
|
|
56
|
+
const viewTop = scroller === window ? 0 : scroller.getBoundingClientRect().top;
|
|
57
|
+
const viewHeight = scroller === window ? window.innerHeight : scroller.clientHeight;
|
|
58
|
+
const top = (rect.top - viewTop) / viewHeight;
|
|
59
|
+
const raw = Math.min(1, Math.max(0, (start - top) / Math.max(0.01, start - end)));
|
|
60
|
+
root.style.setProperty("--bl-p", ease(raw).toFixed(4));
|
|
61
|
+
};
|
|
62
|
+
const schedule = () => {
|
|
63
|
+
if (!frame) frame = requestAnimationFrame(update);
|
|
64
|
+
};
|
|
65
|
+
update();
|
|
66
|
+
scroller.addEventListener("scroll", schedule, { passive: true });
|
|
67
|
+
window.addEventListener("resize", schedule);
|
|
68
|
+
return () => {
|
|
69
|
+
cancelAnimationFrame(frame);
|
|
70
|
+
scroller.removeEventListener("scroll", schedule);
|
|
71
|
+
window.removeEventListener("resize", schedule);
|
|
72
|
+
};
|
|
73
|
+
}, [start, end]);
|
|
74
|
+
return /* @__PURE__ */ jsx(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
ref,
|
|
78
|
+
className: `bl-tilt ${className}`,
|
|
79
|
+
style: {
|
|
80
|
+
"--bl-tilt": `${tilt}deg`,
|
|
81
|
+
"--bl-scale": scale,
|
|
82
|
+
"--bl-lift": `${lift}px`,
|
|
83
|
+
"--bl-perspective": `${perspective}px`,
|
|
84
|
+
"--bl-radius": `${radius}px`,
|
|
85
|
+
"--bl-border": `${borderWidth}px`,
|
|
86
|
+
"--bl-edge": borderColor,
|
|
87
|
+
"--bl-shine": shineColor,
|
|
88
|
+
"--bl-shadow": shadowColor,
|
|
89
|
+
...style
|
|
90
|
+
},
|
|
91
|
+
children: /* @__PURE__ */ jsxs("div", { className: "bl-tilt__card", children: [
|
|
92
|
+
children,
|
|
93
|
+
shine && /* @__PURE__ */ jsx("div", { className: "bl-tilt__shine", "aria-hidden": "true" })
|
|
94
|
+
] })
|
|
95
|
+
}
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
ScrollTilt as default
|
|
100
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type CSSProperties, type ElementType } from 'react';
|
|
2
|
+
export interface ScrollTypographyProps {
|
|
3
|
+
/** Plain text, split into words. */
|
|
4
|
+
text?: string;
|
|
5
|
+
as?: ElementType;
|
|
6
|
+
/** Scroll speed (px/s) that gives the full effect. */
|
|
7
|
+
sensitivity?: number;
|
|
8
|
+
minWeight?: number;
|
|
9
|
+
maxWeight?: number;
|
|
10
|
+
/** Vertical stretch at full speed (0.25 = 25% taller). */
|
|
11
|
+
stretch?: number;
|
|
12
|
+
/** How much wider fast words get, in %, if the font has a width axis. */
|
|
13
|
+
widthRange?: number;
|
|
14
|
+
/** Lean in degrees at full speed; follows the scroll direction. */
|
|
15
|
+
skew?: number;
|
|
16
|
+
/** Delay per word in ms, which makes the ripple. */
|
|
17
|
+
stagger?: number;
|
|
18
|
+
fontSize?: number | string;
|
|
19
|
+
textColor?: string;
|
|
20
|
+
/** Colour at full speed. */
|
|
21
|
+
accentColor?: string;
|
|
22
|
+
fontFamily?: string;
|
|
23
|
+
className?: string;
|
|
24
|
+
style?: CSSProperties;
|
|
25
|
+
}
|
|
26
|
+
export default function ScrollTypography({ text, as: Tag, sensitivity, minWeight, maxWeight, stretch, widthRange, skew, stagger, fontSize, textColor, accentColor, fontFamily, className, style }: ScrollTypographyProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef } from "react";
|
|
4
|
+
const STYLE_ID = "bl-scroll-typography";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-stype { margin: 0; font-family: var(--bl-font); font-size: var(--bl-size); line-height: 1.15; color: var(--bl-ink); text-wrap: balance; }
|
|
7
|
+
.bl-stype__word { --t: 0; --s: 0; display: inline-block; font-weight: calc(var(--bl-wmin) + var(--t) * (var(--bl-wmax) - var(--bl-wmin))); font-stretch: calc(100% + var(--t) * var(--bl-wdth)); color: color-mix(in srgb, var(--bl-accent) calc(var(--t) * 100%), var(--bl-ink)); transform: skewX(calc(var(--s) * var(--bl-skew))) scaleY(calc(1 + var(--t) * var(--bl-stretch))); transform-origin: 50% 100%; will-change: transform; }
|
|
8
|
+
`;
|
|
9
|
+
function useStyles() {
|
|
10
|
+
useInsertionEffect(() => {
|
|
11
|
+
if (typeof document === "undefined") return;
|
|
12
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
13
|
+
style.textContent = CSS;
|
|
14
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
15
|
+
}, []);
|
|
16
|
+
}
|
|
17
|
+
function scrollerOf(node) {
|
|
18
|
+
for (let el = node.parentElement; el && el !== document.body; el = el.parentElement) {
|
|
19
|
+
const { overflowY } = getComputedStyle(el);
|
|
20
|
+
if (/(auto|scroll|overlay)/.test(overflowY) && el.scrollHeight > el.clientHeight) return el;
|
|
21
|
+
}
|
|
22
|
+
return window;
|
|
23
|
+
}
|
|
24
|
+
function ScrollTypography({
|
|
25
|
+
text = "",
|
|
26
|
+
as: Tag = "p",
|
|
27
|
+
sensitivity = 2400,
|
|
28
|
+
minWeight = 300,
|
|
29
|
+
maxWeight = 900,
|
|
30
|
+
stretch = 0.25,
|
|
31
|
+
widthRange = 0,
|
|
32
|
+
skew = 10,
|
|
33
|
+
stagger = 35,
|
|
34
|
+
fontSize = 56,
|
|
35
|
+
textColor = "#f4f4f4",
|
|
36
|
+
accentColor = "#73eff7",
|
|
37
|
+
fontFamily = '"Roboto Flex", ui-sans-serif, system-ui, sans-serif',
|
|
38
|
+
className = "",
|
|
39
|
+
style
|
|
40
|
+
}) {
|
|
41
|
+
useStyles();
|
|
42
|
+
const rootRef = useRef(null);
|
|
43
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
const root = rootRef.current;
|
|
46
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
47
|
+
if (reduced) return void 0;
|
|
48
|
+
const nodes = [...root.querySelectorAll(".bl-stype__word")];
|
|
49
|
+
const scroller = scrollerOf(root);
|
|
50
|
+
const position = () => scroller === window ? window.scrollY : scroller.scrollTop;
|
|
51
|
+
const history = [];
|
|
52
|
+
let lastY = position();
|
|
53
|
+
let smooth = 0;
|
|
54
|
+
let visible = true;
|
|
55
|
+
let raf = 0;
|
|
56
|
+
let last = 0;
|
|
57
|
+
const at = (time) => {
|
|
58
|
+
for (let i = history.length - 1; i >= 0; i--) if (history[i].t <= time) return history[i].v;
|
|
59
|
+
return history.length ? history[0].v : 0;
|
|
60
|
+
};
|
|
61
|
+
const tick = (now) => {
|
|
62
|
+
const dt = Math.max(1e-3, Math.min(0.05, (now - last) / 1e3));
|
|
63
|
+
last = now;
|
|
64
|
+
const y = position();
|
|
65
|
+
const raw = Math.max(-1, Math.min(1, (y - lastY) / dt / sensitivity));
|
|
66
|
+
lastY = y;
|
|
67
|
+
smooth += (raw - smooth) * (1 - Math.exp(-dt * 9));
|
|
68
|
+
history.push({ t: now, v: smooth });
|
|
69
|
+
while (history.length > 2 && now - history[0].t > stagger * nodes.length + 200) history.shift();
|
|
70
|
+
let active = Math.abs(smooth) > 2e-3;
|
|
71
|
+
nodes.forEach((node, i) => {
|
|
72
|
+
const v = at(now - i * stagger);
|
|
73
|
+
if (Math.abs(v) > 2e-3) active = true;
|
|
74
|
+
node.style.setProperty("--t", Math.abs(v).toFixed(3));
|
|
75
|
+
node.style.setProperty("--s", v.toFixed(3));
|
|
76
|
+
});
|
|
77
|
+
if (active) raf = requestAnimationFrame(tick);
|
|
78
|
+
else {
|
|
79
|
+
raf = 0;
|
|
80
|
+
smooth = 0;
|
|
81
|
+
history.length = 0;
|
|
82
|
+
nodes.forEach((node) => {
|
|
83
|
+
node.style.setProperty("--t", "0");
|
|
84
|
+
node.style.setProperty("--s", "0");
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const onScroll = () => {
|
|
89
|
+
if (raf || !visible) return;
|
|
90
|
+
last = performance.now();
|
|
91
|
+
raf = requestAnimationFrame(tick);
|
|
92
|
+
};
|
|
93
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
94
|
+
visible = entry.isIntersecting;
|
|
95
|
+
lastY = position();
|
|
96
|
+
});
|
|
97
|
+
observer.observe(root);
|
|
98
|
+
scroller.addEventListener("scroll", onScroll, { passive: true });
|
|
99
|
+
return () => {
|
|
100
|
+
cancelAnimationFrame(raf);
|
|
101
|
+
observer.disconnect();
|
|
102
|
+
scroller.removeEventListener("scroll", onScroll);
|
|
103
|
+
nodes.forEach((node) => {
|
|
104
|
+
node.style.removeProperty("--t");
|
|
105
|
+
node.style.removeProperty("--s");
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
}, [text, sensitivity, stagger]);
|
|
109
|
+
return /* @__PURE__ */ jsx(
|
|
110
|
+
Tag,
|
|
111
|
+
{
|
|
112
|
+
ref: rootRef,
|
|
113
|
+
className: `bl-stype ${className}`,
|
|
114
|
+
style: { "--bl-size": typeof fontSize === "number" ? `${fontSize}px` : fontSize, "--bl-wmin": minWeight, "--bl-wmax": maxWeight, "--bl-stretch": stretch, "--bl-wdth": `${widthRange}%`, "--bl-skew": `${-skew}deg`, "--bl-ink": textColor, "--bl-accent": accentColor, "--bl-font": fontFamily, ...style },
|
|
115
|
+
children: words.map((word, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
116
|
+
/* @__PURE__ */ jsx("span", { className: "bl-stype__word", children: word }),
|
|
117
|
+
i < words.length - 1 && " "
|
|
118
|
+
] }, i))
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
ScrollTypography as default
|
|
124
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface ScrollZoomProps {
|
|
3
|
+
src?: string;
|
|
4
|
+
alt?: string;
|
|
5
|
+
/** Any node (video, canvas) instead of an image. */
|
|
6
|
+
media?: ReactNode;
|
|
7
|
+
/** Shown above the small frame; fades as the zoom starts. */
|
|
8
|
+
heading?: ReactNode;
|
|
9
|
+
/** Revealed over the full-bleed media after the zoom. */
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
/** Section length in screen heights. */
|
|
12
|
+
length?: number;
|
|
13
|
+
/** Share of the scroll spent zooming; the rest reveals. */
|
|
14
|
+
zoomEnd?: number;
|
|
15
|
+
/** Starting frame width as a share of the stage. */
|
|
16
|
+
startWidth?: number;
|
|
17
|
+
startAspect?: number;
|
|
18
|
+
radius?: number;
|
|
19
|
+
/** Space above the frame for the heading, as a share of the stage height. */
|
|
20
|
+
headingSpace?: number;
|
|
21
|
+
dim?: number;
|
|
22
|
+
dimColor?: string;
|
|
23
|
+
textColor?: string;
|
|
24
|
+
backgroundColor?: string;
|
|
25
|
+
/** Style for the media wrapper (e.g. imageRendering: "pixelated"). */
|
|
26
|
+
mediaStyle?: CSSProperties;
|
|
27
|
+
fontFamily?: string;
|
|
28
|
+
className?: string;
|
|
29
|
+
style?: CSSProperties;
|
|
30
|
+
}
|
|
31
|
+
export default function ScrollZoom({ src, alt, media, heading, children, length, zoomEnd, startWidth, startAspect, radius, headingSpace, dim, dimColor, textColor, backgroundColor, mediaStyle, fontFamily, className, style }: ScrollZoomProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef } from "react";
|
|
4
|
+
const STYLE_ID = "bl-scroll-zoom";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-zoom { position: relative; height: calc(var(--bl-view, 100vh) * var(--bl-length)); color: var(--bl-ink); font-family: var(--bl-font); }
|
|
7
|
+
.bl-zoom__stage { position: sticky; top: 0; height: var(--bl-view, 100vh); overflow: hidden; }
|
|
8
|
+
.bl-zoom__media { position: absolute; inset: 0; clip-path: var(--bl-clip, inset(0)); will-change: clip-path; }
|
|
9
|
+
.bl-zoom__media > img, .bl-zoom__media > video { display: block; width: 100%; height: 100%; object-fit: cover; transform: scale(calc(1.18 - var(--bl-p, 1) * 0.18)); }
|
|
10
|
+
.bl-zoom__media > :not(img):not(video) { width: 100%; height: 100%; }
|
|
11
|
+
.bl-zoom__dim { position: absolute; inset: 0; background: var(--bl-dim-color); opacity: calc(var(--bl-reveal, 1) * var(--bl-dim)); pointer-events: none; }
|
|
12
|
+
.bl-zoom__heading { position: absolute; left: 0; right: 0; top: 0; display: grid; place-items: center; height: var(--bl-head-h); padding: 0 16px; text-align: center; opacity: calc(1 - var(--bl-p, 1) * 3); transform: translateY(calc(var(--bl-p, 1) * -40px)); pointer-events: none; }
|
|
13
|
+
.bl-zoom__reveal { position: absolute; inset: 0; display: grid; place-items: center; padding: 24px; text-align: center; opacity: var(--bl-reveal, 1); transform: translateY(calc((1 - var(--bl-reveal, 1)) * 30px)); }
|
|
14
|
+
.bl-zoom[data-hidden-reveal] .bl-zoom__reveal { visibility: hidden; }
|
|
15
|
+
.bl-zoom[data-static] { height: auto; }
|
|
16
|
+
.bl-zoom[data-static] .bl-zoom__stage { position: relative; height: var(--bl-view, 100vh); }
|
|
17
|
+
`;
|
|
18
|
+
function useStyles() {
|
|
19
|
+
useInsertionEffect(() => {
|
|
20
|
+
if (typeof document === "undefined") return;
|
|
21
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
22
|
+
style.textContent = CSS;
|
|
23
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
24
|
+
}, []);
|
|
25
|
+
}
|
|
26
|
+
function scrollParent(node) {
|
|
27
|
+
for (let el = node?.parentElement; el; el = el.parentElement) {
|
|
28
|
+
if (/(auto|scroll|overlay)/.test(getComputedStyle(el).overflowY) && el.scrollHeight > el.clientHeight) return el;
|
|
29
|
+
}
|
|
30
|
+
return window;
|
|
31
|
+
}
|
|
32
|
+
const clamp = (value) => Math.max(0, Math.min(1, value));
|
|
33
|
+
const ease = (t) => 1 - (1 - t) ** 3;
|
|
34
|
+
function ScrollZoom({
|
|
35
|
+
src,
|
|
36
|
+
alt = "",
|
|
37
|
+
media,
|
|
38
|
+
heading,
|
|
39
|
+
children,
|
|
40
|
+
length = 3,
|
|
41
|
+
zoomEnd = 0.6,
|
|
42
|
+
startWidth = 0.46,
|
|
43
|
+
startAspect = 4 / 3,
|
|
44
|
+
radius = 18,
|
|
45
|
+
headingSpace = 0.24,
|
|
46
|
+
dim = 0.55,
|
|
47
|
+
dimColor = "#0b0b14",
|
|
48
|
+
textColor = "#f4f4f4",
|
|
49
|
+
backgroundColor = "#1a1c2c",
|
|
50
|
+
mediaStyle,
|
|
51
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
52
|
+
className = "",
|
|
53
|
+
style
|
|
54
|
+
}) {
|
|
55
|
+
useStyles();
|
|
56
|
+
const rootRef = useRef(null);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const root = rootRef.current;
|
|
59
|
+
const scroller = scrollParent(root);
|
|
60
|
+
const isWindow = scroller === window;
|
|
61
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
62
|
+
let frame = 0;
|
|
63
|
+
const viewport = () => isWindow ? { top: 0, height: window.innerHeight } : { top: scroller.getBoundingClientRect().top, height: scroller.clientHeight };
|
|
64
|
+
const update = () => {
|
|
65
|
+
frame = 0;
|
|
66
|
+
const view = viewport();
|
|
67
|
+
root.style.setProperty("--bl-view", `${view.height}px`);
|
|
68
|
+
root.style.setProperty("--bl-head-h", `${view.height * headingSpace}px`);
|
|
69
|
+
const rect = root.getBoundingClientRect();
|
|
70
|
+
const travel = rect.height - view.height;
|
|
71
|
+
const p = reduced ? 1 : clamp((view.top - rect.top) / Math.max(1, travel));
|
|
72
|
+
const zoom = ease(clamp(p / zoomEnd));
|
|
73
|
+
const reveal = clamp((p - zoomEnd) / Math.max(0.01, (1 - zoomEnd) * 0.6));
|
|
74
|
+
const width = rect.width;
|
|
75
|
+
const height = view.height;
|
|
76
|
+
const frameW = Math.min(width * startWidth, (height * (1 - headingSpace) - 24) * startAspect);
|
|
77
|
+
const frameH = frameW / startAspect;
|
|
78
|
+
const x = (width - frameW) / 2 * (1 - zoom);
|
|
79
|
+
const top = height * headingSpace * (1 - zoom);
|
|
80
|
+
const bottom = Math.max(0, height - height * headingSpace - frameH) * (1 - zoom);
|
|
81
|
+
root.style.setProperty("--bl-clip", `inset(${top}px ${x}px ${bottom}px ${x}px round ${radius * (1 - zoom)}px)`);
|
|
82
|
+
root.style.setProperty("--bl-p", String(zoom));
|
|
83
|
+
root.style.setProperty("--bl-reveal", String(reveal));
|
|
84
|
+
root.toggleAttribute("data-hidden-reveal", reveal < 0.02);
|
|
85
|
+
};
|
|
86
|
+
const schedule = () => {
|
|
87
|
+
if (!frame) frame = requestAnimationFrame(update);
|
|
88
|
+
};
|
|
89
|
+
if (reduced) root.setAttribute("data-static", "");
|
|
90
|
+
else root.removeAttribute("data-static");
|
|
91
|
+
scroller.addEventListener("scroll", schedule, { passive: true });
|
|
92
|
+
window.addEventListener("resize", schedule);
|
|
93
|
+
const resize = new ResizeObserver(schedule);
|
|
94
|
+
resize.observe(root);
|
|
95
|
+
if (!isWindow) resize.observe(scroller);
|
|
96
|
+
update();
|
|
97
|
+
return () => {
|
|
98
|
+
cancelAnimationFrame(frame);
|
|
99
|
+
scroller.removeEventListener("scroll", schedule);
|
|
100
|
+
window.removeEventListener("resize", schedule);
|
|
101
|
+
resize.disconnect();
|
|
102
|
+
};
|
|
103
|
+
}, [zoomEnd, startWidth, startAspect, radius, headingSpace, length]);
|
|
104
|
+
return /* @__PURE__ */ jsx(
|
|
105
|
+
"section",
|
|
106
|
+
{
|
|
107
|
+
ref: rootRef,
|
|
108
|
+
className: `bl-zoom ${className}`,
|
|
109
|
+
style: { "--bl-length": length, "--bl-dim": dim, "--bl-dim-color": dimColor, "--bl-ink": textColor, "--bl-font": fontFamily, background: backgroundColor, ...style },
|
|
110
|
+
children: /* @__PURE__ */ jsxs("div", { className: "bl-zoom__stage", children: [
|
|
111
|
+
heading && /* @__PURE__ */ jsx("div", { className: "bl-zoom__heading", children: heading }),
|
|
112
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-zoom__media", style: mediaStyle, children: [
|
|
113
|
+
media ?? /* @__PURE__ */ jsx("img", { src, alt }),
|
|
114
|
+
/* @__PURE__ */ jsx("div", { className: "bl-zoom__dim", "aria-hidden": "true" })
|
|
115
|
+
] }),
|
|
116
|
+
children && /* @__PURE__ */ jsx("div", { className: "bl-zoom__reveal", children })
|
|
117
|
+
] })
|
|
118
|
+
}
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
ScrollZoom as default
|
|
123
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface ScrubNumberProps {
|
|
3
|
+
value?: number;
|
|
4
|
+
defaultValue?: number;
|
|
5
|
+
onChange?: (value: number) => void;
|
|
6
|
+
min?: number;
|
|
7
|
+
max?: number;
|
|
8
|
+
/** Step; decimals follow it. */
|
|
9
|
+
step?: number;
|
|
10
|
+
/** Drag distance per step; Shift ×10, Alt ×0.1. */
|
|
11
|
+
pixelsPerStep?: number;
|
|
12
|
+
/** Accessible name. */
|
|
13
|
+
label?: string;
|
|
14
|
+
/** What the drag handle shows (defaults to the label). */
|
|
15
|
+
handle?: ReactNode;
|
|
16
|
+
unit?: string;
|
|
17
|
+
width?: number;
|
|
18
|
+
radius?: number;
|
|
19
|
+
backgroundColor?: string;
|
|
20
|
+
chipColor?: string;
|
|
21
|
+
textColor?: string;
|
|
22
|
+
mutedColor?: string;
|
|
23
|
+
lineColor?: string;
|
|
24
|
+
accentColor?: string;
|
|
25
|
+
fontFamily?: string;
|
|
26
|
+
className?: string;
|
|
27
|
+
style?: CSSProperties;
|
|
28
|
+
}
|
|
29
|
+
export default function ScrubNumber({ value: controlled, defaultValue, onChange, min, max, step, pixelsPerStep, label, handle, unit, width, radius, backgroundColor, chipColor, textColor, mutedColor, lineColor, accentColor, fontFamily, className, style }: ScrubNumberProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-scrub-number";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-scrub { position: relative; display: inline-flex; align-items: stretch; overflow: hidden; border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); font-size: 14px; box-shadow: inset 0 0 0 1px var(--bl-line); }
|
|
7
|
+
.bl-scrub:focus-within { box-shadow: inset 0 0 0 2px var(--bl-accent); }
|
|
8
|
+
.bl-scrub__handle { position: relative; display: grid; place-items: center; min-width: 30px; padding: 0 10px; background: var(--bl-chip); color: var(--bl-muted); font-weight: 700; cursor: ew-resize; user-select: none; touch-action: none; }
|
|
9
|
+
.bl-scrub[data-scrubbing] .bl-scrub__handle { color: var(--bl-accent); }
|
|
10
|
+
.bl-scrub__input { width: var(--bl-w); min-width: 0; margin: 0; padding: 8px 10px; border: 0; background: transparent; color: inherit; font: inherit; font-variant-numeric: tabular-nums; outline: none; }
|
|
11
|
+
.bl-scrub__unit { display: grid; place-items: center; padding-right: 10px; color: var(--bl-muted); }
|
|
12
|
+
.bl-scrub__ruler { position: absolute; left: 0; right: 0; bottom: 0; height: 5px; background: repeating-linear-gradient(90deg, var(--bl-accent) 0 1px, transparent 1px 8px); background-position: var(--off) 0; opacity: 0; pointer-events: none; transition: opacity 120ms; }
|
|
13
|
+
.bl-scrub[data-scrubbing] .bl-scrub__ruler { opacity: 0.8; }
|
|
14
|
+
`;
|
|
15
|
+
function useStyles() {
|
|
16
|
+
useInsertionEffect(() => {
|
|
17
|
+
if (typeof document === "undefined") return;
|
|
18
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
19
|
+
style.textContent = CSS;
|
|
20
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
21
|
+
}, []);
|
|
22
|
+
}
|
|
23
|
+
const decimalsOf = (step) => (String(step).split(".")[1] ?? "").length;
|
|
24
|
+
function ScrubNumber({
|
|
25
|
+
value: controlled,
|
|
26
|
+
defaultValue = 0,
|
|
27
|
+
onChange,
|
|
28
|
+
min = -Infinity,
|
|
29
|
+
max = Infinity,
|
|
30
|
+
step = 1,
|
|
31
|
+
pixelsPerStep = 4,
|
|
32
|
+
label = "Value",
|
|
33
|
+
handle,
|
|
34
|
+
unit = "",
|
|
35
|
+
width = 64,
|
|
36
|
+
radius = 8,
|
|
37
|
+
backgroundColor = "#1a1c2c",
|
|
38
|
+
chipColor = "#262b44",
|
|
39
|
+
textColor = "#f4f4f4",
|
|
40
|
+
mutedColor = "#94b0c2",
|
|
41
|
+
lineColor = "#333c57",
|
|
42
|
+
accentColor = "#73eff7",
|
|
43
|
+
fontFamily = "inherit",
|
|
44
|
+
className = "",
|
|
45
|
+
style
|
|
46
|
+
}) {
|
|
47
|
+
useStyles();
|
|
48
|
+
const id = useId();
|
|
49
|
+
const [inner, setInner] = useState(defaultValue);
|
|
50
|
+
const value = controlled ?? inner;
|
|
51
|
+
const decimals = decimalsOf(step);
|
|
52
|
+
const [draft, setDraft] = useState(null);
|
|
53
|
+
const [scrubbing, setScrubbing] = useState(false);
|
|
54
|
+
const handleRef = useRef(null);
|
|
55
|
+
const rootRef = useRef(null);
|
|
56
|
+
const valueRef = useRef(value);
|
|
57
|
+
valueRef.current = value;
|
|
58
|
+
const commit = (next) => {
|
|
59
|
+
const snapped = Math.round(next / step) * step;
|
|
60
|
+
const v = Number(Math.max(min, Math.min(max, snapped)).toFixed(decimals));
|
|
61
|
+
if (v === valueRef.current) return;
|
|
62
|
+
if (controlled === void 0) setInner(v);
|
|
63
|
+
onChange?.(v);
|
|
64
|
+
};
|
|
65
|
+
const commitRef = useRef(commit);
|
|
66
|
+
commitRef.current = commit;
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
const el = handleRef.current;
|
|
69
|
+
const root = rootRef.current;
|
|
70
|
+
let drag = null;
|
|
71
|
+
const onDown = (event) => {
|
|
72
|
+
if (event.button > 0) return;
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
el.setPointerCapture?.(event.pointerId);
|
|
75
|
+
drag = { x: event.clientX, from: valueRef.current };
|
|
76
|
+
setScrubbing(true);
|
|
77
|
+
};
|
|
78
|
+
const onMove = (event) => {
|
|
79
|
+
if (!drag) return;
|
|
80
|
+
const dx = event.clientX - drag.x;
|
|
81
|
+
const scale = event.shiftKey ? 10 : event.altKey ? 0.1 : 1;
|
|
82
|
+
root.style.setProperty("--off", `${dx}px`);
|
|
83
|
+
commitRef.current(drag.from + Math.round(dx / pixelsPerStep) * step * scale);
|
|
84
|
+
};
|
|
85
|
+
const onUp = () => {
|
|
86
|
+
drag = null;
|
|
87
|
+
setScrubbing(false);
|
|
88
|
+
};
|
|
89
|
+
el.addEventListener("pointerdown", onDown);
|
|
90
|
+
el.addEventListener("pointermove", onMove);
|
|
91
|
+
el.addEventListener("pointerup", onUp);
|
|
92
|
+
el.addEventListener("pointercancel", onUp);
|
|
93
|
+
return () => {
|
|
94
|
+
el.removeEventListener("pointerdown", onDown);
|
|
95
|
+
el.removeEventListener("pointermove", onMove);
|
|
96
|
+
el.removeEventListener("pointerup", onUp);
|
|
97
|
+
el.removeEventListener("pointercancel", onUp);
|
|
98
|
+
};
|
|
99
|
+
}, [step, pixelsPerStep]);
|
|
100
|
+
const finishTyping = () => {
|
|
101
|
+
if (draft === null) return;
|
|
102
|
+
const parsed = Number(draft.replace(",", "."));
|
|
103
|
+
if (Number.isFinite(parsed)) commit(parsed);
|
|
104
|
+
setDraft(null);
|
|
105
|
+
};
|
|
106
|
+
return /* @__PURE__ */ jsxs(
|
|
107
|
+
"span",
|
|
108
|
+
{
|
|
109
|
+
ref: rootRef,
|
|
110
|
+
className: `bl-scrub ${className}`,
|
|
111
|
+
"data-scrubbing": scrubbing || void 0,
|
|
112
|
+
style: { "--bl-w": `${width}px`, "--bl-radius": `${radius}px`, "--bl-bg": backgroundColor, "--bl-chip": chipColor, "--bl-ink": textColor, "--bl-muted": mutedColor, "--bl-line": lineColor, "--bl-accent": accentColor, "--bl-font": fontFamily, "--off": "0px", ...style },
|
|
113
|
+
children: [
|
|
114
|
+
/* @__PURE__ */ jsx("label", { ref: handleRef, htmlFor: id, className: "bl-scrub__handle", title: `Drag to change ${label}`, children: handle ?? label }),
|
|
115
|
+
/* @__PURE__ */ jsx(
|
|
116
|
+
"input",
|
|
117
|
+
{
|
|
118
|
+
id,
|
|
119
|
+
className: "bl-scrub__input",
|
|
120
|
+
type: "text",
|
|
121
|
+
inputMode: "decimal",
|
|
122
|
+
role: "spinbutton",
|
|
123
|
+
"aria-label": handle ? label : void 0,
|
|
124
|
+
"aria-valuenow": value,
|
|
125
|
+
"aria-valuemin": Number.isFinite(min) ? min : void 0,
|
|
126
|
+
"aria-valuemax": Number.isFinite(max) ? max : void 0,
|
|
127
|
+
value: draft ?? value.toFixed(decimals),
|
|
128
|
+
onChange: (event) => setDraft(event.target.value),
|
|
129
|
+
onBlur: finishTyping,
|
|
130
|
+
onKeyDown: (event) => {
|
|
131
|
+
if (event.key === "Enter") {
|
|
132
|
+
finishTyping();
|
|
133
|
+
event.currentTarget.select();
|
|
134
|
+
} else if (event.key === "Escape") setDraft(null);
|
|
135
|
+
else if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
136
|
+
event.preventDefault();
|
|
137
|
+
setDraft(null);
|
|
138
|
+
const scale = event.shiftKey ? 10 : event.altKey ? 0.1 : 1;
|
|
139
|
+
commit(value + (event.key === "ArrowUp" ? 1 : -1) * step * scale);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
),
|
|
144
|
+
unit && /* @__PURE__ */ jsx("span", { className: "bl-scrub__unit", "aria-hidden": "true", children: unit }),
|
|
145
|
+
/* @__PURE__ */ jsx("span", { className: "bl-scrub__ruler", "aria-hidden": "true" })
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
export {
|
|
151
|
+
ScrubNumber as default
|
|
152
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type CSSProperties, type ElementType } from 'react';
|
|
2
|
+
export interface ShadowStackProps {
|
|
3
|
+
text?: string;
|
|
4
|
+
as?: ElementType;
|
|
5
|
+
/** A solid stair-stepped block, or spaced fading copies that breathe. */
|
|
6
|
+
mode?: 'extrude' | 'echo';
|
|
7
|
+
/** Extrude depth in px. */
|
|
8
|
+
depth?: number;
|
|
9
|
+
/** Direction of the depth in degrees (0 = right, 90 = down). */
|
|
10
|
+
angle?: number;
|
|
11
|
+
/** Px per extrude layer. */
|
|
12
|
+
step?: number;
|
|
13
|
+
echoes?: number;
|
|
14
|
+
/** Gap between echoes in px. */
|
|
15
|
+
spacing?: number;
|
|
16
|
+
/** Depth falls away from the pointer, snapped to 15°. */
|
|
17
|
+
follow?: boolean;
|
|
18
|
+
/** Pressing squashes the title like a key. */
|
|
19
|
+
press?: boolean;
|
|
20
|
+
faceColor?: string;
|
|
21
|
+
/** Colours the depth steps through, front to back. */
|
|
22
|
+
colors?: string[];
|
|
23
|
+
fontSize?: number | string;
|
|
24
|
+
fontFamily?: string;
|
|
25
|
+
className?: string;
|
|
26
|
+
style?: CSSProperties;
|
|
27
|
+
}
|
|
28
|
+
export default function ShadowStack({ text, as: Tag, mode, depth, angle, step, echoes, spacing, follow, press, faceColor, colors, fontSize, fontFamily, className, style }: ShadowStackProps): import("react").JSX.Element;
|