react-prayer-widget
Version:
Embeddable prayer times widget components for React applications
1,391 lines (1,388 loc) • 220 kB
JavaScript
import { getCustomAzanName, getCustomAzanGlobalName, removeCustomAzanFileGlobal, getAzanSource, removeCustomAzanFile, storeCustomAzanFile } from './chunk-DVY2OY7Q.js';
import { Globe, ChevronDown, CheckIcon, SearchIcon, Sun, Moon, Sunset, Sunrise, Calendar, MapPin, Settings, Monitor, Bell, Calculator, Pause, Volume2, CircleAlert, Play, SkipForward, XIcon, ChevronDownIcon, Minus, Plus, Rows, Columns, ChevronUpIcon, MousePointer2, Move, Shapes, Layers, Frame, SlidersHorizontal, FileDown, Share2, CircleUserRound, Palette, Edit2, Lock, Pipette, PipetteIcon } from 'lucide-react';
import React, { createContext, memo, useRef, useState, useMemo, useCallback, useEffect, forwardRef, useContext, isValidElement, Fragment as Fragment$1, useImperativeHandle } from 'react';
import { motion, AnimatePresence, isMotionComponent, useMotionValue, useSpring, useInView, useTransform } from 'motion/react';
import { getTimezone, getTimezonesForCountry } from 'countries-and-timezones';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import Color from 'color';
import { Slot } from '@radix-ui/react-slot';
import { cva } from 'class-variance-authority';
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
import * as LabelPrimitive from '@radix-ui/react-label';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { Tabs as Tabs$1, Slider } from 'radix-ui';
import * as SelectPrimitive from '@radix-ui/react-select';
import useMeasure from 'react-use-measure';
import { allCountries } from 'country-region-data';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import * as SwitchPrimitive from '@radix-ui/react-switch';
import { countries } from 'country-data-list';
import { CircleFlag } from 'react-circle-flags';
import { Command as Command$1 } from 'cmdk';
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
import * as SliderPrimitive from '@radix-ui/react-slider';
// shared/lib/geo/country.ts
var countryNameToISO2 = {
"saudi arabia": "SA",
"kingdom of saudi arabia": "SA",
"united arab emirates": "AE",
uae: "AE",
kuwait: "KW",
qatar: "QA",
bahrain: "BH",
oman: "OM",
iraq: "IQ",
syria: "SY",
lebanon: "LB",
jordan: "JO",
palestine: "PS",
egypt: "EG",
morocco: "MA",
tunisia: "TN",
algeria: "DZ",
turkey: "TR",
pakistan: "PK",
bangladesh: "BD",
indonesia: "ID",
malaysia: "MY",
"united kingdom": "GB",
uk: "GB",
"great britain": "GB",
"united states": "US",
usa: "US",
canada: "CA",
australia: "AU"
};
function iso2ToFlag(iso2) {
const code = iso2.trim().toUpperCase();
if (code.length !== 2) {
return "";
}
const A = 127462;
const base = "A".charCodeAt(0);
const chars = Array.from(code).map(
(ch) => String.fromCodePoint(A + (ch.charCodeAt(0) - base))
);
return chars.join("");
}
function countryToFlag(countryOrCode) {
if (!countryOrCode) {
return "";
}
const val = countryOrCode.trim();
if (val.length === 2) {
return iso2ToFlag(val);
}
const iso = countryNameToISO2[val.toLowerCase()];
return iso ? iso2ToFlag(iso) : "";
}
// shared/lib/time/format.ts
var HOURS_IN_HALF_DAY = 12;
var HOUR_RESET = 0;
var PM_THRESHOLD = 12;
var TIME_REGEX = /(\d{1,2}):(\d{2})/;
function getPeriod(isPM, locale) {
if (isPM) {
return locale === "ar" ? "\u0645" : "PM";
}
return locale === "ar" ? "\u0635" : "AM";
}
function formatTimeDisplay(time24h, use24Hour, locale = "en") {
const sanitized = sanitizeTimeString(time24h);
if (use24Hour) {
return sanitized;
}
const [hStr, mStr] = sanitized.split(":");
const hours = Number(hStr);
const isPM = hours >= PM_THRESHOLD;
const period = getPeriod(isPM, locale);
const hour12 = hours % HOURS_IN_HALF_DAY === HOUR_RESET ? HOURS_IN_HALF_DAY : hours % HOURS_IN_HALF_DAY;
return `${hour12}:${mStr} ${period}`;
}
function formatMinutesHHmm(totalMinutes) {
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
const hh = String(hours).padStart(2, "0");
const mm = String(minutes).padStart(2, "0");
return `${hh}:${mm}`;
}
function sanitizeTimeString(raw) {
if (!raw) {
return "00:00";
}
const match = raw.match(TIME_REGEX);
if (!match) {
return "00:00";
}
let h = Number(match[1]);
let m = Number(match[2]);
if (!(Number.isFinite(h) && Number.isFinite(m))) {
return "00:00";
}
const MAX_HOURS = 23;
const MAX_MINUTES = 59;
h = Math.max(0, Math.min(MAX_HOURS, h));
m = Math.max(0, Math.min(MAX_MINUTES, m));
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
}
function formatCurrentTime(date, use24Hour, language = "en") {
const hours = date.getHours();
const minutes = date.getMinutes();
if (use24Hour) {
return `${String(hours).padStart(2, "0")}:${String(minutes).padStart(2, "0")}`;
}
try {
const base = language === "ar" ? "ar-SA" : "en-US";
return new Intl.DateTimeFormat(base, {
hour: "2-digit",
minute: "2-digit",
hour12: true
}).format(date);
} catch {
const isPM = hours >= PM_THRESHOLD;
const period = getPeriod(isPM, language);
const hour12 = hours % HOURS_IN_HALF_DAY === HOUR_RESET ? HOURS_IN_HALF_DAY : hours % HOURS_IN_HALF_DAY;
return `${hour12}:${String(minutes).padStart(2, "0")} ${period}`;
}
}
function useControlledState(props) {
const { value, defaultValue, onChange } = props;
const [state, setInternalState] = useState(
value !== void 0 ? value : defaultValue
);
useEffect(() => {
if (value !== void 0) {
setInternalState(value);
}
}, [value]);
const setState = useCallback(
(next, ...args) => {
setInternalState(next);
onChange?.(next, ...args);
},
[onChange]
);
return [state, setState];
}
function useIsInView(ref, options = {}) {
const { inView, inViewOnce = false, inViewMargin = "0px" } = options;
const localRef = useRef(null);
useImperativeHandle(ref, () => localRef.current);
const inViewResult = useInView(localRef, {
once: inViewOnce,
margin: inViewMargin
});
const isInView = !inView || inViewResult;
return { ref: localRef, isInView };
}
var COUNTRY_TO_TZ_DEFAULT = {
SA: "Asia/Mecca",
AE: "Asia/Dubai",
KW: "Asia/Kuwait",
QA: "Asia/Qatar",
BH: "Asia/Bahrain",
OM: "Asia/Muscat",
IQ: "Asia/Baghdad",
SY: "Asia/Damascus",
LB: "Asia/Beirut",
JO: "Asia/Amman",
PS: "Asia/Jerusalem",
EG: "Africa/Cairo",
MA: "Africa/Casablanca",
TN: "Africa/Tunis",
DZ: "Africa/Algiers",
TR: "Europe/Istanbul",
PK: "Asia/Karachi",
BD: "Asia/Dhaka",
ID: "Asia/Jakarta",
MY: "Asia/Kuala_Lumpur",
GB: "Europe/London",
US: "America/New_York",
CA: "America/Toronto",
AU: "Australia/Sydney"
};
function guessTimezoneFromCountryCode(alpha2) {
return COUNTRY_TO_TZ_DEFAULT[alpha2.toUpperCase()];
}
function getCountryPrimaryTimezone(alpha2) {
const cc = alpha2.toUpperCase();
const tzInfos = getTimezonesForCountry(cc);
if (tzInfos && tzInfos.length > 0) {
const sorted = [...tzInfos].sort((a, b) => {
const aPop = a.population || 0;
const bPop = b.population || 0;
return bPop - aPop;
});
return sorted[0]?.name;
}
return guessTimezoneFromCountryCode(cc);
}
function getCountryUtcOffsetLabel(alpha2) {
const tzName = getCountryPrimaryTimezone(alpha2);
if (!tzName) {
return "GMT+0";
}
const tzInfo = getTimezone(tzName);
if (!tzInfo || typeof tzInfo.utcOffset !== "number") {
return "GMT+0";
}
const offset = tzInfo.utcOffset;
if (offset === 0) {
return "GMT+0";
}
const sign = offset >= 0 ? "+" : "-";
const abs = Math.abs(offset);
const hours = Math.floor(abs / 60);
const mins = abs % 60;
if (mins === 0) {
return `GMT${sign}${hours}`;
}
return `GMT${sign}${hours}:${String(mins).padStart(2, "0")}`;
}
function getCountryCodeFromTimezone(timezone) {
try {
const info = getTimezone(timezone);
if (!info) {
return;
}
const countries2 = info.countries;
if (Array.isArray(countries2) && countries2.length > 0) {
return countries2[0]?.toUpperCase();
}
} catch {
}
return;
}
// shared/config/translations.ts
var translations = {
en: {
settings: {
title: "Prayer Settings",
locationTimezone: "Location & Timezone",
displayOptions: "Display",
horizontalPrayerList: "Vertical first",
verticalFirst: "Vertical first",
general: "General",
calculation: "Calculation",
azan: "Azan",
showOtherPrayers: "Show Other Prayer Cards",
showCity: "Show Current City",
showTicker: "Show Ticker",
showClock: "Show Clock",
showDate: "Show Date",
calculationMethod: "Calculation Method",
asrCalculation: "Asr Calculation",
timeAdjustments: "Time Adjustments (minutes)",
timeAdjustmentsHelp: "Adjust prayer times by \xB130 minutes to match your local mosque or preference",
done: "Done",
timeFormat24h: "24-hour Time",
dimPreviousPrayers: "Dim Previous Prayers",
language: "Language",
autoDetectTimezone: "Auto-detect timezone",
locationPermissionDenied: "Location permission denied. Auto-detect is off. Defaulting to Makkah Al-Mukarramah, Saudi Arabia.",
tickerSpeed: "Ticker change interval"
},
azan: {
enable: "Enable Azan",
volume: "Volume",
volumeLowWarning: "Volume might be too low to hear",
volumeZeroWarning: "Volume is muted. Azan will not play",
type: "Azan type",
customOverride: "Custom (overrides all)",
perPrayer: "Per\u2011prayer",
full: "Full",
short: "Short",
beepOnly: "Beep only",
customFile: "Custom file",
selected: "Selected:",
preview: "Preview azan"
},
uploader: {
dragOr: "Drag a file here or",
browse: "browse",
dropHere: "Drop file here"
},
general: {
remaining: "remaining",
loading: "Loading...",
remainingTillAzan: "remaining time till azan"
},
prayers: {
fajr: "Fajr",
sunrise: "Sunrise",
dhuhr: "Dhuhr",
jumuah: "Jumu\u02BFah",
asr: "Asr",
maghrib: "Maghrib",
isha: "Isha"
},
dates: {
hijriMonths: [
"Muharram",
"Safar",
"Rabi' al-Awwal",
"Rabi' al-Thani",
"Jumada al-Awwal",
"Jumada al-Thani",
"Rajab",
"Sha'ban",
"Ramadan",
"Shawwal",
"Dhu al-Qi'dah",
"Dhu al-Hijjah"
]
}
},
ar: {
settings: {
title: "\u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0627\u0644\u0635\u0644\u0627\u0629",
locationTimezone: "\u0627\u0644\u0645\u0648\u0642\u0639 \u0648\u0627\u0644\u0645\u0646\u0637\u0642\u0629 \u0627\u0644\u0632\u0645\u0646\u064A\u0629",
displayOptions: "\u0627\u0644\u0639\u0631\u0636",
horizontalPrayerList: "\u0627\u0644\u0639\u0631\u0636 \u0627\u0644\u0639\u0645\u0648\u062F\u064A \u0623\u0648\u0644\u064B\u0627",
verticalFirst: "\u0627\u0644\u0639\u0631\u0636 \u0627\u0644\u0639\u0645\u0648\u062F\u064A \u0623\u0648\u0644\u064B\u0627",
general: "\u0639\u0627\u0645",
calculation: "\u0627\u0644\u062D\u0633\u0627\u0628",
azan: "\u0627\u0644\u0623\u0630\u0627\u0646",
showOtherPrayers: "\u0625\u0638\u0647\u0627\u0631 \u0628\u0637\u0627\u0642\u0627\u062A \u0627\u0644\u0635\u0644\u0648\u0627\u062A \u0627\u0644\u0623\u062E\u0631\u0649",
showCity: "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0645\u062F\u064A\u0646\u0629 \u0627\u0644\u062D\u0627\u0644\u064A\u0629",
showTicker: "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0634\u0631\u064A\u0637 \u0627\u0644\u0645\u062A\u062D\u0631\u0643",
showClock: "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u0633\u0627\u0639\u0629",
showDate: "\u0625\u0638\u0647\u0627\u0631 \u0627\u0644\u062A\u0627\u0631\u064A\u062E",
calculationMethod: "\u0637\u0631\u064A\u0642\u0629 \u0627\u0644\u062D\u0633\u0627\u0628",
asrCalculation: "\u062D\u0633\u0627\u0628 \u0627\u0644\u0639\u0635\u0631",
timeAdjustments: "\u062A\u0639\u062F\u064A\u0644 \u0627\u0644\u0623\u0648\u0642\u0627\u062A (\u0628\u0627\u0644\u062F\u0642\u0627\u0626\u0642)",
timeAdjustmentsHelp: "\u0627\u0636\u0628\u0637 \u0623\u0648\u0642\u0627\u062A \u0627\u0644\u0635\u0644\u0627\u0629 \xB130 \u062F\u0642\u064A\u0642\u0629 \u0644\u062A\u0648\u0627\u0641\u0642 \u0645\u0633\u062C\u062F\u0643 \u0627\u0644\u0645\u062D\u0644\u064A",
done: "\u062A\u0645",
timeFormat24h: "\u0646\u0638\u0627\u0645 24 \u0633\u0627\u0639\u0629",
dimPreviousPrayers: "\u062A\u0639\u062A\u064A\u0645 \u0627\u0644\u0635\u0644\u0648\u0627\u062A \u0627\u0644\u0633\u0627\u0628\u0642\u0629",
language: "\u0627\u0644\u0644\u063A\u0629",
autoDetectTimezone: "\u0627\u0643\u062A\u0634\u0627\u0641 \u0627\u0644\u0645\u0646\u0637\u0642\u0629 \u0627\u0644\u0632\u0645\u0646\u064A\u0629 \u062A\u0644\u0642\u0627\u0626\u064A\u064B\u0627",
locationPermissionDenied: "\u062A\u0645 \u0631\u0641\u0636 \u0625\u0630\u0646 \u0627\u0644\u0645\u0648\u0642\u0639. \u0627\u0644\u0627\u0643\u062A\u0634\u0627\u0641 \u0627\u0644\u062A\u0644\u0642\u0627\u0626\u064A \u063A\u064A\u0631 \u0645\u064F\u0641\u0639\u0644. \u0633\u064A\u062A\u0645 \u0627\u0644\u0627\u0641\u062A\u0631\u0627\u0636\u064A \u0625\u0644\u0649 \u0645\u0643\u0629 \u0627\u0644\u0645\u0643\u0631\u0645\u0629\u060C \u0627\u0644\u0633\u0639\u0648\u062F\u064A\u0629.",
tickerSpeed: "\u0645\u062F\u0629 \u062A\u0628\u062F\u064A\u0644 \u0627\u0644\u0634\u0631\u064A\u0637"
},
azan: {
enable: "\u062A\u0641\u0639\u064A\u0644 \u0627\u0644\u0623\u0630\u0627\u0646",
volume: "\u0645\u0633\u062A\u0648\u0649 \u0627\u0644\u0635\u0648\u062A",
volumeLowWarning: "\u0645\u0633\u062A\u0648\u0649 \u0627\u0644\u0635\u0648\u062A \u0642\u062F \u064A\u0643\u0648\u0646 \u0645\u0646\u062E\u0641\u0636\u064B\u0627 \u062C\u062F\u064B\u0627 \u0644\u0633\u0645\u0627\u0639\u0647",
volumeZeroWarning: "\u0627\u0644\u0635\u0648\u062A \u0645\u0639\u0637\u0644. \u0644\u0646 \u064A\u062A\u0645 \u062A\u0634\u063A\u064A\u0644 \u0627\u0644\u0623\u0630\u0627\u0646",
type: "\u0646\u0648\u0639 \u0627\u0644\u0623\u0630\u0627\u0646",
customOverride: "\u0645\u062E\u0635\u0635 (\u064A\u0633\u062A\u0628\u062F\u0644 \u0627\u0644\u0643\u0644)",
perPrayer: "\u0644\u0643\u0644 \u0635\u0644\u0627\u0629",
full: "\u0643\u0627\u0645\u0644",
short: "\u0642\u0635\u064A\u0631",
beepOnly: "\u062A\u0646\u0628\u064A\u0647 \u0641\u0642\u0637",
customFile: "\u0645\u0644\u0641 \u0645\u062E\u0635\u0635",
selected: "\u0627\u0644\u0645\u062D\u062F\u062F:",
preview: "\u0645\u0639\u0627\u064A\u0646\u0629 \u0627\u0644\u0623\u0630\u0627\u0646"
},
uploader: {
dragOr: "\u0627\u0633\u062D\u0628 \u0645\u0644\u0641\u064B\u0627 \u0647\u0646\u0627 \u0623\u0648",
browse: "\u062A\u0635\u0641\u062D",
dropHere: "\u0623\u0641\u0644\u062A \u0627\u0644\u0645\u0644\u0641 \u0647\u0646\u0627"
},
general: {
remaining: "\u0645\u062A\u0628\u0642\u064A",
loading: "\u062C\u0627\u0631\u064A \u0627\u0644\u062A\u062D\u0645\u064A\u0644...",
remainingTillAzan: "\u0627\u0644\u0648\u0642\u062A \u0627\u0644\u0645\u062A\u0628\u0642\u064A \u062D\u062A\u0649 \u0627\u0644\u0623\u0630\u0627\u0646"
},
prayers: {
fajr: "\u0627\u0644\u0641\u062C\u0631",
sunrise: "\u0627\u0644\u0634\u0631\u0648\u0642",
dhuhr: "\u0627\u0644\u0638\u0647\u0631",
jumuah: "\u0627\u0644\u062C\u0645\u0639\u0629",
asr: "\u0627\u0644\u0639\u0635\u0631",
maghrib: "\u0627\u0644\u0645\u063A\u0631\u0628",
isha: "\u0627\u0644\u0639\u0634\u0627\u0621"
},
dates: {
hijriMonths: [
"\u0645\u062D\u0631\u0645",
"\u0635\u0641\u0631",
"\u0631\u0628\u064A\u0639 \u0627\u0644\u0623\u0648\u0644",
"\u0631\u0628\u064A\u0639 \u0627\u0644\u0622\u062E\u0631",
"\u062C\u0645\u0627\u062F\u0649 \u0627\u0644\u0623\u0648\u0644\u0649",
"\u062C\u0645\u0627\u062F\u0649 \u0627\u0644\u0622\u062E\u0631\u0629",
"\u0631\u062C\u0628",
"\u0634\u0639\u0628\u0627\u0646",
"\u0631\u0645\u0636\u0627\u0646",
"\u0634\u0648\u0627\u0644",
"\u0630\u0648 \u0627\u0644\u0642\u0639\u062F\u0629",
"\u0630\u0648 \u0627\u0644\u062D\u062C\u0629"
]
}
}
};
var TranslationContext = createContext(
void 0
);
function TranslationProvider({
children,
language
}) {
const t = (key) => {
const keys = key.split(".");
let value = translations[language];
for (const k of keys) {
if (value && typeof value === "object" && value !== null) {
value = value[k];
} else {
return key;
}
}
return typeof value === "string" ? value : key;
};
return /* @__PURE__ */ jsx(TranslationContext.Provider, { value: { t, language }, children });
}
function useTranslation() {
const context = useContext(TranslationContext);
if (!context) {
throw new Error("useTranslation must be used within a TranslationProvider");
}
return context;
}
var DATE_UPDATE_INTERVAL_MS = 6e4;
function formatHijriDate(now, language) {
const islamicLocale = language === "ar" ? "ar-SA" : "en";
const tryFormats = [
`${islamicLocale}-u-ca-islamic-umalqura`,
`${islamicLocale}-u-ca-islamic`
];
for (const loc of tryFormats) {
try {
const hijri = new Intl.DateTimeFormat(loc, {
day: "numeric",
month: "long",
year: "numeric"
}).format(now);
if (hijri) {
return hijri;
}
} catch {
}
}
return now.toLocaleDateString(language === "ar" ? "ar" : "en-US", {
weekday: "short",
month: "short",
day: "numeric",
year: "numeric"
});
}
function DualDateDisplay({ className }) {
const { language } = useTranslation();
const [dates, setDates] = useState(null);
useEffect(() => {
const updateDates = () => {
const now = /* @__PURE__ */ new Date();
const gregorian = now.toLocaleDateString(
language === "ar" ? "ar" : "en-US",
{
weekday: "short",
month: "short",
day: "numeric",
year: "numeric"
}
);
const hijri = formatHijriDate(now, language);
setDates({ gregorian, hijri });
};
updateDates();
const interval = setInterval(updateDates, DATE_UPDATE_INTERVAL_MS);
return () => clearInterval(interval);
}, [language]);
if (!dates) {
return null;
}
return /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-3 ${className}`, children: [
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 text-amber-400", children: /* @__PURE__ */ jsx(Calendar, { className: "h-4 w-4" }) }),
/* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
/* @__PURE__ */ jsx("div", { className: "font-medium text-foreground text-sm", children: dates.gregorian }),
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-xs", children: dates.hijri })
] })
] });
}
function TopBar({
showDate = true,
showClock = true,
showCity = true,
currentTime,
location,
timeFormat24h = true,
language = "en",
className,
classes
}) {
return /* @__PURE__ */ jsxs(
"div",
{
className: `flex items-center justify-between ${className ?? ""} ${classes?.container ?? ""}`,
children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
!!showDate && /* @__PURE__ */ jsx(DualDateDisplay, { className: classes?.date }),
!!showClock && /* @__PURE__ */ jsx(
"div",
{
className: `font-mono text-muted-foreground text-xs ${classes?.clock ?? ""}`,
children: formatCurrentTime(currentTime, timeFormat24h, language)
}
),
!!showCity && /* @__PURE__ */ jsxs(
"div",
{
className: `flex items-center gap-1 text-muted-foreground text-xs ${classes?.city ?? ""}`,
children: [
/* @__PURE__ */ jsx(MapPin, { className: `h-3 w-3 ${classes?.cityIcon ?? ""}` }),
/* @__PURE__ */ jsx("span", { children: location?.city || (language === "ar" ? "\u062C\u0627\u0631\u064A \u0627\u0644\u062A\u062D\u0645\u064A\u0644..." : "Loading...") }),
/* @__PURE__ */ jsx("span", { className: "ml-1", children: location?.countryCode ? countryToFlag(location.countryCode) : countryToFlag(location?.country || "") })
]
}
)
] }),
/* @__PURE__ */ jsx("div", {})
]
}
);
}
function cn(...inputs) {
return twMerge(clsx(inputs));
}
var buttonVariants = cva(
"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md font-medium text-sm outline-none transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline"
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9"
}
},
defaultVariants: {
variant: "default",
size: "default"
}
}
);
function Button({
className,
variant,
size,
asChild = false,
...props
}) {
const buttonClassName = cn(buttonVariants({ variant, size, className }));
if (asChild) {
const slotProps = props;
return /* @__PURE__ */ jsx(Slot, { className: buttonClassName, "data-slot": "button", ...slotProps });
}
return /* @__PURE__ */ jsx("button", { className: buttonClassName, "data-slot": "button", ...props });
}
function Label({
className,
...props
}) {
return /* @__PURE__ */ jsx(
LabelPrimitive.Root,
{
className: cn(
"flex select-text items-center gap-2 font-medium text-sm leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-50 group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50",
className
),
"data-slot": "label",
...props
}
);
}
function Popover({ ...props }) {
return /* @__PURE__ */ jsx(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
}
function PopoverTrigger({
...props
}) {
return /* @__PURE__ */ jsx(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
}) {
return /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx(
PopoverPrimitive.Content,
{
align,
className: cn(
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=closed]:animate-out data-[state=open]:animate-in",
className
),
"data-slot": "popover-content",
sideOffset,
...props
}
) });
}
function Input({ className, type, ...props }) {
return /* @__PURE__ */ jsx(
"input",
{
className: cn(
"flex h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs outline-none transition-[color,box-shadow] selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:font-medium file:text-foreground file:text-sm placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
className
),
"data-slot": "input",
type,
...props
}
);
}
function Select({ ...props }) {
return /* @__PURE__ */ jsx(SelectPrimitive.Root, { "data-slot": "select", ...props });
}
function SelectValue({
...props
}) {
return /* @__PURE__ */ jsx(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
}
function SelectTrigger({
className,
size = "default",
children,
...props
}) {
return /* @__PURE__ */ jsxs(
SelectPrimitive.Trigger,
{
className: cn(
"flex w-fit items-center justify-between gap-2 whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[size=default]:h-9 data-[size=sm]:h-8 data-[placeholder]:text-muted-foreground *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:hover:bg-input/50 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0",
className
),
"data-size": size,
"data-slot": "select-trigger",
...props,
children: [
children,
/* @__PURE__ */ jsx(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4 opacity-50" }) })
]
}
);
}
function SelectContent({
className,
children,
position = "popper",
...props
}) {
const viewportRef = useRef(null);
useEffect(() => {
const el = viewportRef.current?.querySelector(
"[data-state='checked']"
);
if (el && typeof el.scrollIntoView === "function") {
el.scrollIntoView({ block: "center" });
}
}, []);
return /* @__PURE__ */ jsx(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs(
SelectPrimitive.Content,
{
className: cn(
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-y-auto overflow-x-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=closed]:animate-out data-[state=open]:animate-in",
position === "popper" && "data-[side=left]:-translate-x-1 data-[side=top]:-translate-y-1 data-[side=right]:translate-x-1 data-[side=bottom]:translate-y-1",
className
),
"data-slot": "select-content",
position,
...props,
children: [
/* @__PURE__ */ jsx(SelectScrollUpButton, {}),
/* @__PURE__ */ jsx(
SelectPrimitive.Viewport,
{
className: cn(
"p-1",
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
),
ref: viewportRef,
children
}
),
/* @__PURE__ */ jsx(SelectScrollDownButton, {})
]
}
) });
}
function SelectItem({
className,
children,
...props
}) {
return /* @__PURE__ */ jsxs(
SelectPrimitive.Item,
{
className: cn(
"relative flex w-full cursor-default select-none items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
),
"data-slot": "select-item",
...props,
children: [
/* @__PURE__ */ jsx("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-4" }) }) }),
/* @__PURE__ */ jsx(SelectPrimitive.ItemText, { children })
]
}
);
}
function SelectScrollUpButton({
className,
...props
}) {
return /* @__PURE__ */ jsx(
SelectPrimitive.ScrollUpButton,
{
className: cn(
"flex cursor-default items-center justify-center py-1",
className
),
"data-slot": "select-scroll-up-button",
...props,
children: /* @__PURE__ */ jsx(ChevronUpIcon, { className: "size-4" })
}
);
}
function SelectScrollDownButton({
className,
...props
}) {
return /* @__PURE__ */ jsx(
SelectPrimitive.ScrollDownButton,
{
className: cn(
"flex cursor-default items-center justify-center py-1",
className
),
"data-slot": "select-scroll-down-button",
...props,
children: /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-4" })
}
);
}
var HSL_MAX_SATURATION = 100;
var HSL_MAX_LIGHTNESS = 100;
var HSL_DEFAULT_LIGHTNESS = 50;
var ALPHA_MAX = 100;
var FULL_ALPHA = 1;
var ALMOST_ZERO_THRESHOLD = 0.01;
var LIGHTNESS_RANGE = 50;
var ColorPickerContext = createContext(
void 0
);
var useColorPicker = () => {
const context = useContext(ColorPickerContext);
if (!context) {
throw new Error("useColorPicker must be used within a ColorPickerProvider");
}
return context;
};
var ColorPicker = ({
value,
defaultValue = "#000000",
onChange,
className,
...props
}) => {
const resolveHsl = (input) => {
try {
return Color(input).hsl();
} catch {
return Color(defaultValue).hsl();
}
};
const initial = resolveHsl(value ?? defaultValue);
const [hue, setHue] = useState(initial.hue() || 0);
const [saturation, setSaturation] = useState(
initial.saturationl() || HSL_MAX_SATURATION
);
const [lightness, setLightness] = useState(
initial.lightness() || HSL_DEFAULT_LIGHTNESS
);
const [alpha, setAlpha] = useState(
Math.round((initial.alpha() ?? FULL_ALPHA) * ALPHA_MAX)
);
const [mode, setMode] = useState("hex");
useEffect(() => {
if (value === void 0 || value === null) {
return;
}
try {
const c = Color(value).hsl();
const nextHue = c.hue() || 0;
const nextSat = c.saturationl() || 0;
const nextLight = c.lightness() || 0;
const nextAlpha = Math.round((c.alpha() ?? FULL_ALPHA) * ALPHA_MAX);
setHue(nextHue);
setSaturation(nextSat);
setLightness(nextLight);
setAlpha(nextAlpha);
} catch {
}
}, [value]);
useEffect(() => {
if (onChange) {
const color = Color.hsl(hue, saturation, lightness).alpha(
alpha / ALPHA_MAX
);
const rgba = color.rgb().array();
onChange([rgba[0], rgba[1], rgba[2], alpha / ALPHA_MAX]);
}
}, [hue, saturation, lightness, alpha, onChange]);
return /* @__PURE__ */ jsx(
ColorPickerContext.Provider,
{
value: {
hue,
saturation,
lightness,
alpha,
mode,
setHue,
setSaturation,
setLightness,
setAlpha,
setMode
},
children: /* @__PURE__ */ jsx(
"div",
{
className: cn("flex size-full flex-col gap-4", className),
...props
}
)
}
);
};
var ColorPickerSelection = memo(
({ className, ...props }) => {
const containerRef = useRef(null);
const [isDragging, setIsDragging] = useState(false);
const [positionX, setPositionX] = useState(0);
const [positionY, setPositionY] = useState(0);
const { hue, saturation, lightness, setSaturation, setLightness } = useColorPicker();
const backgroundGradient = useMemo(
() => `linear-gradient(0deg, rgba(0,0,0,1), rgba(0,0,0,0)),
linear-gradient(90deg, rgba(255,255,255,1), rgba(255,255,255,0)),
hsl(${hue}, 100%, 50%)`,
[hue]
);
const handlePointerMove = useCallback(
(event) => {
if (!(isDragging && containerRef.current)) {
return;
}
const rect = containerRef.current.getBoundingClientRect();
const x = Math.max(
0,
Math.min(1, (event.clientX - rect.left) / rect.width)
);
const y = Math.min(1, (event.clientY - rect.top) / rect.height);
setPositionX(x);
setPositionY(y);
setSaturation(x * HSL_MAX_SATURATION);
const topLightness = x < ALMOST_ZERO_THRESHOLD ? HSL_MAX_LIGHTNESS : HSL_DEFAULT_LIGHTNESS + LIGHTNESS_RANGE * (1 - x);
const calculatedLightness = topLightness * (1 - y);
setLightness(calculatedLightness);
},
[isDragging, setSaturation, setLightness]
);
useEffect(() => {
const handlePointerUp = () => setIsDragging(false);
if (isDragging) {
window.addEventListener("pointermove", handlePointerMove);
window.addEventListener("pointerup", handlePointerUp);
}
return () => {
window.removeEventListener("pointermove", handlePointerMove);
window.removeEventListener("pointerup", handlePointerUp);
};
}, [isDragging, handlePointerMove]);
useEffect(() => {
if (isDragging) {
return;
}
const x = Math.max(
0,
Math.min(1, (saturation ?? 0) / HSL_MAX_SATURATION)
);
const denom = HSL_MAX_LIGHTNESS - LIGHTNESS_RANGE * x;
const ratio = denom > 0 ? (lightness ?? 0) / denom : 0;
const y = 1 - Math.max(0, Math.min(1, ratio));
setPositionX(x);
setPositionY(y);
}, [saturation, lightness, isDragging]);
return /* @__PURE__ */ jsx(
"div",
{
className: cn("relative size-full cursor-crosshair rounded", className),
onPointerDown: (e) => {
e.preventDefault();
setIsDragging(true);
handlePointerMove(e.nativeEvent);
},
ref: containerRef,
style: {
background: backgroundGradient
},
...props,
children: /* @__PURE__ */ jsx(
"div",
{
className: "-translate-x-1/2 -translate-y-1/2 pointer-events-none absolute h-4 w-4 rounded-full border-2 border-white",
style: {
left: `${positionX * HSL_MAX_SATURATION}%`,
top: `${positionY * HSL_MAX_SATURATION}%`,
boxShadow: "0 0 0 1px rgba(0,0,0,0.5)"
}
}
)
}
);
}
);
ColorPickerSelection.displayName = "ColorPickerSelection";
var ColorPickerHue = ({
className,
...props
}) => {
const { hue, setHue } = useColorPicker();
return /* @__PURE__ */ jsxs(
Slider.Root,
{
className: cn("relative flex h-4 w-full touch-none", className),
max: 360,
onValueChange: ([newHue]) => setHue(newHue),
step: 1,
value: [hue],
...props,
children: [
/* @__PURE__ */ jsx(Slider.Track, { className: "relative my-0.5 h-3 w-full grow rounded-full bg-[linear-gradient(90deg,#FF0000,#FFFF00,#00FF00,#00FFFF,#0000FF,#FF00FF,#FF0000)]", children: /* @__PURE__ */ jsx(Slider.Range, { className: "absolute h-full" }) }),
/* @__PURE__ */ jsx(Slider.Thumb, { className: "block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" })
]
}
);
};
var ColorPickerAlpha = ({
className,
...props
}) => {
const { alpha, setAlpha } = useColorPicker();
return /* @__PURE__ */ jsxs(
Slider.Root,
{
className: cn("relative flex h-4 w-full touch-none", className),
max: 100,
onValueChange: ([newAlpha]) => setAlpha(newAlpha),
step: 1,
value: [alpha],
...props,
children: [
/* @__PURE__ */ jsxs(
Slider.Track,
{
className: "relative my-0.5 h-3 w-full grow rounded-full",
style: {
background: 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==") left center'
},
children: [
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-full bg-linear-to-r from-transparent to-black/50" }),
/* @__PURE__ */ jsx(Slider.Range, { className: "absolute h-full rounded-full bg-transparent" })
]
}
),
/* @__PURE__ */ jsx(Slider.Thumb, { className: "block h-4 w-4 rounded-full border border-primary/50 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50" })
]
}
);
};
var ColorPickerEyeDropper = ({
className,
...props
}) => {
const { setHue, setSaturation, setLightness, setAlpha } = useColorPicker();
const handleEyeDropper = async () => {
try {
const eyeDropper = new EyeDropper();
const result = await eyeDropper.open();
const color = Color(result.sRGBHex);
const [h, s, l] = color.hsl().array();
setHue(h);
setSaturation(s);
setLightness(l);
setAlpha(ALPHA_MAX);
} catch {
}
};
return /* @__PURE__ */ jsx(
Button,
{
className: cn("shrink-0 text-muted-foreground", className),
onClick: handleEyeDropper,
size: "icon",
type: "button",
variant: "outline",
...props,
children: /* @__PURE__ */ jsx(PipetteIcon, { size: 16 })
}
);
};
var formats = ["hex", "rgb", "css", "hsl"];
var ColorPickerOutput = ({
className,
...props
}) => {
const { mode, setMode } = useColorPicker();
return /* @__PURE__ */ jsxs(Select, { onValueChange: setMode, value: mode, children: [
/* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-20 shrink-0 text-xs", ...props, children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Mode" }) }),
/* @__PURE__ */ jsx(SelectContent, { children: formats.map((format) => /* @__PURE__ */ jsx(SelectItem, { className: "text-xs", value: format, children: format.toUpperCase() }, format)) })
] });
};
function ColorPickerGroup({
label,
value,
onChange,
ariaLabel
}) {
const safeHex = (val) => {
try {
const c = Array.isArray(val) ? Color.rgb(
val
) : Color(String(val || "#ffffff"));
return c.hex().toLowerCase();
} catch {
return "#ffffff";
}
};
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-2", children: [
/* @__PURE__ */ jsx(Label, { className: "whitespace-nowrap text-sm", children: label }),
/* @__PURE__ */ jsxs(Popover, { children: [
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
Button,
{
"aria-label": ariaLabel || `Pick ${label} color`,
className: "flex h-8 w-8 items-center justify-center rounded-md p-0",
style: { backgroundColor: value || "#ffffff" },
type: "button",
variant: "outline",
children: /* @__PURE__ */ jsx(Pipette, { className: "h-3.5 w-3.5 opacity-80" })
}
) }),
/* @__PURE__ */ jsx(PopoverContent, { className: "w-80", children: /* @__PURE__ */ jsxs(
ColorPicker,
{
onChange: (val) => onChange(safeHex(val)),
value: value || "#ffffff",
children: [
/* @__PURE__ */ jsx(ColorPickerSelection, { className: "h-28" }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
/* @__PURE__ */ jsx(ColorPickerEyeDropper, {}),
/* @__PURE__ */ jsxs("div", { className: "grid w-full gap-1", children: [
/* @__PURE__ */ jsx(ColorPickerHue, {}),
/* @__PURE__ */ jsx(ColorPickerAlpha, {})
] })
] }),
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(ColorPickerOutput, {}) })
]
}
) })
] })
] });
}
function getStrictContext(name) {
const Context = createContext(void 0);
const Provider = ({
value,
children
}) => /* @__PURE__ */ jsx(Context.Provider, { value, children });
const useSafeContext = () => {
const ctx = useContext(Context);
if (ctx === void 0) {
throw new Error(`useContext must be used within ${name}`);
}
return ctx;
};
return [Provider, useSafeContext];
}
function mergeRefs(...refs) {
return (node) => {
for (const ref of refs) {
if (!ref) {
continue;
}
if (typeof ref === "function") {
ref(node);
} else {
ref.current = node;
}
}
};
}
function mergeProps(childProps, slotProps) {
const merged = { ...childProps, ...slotProps };
if (childProps.className || slotProps.className) {
merged.className = cn(
childProps.className,
slotProps.className
);
}
if (childProps.style || slotProps.style) {
merged.style = {
...childProps.style,
...slotProps.style
};
}
return merged;
}
function Slot2({
children,
ref,
...props
}) {
const isAlreadyMotion = isValidElement(children) && typeof children.type === "object" && children.type !== null && isMotionComponent(children.type);
const Base = useMemo(() => {
if (isAlreadyMotion && isValidElement(children)) {
return children.type;
}
if (isValidElement(children)) {
return motion.create(children.type);
}
return motion.div;
}, [isAlreadyMotion, children]);
if (!isValidElement(children)) {
const divProps = props;
return /* @__PURE__ */ jsx(motion.div, { ...divProps, ref, children });
}
const { ref: childRef, ...childProps } = children.props;
const mergedProps = mergeProps(childProps, props);
return /* @__PURE__ */ jsx(Base, { ...mergedProps, ref: mergeRefs(childRef, ref) });
}
var MODULO_DIVISOR = 10;
var HALFWAY_OFFSET = 5;
var THOUSAND_SEPARATOR_INTERVAL = 3;
function SlidingNumberRoller({
prevValue,
value,
place,
transition: transition2,
delay = 0
}) {
const startNumber = Math.floor(prevValue / place) % MODULO_DIVISOR;
const targetNumber = Math.floor(value / place) % MODULO_DIVISOR;
const animatedValue = useSpring(startNumber, transition2);
useEffect(() => {
const timeoutId = setTimeout(() => {
animatedValue.set(targetNumber);
}, delay);
return () => clearTimeout(timeoutId);
}, [targetNumber, animatedValue, delay]);
const [measureRef, { height }] = useMeasure();
return /* @__PURE__ */ jsxs(
"span",
{
"data-slot": "sliding-number-roller",
ref: measureRef,
style: {
position: "relative",
display: "inline-block",
width: "1ch",
overflowX: "visible",
overflowY: "clip",
lineHeight: 1,
fontVariantNumeric: "tabular-nums"
},
children: [
/* @__PURE__ */ jsx("span", { style: { visibility: "hidden" }, children: "0" }),
Array.from({ length: MODULO_DIVISOR }, (_, i) => /* @__PURE__ */ jsx(
SlidingNumberDisplay,
{
height,
motionValue: animatedValue,
number: i,
transition: transition2
},
i.toString()
))
]
}
);
}
function SlidingNumberDisplay({
motionValue,
number,
height,
transition: transition2
}) {
const y = useTransform(motionValue, (latest) => {
if (!height) {
return 0;
}
const currentNumber = latest % MODULO_DIVISOR;
const offset = (MODULO_DIVISOR + number - currentNumber) % MODULO_DIVISOR;
let translateY = offset * height;
if (offset > HALFWAY_OFFSET) {
translateY -= MODULO_DIVISOR * height;
}
return translateY;
});
if (!height) {
return /* @__PURE__ */ jsx("span", { style: { visibility: "hidden", position: "absolute" }, children: number });
}
return /* @__PURE__ */ jsx(
motion.span,
{
"data-slot": "sliding-number-display",
style: {
y,
position: "absolute",
inset: 0,
display: "flex",
alignItems: "center",
justifyContent: "center"
},
transition: { ...transition2, type: "spring" },
children: number
}
);
}
function SlidingNumber({
ref,
number,
fromNumber,
onNumberChange,
inView = false,
inViewMargin = "0px",
inViewOnce = true,
padStart = false,
decimalSeparator = ".",
decimalPlaces = 0,
thousandSeparator,
transition: transition2 = { stiffness: 200, damping: 20, mass: 0.4 },
delay = 0,
...props
}) {
const { ref: localRef, isInView } = useIsInView(ref, {
inView,
inViewOnce,
inViewMargin
});
const prevNumberRef = useRef(0);
const hasAnimated = fromNumber !== void 0;
const motionVal = useMotionValue(fromNumber ?? 0);
const springVal = useSpring(motionVal, { stiffness: 90, damping: 50 });
useEffect(() => {
if (!hasAnimated) {
return;
}
const timeoutId = setTimeout(() => {
if (isInView) {
motionVal.set(number);
}
}, delay);
return () => clearTimeout(timeoutId);
}, [hasAnimated, isInView, number, motionVal, delay]);
const [effectiveNumber, setEffectiveNumber] = useState(0);
useEffect(() => {
if (hasAnimated) {
const inferredDecimals = typeof decimalPlaces === "number" && decimalPlaces >= 0 ? decimalPlaces : (() => {
const s = String(number);
const idx = s.indexOf(".");
return idx >= 0 ? s.length - idx - 1 : 0;
})();
const factor = 10 ** inferredDecimals;
const unsubscribe = springVal.on(
"change",
(latestValue) => {
const latest = typeof latestValue === "number" ? latestValue : Number.parseFloat(String(latestValue));
const newValue = inferredDecimals > 0 ? Math.round(latest * factor) / factor : Math.round(latest);
if (effectiveNumber !== newValue) {
setEffectiveNumber(newValue);
onNumberChange?.(newValue);
}
}
);
return () => unsubscribe();
}
setEffectiveNumber(isInView ? Math.abs(Number(number)) : 0);
}, [
hasAnimated,
springVal,
isInView,
number,
decimalPlaces,
onNumberChange,
effectiveNumber
]);
const formatNumber = useCallback(
(num) => decimalPlaces !== null ? num.toFixed(decimalPlaces) : num.toString(),
[decimalPlaces]
);
const numberStr = formatNumber(effectiveNumber);
const [newIntStrRaw, newDecStrRaw = ""] = numberStr.split(".");
const finalIntLength = padStart ? Math.max(
Math.floor(Math.abs(number)).toString().length,
newIntStrRaw.length
) : newIntStrRaw.length;
const newIntStr = padStart ? newIntStrRaw.padStart(finalIntLength, "0") : newIntStrRaw;
const prevFormatted = formatNumber(prevNumberRef.current);
const [prevIntStrRaw = "", prevDecStrRaw = ""] = prevFormatted.split(".");
const prevIntStr = padStart ? prevIntStrRaw.padStart(finalIntLength, "0") : prevIntStrRaw;
const adjustedPrevInt = useMemo(
() => prevIntStr.length > finalIntLength ? prevIntStr.slice(-finalIntLength) : prevIntStr.padStart(finalIntLength, "0"),
[prevIntStr, finalIntLength]
);
const adjustedPrevDec = useMemo(() => {
if (!newDecStrRaw) {
return "";
}
return prevDecStrRaw.length > newDecStrRaw.length ? prevDecStrRaw.slice(0, newDecStrRaw.length) : prevDecStrRaw.padEnd(newDecStrRaw.length, "0");
}, [prevDecStrRaw, newDecStrRaw]);
useEffect(() => {
if (isInView) {
prevNumberRef.current = effectiveNumber;
}
}, [effectiveNumber, isInView]);
const intPlaces = useMemo(
() => Array.from(
{ length: finalIntLength },
(_, i) => MODULO_DIVISOR ** (finalIntLength - i - 1)
),
[finalIntLength]
);
const decPlaces = useMemo(
() => newDecStrRaw ? Array.from(
{ length: newDecStrRaw.length },
(_, i) => MODULO_DIVISOR ** (newDecStrRaw.length - i - 1)
) : [],
[newDecStrRaw]
);
const newDecValue = newDecStrRaw ? Number.parseInt(newDecStrRaw, 10) : 0;
const prevDecValue = adjustedPrevDec ? Number.parseInt(adjustedPrevDec, 10) : 0;
return /* @__PURE__ */ jsxs(
"span",
{
"data-slot": "sliding-number",
ref: localRef,
style: {
display: "inline-flex",
alignItems: "center"
},
...props,
children: [
!!isInView && Number(number) < 0 && /* @__PURE__ */ jsx("span", { style: { marginRight: "0.25rem" }, children: "-" }),
intPlac