@underverse-ui/underverse 0.1.33 → 0.1.35
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/dist/index.cjs +286 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +291 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -214,6 +214,7 @@ var Button = (0, import_react.forwardRef)(
|
|
|
214
214
|
lockMs = 600,
|
|
215
215
|
asContainer = false,
|
|
216
216
|
noWrap = true,
|
|
217
|
+
noHoverOverlay = false,
|
|
217
218
|
...rest
|
|
218
219
|
}, ref) => {
|
|
219
220
|
const baseStyles = asContainer ? "relative inline-flex justify-center rounded-md font-medium transition-colors duration-150 ease-soft outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background active:translate-y-px" : "relative inline-flex items-center justify-center gap-2 rounded-md font-medium overflow-hidden transition-colors duration-150 ease-soft outline-none focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background active:translate-y-px";
|
|
@@ -275,7 +276,7 @@ var Button = (0, import_react.forwardRef)(
|
|
|
275
276
|
"aria-label": rest["aria-label"] || title,
|
|
276
277
|
...rest,
|
|
277
278
|
children: [
|
|
278
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute inset-0 bg-gradient-to-r from-primary-foreground/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-200" }),
|
|
279
|
+
!noHoverOverlay && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "absolute inset-0 bg-gradient-to-r from-primary-foreground/20 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-200" }),
|
|
279
280
|
loading2 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
280
281
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(SpinnerIcon, { className: "w-4 h-4 animate-spin" }),
|
|
281
282
|
loadingText && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "ml-2", "aria-live": "polite", children: loadingText }),
|
|
@@ -6897,57 +6898,296 @@ function ImageUpload({
|
|
|
6897
6898
|
var React27 = __toESM(require("react"), 1);
|
|
6898
6899
|
var import_lucide_react19 = require("lucide-react");
|
|
6899
6900
|
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
6900
|
-
function Carousel({
|
|
6901
|
+
function Carousel({
|
|
6902
|
+
children,
|
|
6903
|
+
autoScroll = true,
|
|
6904
|
+
autoScrollInterval = 5e3,
|
|
6905
|
+
animation = "slide",
|
|
6906
|
+
orientation = "horizontal",
|
|
6907
|
+
showArrows = true,
|
|
6908
|
+
showDots = true,
|
|
6909
|
+
showProgress = false,
|
|
6910
|
+
showThumbnails = false,
|
|
6911
|
+
loop = true,
|
|
6912
|
+
slidesToShow = 1,
|
|
6913
|
+
slidesToScroll = 1,
|
|
6914
|
+
className,
|
|
6915
|
+
containerClassName,
|
|
6916
|
+
slideClassName,
|
|
6917
|
+
onSlideChange,
|
|
6918
|
+
thumbnailRenderer,
|
|
6919
|
+
ariaLabel = "Carousel"
|
|
6920
|
+
}) {
|
|
6901
6921
|
const [currentIndex, setCurrentIndex] = React27.useState(0);
|
|
6902
|
-
const totalSlides = React27.Children.count(children);
|
|
6903
6922
|
const [isPaused, setIsPaused] = React27.useState(false);
|
|
6923
|
+
const [isDragging, setIsDragging] = React27.useState(false);
|
|
6924
|
+
const [startPos, setStartPos] = React27.useState(0);
|
|
6925
|
+
const [currentTranslate, setCurrentTranslate] = React27.useState(0);
|
|
6926
|
+
const [prevTranslate, setPrevTranslate] = React27.useState(0);
|
|
6927
|
+
const [progress, setProgress] = React27.useState(0);
|
|
6928
|
+
const carouselRef = React27.useRef(null);
|
|
6929
|
+
const progressIntervalRef = React27.useRef(null);
|
|
6930
|
+
const totalSlides = React27.Children.count(children);
|
|
6931
|
+
const maxIndex = Math.max(0, totalSlides - slidesToShow);
|
|
6932
|
+
const isHorizontal = orientation === "horizontal";
|
|
6904
6933
|
const scrollPrev = React27.useCallback(() => {
|
|
6905
|
-
setCurrentIndex((prev) =>
|
|
6906
|
-
|
|
6934
|
+
setCurrentIndex((prev) => {
|
|
6935
|
+
if (prev === 0) {
|
|
6936
|
+
return loop ? maxIndex : 0;
|
|
6937
|
+
}
|
|
6938
|
+
return Math.max(0, prev - slidesToScroll);
|
|
6939
|
+
});
|
|
6940
|
+
}, [loop, maxIndex, slidesToScroll]);
|
|
6907
6941
|
const scrollNext = React27.useCallback(() => {
|
|
6908
|
-
setCurrentIndex((prev) =>
|
|
6909
|
-
|
|
6942
|
+
setCurrentIndex((prev) => {
|
|
6943
|
+
if (prev >= maxIndex) {
|
|
6944
|
+
return loop ? 0 : maxIndex;
|
|
6945
|
+
}
|
|
6946
|
+
return Math.min(maxIndex, prev + slidesToScroll);
|
|
6947
|
+
});
|
|
6948
|
+
}, [loop, maxIndex, slidesToScroll]);
|
|
6949
|
+
const scrollTo = React27.useCallback(
|
|
6950
|
+
(index) => {
|
|
6951
|
+
setCurrentIndex(Math.min(maxIndex, Math.max(0, index)));
|
|
6952
|
+
},
|
|
6953
|
+
[maxIndex]
|
|
6954
|
+
);
|
|
6910
6955
|
React27.useEffect(() => {
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
|
|
6928
|
-
|
|
6929
|
-
),
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6956
|
+
const handleKeyDown = (e) => {
|
|
6957
|
+
if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
|
|
6958
|
+
e.preventDefault();
|
|
6959
|
+
scrollPrev();
|
|
6960
|
+
} else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
|
|
6961
|
+
e.preventDefault();
|
|
6962
|
+
scrollNext();
|
|
6963
|
+
} else if (e.key === "Home") {
|
|
6964
|
+
e.preventDefault();
|
|
6965
|
+
scrollTo(0);
|
|
6966
|
+
} else if (e.key === "End") {
|
|
6967
|
+
e.preventDefault();
|
|
6968
|
+
scrollTo(maxIndex);
|
|
6969
|
+
}
|
|
6970
|
+
};
|
|
6971
|
+
const carousel = carouselRef.current;
|
|
6972
|
+
if (carousel) {
|
|
6973
|
+
carousel.addEventListener("keydown", handleKeyDown);
|
|
6974
|
+
return () => carousel.removeEventListener("keydown", handleKeyDown);
|
|
6975
|
+
}
|
|
6976
|
+
}, [scrollPrev, scrollNext, scrollTo, maxIndex]);
|
|
6977
|
+
React27.useEffect(() => {
|
|
6978
|
+
if (!autoScroll || isPaused || totalSlides <= slidesToShow) {
|
|
6979
|
+
setProgress(0);
|
|
6980
|
+
if (progressIntervalRef.current) {
|
|
6981
|
+
clearInterval(progressIntervalRef.current);
|
|
6982
|
+
}
|
|
6983
|
+
return;
|
|
6984
|
+
}
|
|
6985
|
+
setProgress(0);
|
|
6986
|
+
const progressStep = 100 / (autoScrollInterval / 50);
|
|
6987
|
+
progressIntervalRef.current = setInterval(() => {
|
|
6988
|
+
setProgress((prev) => {
|
|
6989
|
+
if (prev >= 100) {
|
|
6990
|
+
scrollNext();
|
|
6991
|
+
return 0;
|
|
6938
6992
|
}
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
{
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
)
|
|
6950
|
-
|
|
6993
|
+
return prev + progressStep;
|
|
6994
|
+
});
|
|
6995
|
+
}, 50);
|
|
6996
|
+
return () => {
|
|
6997
|
+
if (progressIntervalRef.current) {
|
|
6998
|
+
clearInterval(progressIntervalRef.current);
|
|
6999
|
+
}
|
|
7000
|
+
};
|
|
7001
|
+
}, [autoScroll, isPaused, totalSlides, slidesToShow, autoScrollInterval, scrollNext]);
|
|
7002
|
+
const getPositionX = (event) => {
|
|
7003
|
+
return event.type.includes("mouse") ? event.pageX : event.touches[0].clientX;
|
|
7004
|
+
};
|
|
7005
|
+
const getPositionY = (event) => {
|
|
7006
|
+
return event.type.includes("mouse") ? event.pageY : event.touches[0].clientY;
|
|
7007
|
+
};
|
|
7008
|
+
const touchStart = (event) => {
|
|
7009
|
+
setIsDragging(true);
|
|
7010
|
+
const pos = isHorizontal ? getPositionX(event.nativeEvent) : getPositionY(event.nativeEvent);
|
|
7011
|
+
setStartPos(pos);
|
|
7012
|
+
setPrevTranslate(currentTranslate);
|
|
7013
|
+
};
|
|
7014
|
+
const touchMove = (event) => {
|
|
7015
|
+
if (!isDragging) return;
|
|
7016
|
+
const pos = isHorizontal ? getPositionX(event.nativeEvent) : getPositionY(event.nativeEvent);
|
|
7017
|
+
const currentPosition = pos;
|
|
7018
|
+
setCurrentTranslate(prevTranslate + currentPosition - startPos);
|
|
7019
|
+
};
|
|
7020
|
+
const touchEnd = () => {
|
|
7021
|
+
if (!isDragging) return;
|
|
7022
|
+
setIsDragging(false);
|
|
7023
|
+
const movedBy = currentTranslate - prevTranslate;
|
|
7024
|
+
const threshold = 50;
|
|
7025
|
+
if (movedBy < -threshold && currentIndex < maxIndex) {
|
|
7026
|
+
scrollNext();
|
|
7027
|
+
} else if (movedBy > threshold && currentIndex > 0) {
|
|
7028
|
+
scrollPrev();
|
|
7029
|
+
}
|
|
7030
|
+
setCurrentTranslate(0);
|
|
7031
|
+
setPrevTranslate(0);
|
|
7032
|
+
};
|
|
7033
|
+
React27.useEffect(() => {
|
|
7034
|
+
onSlideChange?.(currentIndex);
|
|
7035
|
+
}, [currentIndex, onSlideChange]);
|
|
7036
|
+
const getAnimationStyles = () => {
|
|
7037
|
+
const baseTransform = isHorizontal ? `translateX(-${currentIndex * (100 / slidesToShow)}%)` : `translateY(-${currentIndex * (100 / slidesToShow)}%)`;
|
|
7038
|
+
if (animation === "fade") {
|
|
7039
|
+
return {
|
|
7040
|
+
transition: "opacity 500ms ease-in-out"
|
|
7041
|
+
};
|
|
7042
|
+
}
|
|
7043
|
+
if (animation === "scale") {
|
|
7044
|
+
return {
|
|
7045
|
+
transform: baseTransform,
|
|
7046
|
+
transition: "transform 500ms ease-in-out, scale 500ms ease-in-out"
|
|
7047
|
+
};
|
|
7048
|
+
}
|
|
7049
|
+
return {
|
|
7050
|
+
transform: baseTransform,
|
|
7051
|
+
transition: isDragging ? "none" : "transform 500ms ease-in-out"
|
|
7052
|
+
};
|
|
7053
|
+
};
|
|
7054
|
+
const slideWidth = 100 / slidesToShow;
|
|
7055
|
+
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(
|
|
7056
|
+
"div",
|
|
7057
|
+
{
|
|
7058
|
+
ref: carouselRef,
|
|
7059
|
+
className: cn("relative w-full overflow-hidden focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 rounded-lg", className),
|
|
7060
|
+
onMouseEnter: () => setIsPaused(true),
|
|
7061
|
+
onMouseLeave: () => setIsPaused(false),
|
|
7062
|
+
role: "region",
|
|
7063
|
+
"aria-label": ariaLabel,
|
|
7064
|
+
"aria-roledescription": "carousel",
|
|
7065
|
+
tabIndex: 0,
|
|
7066
|
+
children: [
|
|
7067
|
+
showProgress && autoScroll && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "absolute top-0 left-0 right-0 h-1 bg-muted z-20", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "h-full bg-primary transition-all duration-50 ease-linear", style: { width: `${progress}%` } }) }),
|
|
7068
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
7069
|
+
"div",
|
|
7070
|
+
{
|
|
7071
|
+
className: cn("flex", isHorizontal ? "flex-row" : "flex-col", containerClassName),
|
|
7072
|
+
style: getAnimationStyles(),
|
|
7073
|
+
onTouchStart: touchStart,
|
|
7074
|
+
onTouchMove: touchMove,
|
|
7075
|
+
onTouchEnd: touchEnd,
|
|
7076
|
+
onMouseDown: touchStart,
|
|
7077
|
+
onMouseMove: touchMove,
|
|
7078
|
+
onMouseUp: touchEnd,
|
|
7079
|
+
onMouseLeave: touchEnd,
|
|
7080
|
+
role: "group",
|
|
7081
|
+
"aria-atomic": "false",
|
|
7082
|
+
"aria-live": autoScroll ? "off" : "polite",
|
|
7083
|
+
children: React27.Children.map(children, (child, idx) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
7084
|
+
"div",
|
|
7085
|
+
{
|
|
7086
|
+
className: cn(
|
|
7087
|
+
"flex-shrink-0",
|
|
7088
|
+
isHorizontal ? "h-full" : "w-full",
|
|
7089
|
+
animation === "fade" && idx !== currentIndex && "opacity-0",
|
|
7090
|
+
animation === "scale" && idx !== currentIndex && "scale-95",
|
|
7091
|
+
slideClassName
|
|
7092
|
+
),
|
|
7093
|
+
style: {
|
|
7094
|
+
[isHorizontal ? "width" : "height"]: `${slideWidth}%`
|
|
7095
|
+
},
|
|
7096
|
+
role: "group",
|
|
7097
|
+
"aria-roledescription": "slide",
|
|
7098
|
+
"aria-label": `${idx + 1} of ${totalSlides}`,
|
|
7099
|
+
"aria-hidden": idx < currentIndex || idx >= currentIndex + slidesToShow,
|
|
7100
|
+
children: child
|
|
7101
|
+
},
|
|
7102
|
+
idx
|
|
7103
|
+
))
|
|
7104
|
+
}
|
|
7105
|
+
),
|
|
7106
|
+
showArrows && totalSlides > slidesToShow && /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)(import_jsx_runtime35.Fragment, { children: [
|
|
7107
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
7108
|
+
Button_default,
|
|
7109
|
+
{
|
|
7110
|
+
onClick: scrollPrev,
|
|
7111
|
+
variant: "ghost",
|
|
7112
|
+
size: "icon",
|
|
7113
|
+
icon: import_lucide_react19.ChevronLeft,
|
|
7114
|
+
noHoverOverlay: true,
|
|
7115
|
+
disabled: !loop && currentIndex === 0,
|
|
7116
|
+
className: cn(
|
|
7117
|
+
"absolute top-1/2 -translate-y-1/2 hover:-translate-y-1/2 active:-translate-y-1/2 z-10 rounded-full will-change-transform backdrop-blur-0 hover:backdrop-blur-0 hover:bg-transparent border-0",
|
|
7118
|
+
isHorizontal ? "left-4" : "top-4 left-1/2 -translate-x-1/2 rotate-90"
|
|
7119
|
+
),
|
|
7120
|
+
"aria-label": "Previous slide"
|
|
7121
|
+
}
|
|
7122
|
+
),
|
|
7123
|
+
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
7124
|
+
Button_default,
|
|
7125
|
+
{
|
|
7126
|
+
onClick: scrollNext,
|
|
7127
|
+
variant: "ghost",
|
|
7128
|
+
size: "icon",
|
|
7129
|
+
icon: import_lucide_react19.ChevronRight,
|
|
7130
|
+
noHoverOverlay: true,
|
|
7131
|
+
disabled: !loop && currentIndex >= maxIndex,
|
|
7132
|
+
className: cn(
|
|
7133
|
+
"absolute top-1/2 -translate-y-1/2 hover:-translate-y-1/2 active:-translate-y-1/2 z-10 rounded-full will-change-transform backdrop-blur-0 hover:backdrop-blur-0 hover:bg-transparent border-0",
|
|
7134
|
+
isHorizontal ? "right-4" : "bottom-4 left-1/2 -translate-x-1/2 rotate-90"
|
|
7135
|
+
),
|
|
7136
|
+
"aria-label": "Next slide"
|
|
7137
|
+
}
|
|
7138
|
+
)
|
|
7139
|
+
] }),
|
|
7140
|
+
showDots && totalSlides > slidesToShow && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
7141
|
+
"div",
|
|
7142
|
+
{
|
|
7143
|
+
className: cn(
|
|
7144
|
+
"absolute flex gap-2 z-10",
|
|
7145
|
+
isHorizontal ? "bottom-4 left-1/2 -translate-x-1/2 flex-row" : "right-4 top-1/2 -translate-y-1/2 flex-col"
|
|
7146
|
+
),
|
|
7147
|
+
role: "tablist",
|
|
7148
|
+
"aria-label": "Carousel pagination",
|
|
7149
|
+
children: Array.from({ length: maxIndex + 1 }, (_, idx) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
7150
|
+
"button",
|
|
7151
|
+
{
|
|
7152
|
+
onClick: () => scrollTo(idx),
|
|
7153
|
+
className: cn(
|
|
7154
|
+
"rounded-full transition-all focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2",
|
|
7155
|
+
isHorizontal ? "w-2 h-2" : "w-2 h-2",
|
|
7156
|
+
idx === currentIndex ? `bg-primary ${isHorizontal ? "w-6" : "h-6"}` : "bg-muted-foreground/50 hover:bg-muted-foreground/75"
|
|
7157
|
+
),
|
|
7158
|
+
"aria-label": `Go to slide ${idx + 1}`,
|
|
7159
|
+
"aria-selected": idx === currentIndex,
|
|
7160
|
+
role: "tab"
|
|
7161
|
+
},
|
|
7162
|
+
idx
|
|
7163
|
+
))
|
|
7164
|
+
}
|
|
7165
|
+
),
|
|
7166
|
+
showThumbnails && totalSlides > slidesToShow && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
7167
|
+
"div",
|
|
7168
|
+
{
|
|
7169
|
+
className: cn(
|
|
7170
|
+
"absolute bottom-0 left-0 right-0 flex gap-2 p-4 bg-gradient-to-t from-black/50 to-transparent overflow-x-auto",
|
|
7171
|
+
isHorizontal ? "flex-row" : "flex-col"
|
|
7172
|
+
),
|
|
7173
|
+
children: React27.Children.map(children, (child, idx) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
7174
|
+
"button",
|
|
7175
|
+
{
|
|
7176
|
+
onClick: () => scrollTo(idx),
|
|
7177
|
+
className: cn(
|
|
7178
|
+
"flex-shrink-0 w-16 h-16 rounded-md overflow-hidden border-2 transition-all focus:outline-none focus:ring-2 focus:ring-primary",
|
|
7179
|
+
idx === currentIndex ? "border-primary scale-110" : "border-transparent opacity-70 hover:opacity-100"
|
|
7180
|
+
),
|
|
7181
|
+
"aria-label": `Thumbnail ${idx + 1}`,
|
|
7182
|
+
children: thumbnailRenderer ? thumbnailRenderer(child, idx) : child
|
|
7183
|
+
},
|
|
7184
|
+
idx
|
|
7185
|
+
))
|
|
7186
|
+
}
|
|
7187
|
+
)
|
|
7188
|
+
]
|
|
7189
|
+
}
|
|
7190
|
+
);
|
|
6951
7191
|
}
|
|
6952
7192
|
|
|
6953
7193
|
// ../../components/ui/ClientOnly.tsx
|