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,124 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
function SpriteAnimator({
|
|
5
|
+
src,
|
|
6
|
+
frameWidth = 16,
|
|
7
|
+
frameHeight = 16,
|
|
8
|
+
animations,
|
|
9
|
+
animation,
|
|
10
|
+
row = 0,
|
|
11
|
+
frames = 8,
|
|
12
|
+
fps = 10,
|
|
13
|
+
scale = 6,
|
|
14
|
+
loop = true,
|
|
15
|
+
pingPong = false,
|
|
16
|
+
playing = true,
|
|
17
|
+
flipX = false,
|
|
18
|
+
label = "Animated sprite",
|
|
19
|
+
onEnd,
|
|
20
|
+
className = "",
|
|
21
|
+
style
|
|
22
|
+
}) {
|
|
23
|
+
const canvasRef = useRef(null);
|
|
24
|
+
const state = useRef({ image: null, frame: 0, direction: 1, elapsed: 0 });
|
|
25
|
+
const onEndRef = useRef(onEnd);
|
|
26
|
+
onEndRef.current = onEnd;
|
|
27
|
+
const current = animations && animations[animation] || { row, frames, fps };
|
|
28
|
+
const config = { row: current.row ?? 0, frames: Math.max(1, current.frames ?? 1), fps: current.fps ?? fps, loop: current.loop ?? loop, pingPong: current.pingPong ?? pingPong };
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!src) return void 0;
|
|
31
|
+
let cancelled = false;
|
|
32
|
+
const image = new Image();
|
|
33
|
+
image.onload = () => {
|
|
34
|
+
if (cancelled) return;
|
|
35
|
+
state.current.image = image;
|
|
36
|
+
draw();
|
|
37
|
+
};
|
|
38
|
+
image.src = src;
|
|
39
|
+
return () => {
|
|
40
|
+
cancelled = true;
|
|
41
|
+
};
|
|
42
|
+
}, [src]);
|
|
43
|
+
function draw() {
|
|
44
|
+
const canvas = canvasRef.current;
|
|
45
|
+
const { image, frame } = state.current;
|
|
46
|
+
if (!canvas || !image) return;
|
|
47
|
+
const ctx = canvas.getContext("2d");
|
|
48
|
+
ctx.imageSmoothingEnabled = false;
|
|
49
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
50
|
+
ctx.save();
|
|
51
|
+
if (flipX) {
|
|
52
|
+
ctx.translate(canvas.width, 0);
|
|
53
|
+
ctx.scale(-1, 1);
|
|
54
|
+
}
|
|
55
|
+
ctx.drawImage(image, frame * frameWidth, config.row * frameHeight, frameWidth, frameHeight, 0, 0, canvas.width, canvas.height);
|
|
56
|
+
ctx.restore();
|
|
57
|
+
}
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
state.current.frame = 0;
|
|
60
|
+
state.current.direction = 1;
|
|
61
|
+
state.current.elapsed = 0;
|
|
62
|
+
draw();
|
|
63
|
+
}, [animation, config.row, config.frames, flipX, scale]);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const canvas = canvasRef.current;
|
|
66
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
67
|
+
if (!canvas || !playing || reduced || config.frames < 2) return void 0;
|
|
68
|
+
let visible = true;
|
|
69
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
70
|
+
visible = entry.isIntersecting;
|
|
71
|
+
});
|
|
72
|
+
observer.observe(canvas);
|
|
73
|
+
let frameId = 0;
|
|
74
|
+
let last = performance.now();
|
|
75
|
+
const tick = (now) => {
|
|
76
|
+
const s = state.current;
|
|
77
|
+
const dt = Math.min(250, now - last);
|
|
78
|
+
last = now;
|
|
79
|
+
if (visible && s.image) {
|
|
80
|
+
s.elapsed += dt;
|
|
81
|
+
const step = 1e3 / Math.max(1, config.fps);
|
|
82
|
+
while (s.elapsed >= step) {
|
|
83
|
+
s.elapsed -= step;
|
|
84
|
+
let next = s.frame + s.direction;
|
|
85
|
+
if (config.pingPong && (next >= config.frames || next < 0)) {
|
|
86
|
+
s.direction *= -1;
|
|
87
|
+
next = s.frame + s.direction;
|
|
88
|
+
}
|
|
89
|
+
if (!config.pingPong && next >= config.frames) {
|
|
90
|
+
if (!config.loop) {
|
|
91
|
+
onEndRef.current?.();
|
|
92
|
+
draw();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
next = 0;
|
|
96
|
+
}
|
|
97
|
+
s.frame = Math.max(0, Math.min(config.frames - 1, next));
|
|
98
|
+
}
|
|
99
|
+
draw();
|
|
100
|
+
}
|
|
101
|
+
frameId = requestAnimationFrame(tick);
|
|
102
|
+
};
|
|
103
|
+
frameId = requestAnimationFrame(tick);
|
|
104
|
+
return () => {
|
|
105
|
+
cancelAnimationFrame(frameId);
|
|
106
|
+
observer.disconnect();
|
|
107
|
+
};
|
|
108
|
+
}, [playing, config.fps, config.frames, config.row, config.loop, config.pingPong, src]);
|
|
109
|
+
return /* @__PURE__ */ jsx(
|
|
110
|
+
"canvas",
|
|
111
|
+
{
|
|
112
|
+
ref: canvasRef,
|
|
113
|
+
role: "img",
|
|
114
|
+
"aria-label": label,
|
|
115
|
+
width: frameWidth * scale,
|
|
116
|
+
height: frameHeight * scale,
|
|
117
|
+
className,
|
|
118
|
+
style: { imageRendering: "pixelated", width: frameWidth * scale, height: frameHeight * scale, maxWidth: "100%", ...style }
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
SpriteAnimator as default
|
|
124
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export type StatFormat = 'compact' | 'currency' | 'percent' | 'number' | ((value: number) => string);
|
|
3
|
+
export interface StatCardProps {
|
|
4
|
+
label?: string;
|
|
5
|
+
value?: number;
|
|
6
|
+
/** The previous value; the delta is computed from it. */
|
|
7
|
+
previous?: number;
|
|
8
|
+
/** Or pass the delta directly (0.12 = +12%). */
|
|
9
|
+
delta?: number;
|
|
10
|
+
/** What the delta compares to. */
|
|
11
|
+
period?: string;
|
|
12
|
+
/** Whether up is good (green) or bad (red). */
|
|
13
|
+
goodDirection?: 'up' | 'down' | 'neutral';
|
|
14
|
+
format?: StatFormat;
|
|
15
|
+
currency?: string;
|
|
16
|
+
locale?: string;
|
|
17
|
+
/** Sparkline points (12 is ideal). */
|
|
18
|
+
trend?: number[];
|
|
19
|
+
trendLabels?: string[];
|
|
20
|
+
icon?: ReactNode;
|
|
21
|
+
/** Roll duration in ms. */
|
|
22
|
+
duration?: number;
|
|
23
|
+
width?: number;
|
|
24
|
+
valueSize?: number;
|
|
25
|
+
sparkHeight?: number;
|
|
26
|
+
backgroundColor?: string;
|
|
27
|
+
textColor?: string;
|
|
28
|
+
mutedColor?: string;
|
|
29
|
+
lineColor?: string;
|
|
30
|
+
trendColor?: string;
|
|
31
|
+
accentColor?: string;
|
|
32
|
+
goodColor?: string;
|
|
33
|
+
badColor?: string;
|
|
34
|
+
radius?: number;
|
|
35
|
+
fontFamily?: string;
|
|
36
|
+
className?: string;
|
|
37
|
+
style?: CSSProperties;
|
|
38
|
+
}
|
|
39
|
+
export declare function formatStat(value: number, format?: StatFormat, { locale, currency }?: {
|
|
40
|
+
locale?: string;
|
|
41
|
+
currency?: string;
|
|
42
|
+
}): string;
|
|
43
|
+
export default function StatCard({ label, value, previous, delta, period, goodDirection, format, currency, locale, trend, trendLabels, icon, duration, width, valueSize, sparkHeight, backgroundColor, textColor, mutedColor, lineColor, trendColor, accentColor, goodColor, badColor, radius, fontFamily, className, style }: StatCardProps): import("react").JSX.Element;
|
package/dist/StatCard.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
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-stat-card";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-stat { position: relative; display: grid; gap: 6px; width: 100%; max-width: var(--bl-w); box-sizing: border-box; padding: 16px 18px 14px; border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); box-shadow: 0 0 0 1px var(--bl-line); }
|
|
7
|
+
.bl-stat__top { display: flex; align-items: center; gap: 8px; color: var(--bl-muted); font-size: 13px; font-weight: 600; }
|
|
8
|
+
.bl-stat__icon { display: grid; place-items: center; width: 22px; height: 22px; }
|
|
9
|
+
.bl-stat__value { font-size: var(--bl-size); font-weight: 650; line-height: 1.1; letter-spacing: -0.01em; }
|
|
10
|
+
.bl-stat__delta { display: flex; align-items: center; gap: 6px; font-size: 13px; }
|
|
11
|
+
.bl-stat__chip { display: inline-flex; align-items: center; gap: 3px; font-weight: 700; color: var(--bl-delta); }
|
|
12
|
+
.bl-stat__period { color: var(--bl-muted); }
|
|
13
|
+
.bl-stat__spark { position: relative; height: var(--bl-spark-h); margin-top: 6px; }
|
|
14
|
+
.bl-stat__spark svg { display: block; width: 100%; height: 100%; overflow: visible; }
|
|
15
|
+
.bl-stat__line { fill: none; stroke: var(--bl-trend); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
|
|
16
|
+
.bl-stat__area { fill: var(--bl-trend); opacity: 0.1; }
|
|
17
|
+
.bl-stat__cross { stroke: var(--bl-muted); stroke-width: 1; vector-effect: non-scaling-stroke; }
|
|
18
|
+
.bl-stat__tip { position: absolute; bottom: calc(100% + 6px); padding: 5px 8px; border-radius: 7px; background: var(--bl-ink); color: var(--bl-bg); font-size: 12px; line-height: 1.3; white-space: nowrap; pointer-events: none; transform: translateX(-50%); }
|
|
19
|
+
.bl-stat__tip b { display: block; font-size: 13px; }
|
|
20
|
+
.bl-stat__dot { position: absolute; width: 8px; height: 8px; margin: -4px 0 0 -4px; border-radius: 50%; box-shadow: 0 0 0 2px var(--bl-bg); pointer-events: none; }
|
|
21
|
+
.bl-stat__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
22
|
+
`;
|
|
23
|
+
function useStyles() {
|
|
24
|
+
useInsertionEffect(() => {
|
|
25
|
+
if (typeof document === "undefined") return;
|
|
26
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
27
|
+
style.textContent = CSS;
|
|
28
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
29
|
+
}, []);
|
|
30
|
+
}
|
|
31
|
+
function formatStat(value, format = "compact", { locale, currency = "USD" } = {}) {
|
|
32
|
+
if (typeof format === "function") return format(value);
|
|
33
|
+
const options = format === "currency" ? { style: "currency", currency, currencyDisplay: "narrowSymbol", notation: Math.abs(value) >= 1e4 ? "compact" : "standard", maximumFractionDigits: Math.abs(value) >= 1e4 ? 1 : 2 } : format === "percent" ? { style: "percent", maximumFractionDigits: 1 } : format === "compact" ? { notation: Math.abs(value) >= 1e4 ? "compact" : "standard", maximumFractionDigits: Math.abs(value) >= 1e4 ? 1 : 0 } : { maximumFractionDigits: 2 };
|
|
34
|
+
return new Intl.NumberFormat(locale, options).format(value);
|
|
35
|
+
}
|
|
36
|
+
function StatCard({
|
|
37
|
+
label = "Metric",
|
|
38
|
+
value = 0,
|
|
39
|
+
previous,
|
|
40
|
+
delta,
|
|
41
|
+
period = "vs last period",
|
|
42
|
+
goodDirection = "up",
|
|
43
|
+
format = "compact",
|
|
44
|
+
currency = "USD",
|
|
45
|
+
locale,
|
|
46
|
+
trend = [],
|
|
47
|
+
trendLabels,
|
|
48
|
+
icon,
|
|
49
|
+
duration = 900,
|
|
50
|
+
width = 300,
|
|
51
|
+
valueSize = 34,
|
|
52
|
+
sparkHeight = 44,
|
|
53
|
+
backgroundColor = "#1a1c2c",
|
|
54
|
+
textColor = "#f4f4f4",
|
|
55
|
+
mutedColor = "#94b0c2",
|
|
56
|
+
lineColor = "#333c57",
|
|
57
|
+
trendColor = "#566c86",
|
|
58
|
+
accentColor = "#3987e5",
|
|
59
|
+
goodColor = "#0ca30c",
|
|
60
|
+
badColor = "#e66767",
|
|
61
|
+
radius = 14,
|
|
62
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
63
|
+
className = "",
|
|
64
|
+
style
|
|
65
|
+
}) {
|
|
66
|
+
useStyles();
|
|
67
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
68
|
+
const [shown, setShown] = useState(value);
|
|
69
|
+
const shownRef = useRef(value);
|
|
70
|
+
const [hover, setHover] = useState(null);
|
|
71
|
+
const sparkRef = useRef(null);
|
|
72
|
+
const fmt = (v) => formatStat(v, format, { locale, currency });
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const from = shownRef.current;
|
|
75
|
+
if (from === value) return void 0;
|
|
76
|
+
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches || !duration) {
|
|
77
|
+
shownRef.current = value;
|
|
78
|
+
setShown(value);
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
const start = performance.now();
|
|
82
|
+
let frame = 0;
|
|
83
|
+
const tick = (now) => {
|
|
84
|
+
const p = Math.min(1, (now - start) / duration);
|
|
85
|
+
const eased = 1 - (1 - p) ** 3;
|
|
86
|
+
const v = from + (value - from) * eased;
|
|
87
|
+
shownRef.current = v;
|
|
88
|
+
setShown(v);
|
|
89
|
+
if (p < 1) frame = requestAnimationFrame(tick);
|
|
90
|
+
};
|
|
91
|
+
frame = requestAnimationFrame(tick);
|
|
92
|
+
return () => cancelAnimationFrame(frame);
|
|
93
|
+
}, [value, duration]);
|
|
94
|
+
const change = delta ?? (previous != null && previous !== 0 ? (value - previous) / Math.abs(previous) : null);
|
|
95
|
+
const direction = change == null || change === 0 ? "flat" : change > 0 ? "up" : "down";
|
|
96
|
+
const good = goodDirection === "neutral" || direction === "flat" ? null : direction === goodDirection;
|
|
97
|
+
const deltaColor = good == null ? mutedColor : good ? goodColor : badColor;
|
|
98
|
+
const deltaText = change == null ? "" : `${change > 0 ? "+" : change < 0 ? "\u2212" : ""}${new Intl.NumberFormat(locale, { style: "percent", maximumFractionDigits: 1 }).format(Math.abs(change))}`;
|
|
99
|
+
const n = trend.length;
|
|
100
|
+
const lo = Math.min(...trend);
|
|
101
|
+
const hi = Math.max(...trend);
|
|
102
|
+
const span = hi - lo || 1;
|
|
103
|
+
const pts = trend.map((v, i) => [n > 1 ? i / (n - 1) * 100 : 50, 38 - (v - lo) / span * 34]);
|
|
104
|
+
const line = pts.map((p, i) => `${i ? "L" : "M"}${p[0].toFixed(2)} ${p[1].toFixed(2)}`).join(" ");
|
|
105
|
+
const area = n > 1 ? `${line} L100 40 L0 40 Z` : "";
|
|
106
|
+
const at = hover ?? n - 1;
|
|
107
|
+
const onMove = (event) => {
|
|
108
|
+
const rect = sparkRef.current.getBoundingClientRect();
|
|
109
|
+
const x = (event.clientX - rect.left) / rect.width;
|
|
110
|
+
setHover(Math.max(0, Math.min(n - 1, Math.round(x * (n - 1)))));
|
|
111
|
+
};
|
|
112
|
+
const summary = n > 1 ? `Trend: from ${fmt(trend[0])} to ${fmt(trend[n - 1])}, low ${fmt(lo)}, high ${fmt(hi)}.` : "";
|
|
113
|
+
return /* @__PURE__ */ jsxs(
|
|
114
|
+
"div",
|
|
115
|
+
{
|
|
116
|
+
className: `bl-stat ${className}`,
|
|
117
|
+
role: "group",
|
|
118
|
+
"aria-labelledby": `${uid}-label`,
|
|
119
|
+
style: {
|
|
120
|
+
"--bl-w": `${width}px`,
|
|
121
|
+
"--bl-size": `${valueSize}px`,
|
|
122
|
+
"--bl-spark-h": `${sparkHeight}px`,
|
|
123
|
+
"--bl-bg": backgroundColor,
|
|
124
|
+
"--bl-ink": textColor,
|
|
125
|
+
"--bl-muted": mutedColor,
|
|
126
|
+
"--bl-line": lineColor,
|
|
127
|
+
"--bl-trend": trendColor,
|
|
128
|
+
"--bl-delta": deltaColor,
|
|
129
|
+
"--bl-radius": `${radius}px`,
|
|
130
|
+
"--bl-font": fontFamily,
|
|
131
|
+
...style
|
|
132
|
+
},
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-stat__top", children: [
|
|
135
|
+
icon && /* @__PURE__ */ jsx("span", { className: "bl-stat__icon", "aria-hidden": "true", children: icon }),
|
|
136
|
+
/* @__PURE__ */ jsx("span", { id: `${uid}-label`, children: label })
|
|
137
|
+
] }),
|
|
138
|
+
/* @__PURE__ */ jsx("div", { className: "bl-stat__value", "aria-hidden": "true", children: fmt(shown) }),
|
|
139
|
+
/* @__PURE__ */ jsx("span", { className: "bl-stat__sr", children: fmt(value) }),
|
|
140
|
+
change != null && /* @__PURE__ */ jsxs("div", { className: "bl-stat__delta", children: [
|
|
141
|
+
/* @__PURE__ */ jsxs("span", { className: "bl-stat__chip", children: [
|
|
142
|
+
/* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: direction === "up" ? /* @__PURE__ */ jsx("path", { d: "M6 10V2M2.5 5.5 6 2l3.5 3.5" }) : direction === "down" ? /* @__PURE__ */ jsx("path", { d: "M6 2v8M2.5 6.5 6 10l3.5-3.5" }) : /* @__PURE__ */ jsx("path", { d: "M2 6h8" }) }),
|
|
143
|
+
/* @__PURE__ */ jsx("span", { className: "bl-stat__sr", children: direction === "up" ? "Up" : direction === "down" ? "Down" : "No change" }),
|
|
144
|
+
deltaText
|
|
145
|
+
] }),
|
|
146
|
+
/* @__PURE__ */ jsx("span", { className: "bl-stat__period", children: period })
|
|
147
|
+
] }),
|
|
148
|
+
n > 1 && /* @__PURE__ */ jsxs("div", { ref: sparkRef, className: "bl-stat__spark", onPointerMove: onMove, onPointerLeave: () => setHover(null), "aria-hidden": "true", children: [
|
|
149
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 100 40", preserveAspectRatio: "none", children: [
|
|
150
|
+
/* @__PURE__ */ jsx("path", { className: "bl-stat__area", d: area }),
|
|
151
|
+
/* @__PURE__ */ jsx("path", { className: "bl-stat__line", d: line }),
|
|
152
|
+
hover != null && /* @__PURE__ */ jsx("line", { className: "bl-stat__cross", x1: pts[hover][0], x2: pts[hover][0], y1: 0, y2: 40 })
|
|
153
|
+
] }),
|
|
154
|
+
/* @__PURE__ */ jsx("span", { className: "bl-stat__dot", style: { left: `${pts[at][0]}%`, top: `${pts[at][1] / 40 * 100}%`, background: at === n - 1 ? accentColor : textColor } }),
|
|
155
|
+
hover != null && /* @__PURE__ */ jsxs("span", { className: "bl-stat__tip", style: { left: `${Math.max(12, Math.min(88, pts[hover][0]))}%` }, children: [
|
|
156
|
+
/* @__PURE__ */ jsx("b", { children: fmt(trend[hover]) }),
|
|
157
|
+
trendLabels?.[hover] ?? `Point ${hover + 1} of ${n}`
|
|
158
|
+
] })
|
|
159
|
+
] }),
|
|
160
|
+
summary && /* @__PURE__ */ jsx("span", { className: "bl-stat__sr", children: summary })
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
export {
|
|
166
|
+
StatCard as default,
|
|
167
|
+
formatStat
|
|
168
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export type StepStatus = 'running' | 'done' | 'error';
|
|
3
|
+
export interface LoaderStep {
|
|
4
|
+
id?: string;
|
|
5
|
+
label: string;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export interface StepLoaderProps {
|
|
9
|
+
steps?: LoaderStep[];
|
|
10
|
+
/** Index of the step in progress. */
|
|
11
|
+
current?: number;
|
|
12
|
+
status?: StepStatus;
|
|
13
|
+
title?: ReactNode;
|
|
14
|
+
open?: boolean;
|
|
15
|
+
/** Show as a modal overlay. */
|
|
16
|
+
fixed?: boolean;
|
|
17
|
+
onCancel?: () => void;
|
|
18
|
+
onDone?: () => void;
|
|
19
|
+
onRetry?: () => void;
|
|
20
|
+
cancelLabel?: string;
|
|
21
|
+
doneLabel?: string;
|
|
22
|
+
retryLabel?: string;
|
|
23
|
+
successMessage?: string;
|
|
24
|
+
errorMessage?: string;
|
|
25
|
+
width?: number;
|
|
26
|
+
maxListHeight?: number;
|
|
27
|
+
backgroundColor?: string;
|
|
28
|
+
textColor?: string;
|
|
29
|
+
mutedColor?: string;
|
|
30
|
+
trackColor?: string;
|
|
31
|
+
lineColor?: string;
|
|
32
|
+
accentColor?: string;
|
|
33
|
+
onAccentColor?: string;
|
|
34
|
+
errorColor?: string;
|
|
35
|
+
backdropColor?: string;
|
|
36
|
+
radius?: number;
|
|
37
|
+
fontFamily?: string;
|
|
38
|
+
className?: string;
|
|
39
|
+
style?: CSSProperties;
|
|
40
|
+
}
|
|
41
|
+
export default function StepLoader({ steps, current, status, title, open, fixed, onCancel, onDone, onRetry, cancelLabel, doneLabel, retryLabel, successMessage, errorMessage, width, maxListHeight, backgroundColor, textColor, mutedColor, trackColor, lineColor, accentColor, onAccentColor, errorColor, backdropColor, radius, fontFamily, className, style }: StepLoaderProps): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useInsertionEffect, useRef } from "react";
|
|
4
|
+
const STYLE_ID = "bl-step-loader";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-steps { position: relative; width: 100%; max-width: var(--bl-w); box-sizing: border-box; padding: 22px; border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); box-shadow: 0 0 0 1px var(--bl-line), 0 30px 60px -30px rgba(0,0,0,0.85); }
|
|
7
|
+
.bl-steps-overlay { position: fixed; inset: 0; z-index: 1900; display: grid; place-items: center; padding: 16px; background: var(--bl-backdrop); animation: bl-steps-fade 200ms both; }
|
|
8
|
+
.bl-steps-overlay > .bl-steps { animation: bl-steps-pop 360ms cubic-bezier(.3,1.35,.5,1) both; }
|
|
9
|
+
.bl-steps__title { margin: 0 0 4px; font-size: 17px; font-weight: 700; }
|
|
10
|
+
.bl-steps__meta { display: flex; justify-content: space-between; gap: 12px; color: var(--bl-muted); font-size: 13px; }
|
|
11
|
+
.bl-steps__bar { height: 6px; margin: 10px 0 18px; overflow: hidden; border-radius: 999px; background: var(--bl-track); }
|
|
12
|
+
.bl-steps__fill { height: 100%; width: calc(var(--bl-p) * 100%); border-radius: inherit; background: var(--bl-accent); transition: width 500ms cubic-bezier(.2,.9,.25,1), background-color 200ms; }
|
|
13
|
+
.bl-steps[data-status="error"] .bl-steps__fill { background: var(--bl-error); }
|
|
14
|
+
.bl-steps__list { position: relative; display: grid; gap: 0; max-height: var(--bl-max-h); margin: 0; padding: 0; overflow-y: auto; list-style: none; }
|
|
15
|
+
.bl-steps__step { position: relative; display: grid; grid-template-columns: 28px 1fr; gap: 12px; padding-bottom: 16px; color: var(--bl-muted); transition: color 200ms; }
|
|
16
|
+
.bl-steps__step:last-child { padding-bottom: 0; }
|
|
17
|
+
.bl-steps__step:not(:last-child)::before { content: ""; position: absolute; left: 13px; top: 30px; bottom: 2px; width: 2px; background: var(--bl-track); }
|
|
18
|
+
.bl-steps__step:not(:last-child)::after { content: ""; position: absolute; left: 13px; top: 30px; bottom: 2px; width: 2px; background: var(--bl-accent); transform-origin: top; transform: scaleY(0); transition: transform 400ms cubic-bezier(.2,.9,.25,1); }
|
|
19
|
+
.bl-steps__step[data-state="done"]::after { transform: scaleY(1); }
|
|
20
|
+
.bl-steps__step[data-state="active"], .bl-steps__step[data-state="done"] { color: var(--bl-ink); }
|
|
21
|
+
.bl-steps__step[data-state="error"] { color: var(--bl-error); }
|
|
22
|
+
.bl-steps__icon { position: relative; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--bl-track); color: var(--bl-on-accent); }
|
|
23
|
+
.bl-steps__step[data-state="done"] .bl-steps__icon { background: var(--bl-accent); animation: bl-steps-pop-icon 360ms cubic-bezier(.3,1.6,.5,1); }
|
|
24
|
+
.bl-steps__step[data-state="error"] .bl-steps__icon { background: var(--bl-error); }
|
|
25
|
+
.bl-steps__spin { position: absolute; inset: 0; border-radius: 50%; border: 3px solid var(--bl-track); border-top-color: var(--bl-accent); animation: bl-steps-spin 800ms linear infinite; }
|
|
26
|
+
.bl-steps__icon path { stroke-dasharray: 20; stroke-dashoffset: 20; animation: bl-steps-draw 320ms 80ms cubic-bezier(.2,.9,.25,1) forwards; }
|
|
27
|
+
.bl-steps__text { display: grid; gap: 2px; padding-top: 4px; min-width: 0; }
|
|
28
|
+
.bl-steps__label { font-size: 15px; font-weight: 600; }
|
|
29
|
+
.bl-steps__desc { color: var(--bl-muted); font-size: 13px; line-height: 1.4; }
|
|
30
|
+
.bl-steps__actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
|
|
31
|
+
.bl-steps__btn { padding: 9px 16px; border: 0; border-radius: 8px; background: var(--bl-track); color: var(--bl-ink); font: inherit; font-size: 14px; font-weight: 600; cursor: pointer; }
|
|
32
|
+
.bl-steps__btn[data-primary] { background: var(--bl-accent); color: var(--bl-on-accent); }
|
|
33
|
+
.bl-steps__btn:hover { filter: brightness(1.15); }
|
|
34
|
+
.bl-steps__btn:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
|
|
35
|
+
.bl-steps__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
36
|
+
@keyframes bl-steps-spin { to { transform: rotate(360deg); } }
|
|
37
|
+
@keyframes bl-steps-draw { to { stroke-dashoffset: 0; } }
|
|
38
|
+
@keyframes bl-steps-pop-icon { 40% { transform: scale(1.25); } }
|
|
39
|
+
@keyframes bl-steps-fade { from { opacity: 0; } }
|
|
40
|
+
@keyframes bl-steps-pop { from { opacity: 0; transform: translateY(12px) scale(0.96); } }
|
|
41
|
+
@media (prefers-reduced-motion: reduce) {
|
|
42
|
+
.bl-steps__spin { animation-duration: 2.4s; }
|
|
43
|
+
.bl-steps__icon path { animation: none; stroke-dashoffset: 0; }
|
|
44
|
+
.bl-steps__fill, .bl-steps__step::after { transition: none; }
|
|
45
|
+
.bl-steps__step[data-state="done"] .bl-steps__icon, .bl-steps-overlay, .bl-steps-overlay > .bl-steps { animation: none; }
|
|
46
|
+
}
|
|
47
|
+
`;
|
|
48
|
+
function useStyles() {
|
|
49
|
+
useInsertionEffect(() => {
|
|
50
|
+
if (typeof document === "undefined") return;
|
|
51
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
52
|
+
style.textContent = CSS;
|
|
53
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
54
|
+
}, []);
|
|
55
|
+
}
|
|
56
|
+
const Check = () => /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2.5 7.5 5.5 10.5 11.5 3.5" }) });
|
|
57
|
+
const Cross = () => /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2.5 2.5 9.5 9.5M9.5 2.5 2.5 9.5" }) });
|
|
58
|
+
function StepLoader({
|
|
59
|
+
steps = [],
|
|
60
|
+
current = 0,
|
|
61
|
+
status = "running",
|
|
62
|
+
title = "Working on it",
|
|
63
|
+
open = true,
|
|
64
|
+
fixed = false,
|
|
65
|
+
onCancel,
|
|
66
|
+
onDone,
|
|
67
|
+
onRetry,
|
|
68
|
+
cancelLabel = "Cancel",
|
|
69
|
+
doneLabel = "Done",
|
|
70
|
+
retryLabel = "Retry",
|
|
71
|
+
successMessage = "All done!",
|
|
72
|
+
errorMessage = "Something went wrong.",
|
|
73
|
+
width = 440,
|
|
74
|
+
maxListHeight = 320,
|
|
75
|
+
backgroundColor = "#1a1c2c",
|
|
76
|
+
textColor = "#f4f4f4",
|
|
77
|
+
mutedColor = "#94b0c2",
|
|
78
|
+
trackColor = "#333c57",
|
|
79
|
+
lineColor = "#333c57",
|
|
80
|
+
accentColor = "#38b764",
|
|
81
|
+
onAccentColor = "#0b0b14",
|
|
82
|
+
errorColor = "#b13e53",
|
|
83
|
+
backdropColor = "rgba(8,8,16,0.65)",
|
|
84
|
+
radius = 16,
|
|
85
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
86
|
+
className = "",
|
|
87
|
+
style
|
|
88
|
+
}) {
|
|
89
|
+
useStyles();
|
|
90
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
91
|
+
const listRef = useRef(null);
|
|
92
|
+
const actionRef = useRef(null);
|
|
93
|
+
const total = steps.length;
|
|
94
|
+
const finished = status === "done";
|
|
95
|
+
const failed = status === "error";
|
|
96
|
+
const index = Math.max(0, Math.min(total - 1, current));
|
|
97
|
+
const doneCount = finished ? total : index;
|
|
98
|
+
const progress = total ? doneCount / total : 0;
|
|
99
|
+
const stateOf = (i) => finished || i < index ? "done" : i === index ? failed ? "error" : "active" : "pending";
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const list = listRef.current;
|
|
102
|
+
const node = list?.children[index];
|
|
103
|
+
if (!list || !node) return;
|
|
104
|
+
const top = node.offsetTop;
|
|
105
|
+
const bottom = top + node.offsetHeight;
|
|
106
|
+
const behavior = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth";
|
|
107
|
+
if (top < list.scrollTop) list.scrollTo({ top, behavior });
|
|
108
|
+
else if (bottom > list.scrollTop + list.clientHeight) list.scrollTo({ top: bottom - list.clientHeight, behavior });
|
|
109
|
+
}, [index]);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (open && fixed) actionRef.current?.focus({ preventScroll: true });
|
|
112
|
+
}, [open, fixed, status]);
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (!open || !fixed) return void 0;
|
|
115
|
+
const onKey = (event) => {
|
|
116
|
+
if (event.key === "Escape" && !finished && !failed) onCancel?.();
|
|
117
|
+
};
|
|
118
|
+
window.addEventListener("keydown", onKey);
|
|
119
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
120
|
+
}, [open, fixed, finished, failed, onCancel]);
|
|
121
|
+
if (!open) return null;
|
|
122
|
+
const announcement = finished ? successMessage : failed ? `${errorMessage} ${steps[index]?.label ?? ""}` : `Step ${index + 1} of ${total}: ${steps[index]?.label ?? ""}`;
|
|
123
|
+
const panel = /* @__PURE__ */ jsxs(
|
|
124
|
+
"div",
|
|
125
|
+
{
|
|
126
|
+
className: `bl-steps ${className}`,
|
|
127
|
+
role: fixed ? "dialog" : "region",
|
|
128
|
+
"aria-modal": fixed || void 0,
|
|
129
|
+
"aria-labelledby": `${uid}-title`,
|
|
130
|
+
"aria-busy": !finished && !failed,
|
|
131
|
+
"data-status": status,
|
|
132
|
+
style: {
|
|
133
|
+
"--bl-w": `${width}px`,
|
|
134
|
+
"--bl-max-h": `${maxListHeight}px`,
|
|
135
|
+
"--bl-p": progress,
|
|
136
|
+
"--bl-bg": backgroundColor,
|
|
137
|
+
"--bl-ink": textColor,
|
|
138
|
+
"--bl-muted": mutedColor,
|
|
139
|
+
"--bl-track": trackColor,
|
|
140
|
+
"--bl-line": lineColor,
|
|
141
|
+
"--bl-accent": accentColor,
|
|
142
|
+
"--bl-on-accent": onAccentColor,
|
|
143
|
+
"--bl-error": errorColor,
|
|
144
|
+
"--bl-radius": `${radius}px`,
|
|
145
|
+
"--bl-font": fontFamily,
|
|
146
|
+
...style
|
|
147
|
+
},
|
|
148
|
+
children: [
|
|
149
|
+
/* @__PURE__ */ jsx("h2", { id: `${uid}-title`, className: "bl-steps__title", children: finished ? successMessage : failed ? errorMessage : title }),
|
|
150
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-steps__meta", "aria-hidden": "true", children: [
|
|
151
|
+
/* @__PURE__ */ jsx("span", { children: finished ? `${total} of ${total} steps` : `Step ${index + 1} of ${total}` }),
|
|
152
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
153
|
+
Math.round(progress * 100),
|
|
154
|
+
"%"
|
|
155
|
+
] })
|
|
156
|
+
] }),
|
|
157
|
+
/* @__PURE__ */ jsx("div", { className: "bl-steps__bar", role: "progressbar", "aria-label": "Overall progress", "aria-valuemin": 0, "aria-valuemax": total, "aria-valuenow": doneCount, children: /* @__PURE__ */ jsx("div", { className: "bl-steps__fill" }) }),
|
|
158
|
+
/* @__PURE__ */ jsx("ol", { ref: listRef, className: "bl-steps__list", children: steps.map((step, i) => {
|
|
159
|
+
const state = stateOf(i);
|
|
160
|
+
return /* @__PURE__ */ jsxs("li", { className: "bl-steps__step", "data-state": state, "aria-current": state === "active" ? "step" : void 0, children: [
|
|
161
|
+
/* @__PURE__ */ jsxs("span", { className: "bl-steps__icon", "aria-hidden": "true", children: [
|
|
162
|
+
state === "active" && /* @__PURE__ */ jsx("span", { className: "bl-steps__spin" }),
|
|
163
|
+
state === "done" && /* @__PURE__ */ jsx(Check, {}),
|
|
164
|
+
state === "error" && /* @__PURE__ */ jsx(Cross, {})
|
|
165
|
+
] }),
|
|
166
|
+
/* @__PURE__ */ jsxs("span", { className: "bl-steps__text", children: [
|
|
167
|
+
/* @__PURE__ */ jsxs("span", { className: "bl-steps__label", children: [
|
|
168
|
+
step.label,
|
|
169
|
+
/* @__PURE__ */ jsx("span", { className: "bl-steps__sr", children: state === "done" ? " (done)" : state === "error" ? " (failed)" : state === "active" ? " (in progress)" : "" })
|
|
170
|
+
] }),
|
|
171
|
+
step.description && /* @__PURE__ */ jsx("span", { className: "bl-steps__desc", children: step.description })
|
|
172
|
+
] })
|
|
173
|
+
] }, step.id ?? i);
|
|
174
|
+
}) }),
|
|
175
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-steps__actions", children: [
|
|
176
|
+
finished && /* @__PURE__ */ jsx("button", { ref: actionRef, type: "button", className: "bl-steps__btn", "data-primary": "", onClick: () => onDone?.(), children: doneLabel }),
|
|
177
|
+
failed && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
178
|
+
onCancel && /* @__PURE__ */ jsx("button", { type: "button", className: "bl-steps__btn", onClick: () => onCancel(), children: cancelLabel }),
|
|
179
|
+
/* @__PURE__ */ jsx("button", { ref: actionRef, type: "button", className: "bl-steps__btn", "data-primary": "", onClick: () => onRetry?.(), children: retryLabel })
|
|
180
|
+
] }),
|
|
181
|
+
!finished && !failed && onCancel && /* @__PURE__ */ jsx("button", { ref: actionRef, type: "button", className: "bl-steps__btn", onClick: () => onCancel(), children: cancelLabel })
|
|
182
|
+
] }),
|
|
183
|
+
/* @__PURE__ */ jsx("p", { className: "bl-steps__sr", role: "status", children: announcement })
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
return fixed ? /* @__PURE__ */ jsx("div", { className: "bl-steps-overlay", style: { "--bl-backdrop": backdropColor }, children: panel }) : panel;
|
|
188
|
+
}
|
|
189
|
+
export {
|
|
190
|
+
StepLoader as default
|
|
191
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
type Verdict = true | string | void;
|
|
3
|
+
export interface FormStep {
|
|
4
|
+
id?: string;
|
|
5
|
+
title: ReactNode;
|
|
6
|
+
description?: ReactNode;
|
|
7
|
+
content?: ReactNode | ((api: {
|
|
8
|
+
index: number;
|
|
9
|
+
go: (index: number) => void;
|
|
10
|
+
}) => ReactNode);
|
|
11
|
+
/** Runs after the native field rules; a string is shown as the error. */
|
|
12
|
+
validate?: (data: FormData) => Verdict | Promise<Verdict>;
|
|
13
|
+
}
|
|
14
|
+
export interface StepperFormProps {
|
|
15
|
+
steps?: FormStep[];
|
|
16
|
+
defaultStep?: number;
|
|
17
|
+
onStepChange?: (index: number) => void;
|
|
18
|
+
/** All steps' fields together. */
|
|
19
|
+
onComplete?: (data: FormData) => void;
|
|
20
|
+
doneContent?: ReactNode;
|
|
21
|
+
backLabel?: string;
|
|
22
|
+
nextLabel?: string;
|
|
23
|
+
finishLabel?: string;
|
|
24
|
+
checkingLabel?: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
width?: number;
|
|
27
|
+
backgroundColor?: string;
|
|
28
|
+
textColor?: string;
|
|
29
|
+
mutedColor?: string;
|
|
30
|
+
trackColor?: string;
|
|
31
|
+
lineColor?: string;
|
|
32
|
+
accentColor?: string;
|
|
33
|
+
onAccentColor?: string;
|
|
34
|
+
errorColor?: string;
|
|
35
|
+
radius?: number;
|
|
36
|
+
fontFamily?: string;
|
|
37
|
+
className?: string;
|
|
38
|
+
style?: CSSProperties;
|
|
39
|
+
}
|
|
40
|
+
export default function StepperForm({ steps, defaultStep, onStepChange, onComplete, doneContent, backLabel, nextLabel, finishLabel, checkingLabel, label, width, backgroundColor, textColor, mutedColor, trackColor, lineColor, accentColor, onAccentColor, errorColor, radius, fontFamily, className, style }: StepperFormProps): import("react").JSX.Element;
|
|
41
|
+
export {};
|