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
package/dist/DropZone.js
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
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-dropzone";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-drop { display: grid; gap: 14px; width: 100%; max-width: var(--bl-w); font-family: var(--bl-font); color: var(--bl-ink); }
|
|
7
|
+
.bl-drop__zone { position: relative; display: block; width: 100%; padding: 0; border: 0; background: none; color: inherit; font: inherit; text-align: center; cursor: pointer; perspective: 800px; }
|
|
8
|
+
.bl-drop__zone:focus-visible { outline: none; }
|
|
9
|
+
.bl-drop__zone:focus-visible .bl-drop__card { border-color: var(--bl-accent); box-shadow: 0 0 0 4px var(--bl-ring); }
|
|
10
|
+
.bl-drop__card { display: grid; justify-items: center; gap: 10px; padding: 34px 20px; border-radius: var(--bl-radius); background: var(--bl-bg); border: 1.5px dashed var(--bl-line); transform: translate(var(--bl-mx, 0px), var(--bl-my, 0px)) rotateX(var(--bl-rx, 0deg)) rotateY(var(--bl-ry, 0deg)); transition: transform 220ms cubic-bezier(.2,.9,.25,1), box-shadow 200ms, border-color 200ms, background-color 200ms; }
|
|
11
|
+
.bl-drop[data-dragging] .bl-drop__card { background: var(--bl-over); border-color: var(--bl-accent); border-style: solid; box-shadow: 0 18px 40px -18px var(--bl-accent); transition-duration: 120ms; }
|
|
12
|
+
.bl-drop__icon { display: grid; place-items: center; width: 52px; height: 52px; border-radius: 50%; background: var(--bl-chip); color: var(--bl-accent); transition: transform 300ms cubic-bezier(.2,.9,.25,1.4); }
|
|
13
|
+
.bl-drop[data-dragging] .bl-drop__icon { transform: translateY(-6px) scale(1.12); }
|
|
14
|
+
.bl-drop__title { margin: 0; font-weight: 700; font-size: 16px; }
|
|
15
|
+
.bl-drop__hint { margin: 0; color: var(--bl-muted); font-size: 13px; }
|
|
16
|
+
.bl-drop__list { display: grid; gap: 8px; margin: 0; padding: 0; list-style: none; }
|
|
17
|
+
.bl-drop__file { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; align-items: center; gap: 12px; padding: 10px 12px; border-radius: calc(var(--bl-radius) * 0.7); background: var(--bl-bg); box-shadow: inset 0 0 0 1px var(--bl-line); animation: bl-drop-in 260ms cubic-bezier(.2,.9,.25,1); }
|
|
18
|
+
.bl-drop__thumb { display: grid; place-items: center; width: 44px; height: 44px; overflow: hidden; border-radius: 8px; background: var(--bl-chip); color: var(--bl-accent); font-size: 10px; font-weight: 800; text-transform: uppercase; }
|
|
19
|
+
.bl-drop__thumb img { width: 100%; height: 100%; object-fit: cover; }
|
|
20
|
+
.bl-drop__name { overflow: hidden; font-size: 14px; font-weight: 600; white-space: nowrap; text-overflow: ellipsis; }
|
|
21
|
+
.bl-drop__meta { margin-top: 2px; color: var(--bl-muted); font-size: 12px; }
|
|
22
|
+
.bl-drop__file[data-status="error"] .bl-drop__meta { color: var(--bl-error); }
|
|
23
|
+
.bl-drop__file[data-status="done"] .bl-drop__meta { color: var(--bl-success); }
|
|
24
|
+
.bl-drop__bar { height: 4px; margin-top: 6px; border-radius: 2px; background: var(--bl-chip); overflow: hidden; }
|
|
25
|
+
.bl-drop__fill { height: 100%; border-radius: 2px; background: var(--bl-accent); transform-origin: left; transition: transform 200ms linear, background-color 200ms; }
|
|
26
|
+
.bl-drop__file[data-status="done"] .bl-drop__fill { background: var(--bl-success); }
|
|
27
|
+
.bl-drop__file[data-status="error"] .bl-drop__fill { background: var(--bl-error); }
|
|
28
|
+
.bl-drop__remove { display: grid; place-items: center; width: 30px; height: 30px; border: 0; border-radius: 50%; background: transparent; color: var(--bl-muted); cursor: pointer; }
|
|
29
|
+
.bl-drop__remove:hover { background: var(--bl-chip); color: var(--bl-ink); }
|
|
30
|
+
.bl-drop__remove:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 1px; }
|
|
31
|
+
.bl-drop__rejects { margin: 0; padding: 10px 12px; border-radius: 10px; background: color-mix(in srgb, var(--bl-error) 14%, transparent); color: var(--bl-error); font-size: 13px; list-style: none; }
|
|
32
|
+
.bl-drop__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
33
|
+
@keyframes bl-drop-in { from { opacity: 0; transform: translateY(-6px); } }
|
|
34
|
+
@media (prefers-reduced-motion: reduce) { .bl-drop__card, .bl-drop__icon, .bl-drop__fill { transition: none; transform: none !important; } .bl-drop__file { animation: none; } }
|
|
35
|
+
`;
|
|
36
|
+
function useStyles() {
|
|
37
|
+
useInsertionEffect(() => {
|
|
38
|
+
if (typeof document === "undefined") return;
|
|
39
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
40
|
+
style.textContent = CSS;
|
|
41
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
42
|
+
}, []);
|
|
43
|
+
}
|
|
44
|
+
function formatBytes(bytes) {
|
|
45
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
46
|
+
const units = ["KB", "MB", "GB"];
|
|
47
|
+
let value = bytes / 1024;
|
|
48
|
+
let unit = 0;
|
|
49
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
50
|
+
value /= 1024;
|
|
51
|
+
unit++;
|
|
52
|
+
}
|
|
53
|
+
return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
|
|
54
|
+
}
|
|
55
|
+
function accepts(file, accept) {
|
|
56
|
+
if (!accept) return true;
|
|
57
|
+
const name = file.name.toLowerCase();
|
|
58
|
+
const type = (file.type || "").toLowerCase();
|
|
59
|
+
return accept.split(",").map((rule) => rule.trim().toLowerCase()).filter(Boolean).some((rule) => rule.startsWith(".") ? name.endsWith(rule) : rule.endsWith("/*") ? type.startsWith(rule.slice(0, -1)) : type === rule);
|
|
60
|
+
}
|
|
61
|
+
const extension = (name) => name.includes(".") ? name.split(".").pop().slice(0, 4) : "file";
|
|
62
|
+
let nextId = 1;
|
|
63
|
+
function DropZone({
|
|
64
|
+
accept,
|
|
65
|
+
multiple = true,
|
|
66
|
+
maxFiles = 10,
|
|
67
|
+
maxSize,
|
|
68
|
+
upload,
|
|
69
|
+
onFilesChange,
|
|
70
|
+
onReject,
|
|
71
|
+
title = "Drop files here",
|
|
72
|
+
hint,
|
|
73
|
+
disabled = false,
|
|
74
|
+
magnetic = true,
|
|
75
|
+
width = 520,
|
|
76
|
+
radius = 16,
|
|
77
|
+
backgroundColor = "#1a1c2c",
|
|
78
|
+
overColor = "#202a4a",
|
|
79
|
+
textColor = "#f4f4f4",
|
|
80
|
+
mutedColor = "#94b0c2",
|
|
81
|
+
lineColor = "rgba(255,255,255,0.22)",
|
|
82
|
+
chipColor = "rgba(255,255,255,0.07)",
|
|
83
|
+
accentColor = "#73eff7",
|
|
84
|
+
successColor = "#38b764",
|
|
85
|
+
errorColor = "#ef7d57",
|
|
86
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
87
|
+
className = "",
|
|
88
|
+
style
|
|
89
|
+
}) {
|
|
90
|
+
useStyles();
|
|
91
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
92
|
+
const inputRef = useRef(null);
|
|
93
|
+
const cardRef = useRef(null);
|
|
94
|
+
const depth = useRef(0);
|
|
95
|
+
const controllers = useRef(/* @__PURE__ */ new Map());
|
|
96
|
+
const [files, setFiles] = useState([]);
|
|
97
|
+
const [rejected, setRejected] = useState([]);
|
|
98
|
+
const [dragging, setDragging] = useState(false);
|
|
99
|
+
const [announcement, setAnnouncement] = useState("");
|
|
100
|
+
const filesRef = useRef(files);
|
|
101
|
+
filesRef.current = files;
|
|
102
|
+
const patch = (id, change) => setFiles((prev) => prev.map((f) => f.id === id ? { ...f, ...change } : f));
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
onFilesChange?.(files);
|
|
105
|
+
}, [files]);
|
|
106
|
+
useEffect(
|
|
107
|
+
() => () => {
|
|
108
|
+
filesRef.current.forEach((f) => f.preview && URL.revokeObjectURL(f.preview));
|
|
109
|
+
controllers.current.forEach((controller) => controller.abort());
|
|
110
|
+
},
|
|
111
|
+
[]
|
|
112
|
+
);
|
|
113
|
+
const start = (entry) => {
|
|
114
|
+
if (!upload) {
|
|
115
|
+
patch(entry.id, { status: "done", progress: 1 });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const controller = new AbortController();
|
|
119
|
+
controllers.current.set(entry.id, controller);
|
|
120
|
+
const onProgress = (fraction) => patch(entry.id, { progress: Math.max(0, Math.min(1, fraction)) });
|
|
121
|
+
Promise.resolve().then(() => upload(entry.file, { onProgress, signal: controller.signal })).then(
|
|
122
|
+
() => {
|
|
123
|
+
if (controller.signal.aborted) return;
|
|
124
|
+
patch(entry.id, { status: "done", progress: 1 });
|
|
125
|
+
setAnnouncement(`${entry.name} uploaded.`);
|
|
126
|
+
},
|
|
127
|
+
(error) => {
|
|
128
|
+
if (controller.signal.aborted) return;
|
|
129
|
+
patch(entry.id, { status: "error", error: error?.message ?? "Upload failed" });
|
|
130
|
+
setAnnouncement(`${entry.name} failed to upload.`);
|
|
131
|
+
}
|
|
132
|
+
).finally(() => controllers.current.delete(entry.id));
|
|
133
|
+
};
|
|
134
|
+
const addFiles = (list) => {
|
|
135
|
+
const incoming = Array.from(list ?? []);
|
|
136
|
+
const room = Math.max(0, (multiple ? maxFiles : 1) - filesRef.current.length);
|
|
137
|
+
const accepted = [];
|
|
138
|
+
const rejects = [];
|
|
139
|
+
for (const file of incoming) {
|
|
140
|
+
if (!accepts(file, accept)) rejects.push({ file, reason: "File type not allowed" });
|
|
141
|
+
else if (maxSize && file.size > maxSize) rejects.push({ file, reason: `Larger than ${formatBytes(maxSize)}` });
|
|
142
|
+
else if (accepted.length >= room) rejects.push({ file, reason: multiple ? `Limit of ${maxFiles} files` : "Only one file allowed" });
|
|
143
|
+
else accepted.push(file);
|
|
144
|
+
}
|
|
145
|
+
const entries = accepted.map((file) => ({
|
|
146
|
+
id: nextId++,
|
|
147
|
+
file,
|
|
148
|
+
name: file.name,
|
|
149
|
+
size: file.size,
|
|
150
|
+
type: file.type,
|
|
151
|
+
preview: file.type.startsWith("image/") ? URL.createObjectURL(file) : null,
|
|
152
|
+
progress: 0,
|
|
153
|
+
status: upload ? "uploading" : "done",
|
|
154
|
+
error: null
|
|
155
|
+
}));
|
|
156
|
+
setRejected(rejects);
|
|
157
|
+
if (rejects.length) onReject?.(rejects);
|
|
158
|
+
if (entries.length) {
|
|
159
|
+
setFiles((prev) => [...prev, ...entries]);
|
|
160
|
+
entries.forEach(start);
|
|
161
|
+
}
|
|
162
|
+
setAnnouncement(
|
|
163
|
+
[entries.length ? `${entries.length} file${entries.length === 1 ? "" : "s"} added.` : "", rejects.length ? `${rejects.length} rejected.` : ""].filter(Boolean).join(" ")
|
|
164
|
+
);
|
|
165
|
+
};
|
|
166
|
+
const remove = (id) => {
|
|
167
|
+
const entry = filesRef.current.find((f) => f.id === id);
|
|
168
|
+
controllers.current.get(id)?.abort();
|
|
169
|
+
controllers.current.delete(id);
|
|
170
|
+
if (entry?.preview) URL.revokeObjectURL(entry.preview);
|
|
171
|
+
setFiles((prev) => prev.filter((f) => f.id !== id));
|
|
172
|
+
if (entry) setAnnouncement(`${entry.name} removed.`);
|
|
173
|
+
};
|
|
174
|
+
const lean = (event) => {
|
|
175
|
+
const card = cardRef.current;
|
|
176
|
+
if (!magnetic || !card || window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
|
|
177
|
+
const rect = card.getBoundingClientRect();
|
|
178
|
+
const x = (event.clientX - rect.left) / rect.width - 0.5;
|
|
179
|
+
const y = (event.clientY - rect.top) / rect.height - 0.5;
|
|
180
|
+
card.style.setProperty("--bl-mx", `${x * 14}px`);
|
|
181
|
+
card.style.setProperty("--bl-my", `${y * 10}px`);
|
|
182
|
+
card.style.setProperty("--bl-rx", `${-y * 6}deg`);
|
|
183
|
+
card.style.setProperty("--bl-ry", `${x * 8}deg`);
|
|
184
|
+
};
|
|
185
|
+
const settle = () => {
|
|
186
|
+
for (const name of ["--bl-mx", "--bl-my", "--bl-rx", "--bl-ry"]) cardRef.current?.style.removeProperty(name);
|
|
187
|
+
};
|
|
188
|
+
const hasFiles = (event) => Array.from(event.dataTransfer?.types ?? []).includes("Files");
|
|
189
|
+
return /* @__PURE__ */ jsxs(
|
|
190
|
+
"div",
|
|
191
|
+
{
|
|
192
|
+
className: `bl-drop ${className}`,
|
|
193
|
+
"data-dragging": dragging || void 0,
|
|
194
|
+
style: {
|
|
195
|
+
"--bl-w": `${width}px`,
|
|
196
|
+
"--bl-radius": `${radius}px`,
|
|
197
|
+
"--bl-bg": backgroundColor,
|
|
198
|
+
"--bl-over": overColor,
|
|
199
|
+
"--bl-ink": textColor,
|
|
200
|
+
"--bl-muted": mutedColor,
|
|
201
|
+
"--bl-line": lineColor,
|
|
202
|
+
"--bl-chip": chipColor,
|
|
203
|
+
"--bl-accent": accentColor,
|
|
204
|
+
"--bl-ring": `color-mix(in srgb, ${accentColor} 22%, transparent)`,
|
|
205
|
+
"--bl-success": successColor,
|
|
206
|
+
"--bl-error": errorColor,
|
|
207
|
+
"--bl-font": fontFamily,
|
|
208
|
+
...style
|
|
209
|
+
},
|
|
210
|
+
children: [
|
|
211
|
+
/* @__PURE__ */ jsx(
|
|
212
|
+
"button",
|
|
213
|
+
{
|
|
214
|
+
type: "button",
|
|
215
|
+
className: "bl-drop__zone",
|
|
216
|
+
disabled,
|
|
217
|
+
"aria-describedby": `${uid}-hint`,
|
|
218
|
+
onClick: () => inputRef.current?.click(),
|
|
219
|
+
onDragEnter: (event) => {
|
|
220
|
+
if (!hasFiles(event) || disabled) return;
|
|
221
|
+
event.preventDefault();
|
|
222
|
+
depth.current += 1;
|
|
223
|
+
setDragging(true);
|
|
224
|
+
},
|
|
225
|
+
onDragOver: (event) => {
|
|
226
|
+
if (!hasFiles(event) || disabled) return;
|
|
227
|
+
event.preventDefault();
|
|
228
|
+
event.dataTransfer.dropEffect = "copy";
|
|
229
|
+
lean(event);
|
|
230
|
+
},
|
|
231
|
+
onDragLeave: () => {
|
|
232
|
+
depth.current = Math.max(0, depth.current - 1);
|
|
233
|
+
if (!depth.current) {
|
|
234
|
+
setDragging(false);
|
|
235
|
+
settle();
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
onDrop: (event) => {
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
depth.current = 0;
|
|
241
|
+
setDragging(false);
|
|
242
|
+
settle();
|
|
243
|
+
if (!disabled) addFiles(event.dataTransfer.files);
|
|
244
|
+
},
|
|
245
|
+
children: /* @__PURE__ */ jsxs("span", { ref: cardRef, className: "bl-drop__card", children: [
|
|
246
|
+
/* @__PURE__ */ jsx("span", { className: "bl-drop__icon", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M12 16V4M7 9l5-5 5 5M4 16v3a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-3" }) }) }),
|
|
247
|
+
/* @__PURE__ */ jsx("span", { className: "bl-drop__title", children: dragging ? "Release to add" : title }),
|
|
248
|
+
/* @__PURE__ */ jsx("span", { id: `${uid}-hint`, className: "bl-drop__hint", children: hint ?? [`Click or press Enter to browse`, accept && `\xB7 ${accept}`, maxSize && `\xB7 up to ${formatBytes(maxSize)}`].filter(Boolean).join(" ") })
|
|
249
|
+
] })
|
|
250
|
+
}
|
|
251
|
+
),
|
|
252
|
+
/* @__PURE__ */ jsx(
|
|
253
|
+
"input",
|
|
254
|
+
{
|
|
255
|
+
ref: inputRef,
|
|
256
|
+
type: "file",
|
|
257
|
+
hidden: true,
|
|
258
|
+
accept,
|
|
259
|
+
multiple,
|
|
260
|
+
onChange: (event) => {
|
|
261
|
+
addFiles(event.target.files);
|
|
262
|
+
event.target.value = "";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
),
|
|
266
|
+
rejected.length > 0 && /* @__PURE__ */ jsx("ul", { className: "bl-drop__rejects", children: rejected.map(({ file, reason }, i) => /* @__PURE__ */ jsxs("li", { children: [
|
|
267
|
+
file.name,
|
|
268
|
+
": ",
|
|
269
|
+
reason
|
|
270
|
+
] }, `${file.name}-${i}`)) }),
|
|
271
|
+
files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "bl-drop__list", "aria-label": "Files", children: files.map((entry) => /* @__PURE__ */ jsxs("li", { className: "bl-drop__file", "data-status": entry.status, children: [
|
|
272
|
+
/* @__PURE__ */ jsx("span", { className: "bl-drop__thumb", "aria-hidden": "true", children: entry.preview ? /* @__PURE__ */ jsx("img", { src: entry.preview, alt: "" }) : extension(entry.name) }),
|
|
273
|
+
/* @__PURE__ */ jsxs("span", { style: { minWidth: 0 }, children: [
|
|
274
|
+
/* @__PURE__ */ jsx("span", { className: "bl-drop__name", style: { display: "block" }, children: entry.name }),
|
|
275
|
+
/* @__PURE__ */ jsxs("span", { className: "bl-drop__meta", style: { display: "block" }, children: [
|
|
276
|
+
formatBytes(entry.size),
|
|
277
|
+
" \xB7 ",
|
|
278
|
+
entry.status === "uploading" ? `${Math.round(entry.progress * 100)}%` : entry.status === "done" ? "Done" : entry.error
|
|
279
|
+
] }),
|
|
280
|
+
upload && /* @__PURE__ */ jsx(
|
|
281
|
+
"span",
|
|
282
|
+
{
|
|
283
|
+
className: "bl-drop__bar",
|
|
284
|
+
style: { display: "block" },
|
|
285
|
+
role: "progressbar",
|
|
286
|
+
"aria-label": `${entry.name} upload`,
|
|
287
|
+
"aria-valuemin": 0,
|
|
288
|
+
"aria-valuemax": 100,
|
|
289
|
+
"aria-valuenow": Math.round(entry.progress * 100),
|
|
290
|
+
children: /* @__PURE__ */ jsx("span", { className: "bl-drop__fill", style: { display: "block", transform: `scaleX(${entry.progress})` } })
|
|
291
|
+
}
|
|
292
|
+
)
|
|
293
|
+
] }),
|
|
294
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "bl-drop__remove", "aria-label": `Remove ${entry.name}`, onClick: () => remove(entry.id), children: /* @__PURE__ */ jsx("svg", { width: "12", height: "12", viewBox: "0 0 12 12", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2 2l8 8M10 2 2 10" }) }) })
|
|
295
|
+
] }, entry.id)) }),
|
|
296
|
+
/* @__PURE__ */ jsx("span", { className: "bl-drop__sr", "aria-live": "polite", children: announcement })
|
|
297
|
+
]
|
|
298
|
+
}
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
export {
|
|
302
|
+
DropZone as default,
|
|
303
|
+
formatBytes
|
|
304
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface IslandView {
|
|
3
|
+
/** Width of the island in this view, in px. */
|
|
4
|
+
width: number;
|
|
5
|
+
/** Height of the island in this view, in px. */
|
|
6
|
+
height: number;
|
|
7
|
+
/** Corner radius; defaults to a pill (half the height, capped at 44). */
|
|
8
|
+
radius?: number;
|
|
9
|
+
/** Announced politely to screen readers when this view appears. */
|
|
10
|
+
label?: string;
|
|
11
|
+
content?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export interface DynamicIslandProps {
|
|
14
|
+
/** Every state the island can take. */
|
|
15
|
+
views?: Record<string, IslandView>;
|
|
16
|
+
/** The current view's key. Change it and the island morphs. */
|
|
17
|
+
view?: string;
|
|
18
|
+
/** Makes the island pressable (Enter/Space too). Leave it off when the view has its own buttons. */
|
|
19
|
+
onPress?: (view: string) => void;
|
|
20
|
+
/** Morph length, in ms. */
|
|
21
|
+
duration?: number;
|
|
22
|
+
/** Overshoot: 0 is a plain ease-out, 1 is very springy. */
|
|
23
|
+
bounce?: number;
|
|
24
|
+
backgroundColor?: string;
|
|
25
|
+
textColor?: string;
|
|
26
|
+
lineColor?: string;
|
|
27
|
+
accentColor?: string;
|
|
28
|
+
fontFamily?: string;
|
|
29
|
+
className?: string;
|
|
30
|
+
style?: CSSProperties;
|
|
31
|
+
}
|
|
32
|
+
export default function DynamicIsland({ views, view, onPress, duration, bounce, backgroundColor, textColor, lineColor, accentColor, fontFamily, className, style }: DynamicIslandProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-dynamic-island";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-island { position: relative; display: block; margin: 0 auto; overflow: hidden; width: var(--bl-w); height: var(--bl-h); max-width: 100%; border-radius: var(--bl-r); background: var(--bl-bg); color: var(--bl-ink); font-family: var(--bl-font); box-shadow: 0 0 0 1px var(--bl-line), 0 12px 30px -12px rgba(0,0,0,0.6); transition: width var(--bl-dur) var(--bl-ease), height var(--bl-dur) var(--bl-ease), border-radius var(--bl-dur) var(--bl-ease); }
|
|
7
|
+
[role="button"].bl-island { cursor: pointer; }
|
|
8
|
+
.bl-island:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 3px; }
|
|
9
|
+
.bl-island__layer { position: absolute; inset: 0; display: flex; align-items: center; transition: opacity calc(var(--bl-dur) * 0.6) ease, filter calc(var(--bl-dur) * 0.6) ease, transform var(--bl-dur) var(--bl-ease); }
|
|
10
|
+
.bl-island__layer[data-state="in"] { animation: bl-island-in calc(var(--bl-dur) * 0.75) ease calc(var(--bl-dur) * 0.15) both; }
|
|
11
|
+
@keyframes bl-island-in { from { opacity: 0; filter: blur(6px); transform: scale(0.92); } }
|
|
12
|
+
.bl-island__layer[data-state="out"] { opacity: 0; filter: blur(6px); transform: scale(1.04); pointer-events: none; }
|
|
13
|
+
.bl-island__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
14
|
+
@media (prefers-reduced-motion: reduce) { .bl-island, .bl-island__layer { transition: none !important; animation: none !important; } }
|
|
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
|
+
function DynamicIsland({
|
|
25
|
+
views = {},
|
|
26
|
+
view = "idle",
|
|
27
|
+
onPress,
|
|
28
|
+
duration = 520,
|
|
29
|
+
bounce = 0.35,
|
|
30
|
+
backgroundColor = "#000000",
|
|
31
|
+
textColor = "#f4f4f4",
|
|
32
|
+
lineColor = "rgba(255,255,255,0.08)",
|
|
33
|
+
accentColor = "#73eff7",
|
|
34
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
35
|
+
className = "",
|
|
36
|
+
style
|
|
37
|
+
}) {
|
|
38
|
+
useStyles();
|
|
39
|
+
const current = views[view] ?? { width: 126, height: 36 };
|
|
40
|
+
const [layers, setLayers] = useState(() => [{ key: view, state: "in" }]);
|
|
41
|
+
const timer = useRef(0);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
setLayers((prev) => {
|
|
44
|
+
if (prev.some((layer) => layer.key === view && layer.state === "in")) return prev;
|
|
45
|
+
return [...prev.filter((layer) => layer.key !== view).map((layer) => ({ ...layer, state: "out" })), { key: view, state: "in" }];
|
|
46
|
+
});
|
|
47
|
+
clearTimeout(timer.current);
|
|
48
|
+
timer.current = setTimeout(() => setLayers((prev) => prev.filter((layer) => layer.state !== "out")), duration);
|
|
49
|
+
}, [view, duration]);
|
|
50
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
51
|
+
const radius = current.radius ?? Math.min(current.height / 2, 44);
|
|
52
|
+
const ease = `cubic-bezier(.3, ${(1 + bounce * 1.2).toFixed(2)}, .5, 1)`;
|
|
53
|
+
return (
|
|
54
|
+
// Always a div, so switching onPress on and off never remounts it mid-animation.
|
|
55
|
+
/* @__PURE__ */ jsxs(
|
|
56
|
+
"div",
|
|
57
|
+
{
|
|
58
|
+
className: `bl-island ${className}`,
|
|
59
|
+
role: onPress ? "button" : void 0,
|
|
60
|
+
tabIndex: onPress ? 0 : void 0,
|
|
61
|
+
onClick: onPress ? () => onPress(view) : void 0,
|
|
62
|
+
onKeyDown: onPress ? (event) => {
|
|
63
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
onPress(view);
|
|
66
|
+
}
|
|
67
|
+
} : void 0,
|
|
68
|
+
"aria-label": onPress ? current.label ?? view : void 0,
|
|
69
|
+
style: {
|
|
70
|
+
"--bl-w": `${current.width}px`,
|
|
71
|
+
"--bl-h": `${current.height}px`,
|
|
72
|
+
"--bl-r": `${radius}px`,
|
|
73
|
+
"--bl-dur": `${duration}ms`,
|
|
74
|
+
"--bl-ease": ease,
|
|
75
|
+
"--bl-bg": backgroundColor,
|
|
76
|
+
"--bl-ink": textColor,
|
|
77
|
+
"--bl-line": lineColor,
|
|
78
|
+
"--bl-accent": accentColor,
|
|
79
|
+
"--bl-font": fontFamily,
|
|
80
|
+
...style
|
|
81
|
+
},
|
|
82
|
+
children: [
|
|
83
|
+
layers.map((layer) => /* @__PURE__ */ jsx("span", { className: "bl-island__layer", "data-state": layer.state, "aria-hidden": layer.state === "out" || void 0, inert: layer.state === "out" ? true : void 0, children: views[layer.key]?.content }, layer.key)),
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: "bl-island__sr", role: "status", "aria-live": "polite", children: current.label ?? "" })
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
DynamicIsland as default
|
|
92
|
+
};
|
package/dist/EdgeFX.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface EdgeFXProps {
|
|
3
|
+
/** The card inside the frame. */
|
|
4
|
+
children?: ReactNode;
|
|
5
|
+
/** Marching pixel dashes, a band circling the frame, a pointer-following light, or a flash across the surface. */
|
|
6
|
+
mode?: 'ants' | 'energy' | 'glow' | 'glare';
|
|
7
|
+
/** Hover also lights it while focus is inside. */
|
|
8
|
+
active?: 'always' | 'hover';
|
|
9
|
+
/** Edge thickness in px. */
|
|
10
|
+
width?: number;
|
|
11
|
+
radius?: number;
|
|
12
|
+
/** Dash length for ants, in px. */
|
|
13
|
+
dash?: number;
|
|
14
|
+
/** Cycle speed multiplier. */
|
|
15
|
+
speed?: number;
|
|
16
|
+
/** How far from the card the glow notices the pointer, in px. */
|
|
17
|
+
reach?: number;
|
|
18
|
+
color?: string;
|
|
19
|
+
secondColor?: string;
|
|
20
|
+
/** The resting edge. */
|
|
21
|
+
lineColor?: string;
|
|
22
|
+
backgroundColor?: string;
|
|
23
|
+
glareColor?: string;
|
|
24
|
+
className?: string;
|
|
25
|
+
style?: CSSProperties;
|
|
26
|
+
}
|
|
27
|
+
export default function EdgeFX({ children, mode, active, width, radius, dash, speed, reach, color, secondColor, lineColor, backgroundColor, glareColor, className, style }: EdgeFXProps): import("react").JSX.Element;
|
package/dist/EdgeFX.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useInsertionEffect, useRef } from "react";
|
|
4
|
+
const STYLE_ID = "bl-edge-fx";
|
|
5
|
+
const CSS = `
|
|
6
|
+
@property --bl-edge-turn { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
|
|
7
|
+
.bl-edge { --bl-on: 1; position: relative; display: block; box-sizing: border-box; padding: var(--bl-w); border-radius: var(--bl-radius); background: var(--bl-line); isolation: isolate; }
|
|
8
|
+
.bl-edge__body { position: relative; z-index: 1; height: 100%; overflow: hidden; border-radius: calc(var(--bl-radius) - var(--bl-w)); background: var(--bl-bg); }
|
|
9
|
+
.bl-edge__ring { position: absolute; inset: 0; z-index: 0; border-radius: inherit; opacity: var(--bl-on); transition: opacity 200ms steps(3); pointer-events: none; }
|
|
10
|
+
.bl-edge[data-active="hover"] { --bl-on: 0; }
|
|
11
|
+
.bl-edge[data-active="hover"]:hover, .bl-edge[data-active="hover"]:focus-within { --bl-on: 1; }
|
|
12
|
+
.bl-edge[data-mode="ants"] .bl-edge__ring { background:
|
|
13
|
+
linear-gradient(90deg, var(--bl-a) 50%, var(--bl-b) 0) 0 0 / var(--bl-dash) var(--bl-w) repeat-x,
|
|
14
|
+
linear-gradient(90deg, var(--bl-a) 50%, var(--bl-b) 0) 0 100% / var(--bl-dash) var(--bl-w) repeat-x,
|
|
15
|
+
linear-gradient(0deg, var(--bl-a) 50%, var(--bl-b) 0) 0 0 / var(--bl-w) var(--bl-dash) repeat-y,
|
|
16
|
+
linear-gradient(0deg, var(--bl-a) 50%, var(--bl-b) 0) 100% 0 / var(--bl-w) var(--bl-dash) repeat-y;
|
|
17
|
+
animation: bl-edge-march var(--bl-speed) steps(4) infinite; }
|
|
18
|
+
.bl-edge[data-mode="energy"] .bl-edge__ring { background: conic-gradient(from var(--bl-edge-turn), transparent 0 55%, var(--bl-b) 70%, var(--bl-a) 80%, transparent 92%); animation: bl-edge-spin var(--bl-speed) steps(36) infinite; }
|
|
19
|
+
.bl-edge[data-mode="glow"] .bl-edge__ring { background: radial-gradient(circle var(--bl-reach) at var(--bl-x, 50%) var(--bl-y, -40%), var(--bl-a), var(--bl-b) 45%, transparent 75%); }
|
|
20
|
+
.bl-edge[data-mode="glow"][data-active="hover"] { --bl-on: 0; }
|
|
21
|
+
.bl-edge[data-mode="glow"][data-lit] { --bl-on: 1; }
|
|
22
|
+
.bl-edge__glare { position: absolute; inset: 0; z-index: 2; border-radius: inherit; overflow: hidden; pointer-events: none; }
|
|
23
|
+
.bl-edge__glare::before { content: ""; position: absolute; top: -50%; bottom: -50%; left: 0; width: 40%; background: linear-gradient(100deg, transparent, var(--bl-glare), transparent); transform: translateX(-150%) skewX(-12deg); }
|
|
24
|
+
.bl-edge[data-mode="glare"]:hover .bl-edge__glare::before, .bl-edge[data-mode="glare"]:focus-within .bl-edge__glare::before, .bl-edge[data-mode="glare"][data-active="always"] .bl-edge__glare::before { animation: bl-edge-glare var(--bl-speed) steps(12) infinite; }
|
|
25
|
+
@keyframes bl-edge-march { to { background-position: var(--bl-dash) 0, calc(var(--bl-dash) * -1) 100%, 0 calc(var(--bl-dash) * -1), 100% var(--bl-dash); } }
|
|
26
|
+
@keyframes bl-edge-spin { to { --bl-edge-turn: 360deg; } }
|
|
27
|
+
@keyframes bl-edge-glare { 0% { transform: translateX(-150%) skewX(-12deg); } 60%, 100% { transform: translateX(350%) skewX(-12deg); } }
|
|
28
|
+
@media (prefers-reduced-motion: reduce) { .bl-edge .bl-edge__ring, .bl-edge .bl-edge__glare::before { animation: none !important; transition: none; } }
|
|
29
|
+
`;
|
|
30
|
+
function useStyles() {
|
|
31
|
+
useInsertionEffect(() => {
|
|
32
|
+
if (typeof document === "undefined") return;
|
|
33
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
34
|
+
style.textContent = CSS;
|
|
35
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
36
|
+
}, []);
|
|
37
|
+
}
|
|
38
|
+
function EdgeFX({
|
|
39
|
+
children,
|
|
40
|
+
mode = "ants",
|
|
41
|
+
active = "always",
|
|
42
|
+
width = 3,
|
|
43
|
+
radius = 14,
|
|
44
|
+
dash = 12,
|
|
45
|
+
speed = 0.8,
|
|
46
|
+
reach = 160,
|
|
47
|
+
color = "#73eff7",
|
|
48
|
+
secondColor = "#41a6f6",
|
|
49
|
+
lineColor = "#333c57",
|
|
50
|
+
backgroundColor = "#1a1c2c",
|
|
51
|
+
glareColor = "rgba(244,244,244,0.22)",
|
|
52
|
+
className = "",
|
|
53
|
+
style
|
|
54
|
+
}) {
|
|
55
|
+
useStyles();
|
|
56
|
+
const rootRef = useRef(null);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (mode !== "glow") return void 0;
|
|
59
|
+
const root = rootRef.current;
|
|
60
|
+
let raf = 0;
|
|
61
|
+
const onMove = (event) => {
|
|
62
|
+
if (raf) return;
|
|
63
|
+
raf = requestAnimationFrame(() => {
|
|
64
|
+
raf = 0;
|
|
65
|
+
const r = root.getBoundingClientRect();
|
|
66
|
+
const x = event.clientX - r.left;
|
|
67
|
+
const y = event.clientY - r.top;
|
|
68
|
+
const near = x > -reach && y > -reach && x < r.width + reach && y < r.height + reach;
|
|
69
|
+
root.style.setProperty("--bl-x", `${x.toFixed(0)}px`);
|
|
70
|
+
root.style.setProperty("--bl-y", `${y.toFixed(0)}px`);
|
|
71
|
+
root.toggleAttribute("data-lit", near);
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
window.addEventListener("pointermove", onMove, { passive: true });
|
|
75
|
+
return () => {
|
|
76
|
+
cancelAnimationFrame(raf);
|
|
77
|
+
window.removeEventListener("pointermove", onMove);
|
|
78
|
+
};
|
|
79
|
+
}, [mode, reach]);
|
|
80
|
+
return /* @__PURE__ */ jsxs(
|
|
81
|
+
"div",
|
|
82
|
+
{
|
|
83
|
+
ref: rootRef,
|
|
84
|
+
className: `bl-edge ${className}`,
|
|
85
|
+
"data-mode": mode,
|
|
86
|
+
"data-active": active,
|
|
87
|
+
style: { "--bl-w": `${width}px`, "--bl-radius": `${radius}px`, "--bl-dash": `${dash}px`, "--bl-speed": `${speed * (mode === "energy" ? 4 : mode === "glare" ? 3 : 1)}s`, "--bl-reach": `${reach}px`, "--bl-a": color, "--bl-b": mode === "ants" ? lineColor : secondColor, "--bl-line": lineColor, "--bl-bg": backgroundColor, "--bl-glare": glareColor, ...style },
|
|
88
|
+
children: [
|
|
89
|
+
/* @__PURE__ */ jsx("span", { className: "bl-edge__ring", "aria-hidden": "true" }),
|
|
90
|
+
/* @__PURE__ */ jsx("div", { className: "bl-edge__body", children }),
|
|
91
|
+
mode === "glare" && /* @__PURE__ */ jsx("span", { className: "bl-edge__glare", "aria-hidden": "true" })
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
export {
|
|
97
|
+
EdgeFX as default
|
|
98
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
export interface ElasticStringProps {
|
|
3
|
+
/** How many strings. */
|
|
4
|
+
strings?: number;
|
|
5
|
+
height?: number;
|
|
6
|
+
/** Fixed spacing between strings in px (default: spread evenly). */
|
|
7
|
+
gap?: number;
|
|
8
|
+
thickness?: number;
|
|
9
|
+
/** Colours, cycled per string. */
|
|
10
|
+
colors?: string[];
|
|
11
|
+
/** How close (px) the pointer must come to catch a string. */
|
|
12
|
+
grab?: number;
|
|
13
|
+
/** How far (px) a string bends before it slips free. */
|
|
14
|
+
maxPull?: number;
|
|
15
|
+
stiffness?: number;
|
|
16
|
+
damping?: number;
|
|
17
|
+
/** Called on release with the string index and a 0–1 strength. */
|
|
18
|
+
onPluck?: (index: number, strength: number) => void;
|
|
19
|
+
className?: string;
|
|
20
|
+
style?: CSSProperties;
|
|
21
|
+
}
|
|
22
|
+
export default function ElasticString({ strings, height, gap, thickness, colors, grab, maxPull, stiffness, damping, onPluck, className, style }: ElasticStringProps): import("react").JSX.Element;
|