️ feat: add onReset prop

This commit is contained in:
Wahyu Kurniawan 2024-02-29 10:44:25 +07:00
parent 630af612a2
commit e0c5895e9c
No known key found for this signature in database
GPG Key ID: 040A1549143A8E33
6 changed files with 59 additions and 29 deletions

View File

@ -19,6 +19,7 @@ import {
import './Calendar.css'; import './Calendar.css';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { cn } from 'utils/classnames';
type ValuePiece = Date | null; type ValuePiece = Date | null;
export type Value = ValuePiece | [ValuePiece, ValuePiece]; export type Value = ValuePiece | [ValuePiece, ValuePiece];
@ -63,6 +64,11 @@ export interface CalendarProps extends CustomReactCalendarProps, CalendarTheme {
* @returns None * @returns None
*/ */
onCancel?: () => void; onCancel?: () => void;
/**
* Optional callback function that is called when a reset action is triggered.
* @returns None
*/
onReset?: () => void;
} }
/** /**
@ -80,6 +86,7 @@ export const Calendar = ({
actions, actions,
onSelect, onSelect,
onCancel, onCancel,
onReset,
onChange: onChangeProp, onChange: onChangeProp,
...props ...props
}: CalendarProps): JSX.Element => { }: CalendarProps): JSX.Element => {
@ -217,6 +224,11 @@ export const Calendar = ({
[setValue, setActiveDate, changeNavigationLabel, selectRange], [setValue, setActiveDate, changeNavigationLabel, selectRange],
); );
const handleReset = useCallback(() => {
setValue(null);
onReset?.();
}, [setValue, onReset]);
return ( return (
<div <div
{...wrapperProps} {...wrapperProps}
@ -276,12 +288,20 @@ export const Calendar = ({
{/* Footer or CTA */} {/* Footer or CTA */}
<div <div
{...footerProps} {...footerProps}
className={footer({ className: footerProps?.className })} className={cn(footer({ className: footerProps?.className }), {
'justify-between': value,
})}
> >
{actions ? ( {actions ? (
actions actions
) : ( ) : (
<> <>
{value && (
<Button variant="danger" onClick={handleReset}>
Reset
</Button>
)}
<div className="space-x-3">
<Button variant="tertiary" onClick={onCancel}> <Button variant="tertiary" onClick={onCancel}>
Cancel Cancel
</Button> </Button>
@ -291,6 +311,7 @@ export const Calendar = ({
> >
Select Select
</Button> </Button>
</div>
</> </>
)} )}
</div> </div>

View File

@ -2,7 +2,7 @@ import { VariantProps, tv } from 'tailwind-variants';
export const datePickerTheme = tv({ export const datePickerTheme = tv({
slots: { slots: {
input: [], input: ['w-full'],
}, },
}); });

View File

@ -1,4 +1,4 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { Input, InputProps } from 'components/shared/Input'; import { Input, InputProps } from 'components/shared/Input';
import * as Popover from '@radix-ui/react-popover'; import * as Popover from '@radix-ui/react-popover';
import { datePickerTheme } from './DatePicker.theme'; import { datePickerTheme } from './DatePicker.theme';
@ -27,6 +27,10 @@ export interface DatePickerProps
* Whether to allow the selection of a date range. * Whether to allow the selection of a date range.
*/ */
selectRange?: boolean; selectRange?: boolean;
/**
* Optional callback function that is called when the date picker is reset.
*/
onReset?: () => void;
} }
/** /**
@ -39,6 +43,7 @@ export const DatePicker = ({
calendarProps, calendarProps,
value, value,
onChange, onChange,
onReset,
selectRange = false, selectRange = false,
...props ...props
}: DatePickerProps) => { }: DatePickerProps) => {
@ -50,16 +55,16 @@ export const DatePicker = ({
* Renders the value of the date based on the current state of `props.value`. * Renders the value of the date based on the current state of `props.value`.
* @returns {string | undefined} - The formatted date value or `undefined` if `props.value` is falsy. * @returns {string | undefined} - The formatted date value or `undefined` if `props.value` is falsy.
*/ */
const renderValue = useCallback(() => { const renderValue = useMemo(() => {
if (!value) return undefined; if (!value) return '';
if (Array.isArray(value)) { if (Array.isArray(value)) {
return value return value
.map((date) => format(date as Date, 'dd/MM/yyyy')) .map((date) => format(date as Date, 'dd/MM/yyyy'))
.join(' - '); .join(' - ');
} }
return format(value, 'dd/MM/yyyy'); return format(value, 'dd/MM/yyyy');
}, [value]); }, [value, onReset]);
console.log(renderValue);
/** /**
* Handles the selection of a date from the calendar. * Handles the selection of a date from the calendar.
*/ */
@ -71,15 +76,20 @@ export const DatePicker = ({
[setOpen, onChange], [setOpen, onChange],
); );
const handleReset = useCallback(() => {
setOpen(false);
onReset?.();
}, [setOpen, onReset]);
return ( return (
<Popover.Root open={open}> <Popover.Root open={open}>
<Popover.Trigger> <Popover.Trigger className="w-full">
<Input <Input
{...props} {...props}
rightIcon={<CalendarIcon onClick={() => setOpen(true)} />} rightIcon={<CalendarIcon onClick={() => setOpen(true)} />}
readOnly readOnly
placeholder="Select a date..." placeholder="Select a date..."
value={renderValue()} value={renderValue}
className={input({ className })} className={input({ className })}
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
/> />
@ -93,6 +103,7 @@ export const DatePicker = ({
{...calendarProps} {...calendarProps}
selectRange={selectRange} selectRange={selectRange}
value={value} value={value}
onReset={handleReset}
onCancel={() => setOpen(false)} onCancel={() => setOpen(false)}
onSelect={handleSelect} onSelect={handleSelect}
/> />

View File

@ -2,7 +2,7 @@ import { VariantProps, tv } from 'tailwind-variants';
export const selectTheme = tv({ export const selectTheme = tv({
slots: { slots: {
container: ['flex', 'flex-col', 'relative', 'gap-2'], container: ['flex', 'flex-col', 'relative', 'gap-2', 'w-full'],
label: ['text-sm', 'text-elements-high-em'], label: ['text-sm', 'text-elements-high-em'],
description: ['text-xs', 'text-elements-low-em'], description: ['text-xs', 'text-elements-low-em'],
inputWrapper: [ inputWrapper: [

View File

@ -3,7 +3,6 @@ import React, {
useState, useState,
ComponentPropsWithoutRef, ComponentPropsWithoutRef,
useMemo, useMemo,
useCallback,
MouseEvent, MouseEvent,
useRef, useRef,
useEffect, useEffect,
@ -12,7 +11,7 @@ import { useMultipleSelection, useCombobox } from 'downshift';
import { SelectTheme, selectTheme } from './Select.theme'; import { SelectTheme, selectTheme } from './Select.theme';
import { import {
ChevronDownIcon, ChevronDownIcon,
CrossIcon, CrossCircleIcon,
WarningIcon, WarningIcon,
} from 'components/shared/CustomIcon'; } from 'components/shared/CustomIcon';
import { cloneIcon } from 'utils/cloneIcon'; import { cloneIcon } from 'utils/cloneIcon';
@ -270,11 +269,8 @@ export const Select = ({
itemToString: (item) => (item && !multiple ? item.label : ''), itemToString: (item) => (item && !multiple ? item.label : ''),
}); });
const isSelected = useCallback( const isSelected = (item: SelectOption) =>
(item: SelectOption) => multiple ? selectedItems.includes(item) : selectedItem === item;
multiple ? selectedItems.includes(item) : selectedItem === item,
[selectedItems, selectedItem, multiple],
);
const handleClear = (e: MouseEvent<SVGSVGElement, globalThis.MouseEvent>) => { const handleClear = (e: MouseEvent<SVGSVGElement, globalThis.MouseEvent>) => {
e.stopPropagation(); e.stopPropagation();
@ -306,7 +302,7 @@ export const Select = ({
return ( return (
<div className={theme.iconContainer({ class: 'pr-4 right-0' })}> <div className={theme.iconContainer({ class: 'pr-4 right-0' })}>
{clearable && (selectedItems.length > 0 || selectedItem) && ( {clearable && (selectedItems.length > 0 || selectedItem) && (
<CrossIcon <CrossCircleIcon
className={theme.icon({ class: 'h-4 w-4' })} className={theme.icon({ class: 'h-4 w-4' })}
onClick={handleClear} onClick={handleClear}
/> />
@ -318,7 +314,7 @@ export const Select = ({
)} )}
</div> </div>
); );
}, [cloneIcon, theme, rightIcon]); }, [cloneIcon, theme, rightIcon, selectedItem, selectedItems, clearable]);
const renderHelperText = useMemo( const renderHelperText = useMemo(
() => ( () => (

View File

@ -62,7 +62,9 @@ const SelectItem = forwardRef<HTMLLIElement, SelectItemProps>(
<p className={theme.label()} data-disabled={disabled}> <p className={theme.label()} data-disabled={disabled}>
{label} {label}
</p> </p>
{orientation === 'horizontal' && <span className={theme.dot()} />} {orientation === 'horizontal' && description && (
<span className={theme.dot()} />
)}
{description && ( {description && (
<p className={theme.description()} data-disabled={disabled}> <p className={theme.description()} data-disabled={disabled}>
{description} {description}