@pallasoft/ps-input-components 1.17.1 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ps-calendar-header.d.ts +8 -1
- package/dist/components/ps-calendar-header.js +57 -1
- package/dist/components/ps-cascader-input.js +1 -1
- package/dist/components/ps-date-input.js +5 -1
- package/dist/constants/ps-calendar-view.d.ts +6 -0
- package/dist/constants/ps-calendar-view.js +8 -0
- package/dist/constants/ps-date-input-config.d.ts +4 -0
- package/dist/constants/ps-date-input-config.js +4 -0
- package/dist/esm/components/ps-calendar-header.js +57 -1
- package/dist/esm/components/ps-cascader-input.js +1 -1
- package/dist/esm/components/ps-date-input.js +5 -1
- package/dist/esm/constants/ps-calendar-view.js +5 -0
- package/dist/esm/constants/ps-date-input-config.js +4 -0
- package/dist/esm/services/ps-date-input-service.js +16 -0
- package/dist/services/ps-date-input-service.d.ts +4 -0
- package/dist/services/ps-date-input-service.js +16 -0
- package/package.json +1 -1
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { ReactDatePickerCustomHeaderProps } from 'react-datepicker';
|
|
2
|
-
|
|
2
|
+
import { PsCalendarView } from '../constants/ps-calendar-view';
|
|
3
|
+
export interface PsCalendarHeaderProps extends ReactDatePickerCustomHeaderProps {
|
|
4
|
+
maxDate?: Date;
|
|
5
|
+
minDate?: Date;
|
|
6
|
+
onViewChange: (view: PsCalendarView) => void;
|
|
7
|
+
view: PsCalendarView;
|
|
8
|
+
}
|
|
9
|
+
export declare const PsCalendarHeader: ({ changeMonth, changeYear, decreaseMonth, increaseMonth, maxDate, minDate, monthDate, nextMonthButtonDisabled, onViewChange, prevMonthButtonDisabled, view, }: PsCalendarHeaderProps) => import("react").JSX.Element;
|
|
@@ -2,8 +2,64 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PsCalendarHeader = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
5
7
|
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
8
|
+
const ps_calendar_view_1 = require("../constants/ps-calendar-view");
|
|
9
|
+
const ps_date_input_config_1 = require("../constants/ps-date-input-config");
|
|
6
10
|
const ps_date_input_service_1 = require("../services/ps-date-input-service");
|
|
7
11
|
const navigationButtonClassName = 'ps-focus-ring flex h-8 w-8 items-center justify-center rounded-lg text-ink/60 transition-colors hover:bg-primary/10 hover:text-ink disabled:cursor-not-allowed disabled:opacity-30';
|
|
8
|
-
const
|
|
12
|
+
const titleButtonClassName = 'ps-focus-ring flex items-center gap-1 rounded-lg px-2 py-1 text-sm font-semibold text-ink transition-colors hover:bg-primary/10';
|
|
13
|
+
const cellClassName = 'ps-focus-ring flex h-10 items-center justify-center rounded-[0.625rem] text-sm font-medium text-ink transition-colors hover:bg-primary/10 disabled:cursor-not-allowed disabled:opacity-35 disabled:hover:bg-transparent';
|
|
14
|
+
const selectedCellClassName = 'bg-primary font-bold text-[var(--ps-color-primary-contrast)] hover:bg-primary';
|
|
15
|
+
const PsCalendarHeader = ({ changeMonth, changeYear, decreaseMonth, increaseMonth, maxDate, minDate, monthDate, nextMonthButtonDisabled, onViewChange, prevMonthButtonDisabled, view, }) => {
|
|
16
|
+
const year = monthDate.getFullYear();
|
|
17
|
+
const [pageYear, setPageYear] = (0, react_1.useState)(year);
|
|
18
|
+
const years = ps_date_input_service_1.PsDateInputService.yearPage(pageYear);
|
|
19
|
+
const navigation = {
|
|
20
|
+
[ps_calendar_view_1.PsCalendarView.day]: {
|
|
21
|
+
isNextDisabled: nextMonthButtonDisabled,
|
|
22
|
+
isPreviousDisabled: prevMonthButtonDisabled,
|
|
23
|
+
next: increaseMonth,
|
|
24
|
+
nextLabel: 'Next month',
|
|
25
|
+
previous: decreaseMonth,
|
|
26
|
+
previousLabel: 'Previous month',
|
|
27
|
+
title: ps_date_input_service_1.PsDateInputService.monthLabel(monthDate),
|
|
28
|
+
titleLabel: 'Choose a month',
|
|
29
|
+
titleView: ps_calendar_view_1.PsCalendarView.month,
|
|
30
|
+
},
|
|
31
|
+
[ps_calendar_view_1.PsCalendarView.month]: {
|
|
32
|
+
isNextDisabled: ps_date_input_service_1.PsDateInputService.isYearOutOfRange(year + 1, minDate, maxDate),
|
|
33
|
+
isPreviousDisabled: ps_date_input_service_1.PsDateInputService.isYearOutOfRange(year - 1, minDate, maxDate),
|
|
34
|
+
next: () => changeYear(year + 1),
|
|
35
|
+
nextLabel: 'Next year',
|
|
36
|
+
previous: () => changeYear(year - 1),
|
|
37
|
+
previousLabel: 'Previous year',
|
|
38
|
+
title: String(year),
|
|
39
|
+
titleLabel: 'Choose a year',
|
|
40
|
+
titleView: ps_calendar_view_1.PsCalendarView.year,
|
|
41
|
+
},
|
|
42
|
+
[ps_calendar_view_1.PsCalendarView.year]: {
|
|
43
|
+
isNextDisabled: ps_date_input_service_1.PsDateInputService.isYearOutOfRange(years[years.length - 1] + 1, minDate, maxDate),
|
|
44
|
+
isPreviousDisabled: ps_date_input_service_1.PsDateInputService.isYearOutOfRange(years[0] - 1, minDate, maxDate),
|
|
45
|
+
next: () => setPageYear(pageYear + ps_date_input_config_1.PsDateInputConfig.yearsPerPage),
|
|
46
|
+
nextLabel: 'Later years',
|
|
47
|
+
previous: () => setPageYear(pageYear - ps_date_input_config_1.PsDateInputConfig.yearsPerPage),
|
|
48
|
+
previousLabel: 'Earlier years',
|
|
49
|
+
title: `${years[0]} – ${years[years.length - 1]}`,
|
|
50
|
+
titleLabel: 'Back to the days',
|
|
51
|
+
titleView: ps_calendar_view_1.PsCalendarView.day,
|
|
52
|
+
},
|
|
53
|
+
}[view];
|
|
54
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-2', children: [(0, jsx_runtime_1.jsxs)("div", { className: 'flex items-center justify-between gap-2 px-1', children: [(0, jsx_runtime_1.jsx)("button", { "aria-label": navigation.previousLabel, className: navigationButtonClassName, disabled: navigation.isPreviousDisabled, onClick: navigation.previous, type: 'button', children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4 rotate-180', iconKey: ps_icons_1.PsIconKey.ChevronRight }) }), (0, jsx_runtime_1.jsxs)("button", { "aria-label": navigation.titleLabel, "aria-live": 'polite', className: titleButtonClassName, onClick: () => {
|
|
55
|
+
setPageYear(year);
|
|
56
|
+
onViewChange(navigation.titleView);
|
|
57
|
+
}, type: 'button', children: [navigation.title, (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-3.5 w-3.5 rotate-90 text-ink/50', iconKey: ps_icons_1.PsIconKey.ChevronRight })] }), (0, jsx_runtime_1.jsx)("button", { "aria-label": navigation.nextLabel, className: navigationButtonClassName, disabled: navigation.isNextDisabled, onClick: navigation.next, type: 'button', children: (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: ps_icons_1.PsIconKey.ChevronRight }) })] }), view === ps_calendar_view_1.PsCalendarView.month ? ((0, jsx_runtime_1.jsx)("div", { className: 'grid grid-cols-3 gap-1', children: ps_date_input_service_1.PsDateInputService.monthNames().map((name, month) => ((0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(cellClassName, month === monthDate.getMonth() ? selectedCellClassName : undefined), disabled: ps_date_input_service_1.PsDateInputService.isMonthOutOfRange(year, month, minDate, maxDate), onClick: () => {
|
|
58
|
+
changeMonth(month);
|
|
59
|
+
onViewChange(ps_calendar_view_1.PsCalendarView.day);
|
|
60
|
+
}, type: 'button', children: name }, name))) })) : null, view === ps_calendar_view_1.PsCalendarView.year ? ((0, jsx_runtime_1.jsx)("div", { className: 'grid grid-cols-3 gap-1', children: years.map((candidate) => ((0, jsx_runtime_1.jsx)("button", { className: ps_design_system_1.PallasoftClassNameService.merge(cellClassName, candidate === year ? selectedCellClassName : undefined), disabled: ps_date_input_service_1.PsDateInputService.isYearOutOfRange(candidate, minDate, maxDate), onClick: () => {
|
|
61
|
+
changeYear(candidate);
|
|
62
|
+
onViewChange(ps_calendar_view_1.PsCalendarView.month);
|
|
63
|
+
}, type: 'button', children: candidate }, candidate))) })) : null] }));
|
|
64
|
+
};
|
|
9
65
|
exports.PsCalendarHeader = PsCalendarHeader;
|
|
@@ -50,7 +50,7 @@ const PsCascaderInput = ({ className, disabled, groups, helperText, hideLabel, i
|
|
|
50
50
|
? 'cursor-not-allowed text-ink/40'
|
|
51
51
|
: option.value === value
|
|
52
52
|
? 'bg-primary/10 text-ink'
|
|
53
|
-
: 'text-ink/80 hover:bg-mist/60 hover:text-ink'), disabled: option.isDisabled, onClick: () => select(option.value, close), role: 'option', type: 'button', children: [(0, jsx_runtime_1.jsx)("span", { className: 'flex h-5 w-4 shrink-0 items-center justify-center text-primary', children: option.value === value ? ((0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: ps_icons_1.PsIconKey.Check })) : null }), (0, jsx_runtime_1.jsxs)("span", { className: 'min-w-0 flex-1', children: [(0, jsx_runtime_1.jsx)("span", { className: 'block
|
|
53
|
+
: 'text-ink/80 hover:bg-mist/60 hover:text-ink'), disabled: option.isDisabled, onClick: () => select(option.value, close), role: 'option', type: 'button', children: [(0, jsx_runtime_1.jsx)("span", { className: 'flex h-5 w-4 shrink-0 items-center justify-center text-primary', children: option.value === value ? ((0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: ps_icons_1.PsIconKey.Check })) : null }), (0, jsx_runtime_1.jsxs)("span", { className: 'min-w-0 flex-1', children: [(0, jsx_runtime_1.jsx)("span", { className: 'block break-words', children: option.label }), groupLabel ? ((0, jsx_runtime_1.jsx)("span", { className: 'mt-0.5 block break-words text-xs font-normal text-ink/50', children: groupLabel })) : null, option.description ? ((0, jsx_runtime_1.jsx)("span", { className: 'mt-0.5 block break-words text-xs font-normal text-ink/55', children: option.description })) : null] })] }, option.value));
|
|
54
54
|
return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('block', className), children: [hideLabel ? null : ((0, jsx_runtime_1.jsxs)("span", { className: 'ps-input-label block', children: [label, (0, jsx_runtime_1.jsx)(ps_required_mark_1.PsRequiredMark, { isRequired: required })] })), (0, jsx_runtime_1.jsxs)(react_aria_components_1.DialogTrigger, { isOpen: isOpen, onOpenChange: open, children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.Button, { "aria-label": hideLabel ? label : undefined, className: ps_design_system_1.PallasoftClassNameService.merge('ps-input-control ps-focus-ring relative flex min-h-11 w-full items-center pr-10 text-left text-sm font-semibold disabled:cursor-not-allowed disabled:opacity-60', hideLabel ? '' : 'mt-2'), isDisabled: disabled, children: [(0, jsx_runtime_1.jsx)("span", { className: 'min-w-0 flex-1 truncate', children: selected ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: 'truncate', children: selected.option.label }), (0, jsx_runtime_1.jsx)("span", { className: 'ml-2 text-xs font-medium text-ink/45', children: selected.group.label })] })) : ((0, jsx_runtime_1.jsx)("span", { className: 'font-medium text-ink/45', children: placeholder })) }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute top-1/2 right-3 h-4 w-4 -translate-y-1/2 text-primary', iconKey: ps_icons_1.PsIconKey.Chevron })] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: 'ps-floating-panel w-[min(calc(100vw-2rem),32rem)] min-w-[var(--trigger-width)] outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: (0, jsx_runtime_1.jsx)(react_aria_components_1.Dialog, { "aria-label": label, className: 'flex flex-col outline-none', children: ({ close }) => ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: groups.length === 0 ? ((0, jsx_runtime_1.jsxs)("div", { className: 'flex flex-col gap-1 px-4 py-8 text-center', children: [(0, jsx_runtime_1.jsx)("p", { className: 'text-sm font-semibold text-ink', children: ps_cascader_config_1.PsCascaderConfig.emptyLabel }), (0, jsx_runtime_1.jsx)("p", { className: 'text-xs text-ink/55', children: ps_cascader_config_1.PsCascaderConfig.emptyDescription })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: 'border-b border-line p-2', children: (0, jsx_runtime_1.jsx)("input", { "aria-label": searchPlaceholder, autoFocus: true, className: 'ps-input-control ps-focus-ring h-10 w-full text-sm', onChange: (event) => setQuery(event.target.value), placeholder: searchPlaceholder, type: 'search', value: query }) }), query.trim() === '' ? ((0, jsx_runtime_1.jsxs)("div", { className: 'grid grid-cols-1 sm:grid-cols-[minmax(0,11rem)_minmax(0,1fr)]', children: [(0, jsx_runtime_1.jsx)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-scrollbar-hidden max-h-72 overflow-y-auto p-1.5 sm:border-r sm:border-line', activeGroupKey && groups.length > 1 ? 'hidden sm:block' : ''), children: groups.map((group) => ((0, jsx_runtime_1.jsxs)("button", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-focus-ring flex w-full items-center gap-2 rounded-ps-control px-3 py-2.5 text-left text-sm font-semibold transition-colors', activeGroup?.key === group.key
|
|
55
55
|
? 'bg-mist text-ink'
|
|
56
56
|
: 'text-ink/70 hover:bg-mist/60 hover:text-ink'), onClick: () => setActiveGroupKey(group.key), type: 'button', children: [(0, jsx_runtime_1.jsx)("span", { className: 'min-w-0 flex-1 truncate', children: group.label }), (0, jsx_runtime_1.jsx)("span", { className: 'text-xs font-medium text-ink/45', children: group.options.length }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4 shrink-0 -rotate-90 text-ink/35', iconKey: ps_icons_1.PsIconKey.Chevron })] }, group.key))) }), (0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('ps-scrollbar-hidden max-h-72 overflow-y-auto p-1.5', activeGroupKey && groups.length > 1 ? '' : 'hidden sm:block'), role: 'listbox', children: [groups.length > 1 ? ((0, jsx_runtime_1.jsxs)("button", { className: 'ps-focus-ring mb-1 flex w-full items-center gap-2 rounded-ps-control px-3 py-2 text-left text-xs font-semibold text-ink/60 hover:text-ink sm:hidden', onClick: () => setActiveGroupKey(null), type: 'button', children: [(0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4 rotate-90', iconKey: ps_icons_1.PsIconKey.Chevron }), "All groups"] })) : null, activeGroup
|
|
@@ -10,6 +10,7 @@ const ps_core_components_1 = require("@pallasoft/ps-core-components");
|
|
|
10
10
|
const ps_design_system_1 = require("@pallasoft/ps-design-system");
|
|
11
11
|
const ps_icons_1 = require("@pallasoft/ps-icons");
|
|
12
12
|
const react_datepicker_1 = __importDefault(require("react-datepicker"));
|
|
13
|
+
const ps_calendar_view_1 = require("../constants/ps-calendar-view");
|
|
13
14
|
const ps_date_input_config_1 = require("../constants/ps-date-input-config");
|
|
14
15
|
const ps_date_input_service_1 = require("../services/ps-date-input-service");
|
|
15
16
|
const ps_calendar_header_1 = require("./ps-calendar-header");
|
|
@@ -23,6 +24,9 @@ const PsDateInput = ({ className, defaultValue, disabled, helperText, hideLabel,
|
|
|
23
24
|
const zIndex = (0, ps_core_components_1.usePsLayerZIndex)(ps_design_system_1.PallasoftZIndex.Dropdown);
|
|
24
25
|
const [uncontrolledValue, setUncontrolledValue] = (0, react_1.useState)(defaultValue === undefined ? '' : defaultValue);
|
|
25
26
|
const currentValue = value === undefined ? uncontrolledValue : value;
|
|
27
|
+
const [view, setView] = (0, react_1.useState)(ps_calendar_view_1.PsCalendarView.day);
|
|
28
|
+
const minDate = ps_date_input_service_1.PsDateInputService.parseBound(min);
|
|
29
|
+
const maxDate = ps_date_input_service_1.PsDateInputService.parseBound(max);
|
|
26
30
|
(0, react_1.useEffect)(() => {
|
|
27
31
|
const existing = document.getElementById(portalId);
|
|
28
32
|
const container = existing ? existing : document.createElement('div');
|
|
@@ -40,6 +44,6 @@ const PsDateInput = ({ className, defaultValue, disabled, helperText, hideLabel,
|
|
|
40
44
|
setUncontrolledValue(nextValue);
|
|
41
45
|
onChange?.(nextValue);
|
|
42
46
|
};
|
|
43
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('block', className), children: [(0, jsx_runtime_1.jsxs)("label", { className: hideLabel ? 'sr-only' : 'ps-input-label block', htmlFor: inputId, children: [label, (0, jsx_runtime_1.jsx)(ps_required_mark_1.PsRequiredMark, { isRequired: required })] }), (0, jsx_runtime_1.jsxs)("div", { className: hideLabel ? 'relative' : 'relative mt-2', children: [(0, jsx_runtime_1.jsx)(react_datepicker_1.default, { autoComplete: 'off', calendarClassName: 'ps-floating-panel ps-datepicker', className: 'ps-input-control ps-focus-ring min-h-11 w-full pr-10 text-sm font-semibold', dateFormat: [...ps_date_input_config_1.PsDateInputConfig.inputFormats], disabled: disabled, id: inputId, maxDate:
|
|
47
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: ps_design_system_1.PallasoftClassNameService.merge('block', className), children: [(0, jsx_runtime_1.jsxs)("label", { className: hideLabel ? 'sr-only' : 'ps-input-label block', htmlFor: inputId, children: [label, (0, jsx_runtime_1.jsx)(ps_required_mark_1.PsRequiredMark, { isRequired: required })] }), (0, jsx_runtime_1.jsxs)("div", { className: hideLabel ? 'relative' : 'relative mt-2', children: [(0, jsx_runtime_1.jsx)(react_datepicker_1.default, { autoComplete: 'off', calendarClassName: ps_design_system_1.PallasoftClassNameService.merge('ps-floating-panel ps-datepicker', view === ps_calendar_view_1.PsCalendarView.day ? undefined : ps_date_input_config_1.PsDateInputConfig.pickingClassName), className: 'ps-input-control ps-focus-ring min-h-11 w-full pr-10 text-sm font-semibold', dateFormat: [...ps_date_input_config_1.PsDateInputConfig.inputFormats], disabled: disabled, id: inputId, maxDate: maxDate, minDate: minDate, onCalendarClose: () => setView(ps_calendar_view_1.PsCalendarView.day), onChange: handleChange, placeholderText: placeholder, popperClassName: 'ps-datepicker-popper', popperPlacement: 'bottom-start', popperProps: { strategy: 'fixed' }, portalId: portalId, renderCustomHeader: (headerProps) => ((0, jsx_runtime_1.jsx)(ps_calendar_header_1.PsCalendarHeader, { ...headerProps, maxDate: maxDate, minDate: minDate, onViewChange: setView, view: view })), required: required, selected: ps_date_input_service_1.PsDateInputService.parse(currentValue), showPopperArrow: false, wrapperClassName: 'block w-full' }), (0, jsx_runtime_1.jsx)(ps_icons_1.PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/50', iconKey: ps_icons_1.PsIconKey.Calendar }), name ? (0, jsx_runtime_1.jsx)("input", { name: name, type: 'hidden', value: currentValue }) : null] }), helperText ? (0, jsx_runtime_1.jsx)("span", { className: 'ps-input-helper', children: helperText }) : null] }));
|
|
44
48
|
};
|
|
45
49
|
exports.PsDateInput = PsDateInput;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export declare const PsDateInputConfig: {
|
|
2
2
|
readonly inputFormats: readonly ["MMM d, yyyy", "yyyy-MM-dd", "MM/dd/yyyy", "d MMM yyyy"];
|
|
3
3
|
readonly monthLabelFormat: "MMMM YYYY";
|
|
4
|
+
readonly monthShortFormat: "MMM";
|
|
5
|
+
readonly monthsPerYear: 12;
|
|
6
|
+
readonly pickingClassName: "ps-datepicker-picking";
|
|
7
|
+
readonly yearsPerPage: 12;
|
|
4
8
|
readonly valueFormat: "YYYY-MM-DD";
|
|
5
9
|
readonly placeholder: "Select a date";
|
|
6
10
|
readonly portalId: "ps-floating-root";
|
|
@@ -4,6 +4,10 @@ exports.PsDateInputConfig = void 0;
|
|
|
4
4
|
exports.PsDateInputConfig = {
|
|
5
5
|
inputFormats: ['MMM d, yyyy', 'yyyy-MM-dd', 'MM/dd/yyyy', 'd MMM yyyy'],
|
|
6
6
|
monthLabelFormat: 'MMMM YYYY',
|
|
7
|
+
monthShortFormat: 'MMM',
|
|
8
|
+
monthsPerYear: 12,
|
|
9
|
+
pickingClassName: 'ps-datepicker-picking',
|
|
10
|
+
yearsPerPage: 12,
|
|
7
11
|
valueFormat: 'YYYY-MM-DD',
|
|
8
12
|
placeholder: 'Select a date',
|
|
9
13
|
portalId: 'ps-floating-root',
|
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
2
4
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
5
|
+
import { PsCalendarView } from '../constants/ps-calendar-view';
|
|
6
|
+
import { PsDateInputConfig } from '../constants/ps-date-input-config';
|
|
3
7
|
import { PsDateInputService } from '../services/ps-date-input-service';
|
|
4
8
|
const navigationButtonClassName = 'ps-focus-ring flex h-8 w-8 items-center justify-center rounded-lg text-ink/60 transition-colors hover:bg-primary/10 hover:text-ink disabled:cursor-not-allowed disabled:opacity-30';
|
|
5
|
-
|
|
9
|
+
const titleButtonClassName = 'ps-focus-ring flex items-center gap-1 rounded-lg px-2 py-1 text-sm font-semibold text-ink transition-colors hover:bg-primary/10';
|
|
10
|
+
const cellClassName = 'ps-focus-ring flex h-10 items-center justify-center rounded-[0.625rem] text-sm font-medium text-ink transition-colors hover:bg-primary/10 disabled:cursor-not-allowed disabled:opacity-35 disabled:hover:bg-transparent';
|
|
11
|
+
const selectedCellClassName = 'bg-primary font-bold text-[var(--ps-color-primary-contrast)] hover:bg-primary';
|
|
12
|
+
export const PsCalendarHeader = ({ changeMonth, changeYear, decreaseMonth, increaseMonth, maxDate, minDate, monthDate, nextMonthButtonDisabled, onViewChange, prevMonthButtonDisabled, view, }) => {
|
|
13
|
+
const year = monthDate.getFullYear();
|
|
14
|
+
const [pageYear, setPageYear] = useState(year);
|
|
15
|
+
const years = PsDateInputService.yearPage(pageYear);
|
|
16
|
+
const navigation = {
|
|
17
|
+
[PsCalendarView.day]: {
|
|
18
|
+
isNextDisabled: nextMonthButtonDisabled,
|
|
19
|
+
isPreviousDisabled: prevMonthButtonDisabled,
|
|
20
|
+
next: increaseMonth,
|
|
21
|
+
nextLabel: 'Next month',
|
|
22
|
+
previous: decreaseMonth,
|
|
23
|
+
previousLabel: 'Previous month',
|
|
24
|
+
title: PsDateInputService.monthLabel(monthDate),
|
|
25
|
+
titleLabel: 'Choose a month',
|
|
26
|
+
titleView: PsCalendarView.month,
|
|
27
|
+
},
|
|
28
|
+
[PsCalendarView.month]: {
|
|
29
|
+
isNextDisabled: PsDateInputService.isYearOutOfRange(year + 1, minDate, maxDate),
|
|
30
|
+
isPreviousDisabled: PsDateInputService.isYearOutOfRange(year - 1, minDate, maxDate),
|
|
31
|
+
next: () => changeYear(year + 1),
|
|
32
|
+
nextLabel: 'Next year',
|
|
33
|
+
previous: () => changeYear(year - 1),
|
|
34
|
+
previousLabel: 'Previous year',
|
|
35
|
+
title: String(year),
|
|
36
|
+
titleLabel: 'Choose a year',
|
|
37
|
+
titleView: PsCalendarView.year,
|
|
38
|
+
},
|
|
39
|
+
[PsCalendarView.year]: {
|
|
40
|
+
isNextDisabled: PsDateInputService.isYearOutOfRange(years[years.length - 1] + 1, minDate, maxDate),
|
|
41
|
+
isPreviousDisabled: PsDateInputService.isYearOutOfRange(years[0] - 1, minDate, maxDate),
|
|
42
|
+
next: () => setPageYear(pageYear + PsDateInputConfig.yearsPerPage),
|
|
43
|
+
nextLabel: 'Later years',
|
|
44
|
+
previous: () => setPageYear(pageYear - PsDateInputConfig.yearsPerPage),
|
|
45
|
+
previousLabel: 'Earlier years',
|
|
46
|
+
title: `${years[0]} – ${years[years.length - 1]}`,
|
|
47
|
+
titleLabel: 'Back to the days',
|
|
48
|
+
titleView: PsCalendarView.day,
|
|
49
|
+
},
|
|
50
|
+
}[view];
|
|
51
|
+
return (_jsxs("div", { className: 'flex flex-col gap-2', children: [_jsxs("div", { className: 'flex items-center justify-between gap-2 px-1', children: [_jsx("button", { "aria-label": navigation.previousLabel, className: navigationButtonClassName, disabled: navigation.isPreviousDisabled, onClick: navigation.previous, type: 'button', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4 rotate-180', iconKey: PsIconKey.ChevronRight }) }), _jsxs("button", { "aria-label": navigation.titleLabel, "aria-live": 'polite', className: titleButtonClassName, onClick: () => {
|
|
52
|
+
setPageYear(year);
|
|
53
|
+
onViewChange(navigation.titleView);
|
|
54
|
+
}, type: 'button', children: [navigation.title, _jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-3.5 w-3.5 rotate-90 text-ink/50', iconKey: PsIconKey.ChevronRight })] }), _jsx("button", { "aria-label": navigation.nextLabel, className: navigationButtonClassName, disabled: navigation.isNextDisabled, onClick: navigation.next, type: 'button', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.ChevronRight }) })] }), view === PsCalendarView.month ? (_jsx("div", { className: 'grid grid-cols-3 gap-1', children: PsDateInputService.monthNames().map((name, month) => (_jsx("button", { className: PallasoftClassNameService.merge(cellClassName, month === monthDate.getMonth() ? selectedCellClassName : undefined), disabled: PsDateInputService.isMonthOutOfRange(year, month, minDate, maxDate), onClick: () => {
|
|
55
|
+
changeMonth(month);
|
|
56
|
+
onViewChange(PsCalendarView.day);
|
|
57
|
+
}, type: 'button', children: name }, name))) })) : null, view === PsCalendarView.year ? (_jsx("div", { className: 'grid grid-cols-3 gap-1', children: years.map((candidate) => (_jsx("button", { className: PallasoftClassNameService.merge(cellClassName, candidate === year ? selectedCellClassName : undefined), disabled: PsDateInputService.isYearOutOfRange(candidate, minDate, maxDate), onClick: () => {
|
|
58
|
+
changeYear(candidate);
|
|
59
|
+
onViewChange(PsCalendarView.month);
|
|
60
|
+
}, type: 'button', children: candidate }, candidate))) })) : null] }));
|
|
61
|
+
};
|
|
@@ -47,7 +47,7 @@ export const PsCascaderInput = ({ className, disabled, groups, helperText, hideL
|
|
|
47
47
|
? 'cursor-not-allowed text-ink/40'
|
|
48
48
|
: option.value === value
|
|
49
49
|
? 'bg-primary/10 text-ink'
|
|
50
|
-
: 'text-ink/80 hover:bg-mist/60 hover:text-ink'), disabled: option.isDisabled, onClick: () => select(option.value, close), role: 'option', type: 'button', children: [_jsx("span", { className: 'flex h-5 w-4 shrink-0 items-center justify-center text-primary', children: option.value === value ? (_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Check })) : null }), _jsxs("span", { className: 'min-w-0 flex-1', children: [_jsx("span", { className: 'block
|
|
50
|
+
: 'text-ink/80 hover:bg-mist/60 hover:text-ink'), disabled: option.isDisabled, onClick: () => select(option.value, close), role: 'option', type: 'button', children: [_jsx("span", { className: 'flex h-5 w-4 shrink-0 items-center justify-center text-primary', children: option.value === value ? (_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Check })) : null }), _jsxs("span", { className: 'min-w-0 flex-1', children: [_jsx("span", { className: 'block break-words', children: option.label }), groupLabel ? (_jsx("span", { className: 'mt-0.5 block break-words text-xs font-normal text-ink/50', children: groupLabel })) : null, option.description ? (_jsx("span", { className: 'mt-0.5 block break-words text-xs font-normal text-ink/55', children: option.description })) : null] })] }, option.value));
|
|
51
51
|
return (_jsxs("div", { className: PallasoftClassNameService.merge('block', className), children: [hideLabel ? null : (_jsxs("span", { className: 'ps-input-label block', children: [label, _jsx(PsRequiredMark, { isRequired: required })] })), _jsxs(DialogTrigger, { isOpen: isOpen, onOpenChange: open, children: [_jsxs(Button, { "aria-label": hideLabel ? label : undefined, className: PallasoftClassNameService.merge('ps-input-control ps-focus-ring relative flex min-h-11 w-full items-center pr-10 text-left text-sm font-semibold disabled:cursor-not-allowed disabled:opacity-60', hideLabel ? '' : 'mt-2'), isDisabled: disabled, children: [_jsx("span", { className: 'min-w-0 flex-1 truncate', children: selected ? (_jsxs(_Fragment, { children: [_jsx("span", { className: 'truncate', children: selected.option.label }), _jsx("span", { className: 'ml-2 text-xs font-medium text-ink/45', children: selected.group.label })] })) : (_jsx("span", { className: 'font-medium text-ink/45', children: placeholder })) }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute top-1/2 right-3 h-4 w-4 -translate-y-1/2 text-primary', iconKey: PsIconKey.Chevron })] }), _jsx(Popover, { className: 'ps-floating-panel w-[min(calc(100vw-2rem),32rem)] min-w-[var(--trigger-width)] outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(Dialog, { "aria-label": label, className: 'flex flex-col outline-none', children: ({ close }) => (_jsx(_Fragment, { children: groups.length === 0 ? (_jsxs("div", { className: 'flex flex-col gap-1 px-4 py-8 text-center', children: [_jsx("p", { className: 'text-sm font-semibold text-ink', children: PsCascaderConfig.emptyLabel }), _jsx("p", { className: 'text-xs text-ink/55', children: PsCascaderConfig.emptyDescription })] })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: 'border-b border-line p-2', children: _jsx("input", { "aria-label": searchPlaceholder, autoFocus: true, className: 'ps-input-control ps-focus-ring h-10 w-full text-sm', onChange: (event) => setQuery(event.target.value), placeholder: searchPlaceholder, type: 'search', value: query }) }), query.trim() === '' ? (_jsxs("div", { className: 'grid grid-cols-1 sm:grid-cols-[minmax(0,11rem)_minmax(0,1fr)]', children: [_jsx("div", { className: PallasoftClassNameService.merge('ps-scrollbar-hidden max-h-72 overflow-y-auto p-1.5 sm:border-r sm:border-line', activeGroupKey && groups.length > 1 ? 'hidden sm:block' : ''), children: groups.map((group) => (_jsxs("button", { className: PallasoftClassNameService.merge('ps-focus-ring flex w-full items-center gap-2 rounded-ps-control px-3 py-2.5 text-left text-sm font-semibold transition-colors', activeGroup?.key === group.key
|
|
52
52
|
? 'bg-mist text-ink'
|
|
53
53
|
: 'text-ink/70 hover:bg-mist/60 hover:text-ink'), onClick: () => setActiveGroupKey(group.key), type: 'button', children: [_jsx("span", { className: 'min-w-0 flex-1 truncate', children: group.label }), _jsx("span", { className: 'text-xs font-medium text-ink/45', children: group.options.length }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4 shrink-0 -rotate-90 text-ink/35', iconKey: PsIconKey.Chevron })] }, group.key))) }), _jsxs("div", { className: PallasoftClassNameService.merge('ps-scrollbar-hidden max-h-72 overflow-y-auto p-1.5', activeGroupKey && groups.length > 1 ? '' : 'hidden sm:block'), role: 'listbox', children: [groups.length > 1 ? (_jsxs("button", { className: 'ps-focus-ring mb-1 flex w-full items-center gap-2 rounded-ps-control px-3 py-2 text-left text-xs font-semibold text-ink/60 hover:text-ink sm:hidden', onClick: () => setActiveGroupKey(null), type: 'button', children: [_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4 rotate-90', iconKey: PsIconKey.Chevron }), "All groups"] })) : null, activeGroup
|
|
@@ -4,6 +4,7 @@ import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
|
4
4
|
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
5
5
|
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
6
6
|
import DatePicker from 'react-datepicker';
|
|
7
|
+
import { PsCalendarView } from '../constants/ps-calendar-view';
|
|
7
8
|
import { PsDateInputConfig } from '../constants/ps-date-input-config';
|
|
8
9
|
import { PsDateInputService } from '../services/ps-date-input-service';
|
|
9
10
|
import { PsCalendarHeader } from './ps-calendar-header';
|
|
@@ -17,6 +18,9 @@ export const PsDateInput = ({ className, defaultValue, disabled, helperText, hid
|
|
|
17
18
|
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
18
19
|
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue === undefined ? '' : defaultValue);
|
|
19
20
|
const currentValue = value === undefined ? uncontrolledValue : value;
|
|
21
|
+
const [view, setView] = useState(PsCalendarView.day);
|
|
22
|
+
const minDate = PsDateInputService.parseBound(min);
|
|
23
|
+
const maxDate = PsDateInputService.parseBound(max);
|
|
20
24
|
useEffect(() => {
|
|
21
25
|
const existing = document.getElementById(portalId);
|
|
22
26
|
const container = existing ? existing : document.createElement('div');
|
|
@@ -34,5 +38,5 @@ export const PsDateInput = ({ className, defaultValue, disabled, helperText, hid
|
|
|
34
38
|
setUncontrolledValue(nextValue);
|
|
35
39
|
onChange?.(nextValue);
|
|
36
40
|
};
|
|
37
|
-
return (_jsxs("div", { className: PallasoftClassNameService.merge('block', className), children: [_jsxs("label", { className: hideLabel ? 'sr-only' : 'ps-input-label block', htmlFor: inputId, children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsxs("div", { className: hideLabel ? 'relative' : 'relative mt-2', children: [_jsx(DatePicker, { autoComplete: 'off', calendarClassName: 'ps-floating-panel ps-datepicker', className: 'ps-input-control ps-focus-ring min-h-11 w-full pr-10 text-sm font-semibold', dateFormat: [...PsDateInputConfig.inputFormats], disabled: disabled, id: inputId, maxDate:
|
|
41
|
+
return (_jsxs("div", { className: PallasoftClassNameService.merge('block', className), children: [_jsxs("label", { className: hideLabel ? 'sr-only' : 'ps-input-label block', htmlFor: inputId, children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsxs("div", { className: hideLabel ? 'relative' : 'relative mt-2', children: [_jsx(DatePicker, { autoComplete: 'off', calendarClassName: PallasoftClassNameService.merge('ps-floating-panel ps-datepicker', view === PsCalendarView.day ? undefined : PsDateInputConfig.pickingClassName), className: 'ps-input-control ps-focus-ring min-h-11 w-full pr-10 text-sm font-semibold', dateFormat: [...PsDateInputConfig.inputFormats], disabled: disabled, id: inputId, maxDate: maxDate, minDate: minDate, onCalendarClose: () => setView(PsCalendarView.day), onChange: handleChange, placeholderText: placeholder, popperClassName: 'ps-datepicker-popper', popperPlacement: 'bottom-start', popperProps: { strategy: 'fixed' }, portalId: portalId, renderCustomHeader: (headerProps) => (_jsx(PsCalendarHeader, { ...headerProps, maxDate: maxDate, minDate: minDate, onViewChange: setView, view: view })), required: required, selected: PsDateInputService.parse(currentValue), showPopperArrow: false, wrapperClassName: 'block w-full' }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-ink/50', iconKey: PsIconKey.Calendar }), name ? _jsx("input", { name: name, type: 'hidden', value: currentValue }) : null] }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
|
|
38
42
|
};
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export const PsDateInputConfig = {
|
|
2
2
|
inputFormats: ['MMM d, yyyy', 'yyyy-MM-dd', 'MM/dd/yyyy', 'd MMM yyyy'],
|
|
3
3
|
monthLabelFormat: 'MMMM YYYY',
|
|
4
|
+
monthShortFormat: 'MMM',
|
|
5
|
+
monthsPerYear: 12,
|
|
6
|
+
pickingClassName: 'ps-datepicker-picking',
|
|
7
|
+
yearsPerPage: 12,
|
|
4
8
|
valueFormat: 'YYYY-MM-DD',
|
|
5
9
|
placeholder: 'Select a date',
|
|
6
10
|
portalId: 'ps-floating-root',
|
|
@@ -21,4 +21,20 @@ export class PsDateInputService {
|
|
|
21
21
|
static monthLabel(date) {
|
|
22
22
|
return Common.DateService.formatDate(date.getTime(), PsDateInputConfig.monthLabelFormat);
|
|
23
23
|
}
|
|
24
|
+
static monthNames() {
|
|
25
|
+
return Array.from({ length: PsDateInputConfig.monthsPerYear }, (_, month) => Common.DateService.formatDate(new Date(2000, month, 1).getTime(), PsDateInputConfig.monthShortFormat));
|
|
26
|
+
}
|
|
27
|
+
static yearPage(year) {
|
|
28
|
+
const start = year - Math.floor(PsDateInputConfig.yearsPerPage / 2);
|
|
29
|
+
return Array.from({ length: PsDateInputConfig.yearsPerPage }, (_, index) => start + index);
|
|
30
|
+
}
|
|
31
|
+
static isMonthOutOfRange(year, month, min, max) {
|
|
32
|
+
const first = new Date(year, month, 1);
|
|
33
|
+
const last = new Date(year, month + 1, 0);
|
|
34
|
+
return (min !== undefined && last < min) || (max !== undefined && first > max);
|
|
35
|
+
}
|
|
36
|
+
static isYearOutOfRange(year, min, max) {
|
|
37
|
+
return ((min !== undefined && year < min.getFullYear()) ||
|
|
38
|
+
(max !== undefined && year > max.getFullYear()));
|
|
39
|
+
}
|
|
24
40
|
}
|
|
@@ -3,4 +3,8 @@ export declare class PsDateInputService {
|
|
|
3
3
|
static parseBound(value: string | undefined): Date | undefined;
|
|
4
4
|
static format(date: Date | null): string;
|
|
5
5
|
static monthLabel(date: Date): string;
|
|
6
|
+
static monthNames(): string[];
|
|
7
|
+
static yearPage(year: number): number[];
|
|
8
|
+
static isMonthOutOfRange(year: number, month: number, min?: Date, max?: Date): boolean;
|
|
9
|
+
static isYearOutOfRange(year: number, min?: Date, max?: Date): boolean;
|
|
6
10
|
}
|
|
@@ -24,5 +24,21 @@ class PsDateInputService {
|
|
|
24
24
|
static monthLabel(date) {
|
|
25
25
|
return ps_utilities_1.Common.DateService.formatDate(date.getTime(), ps_date_input_config_1.PsDateInputConfig.monthLabelFormat);
|
|
26
26
|
}
|
|
27
|
+
static monthNames() {
|
|
28
|
+
return Array.from({ length: ps_date_input_config_1.PsDateInputConfig.monthsPerYear }, (_, month) => ps_utilities_1.Common.DateService.formatDate(new Date(2000, month, 1).getTime(), ps_date_input_config_1.PsDateInputConfig.monthShortFormat));
|
|
29
|
+
}
|
|
30
|
+
static yearPage(year) {
|
|
31
|
+
const start = year - Math.floor(ps_date_input_config_1.PsDateInputConfig.yearsPerPage / 2);
|
|
32
|
+
return Array.from({ length: ps_date_input_config_1.PsDateInputConfig.yearsPerPage }, (_, index) => start + index);
|
|
33
|
+
}
|
|
34
|
+
static isMonthOutOfRange(year, month, min, max) {
|
|
35
|
+
const first = new Date(year, month, 1);
|
|
36
|
+
const last = new Date(year, month + 1, 0);
|
|
37
|
+
return (min !== undefined && last < min) || (max !== undefined && first > max);
|
|
38
|
+
}
|
|
39
|
+
static isYearOutOfRange(year, min, max) {
|
|
40
|
+
return ((min !== undefined && year < min.getFullYear()) ||
|
|
41
|
+
(max !== undefined && year > max.getFullYear()));
|
|
42
|
+
}
|
|
27
43
|
}
|
|
28
44
|
exports.PsDateInputService = PsDateInputService;
|