@ryvora/react-scrollable-list
Version:
📜↔️ Horizontally scrollable list with navigation buttons for React. Perfect for carousels and tab lists!
227 lines (224 loc) • 9.22 kB
JavaScript
;
"use client";
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/index.ts
var index_exports = {};
__export(index_exports, {
Item: () => Item,
NextButton: () => NextButton,
PreviousButton: () => PreviousButton,
Root: () => Root,
ScrollableList: () => ScrollableList,
ScrollableListItem: () => ScrollableListItem,
ScrollableListNextButton: () => ScrollableListNextButton,
ScrollableListPreviousButton: () => ScrollableListPreviousButton,
ScrollableListViewport: () => ScrollableListViewport,
Viewport: () => Viewport,
createScrollableListScope: () => createScrollableListScope
});
module.exports = __toCommonJS(index_exports);
// src/scrollable-list.tsx
var import_react = __toESM(require("react"));
var import_react_primitive = require("@ryvora/react-primitive");
var import_react_context = require("@ryvora/react-context");
var import_react_compose_refs = require("@ryvora/react-compose-refs");
var import_primitive = require("@ryvora/primitive");
var import_jsx_runtime = require("react/jsx-runtime");
var SCROLLABLE_LIST_NAME = "ScrollableList";
var [createScrollableListContext, createScrollableListScope] = (0, import_react_context.createContextScope)(SCROLLABLE_LIST_NAME);
var [ScrollableListProvider, useScrollableListContextInternal] = createScrollableListContext(SCROLLABLE_LIST_NAME);
function useScrollContext(callerName, scope) {
const context = useScrollableListContextInternal(callerName, scope);
if (!context) {
throw new Error(`\`${callerName}\` must be used within \`${SCROLLABLE_LIST_NAME}\``);
}
return context;
}
var ScrollableList = import_react.default.forwardRef(
({ __scopeScrollableList, children, ...props }, forwardedRef) => {
const firstRef = import_react.default.useRef(null);
const lastRef = import_react.default.useRef(null);
const scrollRef = import_react.default.useRef(null);
const [isScrollReady, setIsScrollReady] = import_react.default.useState(false);
const [disabledLeft, setDisabledLeft] = import_react.default.useState(true);
const [disabledRight, setDisabledRight] = import_react.default.useState(false);
import_react.default.useEffect(() => {
if (scrollRef.current) {
setIsScrollReady(true);
}
}, []);
let identifiedViewport = null;
const otherChildren = [];
import_react.default.Children.forEach(children, (child) => {
if (import_react.default.isValidElement(child) && typeof child.type !== "string" && child.type.displayName === VIEWPORT_NAME) {
identifiedViewport = child;
} else {
otherChildren.push(child);
}
});
let viewportWithRefs = identifiedViewport;
if (identifiedViewport) {
const viewportProps = identifiedViewport.props;
const itemsFromViewport = import_react.default.Children.toArray(viewportProps.children);
const itemsWithRefs = itemsFromViewport.map((item, idx) => {
if (!import_react.default.isValidElement(item)) return item;
if (idx === 0)
return import_react.default.cloneElement(item, {
ref: firstRef
});
if (idx === itemsFromViewport.length - 1)
return import_react.default.cloneElement(item, {
ref: lastRef
});
return item;
});
viewportWithRefs = import_react.default.cloneElement(identifiedViewport, {}, itemsWithRefs);
}
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
ScrollableListProvider,
{
scope: __scopeScrollableList,
firstRef,
lastRef,
scrollRef,
items: import_react.default.Children.toArray(children),
disabledLeft,
setDisabledLeft,
disabledRight,
setDisabledRight,
isScrollReady,
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_primitive.Primitive.div, { "data-state-scrollable-list-root": true, ...props, ref: forwardedRef, children: [
viewportWithRefs,
otherChildren
] })
}
);
}
);
ScrollableList.displayName = SCROLLABLE_LIST_NAME;
var VIEWPORT_NAME = "ScrollableListViewport";
var ScrollableListViewport = import_react.default.forwardRef(({ __scopeScrollableList, children, style, onScroll, ...props }, forwardedRef) => {
const context = useScrollContext(VIEWPORT_NAME, __scopeScrollableList);
const { setDisabledLeft, setDisabledRight } = context;
const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, context.scrollRef);
const handleScroll = (event) => {
const target = event.currentTarget;
const scrollLeft = target.scrollLeft;
const scrollWidth = target.scrollWidth;
const clientWidth = target.clientWidth;
console.log({
scrollLeft,
scrollWidth,
clientWidth,
isAtStart: scrollLeft === 0,
isAtEnd: clientWidth + scrollLeft >= scrollWidth - 1
});
setDisabledLeft(scrollLeft === 0);
setDisabledRight(clientWidth + scrollLeft >= scrollWidth - 1);
};
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_react_primitive.Primitive.div,
{
"data-state-scrollable-list-viewport": true,
ref: composedRefs,
onScroll: (0, import_primitive.composeEventHandlers)(onScroll, handleScroll),
...props,
children
}
);
});
ScrollableListViewport.displayName = VIEWPORT_NAME;
var ITEM_NAME = "ScrollableListItem";
var ScrollableListItem = import_react.default.forwardRef(({ __scopeScrollableList, ...props }, forwardedRef) => {
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_primitive.Primitive.div, { "data-state-scrollable-list-item": true, ...props, ref: forwardedRef });
});
ScrollableListItem.displayName = ITEM_NAME;
var PREVIOUS_BUTTON_NAME = "ScrollableListPreviousButton";
var ScrollableListPreviousButton = import_react.default.forwardRef(({ __scopeScrollableList, onClick, ...props }, forwardedRef) => {
const { disabledLeft, scrollRef, isScrollReady } = useScrollContext(
PREVIOUS_BUTTON_NAME,
__scopeScrollableList
);
const handleClick = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({
left: -200,
behavior: "smooth"
});
}
};
if (!isScrollReady) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_react_primitive.Primitive.button,
{
"data-state-scrollable-list-prev-button": true,
"data-disabled": disabledLeft ? "" : void 0,
disabled: disabledLeft,
onClick: (0, import_primitive.composeEventHandlers)(onClick, handleClick),
...props,
ref: forwardedRef
}
);
});
ScrollableListPreviousButton.displayName = PREVIOUS_BUTTON_NAME;
var NEXT_BUTTON_NAME = "ScrollableListNextButton";
var ScrollableListNextButton = import_react.default.forwardRef(({ __scopeScrollableList, onClick, ...props }, forwardedRef) => {
const { disabledRight, scrollRef, isScrollReady } = useScrollContext(
NEXT_BUTTON_NAME,
__scopeScrollableList
);
const handleClick = () => {
if (scrollRef.current) {
scrollRef.current.scrollBy({
left: 200,
behavior: "smooth"
});
}
};
if (!isScrollReady) return null;
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
import_react_primitive.Primitive.button,
{
"data-state-scrollable-list-next-button": true,
"aria-disabled": disabledRight || void 0,
"data-disabled": disabledRight ? "" : void 0,
disabled: disabledRight,
onClick: (0, import_primitive.composeEventHandlers)(onClick, handleClick),
...props,
ref: forwardedRef
}
);
});
ScrollableListNextButton.displayName = NEXT_BUTTON_NAME;
var Root = ScrollableList;
var Viewport = ScrollableListViewport;
var Item = ScrollableListItem;
var PreviousButton = ScrollableListPreviousButton;
var NextButton = ScrollableListNextButton;
//# sourceMappingURL=index.js.map