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,111 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-skeleton-shimmer";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-skel { position: relative; }
|
|
7
|
+
.bl-skel__content { transition: opacity var(--bl-fade) ease; }
|
|
8
|
+
.bl-skel[data-loading] .bl-skel__content { opacity: 0; visibility: hidden; }
|
|
9
|
+
.bl-skel__bones { position: absolute; inset: 0; pointer-events: none; transition: opacity var(--bl-fade) ease; }
|
|
10
|
+
.bl-skel__bones[data-leaving] { opacity: 0; }
|
|
11
|
+
.bl-skel__bone { position: absolute; left: var(--x); top: var(--y); width: var(--w); height: var(--h); border-radius: var(--r); background: linear-gradient(100deg, var(--bl-base) 0 40%, var(--bl-shine) 50%, var(--bl-base) 60% 100%); background-size: 300% 100%; animation: bl-skel-shine var(--bl-speed) linear infinite; animation-delay: calc(var(--x01) * var(--bl-speed) * -0.5); }
|
|
12
|
+
.bl-skel__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
13
|
+
@keyframes bl-skel-shine { from { background-position: 100% 0; } to { background-position: 0 0; } }
|
|
14
|
+
@media (prefers-reduced-motion: reduce) { .bl-skel__bone { animation: none; } .bl-skel__content, .bl-skel__bones { transition: none; } }
|
|
15
|
+
`;
|
|
16
|
+
function useStyles() {
|
|
17
|
+
useInsertionEffect(() => {
|
|
18
|
+
if (typeof document === "undefined") return;
|
|
19
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
20
|
+
style.textContent = CSS;
|
|
21
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
22
|
+
}, []);
|
|
23
|
+
}
|
|
24
|
+
const BLOCKS = 'img, svg, video, canvas, picture, iframe, input, textarea, select, button, [role="img"], [data-skeleton]';
|
|
25
|
+
function trace(root, lineScale) {
|
|
26
|
+
const origin = root.getBoundingClientRect();
|
|
27
|
+
const bones = [];
|
|
28
|
+
const add = (rect, radius) => {
|
|
29
|
+
if (rect.width < 2 || rect.height < 2) return;
|
|
30
|
+
bones.push({ x: rect.left - origin.left, y: rect.top - origin.top, w: rect.width, h: rect.height, r: radius });
|
|
31
|
+
};
|
|
32
|
+
for (const node of root.querySelectorAll(BLOCKS)) {
|
|
33
|
+
if (node.parentElement?.closest(BLOCKS) && !node.matches("[data-skeleton]")) continue;
|
|
34
|
+
if (node.closest('[data-skeleton="skip"]')) continue;
|
|
35
|
+
const radius = getComputedStyle(node).borderRadius;
|
|
36
|
+
add(node.getBoundingClientRect(), radius && radius !== "0px" ? radius : "6px");
|
|
37
|
+
}
|
|
38
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
39
|
+
acceptNode: (text) => text.textContent.trim() && !text.parentElement?.closest(`${BLOCKS}, [data-skeleton="skip"]`) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
|
|
40
|
+
});
|
|
41
|
+
const range = document.createRange();
|
|
42
|
+
const lines = [];
|
|
43
|
+
for (let text = walker.nextNode(); text; text = walker.nextNode()) {
|
|
44
|
+
range.selectNodeContents(text);
|
|
45
|
+
for (const rect of range.getClientRects()) {
|
|
46
|
+
if (rect.width < 2) continue;
|
|
47
|
+
const line = lines.find((l) => Math.abs(l.top - rect.top) < rect.height / 2 && rect.left <= l.right + 12 && rect.right >= l.left - 12);
|
|
48
|
+
if (line) {
|
|
49
|
+
line.left = Math.min(line.left, rect.left);
|
|
50
|
+
line.right = Math.max(line.right, rect.right);
|
|
51
|
+
} else {
|
|
52
|
+
lines.push({ top: rect.top, left: rect.left, right: rect.right, height: rect.height });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (const line of lines) {
|
|
57
|
+
const h = line.height * lineScale;
|
|
58
|
+
add({ left: line.left, top: line.top + (line.height - h) / 2, width: line.right - line.left, height: h }, `${Math.min(6, h / 2)}px`);
|
|
59
|
+
}
|
|
60
|
+
return { bones, width: origin.width };
|
|
61
|
+
}
|
|
62
|
+
function SkeletonShimmer({
|
|
63
|
+
loading = false,
|
|
64
|
+
children,
|
|
65
|
+
lineScale = 0.62,
|
|
66
|
+
fade = 320,
|
|
67
|
+
speed = 1400,
|
|
68
|
+
baseColor = "#262b44",
|
|
69
|
+
shineColor = "#3a4266",
|
|
70
|
+
loadingLabel = "Loading\u2026",
|
|
71
|
+
className = "",
|
|
72
|
+
style
|
|
73
|
+
}) {
|
|
74
|
+
useStyles();
|
|
75
|
+
const contentRef = useRef(null);
|
|
76
|
+
const [skeleton, setSkeleton] = useState({ bones: [], width: 1 });
|
|
77
|
+
const [showBones, setShowBones] = useState(loading);
|
|
78
|
+
const [leaving, setLeaving] = useState(false);
|
|
79
|
+
useLayoutEffect(() => {
|
|
80
|
+
if (!loading) return void 0;
|
|
81
|
+
setShowBones(true);
|
|
82
|
+
setLeaving(false);
|
|
83
|
+
const node = contentRef.current;
|
|
84
|
+
const measure = () => setSkeleton(trace(node, lineScale));
|
|
85
|
+
measure();
|
|
86
|
+
const resize = new ResizeObserver(measure);
|
|
87
|
+
resize.observe(node);
|
|
88
|
+
return () => resize.disconnect();
|
|
89
|
+
}, [loading, lineScale]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (loading || !showBones) return void 0;
|
|
92
|
+
setLeaving(true);
|
|
93
|
+
const timer = setTimeout(() => setShowBones(false), fade);
|
|
94
|
+
return () => clearTimeout(timer);
|
|
95
|
+
}, [loading, showBones, fade]);
|
|
96
|
+
return /* @__PURE__ */ jsxs("div", { className: `bl-skel ${className}`, "data-loading": loading || void 0, "aria-busy": loading, style: { "--bl-fade": `${fade}ms`, "--bl-speed": `${speed}ms`, "--bl-base": baseColor, "--bl-shine": shineColor, ...style }, children: [
|
|
97
|
+
/* @__PURE__ */ jsx("div", { ref: contentRef, className: "bl-skel__content", inert: loading || void 0, children }),
|
|
98
|
+
showBones && /* @__PURE__ */ jsx("div", { className: "bl-skel__bones", "data-leaving": leaving || void 0, "aria-hidden": "true", children: skeleton.bones.map((bone, i) => /* @__PURE__ */ jsx(
|
|
99
|
+
"span",
|
|
100
|
+
{
|
|
101
|
+
className: "bl-skel__bone",
|
|
102
|
+
style: { "--x": `${bone.x}px`, "--y": `${bone.y}px`, "--w": `${bone.w}px`, "--h": `${bone.h}px`, "--r": bone.r, "--x01": (bone.x / skeleton.width).toFixed(3) }
|
|
103
|
+
},
|
|
104
|
+
i
|
|
105
|
+
)) }),
|
|
106
|
+
loading && /* @__PURE__ */ jsx("span", { className: "bl-skel__sr", role: "status", children: loadingLabel })
|
|
107
|
+
] });
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
SkeletonShimmer as default
|
|
111
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type ReactNode, type CSSProperties } from 'react';
|
|
2
|
+
export type SketchType = 'underline' | 'highlight' | 'strike' | 'box' | 'circle' | 'bracket' | 'cross';
|
|
3
|
+
export interface SketchHighlightProps {
|
|
4
|
+
/** The inline text being annotated. */
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
type?: SketchType;
|
|
7
|
+
color?: string;
|
|
8
|
+
strokeWidth?: number;
|
|
9
|
+
/** Gap around the text, in px. */
|
|
10
|
+
padding?: number;
|
|
11
|
+
/** Pen passes. */
|
|
12
|
+
iterations?: number;
|
|
13
|
+
/** How shaky the pen is. */
|
|
14
|
+
roughness?: number;
|
|
15
|
+
/** Draw time, in ms. */
|
|
16
|
+
duration?: number;
|
|
17
|
+
/** Delay before drawing, in ms (chain them to annotate in sequence). */
|
|
18
|
+
delay?: number;
|
|
19
|
+
/** false hides the annotation. */
|
|
20
|
+
show?: boolean;
|
|
21
|
+
/** Draw when scrolled into view, or at once. */
|
|
22
|
+
trigger?: 'view' | 'mount';
|
|
23
|
+
highlightOpacity?: number;
|
|
24
|
+
className?: string;
|
|
25
|
+
style?: CSSProperties;
|
|
26
|
+
}
|
|
27
|
+
export default function SketchHighlight({ children, type, color, strokeWidth, padding, iterations, roughness, duration, delay, show, trigger, highlightOpacity, className, style }: SketchHighlightProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-sketch";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-sketch { position: relative; }
|
|
7
|
+
.bl-sketch__svg { position: absolute; left: 0; top: 0; width: 1px; height: 1px; overflow: visible; pointer-events: none; z-index: 0; }
|
|
8
|
+
.bl-sketch[data-type="highlight"] .bl-sketch__svg { z-index: -1; }
|
|
9
|
+
.bl-sketch[data-type="highlight"] { isolation: isolate; }
|
|
10
|
+
.bl-sketch__svg path { fill: none; stroke-linecap: round; stroke-linejoin: round; }
|
|
11
|
+
`;
|
|
12
|
+
function useStyles() {
|
|
13
|
+
useInsertionEffect(() => {
|
|
14
|
+
if (typeof document === "undefined") return;
|
|
15
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
16
|
+
style.textContent = CSS;
|
|
17
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
18
|
+
}, []);
|
|
19
|
+
}
|
|
20
|
+
function seeded(seed) {
|
|
21
|
+
let s = seed >>> 0 || 1;
|
|
22
|
+
return () => {
|
|
23
|
+
s = s * 1664525 + 1013904223 >>> 0;
|
|
24
|
+
return s / 4294967296;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const hash = (text) => [...text].reduce((h, c) => h * 31 + c.charCodeAt(0) >>> 0, 7);
|
|
28
|
+
function roughLine(x1, y1, x2, y2, rand, roughness) {
|
|
29
|
+
const j = () => (rand() - 0.5) * roughness;
|
|
30
|
+
const mx = (x1 + x2) / 2 + j() * 2;
|
|
31
|
+
const my = (y1 + y2) / 2 + j() * 2;
|
|
32
|
+
return `M${(x1 + j()).toFixed(1)},${(y1 + j()).toFixed(1)} Q${mx.toFixed(1)},${my.toFixed(1)} ${(x2 + j()).toFixed(1)},${(y2 + j()).toFixed(1)}`;
|
|
33
|
+
}
|
|
34
|
+
function roughEllipse(cx, cy, rx, ry, rand, roughness) {
|
|
35
|
+
const points = [];
|
|
36
|
+
const start = rand() * Math.PI * 2;
|
|
37
|
+
const sweep = Math.PI * 2 * (1.08 + rand() * 0.1);
|
|
38
|
+
const steps = 28;
|
|
39
|
+
for (let i = 0; i <= steps; i++) {
|
|
40
|
+
const a = start + i / steps * sweep;
|
|
41
|
+
const wobble = 1 + (rand() - 0.5) * (roughness / Math.max(rx, ry));
|
|
42
|
+
points.push([cx + Math.cos(a) * rx * wobble, cy + Math.sin(a) * ry * wobble]);
|
|
43
|
+
}
|
|
44
|
+
let d = `M${points[0][0].toFixed(1)},${points[0][1].toFixed(1)}`;
|
|
45
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
46
|
+
const [x, y] = points[i];
|
|
47
|
+
const [nx, ny] = points[i + 1];
|
|
48
|
+
d += ` Q${x.toFixed(1)},${y.toFixed(1)} ${((x + nx) / 2).toFixed(1)},${((y + ny) / 2).toFixed(1)}`;
|
|
49
|
+
}
|
|
50
|
+
return d;
|
|
51
|
+
}
|
|
52
|
+
function buildPaths(type, lines, box, { padding, iterations, roughness, seed }) {
|
|
53
|
+
const rand = seeded(seed);
|
|
54
|
+
const paths = [];
|
|
55
|
+
const pass = (n) => Array.from({ length: n }, (_, i) => i);
|
|
56
|
+
if (type === "underline" || type === "strike" || type === "highlight") {
|
|
57
|
+
for (const r of lines) {
|
|
58
|
+
const y = type === "underline" ? r.y + r.h + 2 : type === "strike" ? r.y + r.h * 0.55 : r.y + r.h / 2;
|
|
59
|
+
for (const i of pass(type === "highlight" ? 1 : iterations)) paths.push(roughLine(r.x - padding, y + i * 1.5, r.x + r.w + padding, y - i, rand, roughness));
|
|
60
|
+
}
|
|
61
|
+
} else if (type === "box") {
|
|
62
|
+
const x1 = box.x - padding;
|
|
63
|
+
const y1 = box.y - padding;
|
|
64
|
+
const x2 = box.x + box.w + padding;
|
|
65
|
+
const y2 = box.y + box.h + padding;
|
|
66
|
+
for (let i = 0; i < iterations; i++) paths.push(roughLine(x1, y1, x2, y1, rand, roughness), roughLine(x2, y1, x2, y2, rand, roughness), roughLine(x2, y2, x1, y2, rand, roughness), roughLine(x1, y2, x1, y1, rand, roughness));
|
|
67
|
+
} else if (type === "circle") {
|
|
68
|
+
for (let i = 0; i < iterations; i++) paths.push(roughEllipse(box.x + box.w / 2, box.y + box.h / 2, box.w / 2 + padding * 1.6, box.h / 2 + padding * 1.2, rand, roughness * 2));
|
|
69
|
+
} else if (type === "bracket") {
|
|
70
|
+
const x1 = box.x - padding;
|
|
71
|
+
const x2 = box.x + box.w + padding;
|
|
72
|
+
const y1 = box.y - padding / 2;
|
|
73
|
+
const y2 = box.y + box.h + padding / 2;
|
|
74
|
+
const arm = Math.min(10, box.w / 6);
|
|
75
|
+
paths.push(`${roughLine(x1 + arm, y1, x1, y1, rand, roughness)} ${roughLine(x1, y1, x1, y2, rand, roughness).replace("M", "L")} ${roughLine(x1, y2, x1 + arm, y2, rand, roughness).replace("M", "L")}`);
|
|
76
|
+
paths.push(`${roughLine(x2 - arm, y1, x2, y1, rand, roughness)} ${roughLine(x2, y1, x2, y2, rand, roughness).replace("M", "L")} ${roughLine(x2, y2, x2 - arm, y2, rand, roughness).replace("M", "L")}`);
|
|
77
|
+
} else if (type === "cross") {
|
|
78
|
+
paths.push(roughLine(box.x - padding, box.y, box.x + box.w + padding, box.y + box.h, rand, roughness), roughLine(box.x - padding, box.y + box.h, box.x + box.w + padding, box.y, rand, roughness));
|
|
79
|
+
}
|
|
80
|
+
return paths;
|
|
81
|
+
}
|
|
82
|
+
function SketchHighlight({
|
|
83
|
+
children,
|
|
84
|
+
type = "underline",
|
|
85
|
+
color = "#ef7d57",
|
|
86
|
+
strokeWidth = 2.5,
|
|
87
|
+
padding = 4,
|
|
88
|
+
iterations = 2,
|
|
89
|
+
roughness = 2.5,
|
|
90
|
+
duration = 700,
|
|
91
|
+
delay = 0,
|
|
92
|
+
show = true,
|
|
93
|
+
trigger = "view",
|
|
94
|
+
highlightOpacity = 0.55,
|
|
95
|
+
className = "",
|
|
96
|
+
style
|
|
97
|
+
}) {
|
|
98
|
+
useStyles();
|
|
99
|
+
const ref = useRef(null);
|
|
100
|
+
const svgRef = useRef(null);
|
|
101
|
+
const [paths, setPaths] = useState([]);
|
|
102
|
+
const [visible, setVisible] = useState(trigger !== "view");
|
|
103
|
+
const drawn = useRef(false);
|
|
104
|
+
useLayoutEffect(() => {
|
|
105
|
+
const node = ref.current;
|
|
106
|
+
const svg = svgRef.current;
|
|
107
|
+
if (!node || !svg) return void 0;
|
|
108
|
+
const measure = () => {
|
|
109
|
+
const origin = svg.getBoundingClientRect();
|
|
110
|
+
const lines = [...node.getClientRects()].map((r) => ({ x: r.left - origin.left, y: r.top - origin.top, w: r.width, h: r.height }));
|
|
111
|
+
if (!lines.length) return;
|
|
112
|
+
const x = Math.min(...lines.map((l) => l.x));
|
|
113
|
+
const y = Math.min(...lines.map((l) => l.y));
|
|
114
|
+
const box = { x, y, w: Math.max(...lines.map((l) => l.x + l.w)) - x, h: Math.max(...lines.map((l) => l.y + l.h)) - y };
|
|
115
|
+
setPaths(buildPaths(type, lines, box, { padding, iterations, roughness, seed: hash(`${type}:${node.textContent}`) }));
|
|
116
|
+
};
|
|
117
|
+
measure();
|
|
118
|
+
const observer = new ResizeObserver(measure);
|
|
119
|
+
observer.observe(node);
|
|
120
|
+
window.addEventListener("resize", measure);
|
|
121
|
+
return () => {
|
|
122
|
+
observer.disconnect();
|
|
123
|
+
window.removeEventListener("resize", measure);
|
|
124
|
+
};
|
|
125
|
+
}, [type, padding, iterations, roughness]);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (trigger !== "view" || visible) return void 0;
|
|
128
|
+
const observer = new IntersectionObserver(([entry]) => entry.isIntersecting && setVisible(true), { threshold: 0.6 });
|
|
129
|
+
observer.observe(ref.current);
|
|
130
|
+
return () => observer.disconnect();
|
|
131
|
+
}, [trigger, visible]);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
const svg = svgRef.current;
|
|
134
|
+
if (!svg || !paths.length) return void 0;
|
|
135
|
+
const elements = [...svg.querySelectorAll("path")];
|
|
136
|
+
const active = show && visible;
|
|
137
|
+
if (!active) {
|
|
138
|
+
elements.forEach((el) => el.style.opacity = "0");
|
|
139
|
+
drawn.current = false;
|
|
140
|
+
return void 0;
|
|
141
|
+
}
|
|
142
|
+
if (drawn.current || window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
|
143
|
+
elements.forEach((el) => {
|
|
144
|
+
el.style.opacity = "1";
|
|
145
|
+
el.style.strokeDasharray = "";
|
|
146
|
+
el.style.strokeDashoffset = "";
|
|
147
|
+
});
|
|
148
|
+
drawn.current = true;
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
drawn.current = true;
|
|
152
|
+
const each = duration / elements.length;
|
|
153
|
+
const animations = elements.map((el, i) => {
|
|
154
|
+
const length = el.getTotalLength();
|
|
155
|
+
el.style.opacity = "1";
|
|
156
|
+
el.style.strokeDasharray = `${length}`;
|
|
157
|
+
el.style.strokeDashoffset = `${length}`;
|
|
158
|
+
return el.animate([{ strokeDashoffset: length }, { strokeDashoffset: 0 }], { duration: each, delay: delay + i * each, easing: "cubic-bezier(.45,0,.3,1)", fill: "forwards" });
|
|
159
|
+
});
|
|
160
|
+
return () => animations.forEach((a) => a.finish());
|
|
161
|
+
}, [paths, show, visible, duration, delay]);
|
|
162
|
+
const highlight = type === "highlight";
|
|
163
|
+
return /* @__PURE__ */ jsxs("span", { ref, className: `bl-sketch ${className}`, "data-type": type, style, children: [
|
|
164
|
+
/* @__PURE__ */ jsx("svg", { ref: svgRef, className: "bl-sketch__svg", "aria-hidden": "true", focusable: "false", children: paths.map((d, i) => /* @__PURE__ */ jsx(
|
|
165
|
+
"path",
|
|
166
|
+
{
|
|
167
|
+
d,
|
|
168
|
+
stroke: color,
|
|
169
|
+
strokeWidth: highlight ? "1.05em" : strokeWidth,
|
|
170
|
+
strokeOpacity: highlight ? highlightOpacity : 1,
|
|
171
|
+
style: { opacity: 0, strokeLinecap: highlight ? "butt" : "round" }
|
|
172
|
+
},
|
|
173
|
+
i
|
|
174
|
+
)) }),
|
|
175
|
+
children
|
|
176
|
+
] });
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
SketchHighlight as default
|
|
180
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface TabItem {
|
|
3
|
+
id: string;
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
icon?: ReactNode;
|
|
6
|
+
/** Panel content; give items content to render a linked tab panel. */
|
|
7
|
+
content?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export interface SlideTabsProps {
|
|
10
|
+
items?: TabItem[];
|
|
11
|
+
/** Controlled selected id. */
|
|
12
|
+
value?: string;
|
|
13
|
+
/** Uncontrolled starting id (defaults to the first item). */
|
|
14
|
+
defaultValue?: string;
|
|
15
|
+
onChange?: (id: string, item: TabItem) => void;
|
|
16
|
+
variant?: 'pill' | 'underline';
|
|
17
|
+
/** Leading edge moves first and the trailing edge follows. */
|
|
18
|
+
stretch?: boolean;
|
|
19
|
+
/** Arrow keys select immediately, or only move focus until Enter/Space. */
|
|
20
|
+
activation?: 'automatic' | 'manual';
|
|
21
|
+
/** Slide length, in ms. */
|
|
22
|
+
duration?: number;
|
|
23
|
+
/** Accessible name of the tab list. */
|
|
24
|
+
label?: string;
|
|
25
|
+
fontSize?: number;
|
|
26
|
+
gap?: number;
|
|
27
|
+
padding?: number;
|
|
28
|
+
radius?: number;
|
|
29
|
+
textColor?: string;
|
|
30
|
+
mutedColor?: string;
|
|
31
|
+
trackColor?: string;
|
|
32
|
+
lineColor?: string;
|
|
33
|
+
activeColor?: string;
|
|
34
|
+
activeTextColor?: string;
|
|
35
|
+
accentColor?: string;
|
|
36
|
+
fontFamily?: string;
|
|
37
|
+
className?: string;
|
|
38
|
+
style?: CSSProperties;
|
|
39
|
+
}
|
|
40
|
+
export default function SlideTabs({ items, value, defaultValue, onChange, variant, stretch, activation, duration, label, fontSize, gap, padding, radius, textColor, mutedColor, trackColor, lineColor, activeColor, activeTextColor, accentColor, fontFamily, className, style }: SlideTabsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-slide-tabs";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-stabs { min-width: 0; max-width: 100%; font-family: var(--bl-font); color: var(--bl-ink); }
|
|
7
|
+
.bl-stabs__scroller { position: relative; overflow-x: auto; scrollbar-width: none; -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--bl-fade-l), #000 calc(100% - var(--bl-fade-r)), transparent 100%); mask-image: linear-gradient(to right, transparent 0, #000 var(--bl-fade-l), #000 calc(100% - var(--bl-fade-r)), transparent 100%); }
|
|
8
|
+
.bl-stabs__scroller::-webkit-scrollbar { display: none; }
|
|
9
|
+
.bl-stabs__list { position: relative; display: inline-flex; gap: var(--bl-gap); min-width: 100%; padding: var(--bl-pad); border-radius: var(--bl-radius); background: var(--bl-track); box-sizing: border-box; }
|
|
10
|
+
.bl-stabs[data-variant="underline"] .bl-stabs__list { background: transparent; padding: 0; box-shadow: inset 0 -1px 0 var(--bl-line); border-radius: 0; }
|
|
11
|
+
.bl-stabs__indicator { position: absolute; top: var(--bl-pad); bottom: var(--bl-pad); border-radius: calc(var(--bl-radius) - var(--bl-pad) / 2); background: var(--bl-active-bg); box-shadow: 0 1px 3px rgba(0,0,0,0.3); pointer-events: none; }
|
|
12
|
+
.bl-stabs[data-variant="underline"] .bl-stabs__indicator { top: auto; bottom: 0; height: 3px; border-radius: 3px; background: var(--bl-accent); box-shadow: none; }
|
|
13
|
+
.bl-stabs__tab { position: relative; z-index: 1; display: inline-flex; align-items: center; gap: 8px; flex: 0 0 auto; padding: 9px 16px; border: 0; border-radius: calc(var(--bl-radius) - var(--bl-pad) / 2); background: transparent; color: var(--bl-muted); font: inherit; font-size: var(--bl-size); font-weight: 600; white-space: nowrap; cursor: pointer; transition: color 200ms; }
|
|
14
|
+
.bl-stabs[data-variant="underline"] .bl-stabs__tab { padding: 12px 14px; }
|
|
15
|
+
.bl-stabs__tab:hover { color: var(--bl-ink); }
|
|
16
|
+
.bl-stabs__tab[aria-selected="true"] { color: var(--bl-active-ink); }
|
|
17
|
+
.bl-stabs[data-variant="underline"] .bl-stabs__tab[aria-selected="true"] { color: var(--bl-ink); }
|
|
18
|
+
.bl-stabs__tab:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: -2px; }
|
|
19
|
+
.bl-stabs__panel { padding-top: 18px; outline: none; }
|
|
20
|
+
.bl-stabs__panel:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 4px; }
|
|
21
|
+
@media (prefers-reduced-motion: reduce) { .bl-stabs__indicator { transition: none !important; } }
|
|
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 SlideTabs({
|
|
32
|
+
items = [],
|
|
33
|
+
value,
|
|
34
|
+
defaultValue,
|
|
35
|
+
onChange,
|
|
36
|
+
variant = "pill",
|
|
37
|
+
stretch = true,
|
|
38
|
+
activation = "automatic",
|
|
39
|
+
duration = 380,
|
|
40
|
+
label = "Tabs",
|
|
41
|
+
fontSize = 14,
|
|
42
|
+
gap = 2,
|
|
43
|
+
padding = 4,
|
|
44
|
+
radius = 12,
|
|
45
|
+
textColor = "#f4f4f4",
|
|
46
|
+
mutedColor = "#94b0c2",
|
|
47
|
+
trackColor = "rgba(255,255,255,0.06)",
|
|
48
|
+
lineColor = "rgba(255,255,255,0.12)",
|
|
49
|
+
activeColor = "#f4f4f4",
|
|
50
|
+
activeTextColor = "#1a1c2c",
|
|
51
|
+
accentColor = "#73eff7",
|
|
52
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
53
|
+
className = "",
|
|
54
|
+
style
|
|
55
|
+
}) {
|
|
56
|
+
useStyles();
|
|
57
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
58
|
+
const [inner, setInner] = useState(defaultValue ?? items[0]?.id);
|
|
59
|
+
const selected = value ?? inner;
|
|
60
|
+
const [focusIndex, setFocusIndex] = useState(null);
|
|
61
|
+
const scrollerRef = useRef(null);
|
|
62
|
+
const tabRefs = useRef([]);
|
|
63
|
+
const indicatorRef = useRef(null);
|
|
64
|
+
const last = useRef(null);
|
|
65
|
+
const [fades, setFades] = useState([0, 0]);
|
|
66
|
+
const selectedIndex = Math.max(0, items.findIndex((item) => item.id === selected));
|
|
67
|
+
const select = (index) => {
|
|
68
|
+
const id = items[index].id;
|
|
69
|
+
if (value === void 0) setInner(id);
|
|
70
|
+
onChange?.(id, items[index]);
|
|
71
|
+
};
|
|
72
|
+
const place = (animate = true) => {
|
|
73
|
+
const tab = tabRefs.current[selectedIndex];
|
|
74
|
+
const indicator = indicatorRef.current;
|
|
75
|
+
if (!tab || !indicator) return;
|
|
76
|
+
const left = tab.offsetLeft;
|
|
77
|
+
const right = tab.offsetParent.clientWidth - (tab.offsetLeft + tab.offsetWidth);
|
|
78
|
+
const prev = last.current;
|
|
79
|
+
if (!animate || !prev) indicator.style.transition = "none";
|
|
80
|
+
else {
|
|
81
|
+
const movingRight = left > prev.left;
|
|
82
|
+
const lead = `${duration * 0.6}ms cubic-bezier(.3,.8,.3,1)`;
|
|
83
|
+
const follow = `${duration}ms cubic-bezier(.5,0,.2,1) ${stretch ? duration * 0.18 : 0}ms`;
|
|
84
|
+
indicator.style.transition = movingRight ? `right ${lead}, left ${follow}` : `left ${lead}, right ${follow}`;
|
|
85
|
+
}
|
|
86
|
+
indicator.style.left = `${left}px`;
|
|
87
|
+
indicator.style.right = `${right}px`;
|
|
88
|
+
last.current = { left, right };
|
|
89
|
+
};
|
|
90
|
+
const updateFades = () => {
|
|
91
|
+
const el = scrollerRef.current;
|
|
92
|
+
if (!el) return;
|
|
93
|
+
const next = [el.scrollLeft > 2 ? 28 : 0, el.scrollLeft + el.clientWidth < el.scrollWidth - 2 ? 28 : 0];
|
|
94
|
+
setFades((prev) => prev[0] === next[0] && prev[1] === next[1] ? prev : next);
|
|
95
|
+
};
|
|
96
|
+
useLayoutEffect(() => {
|
|
97
|
+
place(true);
|
|
98
|
+
const tab = tabRefs.current[selectedIndex];
|
|
99
|
+
const scroller = scrollerRef.current;
|
|
100
|
+
if (tab && scroller && scroller.scrollWidth > scroller.clientWidth) {
|
|
101
|
+
const target = tab.offsetLeft - (scroller.clientWidth - tab.offsetWidth) / 2;
|
|
102
|
+
scroller.scrollTo({ left: target, behavior: window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth" });
|
|
103
|
+
}
|
|
104
|
+
}, [selectedIndex, variant]);
|
|
105
|
+
useLayoutEffect(() => {
|
|
106
|
+
const observer = new ResizeObserver(() => {
|
|
107
|
+
place(false);
|
|
108
|
+
updateFades();
|
|
109
|
+
});
|
|
110
|
+
observer.observe(scrollerRef.current);
|
|
111
|
+
tabRefs.current.forEach((tab) => tab && observer.observe(tab));
|
|
112
|
+
updateFades();
|
|
113
|
+
return () => observer.disconnect();
|
|
114
|
+
}, [items.length, selectedIndex]);
|
|
115
|
+
const onKeyDown = (event) => {
|
|
116
|
+
const current = focusIndex ?? selectedIndex;
|
|
117
|
+
const keys = { ArrowRight: current + 1, ArrowLeft: current - 1, Home: 0, End: items.length - 1 };
|
|
118
|
+
if (!(event.key in keys)) {
|
|
119
|
+
if ((event.key === "Enter" || event.key === " ") && activation === "manual" && focusIndex != null) {
|
|
120
|
+
event.preventDefault();
|
|
121
|
+
select(focusIndex);
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
const next = (keys[event.key] + items.length) % items.length;
|
|
127
|
+
setFocusIndex(next);
|
|
128
|
+
tabRefs.current[next]?.focus();
|
|
129
|
+
if (activation === "automatic") select(next);
|
|
130
|
+
};
|
|
131
|
+
const panel = items[selectedIndex]?.content;
|
|
132
|
+
return /* @__PURE__ */ jsxs(
|
|
133
|
+
"div",
|
|
134
|
+
{
|
|
135
|
+
className: `bl-stabs ${className}`,
|
|
136
|
+
"data-variant": variant,
|
|
137
|
+
style: {
|
|
138
|
+
"--bl-gap": `${gap}px`,
|
|
139
|
+
"--bl-pad": variant === "pill" ? `${padding}px` : "0px",
|
|
140
|
+
"--bl-radius": `${radius}px`,
|
|
141
|
+
"--bl-size": `${fontSize}px`,
|
|
142
|
+
"--bl-ink": textColor,
|
|
143
|
+
"--bl-muted": mutedColor,
|
|
144
|
+
"--bl-track": trackColor,
|
|
145
|
+
"--bl-line": lineColor,
|
|
146
|
+
"--bl-active-bg": activeColor,
|
|
147
|
+
"--bl-active-ink": activeTextColor,
|
|
148
|
+
"--bl-accent": accentColor,
|
|
149
|
+
"--bl-font": fontFamily,
|
|
150
|
+
"--bl-fade-l": `${fades[0]}px`,
|
|
151
|
+
"--bl-fade-r": `${fades[1]}px`,
|
|
152
|
+
...style
|
|
153
|
+
},
|
|
154
|
+
children: [
|
|
155
|
+
/* @__PURE__ */ jsx("div", { ref: scrollerRef, className: "bl-stabs__scroller", onScroll: updateFades, children: /* @__PURE__ */ jsxs("div", { className: "bl-stabs__list", role: "tablist", "aria-label": label, onKeyDown, children: [
|
|
156
|
+
/* @__PURE__ */ jsx("span", { ref: indicatorRef, className: "bl-stabs__indicator", "aria-hidden": "true" }),
|
|
157
|
+
items.map((item, index) => /* @__PURE__ */ jsxs(
|
|
158
|
+
"button",
|
|
159
|
+
{
|
|
160
|
+
ref: (node) => {
|
|
161
|
+
tabRefs.current[index] = node;
|
|
162
|
+
},
|
|
163
|
+
id: `${uid}-tab-${index}`,
|
|
164
|
+
className: "bl-stabs__tab",
|
|
165
|
+
role: "tab",
|
|
166
|
+
type: "button",
|
|
167
|
+
"aria-selected": index === selectedIndex,
|
|
168
|
+
"aria-controls": item.content !== void 0 ? `${uid}-panel` : void 0,
|
|
169
|
+
tabIndex: index === (focusIndex ?? selectedIndex) ? 0 : -1,
|
|
170
|
+
onClick: () => {
|
|
171
|
+
setFocusIndex(index);
|
|
172
|
+
select(index);
|
|
173
|
+
},
|
|
174
|
+
onBlur: (event) => !event.currentTarget.parentElement.contains(event.relatedTarget) && setFocusIndex(null),
|
|
175
|
+
children: [
|
|
176
|
+
item.icon,
|
|
177
|
+
item.label
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
item.id
|
|
181
|
+
))
|
|
182
|
+
] }) }),
|
|
183
|
+
panel !== void 0 && /* @__PURE__ */ jsx("div", { id: `${uid}-panel`, className: "bl-stabs__panel", role: "tabpanel", "aria-labelledby": `${uid}-tab-${selectedIndex}`, tabIndex: 0, children: panel })
|
|
184
|
+
]
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
export {
|
|
189
|
+
SlideTabs as default
|
|
190
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface NavLink {
|
|
3
|
+
href: string;
|
|
4
|
+
label: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
export interface SmartNavbarProps {
|
|
7
|
+
/** Logo area. */
|
|
8
|
+
brand?: ReactNode;
|
|
9
|
+
brandHref?: string;
|
|
10
|
+
links?: NavLink[];
|
|
11
|
+
/** Buttons on the right (also shown in the mobile menu). */
|
|
12
|
+
actions?: ReactNode;
|
|
13
|
+
/** href of the current page; marked with aria-current. */
|
|
14
|
+
current?: string;
|
|
15
|
+
/** Turns links into client-side navigation (default navigation is prevented). */
|
|
16
|
+
onNavigate?: (href: string, link?: NavLink) => void;
|
|
17
|
+
/** Scroll distance after which the bar becomes a pill, in px. */
|
|
18
|
+
shrinkAt?: number;
|
|
19
|
+
/** Hide while scrolling down; scrolling up or focusing a link shows it. */
|
|
20
|
+
hideOnScroll?: boolean;
|
|
21
|
+
/** Never hide above this scroll position, in px. */
|
|
22
|
+
hideAfter?: number;
|
|
23
|
+
height?: number;
|
|
24
|
+
pillHeight?: number;
|
|
25
|
+
pillWidth?: number;
|
|
26
|
+
/** Gap above the pill, in px. */
|
|
27
|
+
pillTop?: number;
|
|
28
|
+
padding?: number;
|
|
29
|
+
radius?: number;
|
|
30
|
+
/** Pill backdrop blur, in px. */
|
|
31
|
+
blur?: number;
|
|
32
|
+
duration?: number;
|
|
33
|
+
textColor?: string;
|
|
34
|
+
mutedColor?: string;
|
|
35
|
+
/** Bar background while at the top. */
|
|
36
|
+
topColor?: string;
|
|
37
|
+
topLineColor?: string;
|
|
38
|
+
pillColor?: string;
|
|
39
|
+
lineColor?: string;
|
|
40
|
+
hoverColor?: string;
|
|
41
|
+
accentColor?: string;
|
|
42
|
+
fontFamily?: string;
|
|
43
|
+
/** Accessible name of the nav landmark. */
|
|
44
|
+
label?: string;
|
|
45
|
+
className?: string;
|
|
46
|
+
style?: CSSProperties;
|
|
47
|
+
}
|
|
48
|
+
export default function SmartNavbar({ brand, brandHref, links, actions, current, onNavigate, shrinkAt, hideOnScroll, hideAfter, height, pillHeight, pillWidth, pillTop, padding, radius, blur, duration, textColor, mutedColor, topColor, topLineColor, pillColor, lineColor, hoverColor, accentColor, fontFamily, label, className, style }: SmartNavbarProps): import("react").JSX.Element;
|