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,33 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface MorphPopoverProps {
|
|
3
|
+
/** Button content; it stays visible while the panel grows. */
|
|
4
|
+
label?: ReactNode;
|
|
5
|
+
icon?: ReactNode;
|
|
6
|
+
/** Panel content. The function form receives close(). */
|
|
7
|
+
children?: ReactNode | ((api: {
|
|
8
|
+
close: () => void;
|
|
9
|
+
}) => ReactNode);
|
|
10
|
+
/** Accessible name of the panel (defaults to the label when it's a string). */
|
|
11
|
+
panelLabel?: string;
|
|
12
|
+
/** Panel width, in px; its height follows the content. */
|
|
13
|
+
panelWidth?: number;
|
|
14
|
+
/** Which edge stays pinned to the button. */
|
|
15
|
+
align?: 'start' | 'center' | 'end';
|
|
16
|
+
/** Which way the panel grows. */
|
|
17
|
+
side?: 'bottom' | 'top';
|
|
18
|
+
/** Button height, in px. */
|
|
19
|
+
height?: number;
|
|
20
|
+
radius?: number;
|
|
21
|
+
/** Morph length, in ms. */
|
|
22
|
+
duration?: number;
|
|
23
|
+
open?: boolean;
|
|
24
|
+
onOpenChange?: (open: boolean) => void;
|
|
25
|
+
backgroundColor?: string;
|
|
26
|
+
textColor?: string;
|
|
27
|
+
lineColor?: string;
|
|
28
|
+
accentColor?: string;
|
|
29
|
+
fontFamily?: string;
|
|
30
|
+
className?: string;
|
|
31
|
+
style?: CSSProperties;
|
|
32
|
+
}
|
|
33
|
+
export default function MorphPopover({ label, icon, children, panelLabel, panelWidth, align, side, height, radius, duration, open: controlledOpen, onOpenChange, backgroundColor, textColor, lineColor, accentColor, fontFamily, className, style }: MorphPopoverProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useInsertionEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
+
const STYLE_ID = "bl-morph-popover";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-mp { position: relative; display: inline-block; vertical-align: top; font-family: var(--bl-font); }
|
|
7
|
+
.bl-mp__button { display: inline-flex; align-items: center; gap: 8px; height: var(--bl-h); padding: 0 18px; border: 0; border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); font: inherit; font-weight: 600; cursor: pointer; box-shadow: inset 0 0 0 1px var(--bl-line); white-space: nowrap; }
|
|
8
|
+
.bl-mp__button:focus-visible { outline: 2px solid var(--bl-accent); outline-offset: 2px; }
|
|
9
|
+
.bl-mp__button[data-hidden] { visibility: hidden; }
|
|
10
|
+
.bl-mp__shell { position: absolute; z-index: 50; overflow: hidden; border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); box-shadow: inset 0 0 0 1px var(--bl-line), 0 24px 50px -20px rgba(0,0,0,0.65); outline: none; }
|
|
11
|
+
.bl-mp[data-align="start"] .bl-mp__shell { left: 0; }
|
|
12
|
+
.bl-mp[data-align="end"] .bl-mp__shell { right: 0; }
|
|
13
|
+
.bl-mp[data-align="center"] .bl-mp__shell { left: 50%; transform: translateX(-50%); }
|
|
14
|
+
.bl-mp[data-side="bottom"] .bl-mp__shell { top: 0; }
|
|
15
|
+
.bl-mp[data-side="top"] .bl-mp__shell { bottom: 0; }
|
|
16
|
+
.bl-mp__ghost { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 8px; font-weight: 600; white-space: nowrap; pointer-events: none; }
|
|
17
|
+
.bl-mp__panel { width: var(--bl-panel-w); padding: 16px; }
|
|
18
|
+
`;
|
|
19
|
+
function useStyles() {
|
|
20
|
+
useInsertionEffect(() => {
|
|
21
|
+
if (typeof document === "undefined") return;
|
|
22
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
23
|
+
style.textContent = CSS;
|
|
24
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
25
|
+
}, []);
|
|
26
|
+
}
|
|
27
|
+
function MorphPopover({
|
|
28
|
+
label = "Feedback",
|
|
29
|
+
icon,
|
|
30
|
+
children,
|
|
31
|
+
panelLabel,
|
|
32
|
+
panelWidth = 320,
|
|
33
|
+
align = "start",
|
|
34
|
+
side = "bottom",
|
|
35
|
+
height = 42,
|
|
36
|
+
radius = 14,
|
|
37
|
+
duration = 380,
|
|
38
|
+
open: controlledOpen,
|
|
39
|
+
onOpenChange,
|
|
40
|
+
backgroundColor = "#1a1c2c",
|
|
41
|
+
textColor = "#f4f4f4",
|
|
42
|
+
lineColor = "rgba(255,255,255,0.14)",
|
|
43
|
+
accentColor = "#73eff7",
|
|
44
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
45
|
+
className = "",
|
|
46
|
+
style
|
|
47
|
+
}) {
|
|
48
|
+
useStyles();
|
|
49
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
50
|
+
const rootRef = useRef(null);
|
|
51
|
+
const buttonRef = useRef(null);
|
|
52
|
+
const shellRef = useRef(null);
|
|
53
|
+
const panelRef = useRef(null);
|
|
54
|
+
const ghostRef = useRef(null);
|
|
55
|
+
const [innerOpen, setInnerOpen] = useState(false);
|
|
56
|
+
const [mounted, setMounted] = useState(false);
|
|
57
|
+
const open = controlledOpen ?? innerOpen;
|
|
58
|
+
const closing = useRef(false);
|
|
59
|
+
const setOpen = (next) => {
|
|
60
|
+
if (controlledOpen === void 0) setInnerOpen(next);
|
|
61
|
+
onOpenChange?.(next);
|
|
62
|
+
};
|
|
63
|
+
const close = () => setOpen(false);
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
if (open) setMounted(true);
|
|
66
|
+
}, [open]);
|
|
67
|
+
const reduced = () => window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
68
|
+
const size = (w, h) => ({ width: `${w}px`, height: `${h}px` });
|
|
69
|
+
useLayoutEffect(() => {
|
|
70
|
+
if (!mounted || !open) return;
|
|
71
|
+
const button = buttonRef.current;
|
|
72
|
+
const panel = panelRef.current;
|
|
73
|
+
const from = [button.offsetWidth, button.offsetHeight];
|
|
74
|
+
const width = Math.min(panelWidth, window.innerWidth - 32);
|
|
75
|
+
panel.style.width = `${width}px`;
|
|
76
|
+
const to = [width, panel.offsetHeight];
|
|
77
|
+
Object.assign(shellRef.current.style, size(...to));
|
|
78
|
+
button.setAttribute("data-hidden", "");
|
|
79
|
+
if (!reduced()) {
|
|
80
|
+
const easing = "cubic-bezier(.2,.9,.25,1.05)";
|
|
81
|
+
shellRef.current.animate([size(...from), size(...to)], { duration, easing });
|
|
82
|
+
panel.animate({ opacity: [0, 0, 1], filter: ["blur(4px)", "blur(4px)", "none"] }, { duration, easing: "ease-out" });
|
|
83
|
+
ghostRef.current.animate({ opacity: [1, 0, 0] }, { duration: duration * 0.6, fill: "forwards" });
|
|
84
|
+
} else {
|
|
85
|
+
ghostRef.current.style.opacity = "0";
|
|
86
|
+
}
|
|
87
|
+
(panel.querySelector('input, textarea, select, button, [href], [tabindex]:not([tabindex="-1"])') ?? shellRef.current).focus({ preventScroll: true });
|
|
88
|
+
}, [mounted, open]);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (open || !mounted || closing.current) return;
|
|
91
|
+
closing.current = true;
|
|
92
|
+
const shell = shellRef.current;
|
|
93
|
+
const button = buttonRef.current;
|
|
94
|
+
const finish = () => {
|
|
95
|
+
closing.current = false;
|
|
96
|
+
button.removeAttribute("data-hidden");
|
|
97
|
+
setMounted(false);
|
|
98
|
+
if (rootRef.current?.contains(document.activeElement) || document.activeElement === document.body) button.focus({ preventScroll: true });
|
|
99
|
+
};
|
|
100
|
+
if (reduced()) return finish();
|
|
101
|
+
const from = [shell.offsetWidth, shell.offsetHeight];
|
|
102
|
+
const to = [button.offsetWidth, button.offsetHeight];
|
|
103
|
+
panelRef.current.animate({ opacity: [1, 0] }, { duration: duration * 0.35, fill: "forwards" });
|
|
104
|
+
ghostRef.current.animate({ opacity: [0, 1] }, { duration: duration * 0.5, delay: duration * 0.4, fill: "forwards" });
|
|
105
|
+
shell.animate([size(...from), size(...to)], { duration: duration * 0.9, easing: "cubic-bezier(.4,0,.2,1)", fill: "forwards" }).onfinish = finish;
|
|
106
|
+
}, [open, mounted]);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!open) return;
|
|
109
|
+
const onDown = (event) => {
|
|
110
|
+
if (!rootRef.current.contains(event.target)) close();
|
|
111
|
+
};
|
|
112
|
+
document.addEventListener("pointerdown", onDown);
|
|
113
|
+
return () => document.removeEventListener("pointerdown", onDown);
|
|
114
|
+
}, [open]);
|
|
115
|
+
return /* @__PURE__ */ jsxs(
|
|
116
|
+
"div",
|
|
117
|
+
{
|
|
118
|
+
ref: rootRef,
|
|
119
|
+
className: `bl-mp ${className}`,
|
|
120
|
+
"data-align": align,
|
|
121
|
+
"data-side": side,
|
|
122
|
+
style: {
|
|
123
|
+
"--bl-h": `${height}px`,
|
|
124
|
+
"--bl-radius": `${radius}px`,
|
|
125
|
+
"--bl-bg": backgroundColor,
|
|
126
|
+
"--bl-ink": textColor,
|
|
127
|
+
"--bl-line": lineColor,
|
|
128
|
+
"--bl-accent": accentColor,
|
|
129
|
+
"--bl-font": fontFamily,
|
|
130
|
+
"--bl-panel-w": `${panelWidth}px`,
|
|
131
|
+
...style
|
|
132
|
+
},
|
|
133
|
+
children: [
|
|
134
|
+
/* @__PURE__ */ jsxs("button", { ref: buttonRef, type: "button", className: "bl-mp__button", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": mounted ? `${uid}-panel` : void 0, onClick: () => setOpen(!open), children: [
|
|
135
|
+
icon,
|
|
136
|
+
label
|
|
137
|
+
] }),
|
|
138
|
+
mounted && /* @__PURE__ */ jsxs(
|
|
139
|
+
"div",
|
|
140
|
+
{
|
|
141
|
+
ref: shellRef,
|
|
142
|
+
id: `${uid}-panel`,
|
|
143
|
+
className: "bl-mp__shell",
|
|
144
|
+
role: "dialog",
|
|
145
|
+
"aria-label": panelLabel ?? (typeof label === "string" ? label : void 0),
|
|
146
|
+
tabIndex: -1,
|
|
147
|
+
onKeyDown: (event) => {
|
|
148
|
+
if (event.key === "Escape") {
|
|
149
|
+
event.stopPropagation();
|
|
150
|
+
close();
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
children: [
|
|
154
|
+
/* @__PURE__ */ jsxs("div", { ref: ghostRef, className: "bl-mp__ghost", "aria-hidden": "true", children: [
|
|
155
|
+
icon,
|
|
156
|
+
label
|
|
157
|
+
] }),
|
|
158
|
+
/* @__PURE__ */ jsx("div", { ref: panelRef, className: "bl-mp__panel", children: typeof children === "function" ? children({ close }) : children })
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
export {
|
|
167
|
+
MorphPopover as default
|
|
168
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
export interface MorphWordsProps {
|
|
3
|
+
/** Words to cycle through; the box sizes to the widest. */
|
|
4
|
+
words?: string[];
|
|
5
|
+
/** How long each word holds, in ms. */
|
|
6
|
+
interval?: number;
|
|
7
|
+
/** Morph length, in ms. */
|
|
8
|
+
morphTime?: number;
|
|
9
|
+
/** Alignment of shorter words inside the box. */
|
|
10
|
+
align?: 'center' | 'start' | 'end';
|
|
11
|
+
/** Announce each word to screen readers. */
|
|
12
|
+
announce?: boolean;
|
|
13
|
+
/** Text color (defaults to the inherited color). */
|
|
14
|
+
color?: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
style?: CSSProperties;
|
|
17
|
+
}
|
|
18
|
+
export default function MorphWords({ words, interval, morphTime, align, announce, color, className, style }: MorphWordsProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,136 @@
|
|
|
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-morph-words";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-morph { position: relative; display: inline-grid; vertical-align: bottom; color: var(--bl-ink); }
|
|
7
|
+
.bl-morph__sizer, .bl-morph__stage { grid-area: 1 / 1; }
|
|
8
|
+
.bl-morph__sizer { display: grid; visibility: hidden; }
|
|
9
|
+
.bl-morph__sizer > span { grid-area: 1 / 1; white-space: nowrap; }
|
|
10
|
+
.bl-morph__stage { position: relative; }
|
|
11
|
+
.bl-morph__word { position: absolute; inset: 0; display: flex; align-items: center; justify-content: var(--bl-align); white-space: nowrap; user-select: none; }
|
|
12
|
+
.bl-morph__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
|
|
13
|
+
`;
|
|
14
|
+
function useStyles() {
|
|
15
|
+
useInsertionEffect(() => {
|
|
16
|
+
if (typeof document === "undefined") return;
|
|
17
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
18
|
+
style.textContent = CSS;
|
|
19
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
20
|
+
}, []);
|
|
21
|
+
}
|
|
22
|
+
function MorphWords({
|
|
23
|
+
words = ["pixels", "worlds", "quests", "magic"],
|
|
24
|
+
interval = 1800,
|
|
25
|
+
morphTime = 900,
|
|
26
|
+
align = "center",
|
|
27
|
+
announce = false,
|
|
28
|
+
color,
|
|
29
|
+
className = "",
|
|
30
|
+
style
|
|
31
|
+
}) {
|
|
32
|
+
useStyles();
|
|
33
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
34
|
+
const rootRef = useRef(null);
|
|
35
|
+
const fromRef = useRef(null);
|
|
36
|
+
const toRef = useRef(null);
|
|
37
|
+
const stageRef = useRef(null);
|
|
38
|
+
const [index, setIndex] = useState(0);
|
|
39
|
+
const [reduced, setReduced] = useState(false);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
setReduced(Boolean(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches));
|
|
42
|
+
}, []);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (words.length < 2) return void 0;
|
|
45
|
+
const from = fromRef.current;
|
|
46
|
+
const to = toRef.current;
|
|
47
|
+
const stage = stageRef.current;
|
|
48
|
+
let frame = 0;
|
|
49
|
+
let timer = 0;
|
|
50
|
+
let visible = true;
|
|
51
|
+
let current = 0;
|
|
52
|
+
const paint = (t) => {
|
|
53
|
+
const inT = Math.max(1e-4, t);
|
|
54
|
+
const outT = Math.max(1e-4, 1 - t);
|
|
55
|
+
to.style.filter = `blur(${Math.min(8 / inT - 8, 100)}px)`;
|
|
56
|
+
to.style.opacity = `${Math.pow(inT, 0.4) * 100}%`;
|
|
57
|
+
from.style.filter = `blur(${Math.min(8 / outT - 8, 100)}px)`;
|
|
58
|
+
from.style.opacity = `${Math.pow(outT, 0.4) * 100}%`;
|
|
59
|
+
};
|
|
60
|
+
const rest = () => {
|
|
61
|
+
stage.style.filter = "";
|
|
62
|
+
from.style.filter = "";
|
|
63
|
+
from.style.opacity = "100%";
|
|
64
|
+
to.style.filter = "";
|
|
65
|
+
to.style.opacity = "0%";
|
|
66
|
+
};
|
|
67
|
+
const morph = () => {
|
|
68
|
+
const next = (current + 1) % words.length;
|
|
69
|
+
from.textContent = words[current];
|
|
70
|
+
to.textContent = words[next];
|
|
71
|
+
if (reduced) {
|
|
72
|
+
current = next;
|
|
73
|
+
from.textContent = words[current];
|
|
74
|
+
setIndex(current);
|
|
75
|
+
timer = setTimeout(morph, interval);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
stage.style.filter = `url(#${uid}-goo)`;
|
|
79
|
+
const start = performance.now();
|
|
80
|
+
const step = (now) => {
|
|
81
|
+
const t = Math.min(1, (now - start) / morphTime);
|
|
82
|
+
paint(t);
|
|
83
|
+
if (t < 1) {
|
|
84
|
+
frame = requestAnimationFrame(step);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
current = next;
|
|
88
|
+
from.textContent = words[current];
|
|
89
|
+
rest();
|
|
90
|
+
setIndex(current);
|
|
91
|
+
if (visible) timer = setTimeout(morph, interval);
|
|
92
|
+
};
|
|
93
|
+
frame = requestAnimationFrame(step);
|
|
94
|
+
};
|
|
95
|
+
rest();
|
|
96
|
+
from.textContent = words[0];
|
|
97
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
98
|
+
const was = visible;
|
|
99
|
+
visible = entry.isIntersecting;
|
|
100
|
+
if (visible && !was) timer = setTimeout(morph, interval);
|
|
101
|
+
if (!visible) {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
cancelAnimationFrame(frame);
|
|
104
|
+
rest();
|
|
105
|
+
from.textContent = words[current];
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
observer.observe(rootRef.current);
|
|
109
|
+
timer = setTimeout(morph, interval);
|
|
110
|
+
return () => {
|
|
111
|
+
clearTimeout(timer);
|
|
112
|
+
cancelAnimationFrame(frame);
|
|
113
|
+
observer.disconnect();
|
|
114
|
+
};
|
|
115
|
+
}, [words.join("|"), interval, morphTime, reduced, uid]);
|
|
116
|
+
return /* @__PURE__ */ jsxs(
|
|
117
|
+
"span",
|
|
118
|
+
{
|
|
119
|
+
ref: rootRef,
|
|
120
|
+
className: `bl-morph ${className}`,
|
|
121
|
+
style: { "--bl-align": align === "start" ? "flex-start" : align === "end" ? "flex-end" : "center", ...color ? { "--bl-ink": color } : { "--bl-ink": "currentColor" }, ...style },
|
|
122
|
+
children: [
|
|
123
|
+
/* @__PURE__ */ jsx("svg", { width: "0", height: "0", style: { position: "absolute" }, "aria-hidden": "true", focusable: "false", children: /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("filter", { id: `${uid}-goo`, children: /* @__PURE__ */ jsx("feColorMatrix", { in: "SourceGraphic", type: "matrix", values: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 255 -140" }) }) }) }),
|
|
124
|
+
/* @__PURE__ */ jsx("span", { className: "bl-morph__sizer", "aria-hidden": "true", children: words.map((word) => /* @__PURE__ */ jsx("span", { children: word }, word)) }),
|
|
125
|
+
/* @__PURE__ */ jsxs("span", { ref: stageRef, className: "bl-morph__stage", "aria-hidden": "true", children: [
|
|
126
|
+
/* @__PURE__ */ jsx("span", { ref: fromRef, className: "bl-morph__word", children: words[0] }),
|
|
127
|
+
/* @__PURE__ */ jsx("span", { ref: toRef, className: "bl-morph__word", style: { opacity: 0 } })
|
|
128
|
+
] }),
|
|
129
|
+
/* @__PURE__ */ jsx("span", { className: "bl-morph__sr", "aria-live": announce ? "polite" : void 0, children: words[index] ?? "" })
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
export {
|
|
135
|
+
MorphWords as default
|
|
136
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from 'react';
|
|
2
|
+
export interface OTPInputProps {
|
|
3
|
+
/** Number of characters. */
|
|
4
|
+
length?: number;
|
|
5
|
+
/** Controlled code. */
|
|
6
|
+
value?: string;
|
|
7
|
+
defaultValue?: string;
|
|
8
|
+
onChange?: (code: string) => void;
|
|
9
|
+
/** Called once per complete code (typed, pasted or autofilled). */
|
|
10
|
+
onComplete?: (code: string) => void;
|
|
11
|
+
/** error shakes and turns red (reset it on the next change); success fuses the cells and locks the input. */
|
|
12
|
+
status?: 'idle' | 'error' | 'success';
|
|
13
|
+
/** Line under the cells; announced as an alert on error. */
|
|
14
|
+
message?: ReactNode;
|
|
15
|
+
/** Allowed characters; alphanumeric is uppercased. */
|
|
16
|
+
type?: 'numeric' | 'alphanumeric';
|
|
17
|
+
/** Hide the characters. */
|
|
18
|
+
mask?: boolean;
|
|
19
|
+
/** Separator every N cells; 0 for none. */
|
|
20
|
+
groupSize?: number;
|
|
21
|
+
autoFocus?: boolean;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
/** Accessible name of the input. */
|
|
24
|
+
label?: string;
|
|
25
|
+
cellWidth?: number;
|
|
26
|
+
cellHeight?: number;
|
|
27
|
+
gap?: number;
|
|
28
|
+
radius?: number;
|
|
29
|
+
borderWidth?: number;
|
|
30
|
+
backgroundColor?: string;
|
|
31
|
+
textColor?: string;
|
|
32
|
+
mutedColor?: string;
|
|
33
|
+
lineColor?: string;
|
|
34
|
+
filledLineColor?: string;
|
|
35
|
+
accentColor?: string;
|
|
36
|
+
errorColor?: string;
|
|
37
|
+
successColor?: string;
|
|
38
|
+
successTextColor?: string;
|
|
39
|
+
fontFamily?: string;
|
|
40
|
+
className?: string;
|
|
41
|
+
style?: CSSProperties;
|
|
42
|
+
}
|
|
43
|
+
export default function OTPInput({ length, value, defaultValue, onChange, onComplete, status, message, type, mask, groupSize, autoFocus, disabled, label, cellWidth, cellHeight, gap, radius, borderWidth, backgroundColor, textColor, mutedColor, lineColor, filledLineColor, accentColor, errorColor, successColor, successTextColor, fontFamily, className, style }: OTPInputProps): import("react").JSX.Element;
|
package/dist/OTPInput.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
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-otp";
|
|
5
|
+
const CSS = `
|
|
6
|
+
.bl-otp { position: relative; display: inline-flex; max-width: 100%; flex-direction: column; align-items: center; gap: 10px; font-family: var(--bl-font); }
|
|
7
|
+
.bl-otp__cells { position: relative; display: flex; max-width: 100%; align-items: center; gap: var(--bl-gap); transition: gap 320ms cubic-bezier(.2,.9,.25,1); }
|
|
8
|
+
.bl-otp__cell { position: relative; display: grid; place-items: center; flex: 0 1 var(--bl-w); width: var(--bl-w); min-width: 22px; height: var(--bl-h); border-radius: var(--bl-radius); background: var(--bl-bg); color: var(--bl-ink); font-size: calc(var(--bl-h) * 0.42); font-weight: 700; font-variant-numeric: tabular-nums; box-shadow: inset 0 0 0 var(--bl-border) var(--bl-line); transition: box-shadow 160ms, background-color 200ms, border-radius 320ms cubic-bezier(.2,.9,.25,1), transform 160ms; }
|
|
9
|
+
.bl-otp__cell[data-filled] { box-shadow: inset 0 0 0 var(--bl-border) var(--bl-line-filled); }
|
|
10
|
+
.bl-otp[data-focused] .bl-otp__cell[data-active] { box-shadow: inset 0 0 0 calc(var(--bl-border) + 1px) var(--bl-accent), 0 0 0 4px var(--bl-ring); }
|
|
11
|
+
.bl-otp__char { animation: bl-otp-pop 160ms cubic-bezier(.2,.9,.25,1.4); }
|
|
12
|
+
.bl-otp__caret { width: 2px; height: 42%; background: var(--bl-accent); animation: bl-otp-blink 1s steps(1) infinite; }
|
|
13
|
+
.bl-otp__sep { width: 10px; height: 2px; border-radius: 1px; background: var(--bl-line-filled); transition: width 320ms, opacity 200ms; }
|
|
14
|
+
.bl-otp__input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; border: 0; background: transparent; color: transparent; caret-color: transparent; font-size: 16px; letter-spacing: 0; outline: none; cursor: text; opacity: 0.001; }
|
|
15
|
+
.bl-otp__input:disabled { cursor: not-allowed; }
|
|
16
|
+
.bl-otp[data-status="error"] .bl-otp__cells { animation: bl-otp-shake 420ms cubic-bezier(.36,.07,.19,.97); }
|
|
17
|
+
.bl-otp[data-status="error"] .bl-otp__cell { box-shadow: inset 0 0 0 calc(var(--bl-border) + 1px) var(--bl-error); color: var(--bl-error); }
|
|
18
|
+
.bl-otp[data-status="success"] .bl-otp__cells { gap: 0; }
|
|
19
|
+
.bl-otp[data-status="success"] .bl-otp__sep { width: 0; opacity: 0; }
|
|
20
|
+
.bl-otp[data-status="success"] .bl-otp__cell { background: var(--bl-success); color: var(--bl-success-ink); box-shadow: none; border-radius: 0; }
|
|
21
|
+
.bl-otp[data-status="success"] .bl-otp__cell[data-edge="first"] { border-radius: var(--bl-radius) 0 0 var(--bl-radius); }
|
|
22
|
+
.bl-otp[data-status="success"] .bl-otp__cell[data-edge="last"] { border-radius: 0 var(--bl-radius) var(--bl-radius) 0; }
|
|
23
|
+
.bl-otp__check { position: absolute; right: -14px; top: 50%; display: grid; place-items: center; width: 28px; height: 28px; border-radius: 50%; background: var(--bl-success); color: var(--bl-success-ink); transform: translate(100%, -50%) scale(0); transition: transform 300ms cubic-bezier(.2,.9,.25,1.5) 200ms; }
|
|
24
|
+
.bl-otp[data-status="success"] .bl-otp__check { transform: translate(100%, -50%) scale(1); }
|
|
25
|
+
.bl-otp[data-disabled] { opacity: 0.55; }
|
|
26
|
+
.bl-otp__message { min-height: 1.3em; margin: 0; color: var(--bl-muted); font-size: 13px; }
|
|
27
|
+
.bl-otp[data-status="error"] .bl-otp__message { color: var(--bl-error); }
|
|
28
|
+
.bl-otp[data-status="success"] .bl-otp__message { color: var(--bl-success); }
|
|
29
|
+
@keyframes bl-otp-pop { from { transform: scale(0.5); opacity: 0; } }
|
|
30
|
+
@keyframes bl-otp-blink { 50% { opacity: 0; } }
|
|
31
|
+
@keyframes bl-otp-shake { 10%, 90% { transform: translateX(-2px); } 20%, 80% { transform: translateX(4px); } 30%, 50%, 70% { transform: translateX(-7px); } 40%, 60% { transform: translateX(7px); } }
|
|
32
|
+
@media (prefers-reduced-motion: reduce) {
|
|
33
|
+
.bl-otp__cells, .bl-otp__cell, .bl-otp__sep, .bl-otp__check { transition: none; }
|
|
34
|
+
.bl-otp__char, .bl-otp__caret, .bl-otp[data-status="error"] .bl-otp__cells { animation: none; }
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
function useStyles() {
|
|
38
|
+
useInsertionEffect(() => {
|
|
39
|
+
if (typeof document === "undefined") return;
|
|
40
|
+
const style = document.getElementById(STYLE_ID) ?? Object.assign(document.createElement("style"), { id: STYLE_ID });
|
|
41
|
+
style.textContent = CSS;
|
|
42
|
+
if (!style.isConnected) document.head.appendChild(style);
|
|
43
|
+
}, []);
|
|
44
|
+
}
|
|
45
|
+
const PATTERNS = { numeric: /[^0-9]/g, alphanumeric: /[^0-9a-z]/gi };
|
|
46
|
+
function OTPInput({
|
|
47
|
+
length = 6,
|
|
48
|
+
value,
|
|
49
|
+
defaultValue = "",
|
|
50
|
+
onChange,
|
|
51
|
+
onComplete,
|
|
52
|
+
status = "idle",
|
|
53
|
+
message,
|
|
54
|
+
type = "numeric",
|
|
55
|
+
mask = false,
|
|
56
|
+
groupSize = 3,
|
|
57
|
+
autoFocus = false,
|
|
58
|
+
disabled = false,
|
|
59
|
+
label = "One-time code",
|
|
60
|
+
cellWidth = 48,
|
|
61
|
+
cellHeight = 58,
|
|
62
|
+
gap = 8,
|
|
63
|
+
radius = 12,
|
|
64
|
+
borderWidth = 1.5,
|
|
65
|
+
backgroundColor = "#1a1c2c",
|
|
66
|
+
textColor = "#f4f4f4",
|
|
67
|
+
mutedColor = "#94b0c2",
|
|
68
|
+
lineColor = "rgba(255,255,255,0.14)",
|
|
69
|
+
filledLineColor = "rgba(255,255,255,0.32)",
|
|
70
|
+
accentColor = "#73eff7",
|
|
71
|
+
errorColor = "#ef7d57",
|
|
72
|
+
successColor = "#38b764",
|
|
73
|
+
successTextColor = "#0b0b14",
|
|
74
|
+
fontFamily = "ui-sans-serif, system-ui, sans-serif",
|
|
75
|
+
className = "",
|
|
76
|
+
style
|
|
77
|
+
}) {
|
|
78
|
+
useStyles();
|
|
79
|
+
const uid = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
80
|
+
const inputRef = useRef(null);
|
|
81
|
+
const [inner, setInner] = useState(defaultValue);
|
|
82
|
+
const [focused, setFocused] = useState(false);
|
|
83
|
+
const code = (value ?? inner).slice(0, length);
|
|
84
|
+
const lastComplete = useRef("");
|
|
85
|
+
const clean = (raw) => {
|
|
86
|
+
const filtered = raw.replace(PATTERNS[type] ?? PATTERNS.numeric, "");
|
|
87
|
+
return (type === "alphanumeric" ? filtered.toUpperCase() : filtered).slice(0, length);
|
|
88
|
+
};
|
|
89
|
+
const update = (next) => {
|
|
90
|
+
if (value === void 0) setInner(next);
|
|
91
|
+
onChange?.(next);
|
|
92
|
+
};
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (code.length === length && code !== lastComplete.current) {
|
|
95
|
+
lastComplete.current = code;
|
|
96
|
+
onComplete?.(code);
|
|
97
|
+
}
|
|
98
|
+
if (code.length < length) lastComplete.current = "";
|
|
99
|
+
}, [code, length]);
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (status === "error") inputRef.current?.select();
|
|
102
|
+
}, [status]);
|
|
103
|
+
const active = Math.min(code.length, length - 1);
|
|
104
|
+
const cells = Array.from({ length }, (_, i) => i);
|
|
105
|
+
return /* @__PURE__ */ jsxs(
|
|
106
|
+
"div",
|
|
107
|
+
{
|
|
108
|
+
className: `bl-otp ${className}`,
|
|
109
|
+
"data-status": status,
|
|
110
|
+
"data-focused": focused || void 0,
|
|
111
|
+
"data-disabled": disabled || void 0,
|
|
112
|
+
style: {
|
|
113
|
+
"--bl-w": `${cellWidth}px`,
|
|
114
|
+
"--bl-h": `${cellHeight}px`,
|
|
115
|
+
"--bl-gap": `${gap}px`,
|
|
116
|
+
"--bl-radius": `${radius}px`,
|
|
117
|
+
"--bl-border": `${borderWidth}px`,
|
|
118
|
+
"--bl-bg": backgroundColor,
|
|
119
|
+
"--bl-ink": textColor,
|
|
120
|
+
"--bl-muted": mutedColor,
|
|
121
|
+
"--bl-line": lineColor,
|
|
122
|
+
"--bl-line-filled": filledLineColor,
|
|
123
|
+
"--bl-accent": accentColor,
|
|
124
|
+
"--bl-ring": `color-mix(in srgb, ${accentColor} 25%, transparent)`,
|
|
125
|
+
"--bl-error": errorColor,
|
|
126
|
+
"--bl-success": successColor,
|
|
127
|
+
"--bl-success-ink": successTextColor,
|
|
128
|
+
"--bl-font": fontFamily,
|
|
129
|
+
...style
|
|
130
|
+
},
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ jsxs("div", { className: "bl-otp__cells", children: [
|
|
133
|
+
cells.map((i) => /* @__PURE__ */ jsxs("span", { style: { display: "contents" }, children: [
|
|
134
|
+
groupSize > 0 && i > 0 && i % groupSize === 0 && /* @__PURE__ */ jsx("span", { className: "bl-otp__sep", "aria-hidden": "true" }),
|
|
135
|
+
/* @__PURE__ */ jsx("span", { className: "bl-otp__cell", "data-edge": i === 0 ? "first" : i === length - 1 ? "last" : void 0, "data-filled": i < code.length || void 0, "data-active": i === active && code.length < length ? "" : void 0, "aria-hidden": "true", children: i < code.length ? /* @__PURE__ */ jsx("span", { className: "bl-otp__char", children: mask ? "\u2022" : code[i] }, code[i] + i) : focused && i === code.length && /* @__PURE__ */ jsx("span", { className: "bl-otp__caret" }) })
|
|
136
|
+
] }, i)),
|
|
137
|
+
/* @__PURE__ */ jsx("span", { className: "bl-otp__check", "aria-hidden": "true", children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M2.5 7.5 5.5 10.5 11.5 3.5" }) }) }),
|
|
138
|
+
/* @__PURE__ */ jsx(
|
|
139
|
+
"input",
|
|
140
|
+
{
|
|
141
|
+
ref: inputRef,
|
|
142
|
+
className: "bl-otp__input",
|
|
143
|
+
value: code,
|
|
144
|
+
onChange: (event) => update(clean(event.target.value)),
|
|
145
|
+
onFocus: (event) => {
|
|
146
|
+
setFocused(true);
|
|
147
|
+
const end = event.target.value.length;
|
|
148
|
+
event.target.setSelectionRange(end, end);
|
|
149
|
+
},
|
|
150
|
+
onBlur: () => setFocused(false),
|
|
151
|
+
inputMode: type === "numeric" ? "numeric" : "text",
|
|
152
|
+
autoComplete: "one-time-code",
|
|
153
|
+
autoCapitalize: type === "alphanumeric" ? "characters" : "off",
|
|
154
|
+
autoCorrect: "off",
|
|
155
|
+
spellCheck: false,
|
|
156
|
+
autoFocus,
|
|
157
|
+
maxLength: length,
|
|
158
|
+
pattern: type === "numeric" ? `[0-9]{${length}}` : void 0,
|
|
159
|
+
disabled: disabled || status === "success",
|
|
160
|
+
type: mask ? "password" : "text",
|
|
161
|
+
"aria-label": label,
|
|
162
|
+
"aria-invalid": status === "error" || void 0,
|
|
163
|
+
"aria-describedby": message ? `${uid}-msg` : void 0
|
|
164
|
+
}
|
|
165
|
+
)
|
|
166
|
+
] }),
|
|
167
|
+
message !== void 0 && /* @__PURE__ */ jsx("p", { id: `${uid}-msg`, className: "bl-otp__message", role: status === "error" ? "alert" : "status", children: message })
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
export {
|
|
173
|
+
OTPInput as default
|
|
174
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { type CSSProperties, type HTMLAttributes, type ReactElement, type ReactNode, type Ref } from 'react';
|
|
2
|
+
export type Side = 'top' | 'bottom' | 'left' | 'right';
|
|
3
|
+
export type Align = 'start' | 'center' | 'end';
|
|
4
|
+
/** The element a trigger or tooltip child must be: anything that takes DOM props and a ref. */
|
|
5
|
+
type TriggerProps = HTMLAttributes<HTMLElement> & {
|
|
6
|
+
ref?: Ref<HTMLElement>;
|
|
7
|
+
};
|
|
8
|
+
type TriggerElement = ReactElement<TriggerProps>;
|
|
9
|
+
type RenderChildren = ReactNode | ((api: {
|
|
10
|
+
close: () => void;
|
|
11
|
+
}) => ReactNode);
|
|
12
|
+
export interface DialogProps {
|
|
13
|
+
open?: boolean;
|
|
14
|
+
defaultOpen?: boolean;
|
|
15
|
+
onOpenChange?: (open: boolean) => void;
|
|
16
|
+
/** An element (usually a button) that opens it; cloned with ARIA and onClick. */
|
|
17
|
+
trigger?: TriggerElement;
|
|
18
|
+
title?: ReactNode;
|
|
19
|
+
description?: ReactNode;
|
|
20
|
+
/** Content, or a function receiving { close }. Mark a button data-autofocus to focus it first. */
|
|
21
|
+
children?: RenderChildren;
|
|
22
|
+
/** Panel width in px. */
|
|
23
|
+
width?: number;
|
|
24
|
+
closeLabel?: string;
|
|
25
|
+
className?: string;
|
|
26
|
+
style?: CSSProperties;
|
|
27
|
+
}
|
|
28
|
+
export interface SheetProps extends DialogProps {
|
|
29
|
+
/** Edge the sheet slides in from. */
|
|
30
|
+
side?: Side;
|
|
31
|
+
}
|
|
32
|
+
export interface TooltipProps {
|
|
33
|
+
content: ReactNode;
|
|
34
|
+
/** One element; it receives the hover/focus handlers and aria-describedby. */
|
|
35
|
+
children: TriggerElement;
|
|
36
|
+
side?: Side;
|
|
37
|
+
align?: Align;
|
|
38
|
+
/** Hover delay before showing, in ms. */
|
|
39
|
+
delay?: number;
|
|
40
|
+
/** Delay before hiding after the pointer leaves, in ms. */
|
|
41
|
+
closeDelay?: number;
|
|
42
|
+
className?: string;
|
|
43
|
+
style?: CSSProperties;
|
|
44
|
+
}
|
|
45
|
+
export interface PopoverProps {
|
|
46
|
+
trigger?: TriggerElement;
|
|
47
|
+
children?: RenderChildren;
|
|
48
|
+
/** Accessible name of the popover panel. */
|
|
49
|
+
label?: string;
|
|
50
|
+
open?: boolean;
|
|
51
|
+
defaultOpen?: boolean;
|
|
52
|
+
onOpenChange?: (open: boolean) => void;
|
|
53
|
+
side?: Side;
|
|
54
|
+
align?: Align;
|
|
55
|
+
width?: number;
|
|
56
|
+
className?: string;
|
|
57
|
+
style?: CSSProperties;
|
|
58
|
+
}
|
|
59
|
+
export interface DropdownAction {
|
|
60
|
+
type?: 'item';
|
|
61
|
+
label: ReactNode;
|
|
62
|
+
icon?: ReactNode;
|
|
63
|
+
shortcut?: ReactNode;
|
|
64
|
+
onSelect?: () => void;
|
|
65
|
+
disabled?: boolean;
|
|
66
|
+
danger?: boolean;
|
|
67
|
+
}
|
|
68
|
+
export type DropdownItem = DropdownAction | {
|
|
69
|
+
type: 'separator';
|
|
70
|
+
} | {
|
|
71
|
+
type: 'label';
|
|
72
|
+
label: ReactNode;
|
|
73
|
+
};
|
|
74
|
+
export interface DropdownProps {
|
|
75
|
+
trigger?: TriggerElement;
|
|
76
|
+
items?: DropdownItem[];
|
|
77
|
+
/** Accessible name of the menu. */
|
|
78
|
+
label?: string;
|
|
79
|
+
side?: Side;
|
|
80
|
+
align?: Align;
|
|
81
|
+
className?: string;
|
|
82
|
+
style?: CSSProperties;
|
|
83
|
+
}
|
|
84
|
+
export declare const MOTION: {
|
|
85
|
+
fast: number;
|
|
86
|
+
base: number;
|
|
87
|
+
slow: number;
|
|
88
|
+
easeOut: string;
|
|
89
|
+
easeIn: string;
|
|
90
|
+
spring: string;
|
|
91
|
+
};
|
|
92
|
+
export declare function Dialog(props: DialogProps): import("react").JSX.Element;
|
|
93
|
+
export declare function Sheet(props: SheetProps): import("react").JSX.Element;
|
|
94
|
+
export declare function Tooltip({ content, children, side, align, delay, closeDelay, className, style }: TooltipProps): import("react").JSX.Element;
|
|
95
|
+
export declare function Popover({ trigger, children, label, open, defaultOpen, onOpenChange, side, align, width, className, style }: PopoverProps): import("react").JSX.Element;
|
|
96
|
+
export declare function Dropdown({ trigger, items, label, side, align, className, style }: DropdownProps): import("react").JSX.Element;
|
|
97
|
+
export default Dialog;
|