@helpwave/hightide
Version:
helpwave's component and theming library
641 lines (634 loc) • 23.5 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/components/layout-and-navigation/Carousel.tsx
var Carousel_exports = {};
__export(Carousel_exports, {
Carousel: () => Carousel
});
module.exports = __toCommonJS(Carousel_exports);
var import_react2 = require("react");
var import_clsx2 = __toESM(require("clsx"));
var import_lucide_react = require("lucide-react");
// src/util/array.ts
var defaultRangeOptions = {
allowEmptyRange: false,
stepSize: 1,
exclusiveStart: false,
exclusiveEnd: true
};
var range = (endOrRange, options) => {
const { allowEmptyRange, stepSize, exclusiveStart, exclusiveEnd } = { ...defaultRangeOptions, ...options };
let start = 0;
let end;
if (typeof endOrRange === "number") {
end = endOrRange;
} else {
start = endOrRange[0];
end = endOrRange[1];
}
if (!exclusiveEnd) {
end -= 1;
}
if (exclusiveStart) {
start += 1;
}
if (end - 1 < start) {
if (!allowEmptyRange) {
console.warn(`range: end (${end}) < start (${start}) should be allowed explicitly, set options.allowEmptyRange to true`);
}
return [];
}
return Array.from({ length: end - start }, (_, index) => index * stepSize + start);
};
var createLoopingListWithIndex = (list, startIndex = 0, length = 0, forwards = true) => {
if (length < 0) {
console.warn(`createLoopingList: length must be >= 0, given ${length}`);
} else if (length === 0) {
length = list.length;
}
const returnList = [];
if (forwards) {
for (let i = startIndex; returnList.length < length; i = (i + 1) % list.length) {
returnList.push([i, list[i]]);
}
} else {
for (let i = startIndex; returnList.length < length; i = i === 0 ? i = list.length - 1 : i - 1) {
returnList.push([i, list[i]]);
}
}
return returnList;
};
// src/util/math.ts
var clamp = (value, min = 0, max = 1) => {
return Math.min(Math.max(value, min), max);
};
// src/util/easeFunctions.ts
var EaseFunctions = class _EaseFunctions {
static cubicBezierGeneric(x1, y1, x2, y2) {
const cx = 3 * x1;
const bx = 3 * (x2 - x1) - cx;
const ax = 1 - cx - bx;
const cy = 3 * y1;
const by = 3 * (y2 - y1) - cy;
const ay = 1 - cy - by;
const x = (t) => ((ax * t + bx) * t + cx) * t;
const y = (t) => ((ay * t + by) * t + cy) * t;
return {
x,
y
};
}
static cubicBezier(x1, y1, x2, y2) {
const { y } = _EaseFunctions.cubicBezierGeneric(x1, y1, x2, y2);
return (t) => {
t = clamp(t);
return y(t);
};
}
static easeInEaseOut(t) {
return _EaseFunctions.cubicBezier(0.65, 0, 0.35, 1)(t);
}
};
// src/util/loopingArray.ts
var LoopingArrayCalculator = class _LoopingArrayCalculator {
constructor(length, isLooping = true, allowedOverScroll = 0.1) {
if (allowedOverScroll < 0 || length < 1) {
throw new Error("Invalid parameters: allowedOverScroll >= 0 and length >= 1 must be true");
}
this.length = length;
this.isLooping = isLooping;
this.allowedOverScroll = allowedOverScroll;
}
getCorrectedPosition(position) {
if (!this.isLooping) {
return Math.max(-this.allowedOverScroll, Math.min(this.allowedOverScroll + this.length - 1, position));
}
if (position >= this.length) {
return position % this.length;
}
if (position < 0) {
return this.length - Math.abs(position) % this.length;
}
return position;
}
static withoutOffset(position) {
return position + _LoopingArrayCalculator.getOffset(position);
}
static getOffset(position) {
return Math.round(position) - position;
}
/**
* @return absolute distance forwards or Infinity when the target cannot be reached (only possible when not isLooping)
*/
getDistanceDirectional(position, target, direction) {
if (!this.isLooping && (position < -this.allowedOverScroll || position > this.allowedOverScroll + this.length - 1)) {
throw new Error("Invalid parameters: position is out of bounds.");
}
const isForwardInvalid = direction === 1 && position > target;
const isBackwardInvalid = direction === -1 && target < position;
if (!this.isLooping && (isForwardInvalid || isBackwardInvalid)) {
return Infinity;
}
if (direction === -1) {
return this.getDistanceDirectional(target, position, 1);
}
position = this.getCorrectedPosition(position);
target = this.getCorrectedPosition(target);
let distance = (target - position) * direction;
if (distance < 0) {
distance = this.length - Math.abs(position) % this.length + target;
}
return distance;
}
getDistanceForward(position, target) {
return this.getDistanceDirectional(position, target, 1);
}
getDistanceBackward(position, target) {
return this.getDistanceDirectional(position, target, -1);
}
getDistance(position, target) {
const forwardDistance = this.getDistanceForward(position, target);
const backwardDistance = this.getDistanceBackward(position, target);
return Math.min(forwardDistance, backwardDistance);
}
getBestDirection(position, target) {
const forwardDistance = this.getDistanceForward(position, target);
const backwardDistance = this.getDistanceBackward(position, target);
return forwardDistance < backwardDistance ? 1 : -1;
}
};
// src/components/user-action/Button.tsx
var import_react = require("react");
var import_clsx = __toESM(require("clsx"));
var import_jsx_runtime = require("react/jsx-runtime");
var ButtonColorUtil = {
solid: ["primary", "secondary", "tertiary", "positive", "warning", "negative", "neutral"],
text: ["primary", "negative", "neutral"],
outline: ["primary"]
};
var IconButtonUtil = {
icon: [...ButtonColorUtil.solid, "transparent"]
};
var paddingMapping = {
small: "btn-sm",
medium: "btn-md",
large: "btn-lg"
};
var iconPaddingMapping = {
tiny: "icon-btn-xs",
small: "icon-btn-sm",
medium: "icon-btn-md",
large: "icon-btn-lg"
};
var ButtonUtil = {
paddingMapping,
iconPaddingMapping
};
var SolidButton = (0, import_react.forwardRef)(function SolidButton2({
children,
color = "primary",
size = "medium",
startIcon,
endIcon,
onClick,
className,
...restProps
}, ref) {
const colorClasses = {
primary: "not-disabled:bg-button-solid-primary-background not-disabled:text-button-solid-primary-text",
secondary: "not-disabled:bg-button-solid-secondary-background not-disabled:text-button-solid-secondary-text",
tertiary: "not-disabled:bg-button-solid-tertiary-background not-disabled:text-button-solid-tertiary-text",
positive: "not-disabled:bg-button-solid-positive-background not-disabled:text-button-solid-positive-text",
warning: "not-disabled:bg-button-solid-warning-background not-disabled:text-button-solid-warning-text",
negative: "not-disabled:bg-button-solid-negative-background not-disabled:text-button-solid-negative-text",
neutral: "not-disabled:bg-button-solid-neutral-background not-disabled:text-button-solid-neutral-text"
}[color];
const iconColorClasses = {
primary: "not-group-disabled:text-button-solid-primary-icon",
secondary: "not-group-disabled:text-button-solid-secondary-icon",
tertiary: "not-group-disabled:text-button-solid-tertiary-icon",
positive: "not-group-disabled:text-button-solid-positive-icon",
warning: "not-group-disabled:text-button-solid-warning-icon",
negative: "not-group-disabled:text-button-solid-negative-icon",
neutral: "not-group-disabled:text-button-solid-neutral-icon"
}[color];
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"button",
{
ref,
onClick,
className: (0, import_clsx.default)(
"group font-semibold",
colorClasses,
"not-disabled:hover:brightness-90",
"disabled:text-disabled-text disabled:bg-disabled-background",
ButtonUtil.paddingMapping[size],
className
),
...restProps,
children: [
startIcon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"span",
{
className: (0, import_clsx.default)(
iconColorClasses,
"group-disabled:text-disabled-icon"
),
children: startIcon
}
),
children,
endIcon && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"span",
{
className: (0, import_clsx.default)(
iconColorClasses,
"group-disabled:text-disabled-icon"
),
children: endIcon
}
)
]
}
);
});
var IconButton = ({
children,
color = "primary",
size = "medium",
className,
...restProps
}) => {
const colorClasses = {
primary: "not-disabled:bg-button-solid-primary-background not-disabled:text-button-solid-primary-text",
secondary: "not-disabled:bg-button-solid-secondary-background not-disabled:text-button-solid-secondary-text",
tertiary: "not-disabled:bg-button-solid-tertiary-background not-disabled:text-button-solid-tertiary-text",
positive: "not-disabled:bg-button-solid-positive-background not-disabled:text-button-solid-positive-text",
warning: "not-disabled:bg-button-solid-warning-background not-disabled:text-button-solid-warning-text",
negative: "not-disabled:bg-button-solid-negative-background not-disabled:text-button-solid-negative-text",
neutral: "not-disabled:bg-button-solid-neutral-background not-disabled:text-button-solid-neutral-text",
transparent: "not-disabled:bg-transparent"
}[color];
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"button",
{
className: (0, import_clsx.default)(
colorClasses,
"not-disabled:hover:brightness-90",
"disabled:text-disabled-text",
{
"disabled:bg-disabled-background": color !== "transparent",
"disabled:opacity-70": color === "transparent",
"not-disabled:hover:bg-button-text-hover-background": color === "transparent"
},
ButtonUtil.iconPaddingMapping[size],
className
),
...restProps,
children
}
);
};
// src/components/layout-and-navigation/Carousel.tsx
var import_jsx_runtime2 = require("react/jsx-runtime");
var Carousel = ({
children,
animationTime = 200,
isLooping = false,
isAutoLooping = false,
autoLoopingTimeOut = 5e3,
autoLoopAnimationTime = 500,
hintNext = false,
arrows = false,
dots = true,
overScrollThreshold = 0.1,
blurColor = "from-background",
className = "",
heightClassName = "h-96",
widthClassName = "w-[70%] desktop:w-1/2"
}) => {
if (isAutoLooping && !isLooping) {
console.error("When isAutoLooping is true, isLooping should also be true");
isLooping = true;
}
const [{
currentPosition,
dragState,
animationState
}, setCarouselInformation] = (0, import_react2.useState)({
currentPosition: 0
});
const animationId = (0, import_react2.useRef)(void 0);
const timeOut = (0, import_react2.useRef)(void 0);
autoLoopingTimeOut = Math.max(0, autoLoopingTimeOut);
const length = children.length;
const paddingItemCount = 3;
const util = (0, import_react2.useMemo)(() => new LoopingArrayCalculator(length, isLooping, overScrollThreshold), [length, isLooping, overScrollThreshold]);
const currentIndex = util.getCorrectedPosition(LoopingArrayCalculator.withoutOffset(currentPosition));
animationTime = Math.max(200, animationTime);
autoLoopAnimationTime = Math.max(200, autoLoopAnimationTime);
const getStyleOffset = (index) => {
const baseOffset = -50 + (index - currentPosition) * 100;
return `${baseOffset}%`;
};
const animation = (0, import_react2.useCallback)((time) => {
let keepAnimating = true;
setCarouselInformation((state) => {
const {
animationState: animationState2,
dragState: dragState2
} = state;
if (animationState2 === void 0 || dragState2 !== void 0) {
keepAnimating = false;
return state;
}
if (!animationState2.startTime || !animationState2.lastUpdateTime) {
return {
...state,
animationState: {
...animationState2,
startTime: time,
lastUpdateTime: time
}
};
}
const useAnimationTime = animationState2.isAutoLooping ? autoLoopAnimationTime : animationTime;
const progress = clamp((time - animationState2.startTime) / useAnimationTime);
const easedProgress = EaseFunctions.easeInEaseOut(progress);
const distance = util.getDistanceDirectional(animationState2.startPosition, animationState2.targetPosition, animationState2.direction);
const newPosition = util.getCorrectedPosition(easedProgress * distance * animationState2.direction + animationState2.startPosition);
if (animationState2.targetPosition === newPosition || progress === 1) {
keepAnimating = false;
return {
currentPosition: LoopingArrayCalculator.withoutOffset(newPosition),
animationState: void 0
};
}
return {
currentPosition: newPosition,
animationState: {
...animationState2,
lastUpdateTime: time
}
};
});
if (keepAnimating) {
animationId.current = requestAnimationFrame((time1) => animation(time1));
}
}, [animationTime, autoLoopAnimationTime, util]);
(0, import_react2.useEffect)(() => {
if (animationState) {
animationId.current = requestAnimationFrame(animation);
}
return () => {
if (animationId.current) {
cancelAnimationFrame(animationId.current);
animationId.current = 0;
}
};
}, [animationState]);
const startAutoLoop = () => setCarouselInformation((prevState) => ({
...prevState,
dragState: prevState.dragState,
animationState: prevState.animationState || prevState.dragState ? prevState.animationState : {
startPosition: currentPosition,
targetPosition: (currentPosition + 1) % length,
direction: 1,
// always move forward
isAutoLooping: true
}
}));
(0, import_react2.useEffect)(() => {
if (!animationId.current && !animationState && !dragState && !timeOut.current) {
if (autoLoopingTimeOut > 0) {
timeOut.current = setTimeout(() => {
startAutoLoop();
timeOut.current = void 0;
}, autoLoopingTimeOut);
} else {
startAutoLoop();
}
}
}, [animationState, dragState, animationId.current, timeOut.current]);
const startAnimation = (targetPosition) => {
if (targetPosition === void 0) {
targetPosition = LoopingArrayCalculator.withoutOffset(currentPosition);
}
if (targetPosition === currentPosition) {
return;
}
const direction = util.getBestDirection(currentPosition, targetPosition);
clearTimeout(timeOut.current);
timeOut.current = void 0;
if (animationId.current) {
cancelAnimationFrame(animationId.current);
animationId.current = void 0;
}
setCarouselInformation((prevState) => ({
...prevState,
dragState: void 0,
animationState: {
targetPosition,
direction,
startPosition: currentPosition,
isAutoLooping: false
},
timeOut: void 0
}));
};
const canGoLeft = () => {
return isLooping || currentPosition !== 0;
};
const canGoRight = () => {
return isLooping || currentPosition !== length - 1;
};
const left = () => {
if (canGoLeft()) {
startAnimation(currentPosition === 0 ? length - 1 : LoopingArrayCalculator.withoutOffset(currentPosition - 1));
}
};
const right = () => {
if (canGoRight()) {
startAnimation(LoopingArrayCalculator.withoutOffset((currentPosition + 1) % length));
}
};
let items = children.map((item, index) => ({
index,
item
}));
if (isLooping) {
const before = createLoopingListWithIndex(children, length - 1, paddingItemCount, false).reverse().map(([index, item]) => ({
index,
item
}));
const after = createLoopingListWithIndex(children, 0, paddingItemCount).map(([index, item]) => ({
index,
item
}));
items = [
...before,
...items,
...after
];
}
const onDragStart = (x) => setCarouselInformation((prevState) => ({
...prevState,
dragState: {
lastX: x,
startX: x,
startTime: Date.now(),
startIndex: currentPosition
},
animationState: void 0
// cancel animation
}));
const onDrag = (x, width) => {
if (!dragState || x === 0) {
return;
}
const offsetUpdate = (dragState.lastX - x) / width;
const newPosition = util.getCorrectedPosition(currentPosition + offsetUpdate);
setCarouselInformation((prevState) => ({
...prevState,
currentPosition: newPosition,
dragState: {
...dragState,
lastX: x
}
}));
};
const onDragEnd = (x, width) => {
if (!dragState) {
return;
}
const distance = dragState.startX - x;
const relativeDistance = distance / width;
const duration = Date.now() - dragState.startTime;
const velocity = distance / (Date.now() - dragState.startTime);
const isSlide = Math.abs(velocity) > 2 || duration < 200 && (Math.abs(relativeDistance) > 0.2 || Math.abs(distance) > 50);
if (isSlide) {
if (distance > 0 && canGoRight()) {
right();
return;
} else if (distance < 0 && canGoLeft()) {
left();
return;
}
}
startAnimation();
};
const dragHandlers = {
draggable: true,
onDragStart: (event) => {
onDragStart(event.clientX);
event.dataTransfer.setDragImage(document.createElement("div"), 0, 0);
},
onDrag: (event) => onDrag(event.clientX, event.target.getBoundingClientRect().width),
onDragEnd: (event) => onDragEnd(event.clientX, event.target.getBoundingClientRect().width),
onTouchStart: (event) => onDragStart(event.touches[0].clientX),
onTouchMove: (event) => onDrag(event.touches[0].clientX, event.target.getBoundingClientRect().width),
onTouchEnd: (event) => onDragEnd(event.changedTouches[0].clientX, event.target.getBoundingClientRect().width),
onTouchCancel: (event) => onDragEnd(event.changedTouches[0].clientX, event.target.getBoundingClientRect().width)
};
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex-col-2 items-center w-full", children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: (0, import_clsx2.default)(`relative w-full overflow-hidden`, heightClassName, className), children: [
arrows && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
IconButton,
{
color: "neutral",
className: (0, import_clsx2.default)("absolute z-10 left-0 top-1/2 -translate-y-1/2 shadow-md", { hidden: !canGoLeft() }),
disabled: !canGoLeft(),
onClick: () => left(),
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.ChevronLeft, { size: 24 })
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
IconButton,
{
color: "neutral",
className: (0, import_clsx2.default)("absolute z-10 right-0 top-1/2 -translate-y-1/2 shadow-md", { hidden: !canGoRight() }),
disabled: !canGoRight(),
onClick: () => right(),
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react.ChevronRight, { size: 24 })
}
)
] }),
hintNext ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: (0, import_clsx2.default)(`flex-row-2 relative h-full`, heightClassName), children: [
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex-row-2 relative h-full w-full px-2 overflow-hidden", children: items.map(({
item,
index
}, listIndex) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"div",
{
className: (0, import_clsx2.default)(`absolute left-[50%] h-full overflow-hidden`, widthClassName, { "!cursor-grabbing": !!dragState }),
style: { translate: getStyleOffset(listIndex - (isLooping ? paddingItemCount : 0)) },
...dragHandlers,
onClick: () => startAnimation(index),
children: item
},
listIndex
)) }),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"div",
{
className: (0, import_clsx2.default)(`hidden pointer-events-none desktop:block absolute left-0 h-full w-[20%] bg-gradient-to-r to-transparent`, blurColor)
}
),
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"div",
{
className: (0, import_clsx2.default)(`hidden pointer-events-none desktop:block absolute right-0 h-full w-[20%] bg-gradient-to-l to-transparent`, blurColor)
}
)
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: (0, import_clsx2.default)("px-16 h-full", { "!cursor-grabbing": !!dragState }), ...dragHandlers, children: children[currentIndex] })
] }),
dots && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"div",
{
className: "flex-row-2 items-center justify-center w-full my-2",
children: range(length).map((index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
"button",
{
className: (0, import_clsx2.default)("w-8 min-w-8 h-3 min-h-3 first:rounded-l-md last:rounded-r-md", {
"bg-carousel-dot-disabled hover:bg-carousel-dot-active": currentIndex !== index,
"bg-carousel-dot-active hover:brightness-90": currentIndex === index
}),
onClick: () => startAnimation(index)
},
index
))
}
)
] });
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Carousel
});
//# sourceMappingURL=Carousel.js.map