diff --git a/index.d.ts b/index.d.ts index 33ca13f25..c274c78aa 100644 --- a/index.d.ts +++ b/index.d.ts @@ -23,6 +23,7 @@ export { BarGraphIcon, BaseInput, BellIcon, + BookIcon, BookmarkIcon, BoxIcon, Breadcrumb, @@ -89,6 +90,7 @@ export { DisabledIcon, DisputeIcon, Dot, + DotProgress, DotCircleIcon, DoubleCheckIcon, DownArrowIcon, @@ -183,6 +185,7 @@ export { PiggyBankIcon, PinIcon, PipeIcon, + PlayIcon, PlusIcon, PolicyIcon, Popover, @@ -242,6 +245,9 @@ export { Tooltip, TransferArrowIcon, TrashIcon, + TutorialsBadgeIcon, + TutorialsButtonIcon, + TutorialsSquareIcon, UnlinkIcon, useId, useIsClient, diff --git a/package.json b/package.json index 614a2cd77..37bd93c74 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "files": [ "build", "index.css", + "index.d.ts", "tailwind.config.js", "postcss.config.js", "index.d.ts" diff --git a/src/components/DatePicker/DatePicker.jsx b/src/components/DatePicker/DatePicker.jsx index dc8af42cf..b809345d7 100644 --- a/src/components/DatePicker/DatePicker.jsx +++ b/src/components/DatePicker/DatePicker.jsx @@ -9,8 +9,10 @@ import { isArray, isFunction } from "lodash"; import { Tooltip } from "../.."; import { Day } from "./Day"; import { MonthYearSelector } from "./MonthYearSelector"; -import { NavbarElement } from "./NavbarElement"; import { RelativeDateRange } from "./RelativeDateRange"; +import RangeDatePicker from "./RangeDatePicker"; +import { UpcomingDatePicker } from "./UpcomingDatePicker"; +import { NavbarElement } from "./NavbarElement"; const variants = { single: "single", @@ -38,8 +40,14 @@ export const DatePicker = ({ timezoneName = null, // seller timezone (e.g. "America/Los_Angeles") to return correct today date ...rest }) => { - const initialValue = variant === variants.single ? value : value.from; + const initialValue = variant === variants.single ? value : value?.from; const [currentMonth, setCurrentMonth] = useState(initialValue); + const [startMonth, setStartMonth] = useState(value?.from); + const [endMonth, setEndMonth] = useState( + dayjs(value?.to).isSame(dayjs(value?.from), "month") + ? dayjs(value?.from).add(1, "month").toDate() + : value?.to ?? dayjs(value?.from).add(1, "month").toDate(), + ); const [rangeName, setRangeName] = useState(""); const isRangeVariant = variant === variants.range; @@ -48,30 +56,6 @@ export const DatePicker = ({ onMonthChange?.(currentMonth); }, [currentMonth, onMonthChange]); - const handleDayClick = (day, options, event) => { - if (options.disabled) { - return; - } - - setRangeName(""); - if (isRangeVariant) { - if (value.from && value.to) { - // This allows us to easily select another date range, - // if both dates are selected. - onChange({ from: day, to: null }, options, event); - } else if ((value.from || value.to).getTime() === day.getTime()) { - const from = dayjs(day).startOf("day").toDate(); - const to = dayjs(day).endOf("day").toDate(); - - onChange({ from, to }, options, event); - } else { - onChange(DateUtils.addDayToRange(day, value), options, event); - } - } else { - onChange(day, options, event); - } - }; - const handleTodayClick = (day, options, event) => { if (isRangeVariant) { return; @@ -87,8 +71,21 @@ export const DatePicker = ({ } }; + const isDisabled = (date) => { + if (isArray(disabledDays)) { + return disabledDays.some((_date) => dayjs(_date).isSame(date, "day")); + } + + if (isFunction(disabledDays)) { + return disabledDays(date); + } + + return disabledDays(date); + }; + const handleRelativeRangeChanged = (rangeName, range) => { setCurrentMonth(range.from); + setStartMonth(range.from); onChange(range, modifiers, null); }; @@ -97,25 +94,52 @@ export const DatePicker = ({ onMonthChange?.(m); }; - const captionElement = shouldShowYearPicker - ? ({ date }) => - : undefined; + const handleStartMonthChange = (m) => { + setStartMonth(m); + onMonthChange?.(m); + }; - const isDisabled = (date) => { - if (isArray(disabledDays)) { - return disabledDays.some((_date) => dayjs(_date).isSame(date, "day")); + const handleEndMonthChange = (m) => { + setEndMonth(m); + onMonthChange?.(m); + }; + + const handleDayClick = (day, options, event) => { + if (options.disabled) { + return; } - if (isFunction(disabledDays)) { - return disabledDays(date); + if (dayjs(value?.from).isSame(day, "month")) { + handleStartMonthChange(day); } - return disabledDays(date); + setRangeName(""); + if (isRangeVariant) { + if (value?.from && value.to) { + // This allows us to easily select another date range, + // if both dates are selected. + onChange({ from: day, to: null }, options, event); + } else if ((value?.from || value.to).getTime() === day.getTime()) { + const from = dayjs(day).startOf("day").toDate(); + const to = dayjs(day).endOf("day").toDate(); + + onChange({ from, to }, options, event); + } else { + onChange(DateUtils.addDayToRange(day, value), options, event); + } + } else { + onChange(day, options, event); + } }; + const CaptionElement = shouldShowYearPicker + ? ({ date }) => + : undefined; + const renderDay = (date) => { const tooltipContent = getTooltip?.(date); const disabled = isDisabled(date); + return tooltipContent ? (
{upcomingDates ? ( -
-

Upcoming

- {upcomingDates?.length > 0 ? ( -
- {upcomingDates?.map((date) => { - const isSameDay = dayjs(date).isSame(dayjs(value), "day"); - const key = dayjs(date).format(); - return ( -
{ - handleDayClick(date, {}, event); - handleMonthChange(date); - }} - > - {dayjs(date).format("ddd DD MMMM")} -
- ); - })} -
- ) : ( -
- There is no future availability for this product. -
- )} -
+ ) : null} - + {isRangeVariant ? ( + + ) : ( + + )}
{components.Footer ? : null} @@ -229,6 +246,8 @@ DatePicker.propTypes = { onMonthChange: PropTypes.func, disabledDays: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.func]), shouldShowYearPicker: PropTypes.bool, + isDateRangeStyle: PropTypes.bool, + isRangeVariant: PropTypes.bool, getDayContent: PropTypes.func, modifiers: PropTypes.object, ranges: PropTypes.arrayOf(PropTypes.oneOf(["day", "week", "month", "quarter", "year"])), diff --git a/src/components/DatePicker/RangeDatePicker.jsx b/src/components/DatePicker/RangeDatePicker.jsx new file mode 100644 index 000000000..90b4f7abf --- /dev/null +++ b/src/components/DatePicker/RangeDatePicker.jsx @@ -0,0 +1,185 @@ +import React from "react"; +import PropTypes from "prop-types"; +import DayPicker from "react-day-picker"; +import clsx from "clsx"; +import dayjs from "dayjs"; +import { isArray, isFunction } from "lodash"; +import { Tooltip } from "../Tooltip"; +import { NavbarElement } from "./NavbarElement"; +import { MonthYearSelector } from "./MonthYearSelector"; +import { Day } from "./Day"; + +const RangeDatePicker = ({ + getTooltip, + value, + getDayContent, + isDateRangeStyle, + shouldShowYearPicker, + startMonth, + disabledDays, + endMonth, + modifiers, + selectedDays, + handleDayClick, + handleStartMonthChange, + handleEndMonthChange, + handleTodayClick, + ...rest +}) => { + const isStartDateIsTheSameMonth = dayjs(value?.from).isSame(dayjs(value?.to), "month"); + + const CaptionStartElement = shouldShowYearPicker + ? ({ date }) => + : undefined; + + const CaptionEndElement = shouldShowYearPicker + ? ({ date }) => + : undefined; + + const isDisabledStartDays = (date) => { + if (isFunction(disabledDays)) { + return disabledDays(date) || dayjs(date).isAfter(value.to, "day"); + } + + if (isArray(disabledDays)) { + return ( + disabledDays.some((_date) => dayjs(_date).isSame(date, "day")) || dayjs(date).isAfter(value.to, "day") + ); + } + + return dayjs(date).isAfter(value.to, "day"); + }; + + const isDisabledEndDays = (date) => { + if (isStartDateIsTheSameMonth) { + return true; + } + + if (isFunction(disabledDays)) { + return disabledDays(date) || dayjs(date).isBefore(value?.from, "day"); + } + + if (isArray(disabledDays)) { + return ( + disabledDays.some((_date) => dayjs(_date).isSame(date, "day")) || + dayjs(date).isBefore(value?.from, "day") + ); + } + + return dayjs(date).isBefore(value?.from, "day"); + }; + + const renderStartDay = (date) => { + const tooltipContent = getTooltip?.(date); + const disabled = isDisabledStartDays(date); + + return tooltipContent ? ( + + + + ) : ( + + ); + }; + + const renderEndDay = (date) => { + const tooltipContent = getTooltip?.(date); + const disabled = isDisabledEndDays(date); + + return tooltipContent ? ( + + + + ) : ( + + ); + }; + + return ( +
+ handleDayClick(day, options, event, true)} + onMonthChange={handleStartMonthChange} + onTodayButtonClick={handleTodayClick} + {...rest} + /> + handleDayClick(day, options, event, false)} + onMonthChange={handleEndMonthChange} + onTodayButtonClick={handleTodayClick} + {...rest} + /> +
+ ); +}; + +RangeDatePicker.propTypes = { + getTooltip: PropTypes.func, + value: PropTypes.object, + getDayContent: PropTypes.func, + isDisabled: PropTypes.func, + isDateRangeStyle: PropTypes.bool, + startMonth: PropTypes.instanceOf(Date), + endMonth: PropTypes.instanceOf(Date), + modifiers: PropTypes.object, + disabledStartDays: PropTypes.func, + disabledEndDays: PropTypes.func, + handleDayClick: PropTypes.func, + handleStartMonthChange: PropTypes.func, + handleEndMonthChange: PropTypes.func, + handleTodayClick: PropTypes.func, +}; + +export default RangeDatePicker; diff --git a/src/components/DatePicker/UpcomingDatePicker.jsx b/src/components/DatePicker/UpcomingDatePicker.jsx new file mode 100644 index 000000000..a6b509731 --- /dev/null +++ b/src/components/DatePicker/UpcomingDatePicker.jsx @@ -0,0 +1,47 @@ +import React from "react"; +import dayjs from "dayjs"; +import clsx from "clsx"; +import PropTypes from "prop-types"; + +export const UpcomingDatePicker = ({ value, upcomingDates, onChange, onMonthChange }) => { + return ( +
+

Upcoming

+ {upcomingDates?.length > 0 ? ( +
+ {upcomingDates?.map((date) => { + const isSameDay = dayjs(date).isSame(dayjs(value), "day"); + const key = dayjs(date).format(); + return ( +
{ + onChange(date, {}, event); + onMonthChange(date); + }} + > + {dayjs(date).format("ddd DD MMMM")} +
+ ); + })} +
+ ) : ( +
+ There is no future availability for this product. +
+ )} +
+ ); +}; + +UpcomingDatePicker.propTypes = { + value: PropTypes.objectOf(Date), + upcomingDates: PropTypes.arrayOf(Date).isRequired, + onChange: PropTypes.func.isRequired, + onMonthChange: PropTypes.func.isRequired, +}; diff --git a/src/components/Dot.jsx b/src/components/Dot/Dot.jsx similarity index 60% rename from src/components/Dot.jsx rename to src/components/Dot/Dot.jsx index cc15107d9..a09c07294 100644 --- a/src/components/Dot.jsx +++ b/src/components/Dot/Dot.jsx @@ -11,10 +11,16 @@ const colors = { caution: "bg-caution", }; -export const Dot = ({ color = "primary", className, ...rest }) => { +const sizes = { + small: "h-1 w-1", + medium: "h-1.5 w-1.5", + large: "h-2 w-2", +}; + +export const Dot = ({ color = "primary", size = "medium", className, ...rest }) => { return ( ); diff --git a/src/components/Dot/DotProgress.jsx b/src/components/Dot/DotProgress.jsx new file mode 100644 index 000000000..a3cade55e --- /dev/null +++ b/src/components/Dot/DotProgress.jsx @@ -0,0 +1,25 @@ +import { range } from "lodash"; +import PropTypes from "prop-types"; +import React from "react"; +import { Dot } from "./Dot"; + +export const DotProgress = ({ current, total }) => { + if (total <= 1) { + return null; + } + + return ( +
+ {range(0, total).map((index) => { + const currentValue = current <= 0 ? 0 : current >= total ? current - 1 : current; + const color = index === currentValue ? "primary" : "secondary"; + return ; + })} +
+ ); +}; + +DotProgress.propTypes = { + total: PropTypes.number.isRequired, + current: PropTypes.number.isRequired, +}; diff --git a/src/components/Drawer.jsx b/src/components/Drawer.jsx index 0b7718886..08d30eadc 100644 --- a/src/components/Drawer.jsx +++ b/src/components/Drawer.jsx @@ -5,7 +5,23 @@ import React, { Fragment } from "react"; import { CloseIcon } from "../icons/CloseIcon"; import { Button } from "./Buttons/Button"; -export const Drawer = ({ isOpen = false, title, content, onClose, classNames = {}, position = "right" }) => { +const sizes = { + small: "w-72", + medium: "w-85", + large: "w-110", + xl: "w-200", + "2xl": "w-screen md:max-w-screen-md 2xl:max-w-screen-lg", // This was the old size +}; + +export const Drawer = ({ + isOpen = false, + title, + size = "medium", + content, + onClose, + classNames = {}, + position = "right", +}) => { return ( -
+
{position === "right" ? : null}
-
+
{/* eslint-disable-next-line react/jsx-max-depth */} {title}
diff --git a/src/components/Forms/BaseInput.jsx b/src/components/Forms/BaseInput.jsx index 46eeb4048..2aa4e713b 100644 --- a/src/components/Forms/BaseInput.jsx +++ b/src/components/Forms/BaseInput.jsx @@ -2,7 +2,7 @@ import clsx from "clsx"; import PropTypes from "prop-types"; import React from "react"; import { isEmpty, isString } from "lodash"; -import { Dot } from "../Dot"; +import { Dot } from "../Dot/Dot"; const sizes = { small: "px-3 py-1.5 text-sm leading-sm", // 30px @@ -24,8 +24,8 @@ export const BaseInput = ({ as: Tag, size = "medium", isError, className, isRequ "border hover:border-black hover:bg-gray-lighter focus:text-black active:text-black disabled:bg-gray-lighter", sizes[size], isError - ? "border-danger focus:border-danger focus:ring-0 focus:ring-danger" - : "border-gray-light focus:border-primary focus:ring-0 focus:ring-primary", + ? "!focus:border-danger !border-danger focus:ring-0 focus:ring-danger" + : "!border-gray-light focus:border-primary focus:ring-0 focus:ring-primary", className, )} value={value} diff --git a/src/components/ImageUpload.jsx b/src/components/ImageUpload.jsx index 165f2cb9d..de9d1d31a 100644 --- a/src/components/ImageUpload.jsx +++ b/src/components/ImageUpload.jsx @@ -1,18 +1,20 @@ -import React, { useRef, useState } from "react"; -import PropTypes from "prop-types"; import clsx from "clsx"; -import { Button, ImageIcon, Logo, Spinner, TrashIcon } from ".."; +import PropTypes from "prop-types"; +import React, { useRef, useState } from "react"; +import { Button, ImageIcon, Logo, SubmitButton, TrashIcon } from ".."; export const ImageUpload = ({ src, size = "large", - onChange, - onDelete, - onError, isLoading = false, maxSize = 5, hasDelete = true, requirements = null, + caption = null, + csvAcceptFormats = "image/png,image/jpeg", + onChange, + onDelete, + onError, ...props }) => { const inputReference = useRef(); @@ -39,20 +41,28 @@ export const ImageUpload = ({ onDelete(); }; + const hasRequirements = typeof requirements === "string" ? requirements?.trim().length > 0 : !!requirements; + const hasCaption = caption ? caption.trim().length > 0 : false; + return ( -
+
{src ? ( ) : ( -
+
)}
-
+
{hasDelete ? ( <> - - {isLoading && } + + {hasCaption ? caption.trim() : "Upload New Photo"} + ) : ( - + + {hasCaption ? caption.trim() : src ? "Replace Photo" : "Upload New Photo"} + )}
- {requirements ?? ( + {hasRequirements ? ( + requirements + ) : (
Check that the image is in PNG or JPG format {maxSize ? ` and does not exceed ${maxSize}MB` : ""} @@ -101,11 +112,13 @@ export const ImageUpload = ({ ImageUpload.propTypes = { src: PropTypes.string, size: PropTypes.oneOf(["small", "medium", "large"]), - onChange: PropTypes.func.isRequired, - onDelete: PropTypes.func.isRequired, - onError: PropTypes.func.isRequired, isLoading: PropTypes.bool, maxSize: PropTypes.number, hasDelete: PropTypes.bool, - requirements: PropTypes.oneOf([PropTypes.string, PropTypes.node]), + caption: PropTypes.string, + requirements: PropTypes.oneOfType([PropTypes.node, PropTypes.string]), + csvAcceptFormats: PropTypes.string, + onChange: PropTypes.func.isRequired, + onDelete: PropTypes.func.isRequired, + onError: PropTypes.func.isRequired, }; diff --git a/src/components/Modal.jsx b/src/components/Modal.jsx index 4f8a3c88c..4e06c2f6b 100644 --- a/src/components/Modal.jsx +++ b/src/components/Modal.jsx @@ -5,14 +5,56 @@ import React, { Fragment } from "react"; import { CloseIcon } from "../icons/CloseIcon"; const sizes = { - small: "max-w-100", - medium: "max-w-125", - large: "max-w-150", - huge: "max-w-200", + small: "max-w-100", // 400px + medium: "max-w-125", // 500px + large: "max-w-150", // 600px + huge: "max-w-200", // 800px +}; + +const positions = { + center: "inline-block", + topLeft: "absolute m-4 top-0 left-0", + topRight: "absolute m-4 top-0 right-0", + bottomLeft: "absolute m-4 bottom-0 left-0", + bottomRight: "absolute m-4 bottom-0 right-0", +}; + +const animations = { + center: { + enterFrom: "translate-y-4 sm:translate-y-0 sm:scale-95", + enterTo: "translate-y-0 sm:scale-100", + leaveFrom: "translate-y-0 sm:scale-100", + leaveTo: "translate-y-4 sm:translate-y-0 sm:scale-95", + }, + topLeft: { + enterFrom: "scale-0 origin-top-left", + enterTo: "scale-100 origin-top-left", + leaveFrom: "scale-100 origin-top-left", + leaveTo: "scale-0 origin-top-left", + }, + topRight: { + enterFrom: "scale-0 origin-top-right", + enterTo: "scale-100 origin-top-right", + leaveFrom: "scale-100 origin-top-right", + leaveTo: "scale-0 origin-top-right", + }, + bottomLeft: { + enterFrom: "scale-0 origin-bottom-left", + enterTo: "scale-100 origin-bottom-left", + leaveFrom: "scale-100 origin-bottom-left", + leaveTo: "scale-0 origin-bottom-left", + }, + bottomRight: { + enterFrom: "scale-0 origin-bottom-right", + enterTo: "scale-100 origin-bottom-right", + leaveFrom: "scale-100 origin-bottom-right", + leaveTo: "scale-0 origin-bottom-right", + }, }; export const Modal = ({ size = "medium", + position = "center", isOpen = false, shouldCloseOnOutsideClick = false, onClose, @@ -42,24 +84,27 @@ export const Modal = ({ {/* This element is to trick the browser into centering the modal contents. */} - + {position === "center" && ( + + )}
{onClose ? ( @@ -81,6 +126,16 @@ export const Modal = ({ ); }; +Modal.propTypes = { + size: PropTypes.oneOf(Object.keys(sizes)), + position: PropTypes.oneOf(Object.keys(positions)), + isOpen: PropTypes.bool.isRequired, + shouldCloseOnOutsideClick: PropTypes.bool, + onClose: PropTypes.func.isRequired, + children: PropTypes.node.isRequired, + className: PropTypes.string, +}; + const Header = ({ children, description, className, ...rest }) => { return ( diff --git a/src/components/Popover/PopoverList.jsx b/src/components/Popover/PopoverList.jsx index a6fc1e284..97f636204 100644 --- a/src/components/Popover/PopoverList.jsx +++ b/src/components/Popover/PopoverList.jsx @@ -38,8 +38,8 @@ PopoverList.propTypes = { children: PropTypes.node.isRequired, }; -const Item = ({ name, isActive = false, position, total, children, className, onClickItem, ...rest }) => { - const onClick = (event) => onClickItem(event, name); +const Item = ({ name, isActive = false, id = null, position, total, children, className, onClickItem, ...rest }) => { + const onClick = (event) => onClickItem(event, name, id); return (
-
+
{label &&
{label}
} @@ -128,7 +128,7 @@ export const Login = ({