UNPKG

@marskat/skeui

Version:

A React component library for skeuomorphic aesthetics

827 lines (816 loc) 28.5 kB
// src/components/Button/Button.tsx import cn from "classnames"; // src/hooks/useIsButtonActive.tsx import { useCallback, useState } from "react"; function useIsButtonActive() { const [isActive, setIsActive] = useState(false); const handlePointerDown = useCallback(() => setIsActive(true), []); const handlePointerUp = useCallback(() => setIsActive(false), []); const handlePointerLeave = useCallback(() => setIsActive(false), []); const handleKeyDown = useCallback((e) => { if (e.key === " " || e.key === "Enter") setIsActive(true); }, []); const handleKeyUp = useCallback((e) => { if (e.key === " " || e.key === "Enter") setIsActive(false); }, []); const handleBlur = useCallback(() => setIsActive(false), []); return [ isActive, { onPointerDown: handlePointerDown, onPointerUp: handlePointerUp, onPointerLeave: handlePointerLeave, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, onBlur: handleBlur } ]; } // src/hooks/useIsButtonHover.tsx import { useCallback as useCallback2, useState as useState2 } from "react"; function useIsButtonHover() { const [isHover, setIsHover] = useState2(false); const handleMouseEnter = useCallback2(() => setIsHover(true), []); const handleMouseLeave = useCallback2(() => setIsHover(false), []); const handleBlur = useCallback2(() => setIsHover(false), []); return [ isHover, { onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave, onBlur: handleBlur } ]; } // src/components/Button/Button.tsx import { jsx, jsxs } from "react/jsx-runtime"; var Button = ({ children, className, aesthetic = "neumorphic", isDarkMode = false, ...props }) => { const [isActive, eventHandlers] = useIsButtonActive(); const [isHover, hoverEventHandlers] = useIsButtonHover(); return /* @__PURE__ */ jsxs("div", { className: "skeui", children: [ aesthetic === "neumorphic" && /* @__PURE__ */ jsx( "div", { className: cn( !isActive ? isDarkMode ? "before:skeui-nm-outside-highlight-dark" : "before:skeui-nm-outside-highlight" : "", "before:skeui-rounded-full before:skeui-block before:skeui-absolute before:skeui-inset-0 before:skeui-content-[''] skeui-inline-block skeui-size-fit skeui-relative skeui-pointer-events-none" ), children: /* @__PURE__ */ jsx( "div", { className: cn( isActive ? "before:skeui-nm-inside-shadow" : isDarkMode ? "before:skeui-nm-outside-shadow-dark" : "before:skeui-nm-outside-shadow", "before:skeui-rounded-full before:skeui-block before:skeui-absolute before:skeui-inset-0 before:skeui-content-[''] before:skeui-z-10", "skeui-inline-block skeui-size-fit skeui-relative skeui-pointer-events-none" ), children: /* @__PURE__ */ jsx( "button", { ...props, ...eventHandlers, ...hoverEventHandlers, className: cn( "skeui-size-fit skeui-p-2 skeui-rounded-full skeui-cursor-pointer skeui-pointer-events-auto skeui-relative skeui-z-0 skeui-hard-transition", { "skeui-brightness-95": isHover }, // TODO: use isHover to do better styling className ), children } ) } ) } ), aesthetic === "glassmorphic" && /* @__PURE__ */ jsx( "div", { className: cn( "before:skeui-gp-outside-shadow before:skeui-rounded-full before:skeui-block before:skeui-absolute before:skeui-inset-0 before:skeui-content-[''] before:skeui-z-10 before:skeui-hard-transition", "skeui-inline-block skeui-size-fit skeui-relative skeui-pointer-events-none skeui-hard-transition", `${isActive ? "skeui-translate-y-[0.05rem] before:skeui-gp-outside-shadow-intense" : "hover:-skeui-translate-y-[0.05rem] before:skeui-gp-outside-shadow-faint"}` ), children: /* @__PURE__ */ jsx( "button", { ...props, ...eventHandlers, ...hoverEventHandlers, className: cn( `skeui-backdrop-blur-2xl skeui-hard-transition skeui-gp-inside-shadow skeui-size-fit skeui-overflow-clip skeui-rounded-full skeui-cursor-pointer skeui-pointer-events-auto skeui-relative`, className ), children } ) } ) ] }); }; // src/components/Card/card.tsx import cn2 from "classnames"; import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime"; var Card = ({ children, className, inset = false, isDarkMode = false, fullyRounded = false, aesthetic = "glassmorphic", ...props }) => { return /* @__PURE__ */ jsxs2("div", { className: "skeui skeui-size-full", children: [ aesthetic === "glassmorphic" && /* @__PURE__ */ jsxs2( "div", { className: cn2( "skeui-backdrop-blur-2xl skeui-soft-transition skeui-gp-inside-shadow skeui-size-full skeui-relative", className ), ...props, children: [ children, /* @__PURE__ */ jsx2( "div", { className: cn2( "skeui-size-full skeui-absolute skeui-top-0 skeui-left-0 skeui-mt-0.5 skeui-ml-0.5 skeui-opacity-10", className ), ...props, children } ) ] } ), aesthetic === "neumorphic" && /* @__PURE__ */ jsxs2(Fragment, { children: [ !inset && /* @__PURE__ */ jsx2( "div", { className: cn2( "before:skeui-block before:skeui-absolute before:skeui-inset-0 before:skeui-content-[''] skeui-inline-block skeui-size-full skeui-relative skeui-pointer-events-none", `${fullyRounded ? "before:skeui-rounded-t-full before:skeui-rounded-b-full" : "before:skeui-rounded-t before:skeui-rounded-b"}`, `${isDarkMode ? "before:skeui-nm-outside-shadow-dark" : "before:skeui-nm-outside-shadow"}`, className ), children: /* @__PURE__ */ jsx2( "div", { className: cn2( "before:skeui-nm-outside-highlight before:skeui-block before:skeui-absolute before:skeui-inset-0 before:skeui-content-[''] skeui-inline-block skeui-size-full skeui-relative before:skeui-z-10 skeui-pointer-events-none", `${fullyRounded ? "before:skeui-rounded-t-full before:skeui-rounded-b-full" : "before:skeui-rounded-t before:skeui-rounded-b"}`, `${isDarkMode ? "before:skeui-nm-outside-highlight-dark" : "before:skeui-nm-outside-highlight"}`, className ), children: /* @__PURE__ */ jsx2( "div", { className: cn2( "skeui-size-full skeui-p-2 skeui-rounded skeui-rounded-t skeui-rounded-b skeui-z-0 skeui-pointer-events-auto", `${fullyRounded ? "skeui-rounded-t-full skeui-rounded-b-full" : "skeui-rounded-t skeui-rounded-b"}` ), children } ) } ) } ), inset && /* @__PURE__ */ jsx2( "div", { className: cn2( "before:skeui-nm-inside-shadow before:skeui-block before:skeui-absolute before:skeui-inset-0 before:skeui-content-[''] skeui-inline-block skeui-size-full skeui-relative before:skeui-z-10 skeui-pointer-events-none", `${fullyRounded ? "before:skeui-rounded-full" : "before:skeui-rounded"}` ), children: /* @__PURE__ */ jsx2( "div", { className: (cn2("skeui-size-full skeui-z-20 skeui-pointer-events-auto"), `${fullyRounded ? "skeui-rounded-full" : "skeui-rounded"}`), children } ) } ) ] }) ] }); }; // src/components/Carousel/Carousel.tsx import cn3 from "classnames"; import { useState as useState4 } from "react"; // src/hooks/useIsDesktop.tsx import { useEffect, useState as useState3 } from "react"; var useIsDesktop = () => { const [isDesktop, setIsDesktop] = useState3(false); const handleResize = () => { setIsDesktop(window.innerWidth > 768); }; useEffect(() => { handleResize(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); return isDesktop; }; // src/components/Carousel/Carousel.tsx import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime"; var Carousel = ({ slides, size = { h: "28rem", w: "24rem" }, indicators = { on: "\u25CF", off: "\u25CB" }, navButtons = { prev: /* @__PURE__ */ jsx3("div", { title: "previous slide", children: "<" }), next: /* @__PURE__ */ jsx3("div", { title: "next slide", children: ">" }) }, classNames, isDarkMode = false, ...props }) => { const [visibleProject, setVisibleProject] = useState4(0); const [playExitAnimation, setPlayExitAnimation] = useState4(false); const [isRightSwipe, setIsRightSwipe] = useState4(false); const handleSlideChange = async (slide) => { setPlayExitAnimation(true); setTimeout(() => { setVisibleProject(slide); }, 200); }; return /* @__PURE__ */ jsx3("div", { className: "skeui", children: /* @__PURE__ */ jsxs3("div", { className: "skeui-flex skeui-justify-center skeui-place-items-center skeui-size-fit", children: [ /* @__PURE__ */ jsx3( "button", { onClick: () => { setIsRightSwipe(true); handleSlideChange( visibleProject === 0 ? slides.length - 1 : visibleProject - 1 ); }, className: cn3( "skeui-p-2 hover:skeui-scale-110", classNames?.navButtons ), children: navButtons.prev } ), /* @__PURE__ */ jsx3("div", { className: "skeui-w-fit", children: /* @__PURE__ */ jsx3( CarouselSlide, { slides, handleSlideChange, visibleProject, playExitAnimation, setPlayExitAnimation, isRightSwipe, setIsRightSwipe, isDesktop: useIsDesktop(), indicators, classNames, size, isDarkMode, ...props } ) }), /* @__PURE__ */ jsx3( "button", { onClick: () => { setIsRightSwipe(false); handleSlideChange((visibleProject + 1) % slides.length); }, className: cn3( "skeui-p-2 hover:skeui-scale-110", classNames?.navButtons ), children: navButtons.next } ) ] }) }); }; var CarouselSlide = ({ handleSlideChange, visibleProject, playExitAnimation, setPlayExitAnimation, isRightSwipe, setIsRightSwipe, isDesktop, slides, size, classNames, indicators, aesthetic, isDarkMode, ...props }) => { const [touchStart, setTouchStart] = useState4(0); const [touchEnd, setTouchEnd] = useState4(0); const minSwipeDistance = 50; const onTouchStart = (e) => { setTouchEnd(0); setTouchStart(e.targetTouches[0].clientX); }; const onTouchMove = (e) => setTouchEnd(e.targetTouches[0].clientX); const onTouchEnd = () => { if (!touchStart || !touchEnd) return; const distance = touchStart - touchEnd; if (distance > minSwipeDistance) { setIsRightSwipe(false); handleSlideChange((visibleProject + 1) % slides.length); } if (distance < -minSwipeDistance) { setIsRightSwipe(true); handleSlideChange( visibleProject === 0 ? slides.length - 1 : visibleProject - 1 ); } }; return /* @__PURE__ */ jsx3("div", { className: "skeui", children: /* @__PURE__ */ jsxs3("div", { className: "skeui-flex skeui-flex-col skeui-justify-center", children: [ /* @__PURE__ */ jsx3("div", { className: "skeui-flex skeui-justify-center", children: /* @__PURE__ */ jsx3( Card, { className: cn3("skeui-rounded", classNames?.card), aesthetic, isDarkMode, ...props, children: /* @__PURE__ */ jsx3("div", { className: "skeui-size-fit", children: /* @__PURE__ */ jsx3( "div", { className: "skeui-p-3 skeui-rounded-lg skeui-overflow-clip skeui-relative skeui-h-[80svh] skeui-w-[80svw] sm:skeui-size-fit", onTouchStart, onTouchMove, onTouchEnd, children: /* @__PURE__ */ jsx3( "div", { className: "skeui-size-full skeui-place-content-start sm:skeui-place-content-end skeui-grid skeui-grid-cols-1 skeui-gap-2", onAnimationEnd: () => setPlayExitAnimation(false), children: /* @__PURE__ */ jsxs3( "div", { className: "skeui-flex skeui-flex-col skeui-items-center skeui-w-full", style: { height: size?.h }, children: [ /* @__PURE__ */ jsx3( "div", { className: `skeui-w-full skeui-overflow-clip sm:skeui-place-content-end skeui-relative ${getAnimationClasses( playExitAnimation, isRightSwipe )}`, style: { maxWidth: size?.w }, onAnimationEnd: () => setPlayExitAnimation(false), children: /* @__PURE__ */ jsx3( "img", { src: isDesktop ? slides[visibleProject].desktopImage.src : slides[visibleProject].mobileImage?.src ?? slides[visibleProject].desktopImage.src, className: "skeui-soft-transition skeui-w-full skeui-h-auto skeui-hover:scale-110" } ) } ), /* @__PURE__ */ jsx3( "div", { className: "skeui-w-full skeui-text-pretty skeui-h-full", style: { maxWidth: size?.w }, children: slides[visibleProject].slideContent } ) ] } ) } ) } ) }) } ) }), /* @__PURE__ */ jsx3( "div", { className: cn3( "skeui-flex skeui-gap-1 skeui-justify-center skeui-p-2", classNames?.indicators ), children: slides.map((_, i) => { return /* @__PURE__ */ jsxs3( "button", { className: cn3(classNames?.indicator), onClick: () => handleSlideChange(i), children: [ visibleProject === i && indicators.on, visibleProject !== i && indicators.off ] }, i ); }) } ) ] }) }); }; var getAnimationClasses = (playExitAnimation, isRightSwipe) => { return playExitAnimation && isRightSwipe ? " skeui-animate-rightSwipeExit" : playExitAnimation && !isRightSwipe ? " skeui-animate-leftSwipeExit" : !playExitAnimation && isRightSwipe ? " skeui-animate-rightSwipeEnter skeui-opacity-0" : !playExitAnimation && !isRightSwipe ? " skeui-animate-leftSwipeEnter skeui-opacity-0" : ""; }; // src/components/NavBar/NavBar.tsx import cn4 from "classnames"; import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime"; var NavBar = ({ children, className, placement = "bottom", aesthetic = "glassmorphic", isDarkMode = false, ...props }) => { return /* @__PURE__ */ jsxs4("div", { className: "skeui", children: [ aesthetic === "glassmorphic" && /* @__PURE__ */ jsxs4(Fragment2, { children: [ /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-fixed skeui-z-50 skeui-flex skeui-place-content-center skeui-w-fit", { "skeui-w-full skeui-bottom-[10%] skeui-place-content-center": placement === "bottom" }, { "skeui-w-full skeui-top-[10%] skeui-place-content-center": placement === "top" }, { "skeui-h-full skeui-right-[5%] skeui-place-items-center": placement === "right" }, { "skeui-h-full skeui-left-[5%] skeui-place-items-center": placement === "left" } ), ...props, children: /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-flex skeui-p-2 skeui-gap-6 skeui-rounded-t-full skeui-rounded-b-full", className ), children: /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-gap-6 skeui-justify-around", { "skeui-grid skeui-grid-flow-col skeui-auto-cols-max": placement === "bottom" || placement === "top" }, { "skeui-grid": placement === "right" || placement === "left" } ), children } ) } ) } ), /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-fixed skeui-z-50 skeui-flex skeui-place-content-center skeui-w-fit skeui-mix-blend-soft-light", { "skeui-w-full skeui-bottom-[10%] skeui-place-content-center": placement === "bottom" }, { "skeui-w-full skeui-top-[10%] skeui-place-content-center": placement === "top" }, { "skeui-h-full skeui-right-[5%] skeui-place-items-center": placement === "right" }, { "skeui-h-full skeui-left-[5%] skeui-place-items-center": placement === "left" } ), ...props, children: /* @__PURE__ */ jsx4("div", { className: "skeui-size-fit", children: /* @__PURE__ */ jsx4( Card, { aesthetic: "glassmorphic", className: "skeui-size-fit skeui-gp-outside-shadow skeui-rounded-full", isDarkMode, children: /* @__PURE__ */ jsx4("div", { className: "skeui-flex skeui-p-2 skeui-gap-6", children: /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-gap-6 skeui-justify-around", { "skeui-grid skeui-grid-flow-col skeui-auto-cols-max": placement === "bottom" || placement === "top" }, { "skeui-grid": placement === "right" || placement === "left" } ), children } ) }) } ) }) } ) ] }), aesthetic === "neumorphic" && /* @__PURE__ */ jsxs4(Fragment2, { children: [ /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-fixed skeui-z-50 skeui-flex skeui-place-content-center skeui-w-fit", { "skeui-w-full skeui-bottom-[10%] skeui-place-content-center": placement === "bottom" }, { "skeui-w-full skeui-top-[10%] skeui-place-content-center": placement === "top" }, { "skeui-h-full skeui-right-[5%] skeui-place-items-center": placement === "right" }, { "skeui-h-full skeui-left-[5%] skeui-place-items-center": placement === "left" } ), ...props, children: /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-flex skeui-p-2 skeui-gap-6 skeui-rounded-t-full skeui-rounded-b-full", className ), children: /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-gap-6 skeui-justify-around", { "skeui-grid skeui-grid-flow-col skeui-auto-cols-max": placement === "bottom" || placement === "top" }, { "skeui-grid": placement === "right" || placement === "left" } ), children } ) } ) } ), /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-fixed skeui-z-50 skeui-flex skeui-place-content-center skeui-w-fit skeui-mix-blend-soft-light", { "skeui-w-full skeui-bottom-[10%] skeui-place-content-center": placement === "bottom" }, { "skeui-w-full skeui-top-[10%] skeui-place-content-center": placement === "top" }, { "skeui-h-full skeui-right-[5%] skeui-place-items-center": placement === "right" }, { "skeui-h-full skeui-left-[5%] skeui-place-items-center": placement === "left" } ), ...props, children: /* @__PURE__ */ jsx4("div", { className: "skeui-size-fit", children: /* @__PURE__ */ jsx4( Card, { aesthetic: "neumorphic", className: "skeui-size-fit", fullyRounded: true, isDarkMode, children: /* @__PURE__ */ jsx4("div", { className: "skeui-flex skeui-gap-6 skeui-size-fit", children: /* @__PURE__ */ jsx4( "div", { className: cn4( "skeui-gap-6 skeui-justify-around ", { "skeui-grid skeui-grid-flow-col skeui-auto-cols-max": placement === "bottom" || placement === "top" }, { "skeui-grid": placement === "right" || placement === "left" } ), children } ) }) } ) }) } ) ] }) ] }); }; // src/components/SearchBar/SearchBar.tsx import cn5 from "classnames"; import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime"; var SearchBar = ({ aesthetic = "glassmorphic", inset, isDarkMode = false, icon = "\u{1F50D}", placeholder = "Search...", setSearch, classNames, className, ...props }) => { return /* @__PURE__ */ jsxs5("div", { className: "skeui", children: [ aesthetic === "glassmorphic" && /* @__PURE__ */ jsx5( "div", { className: cn5( "skeui-backdrop-blur-2xl skeui-soft-transition skeui-gp-inside-shadow skeui-size-full", className ), ...props, children: /* @__PURE__ */ jsxs5("div", { className: "skeui-flex skeui-gap-2 skeui-items-center skeui-p-2", children: [ /* @__PURE__ */ jsx5("div", { className: cn5(classNames?.icon), children: icon }), /* @__PURE__ */ jsx5( "input", { placeholder, className: cn5( "skeui-size-full skeui-bg-transparent skeui-p-1 skeui-outline-none skeui-pointer-events-auto z-50", classNames?.input ), onInput: (e) => setSearch?.(e.currentTarget.value) } ) ] }) } ), aesthetic === "neumorphic" && /* @__PURE__ */ jsx5( Card, { aesthetic, isDarkMode, inset, ...props, children: /* @__PURE__ */ jsxs5("div", { className: "skeui-flex skeui-gap-2 skeui-items-center skeui-p-1", children: [ /* @__PURE__ */ jsx5( "div", { className: cn5( `${inset === true ? "skeui-pl-2" : ""}`, classNames?.icon ), children: icon } ), /* @__PURE__ */ jsx5( "input", { placeholder, className: cn5( "skeui-size-full skeui-bg-transparent skeui-outline-none skeui-pointer-events-auto z-50", `${inset === true ? "skeui-p-2" : "skeui-p-1"}`, classNames?.input ), onInput: (e) => setSearch?.(e.currentTarget.value) } ) ] }) } ) ] }); }; // src/components/Toggle/Toggle.tsx import cn6 from "classnames"; import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime"; var Toggle = ({ aesthetic = "glassmorphic", isOn = false, setIsOn, thumb = "\u26AA", classNames, ...props }) => { return /* @__PURE__ */ jsxs6("div", { className: "skeui", children: [ aesthetic == "glassmorphic" && /* @__PURE__ */ jsx6( "button", { className: "skeui-size-fit", onClick: () => setIsOn?.(!isOn), ...props, children: /* @__PURE__ */ jsx6( Card, { aesthetic: "glassmorphic", className: cn6( "skeui-rounded-full before:skeui-rounded-full", classNames?.track ), children: /* @__PURE__ */ jsxs6( "div", { className: cn6( "skeui-flex skeui-text-2xl skeui-scale-110", classNames?.thumb ), children: [ /* @__PURE__ */ jsx6( "span", { className: cn6( `${!isOn && "skeui-invisible"}`, "skeui-transition-hard", `${isOn && "skeui-animate-toggleToRight"}` ), children: thumb } ), /* @__PURE__ */ jsx6( "span", { className: cn6( `${isOn && "skeui-invisible"}`, "skeui-transition-hard", `${!isOn && "skeui-animate-toggleToLeft"}` ), children: thumb } ) ] } ) } ) } ), aesthetic == "neumorphic" && /* @__PURE__ */ jsx6( "button", { className: "skeui-size-fit", onClick: () => setIsOn?.(!isOn), ...props, children: /* @__PURE__ */ jsx6( Card, { aesthetic: "neumorphic", inset: true, fullyRounded: true, className: cn6(classNames?.track), children: /* @__PURE__ */ jsxs6( "div", { className: cn6( "skeui-flex skeui-text-2xl skeui-scale-110", classNames?.thumb ), children: [ /* @__PURE__ */ jsx6( "span", { className: cn6( `${!isOn && "skeui-invisible"}`, "skeui-transition-hard", `${isOn && "skeui-animate-toggleToRight"}` ), children: thumb } ), /* @__PURE__ */ jsx6( "span", { className: cn6( `${isOn && "skeui-invisible"}`, "skeui-transition-hard", `${!isOn && "skeui-animate-toggleToLeft"}` ), children: thumb } ) ] } ) } ) } ) ] }); }; export { Button, Card, Carousel, NavBar, SearchBar, Toggle };