@pallasoft/ps-input-components 1.10.0 → 1.12.0
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-cascader-input.d.ts +3 -1
- package/dist/components/ps-cascader-input.js +8 -4
- package/dist/components/ps-color-input.d.ts +2 -1
- package/dist/components/ps-color-input.js +3 -2
- package/dist/components/ps-combobox-input.d.ts +2 -1
- package/dist/components/ps-combobox-input.js +3 -2
- package/dist/components/ps-date-input.d.ts +2 -1
- package/dist/components/ps-date-input.js +6 -2
- package/dist/components/ps-email-input.d.ts +3 -0
- package/dist/components/ps-email-input.js +17 -0
- package/dist/components/ps-field-skeleton.d.ts +7 -0
- package/dist/components/ps-field-skeleton.js +7 -0
- package/dist/components/ps-multi-select-input.js +3 -2
- package/dist/components/ps-percent-input.d.ts +3 -0
- package/dist/components/ps-percent-input.js +16 -0
- package/dist/components/ps-phone-input.d.ts +14 -0
- package/dist/components/ps-phone-input.js +55 -0
- package/dist/components/ps-postal-code-input.d.ts +5 -0
- package/dist/components/ps-postal-code-input.js +14 -0
- package/dist/components/ps-required-mark.d.ts +4 -0
- package/dist/components/ps-required-mark.js +6 -0
- package/dist/components/ps-select-input.d.ts +2 -1
- package/dist/components/ps-select-input.js +6 -2
- package/dist/components/ps-text-area-input.d.ts +2 -1
- package/dist/components/ps-text-area-input.js +3 -1
- package/dist/components/ps-text-input.d.ts +2 -1
- package/dist/components/ps-text-input.js +6 -2
- package/dist/components/ps-url-input.d.ts +3 -0
- package/dist/components/ps-url-input.js +17 -0
- package/dist/constants/ps-phone-input-config.d.ts +10 -0
- package/dist/constants/ps-phone-input-config.js +13 -0
- package/dist/constants/ps-postal-code-formats.d.ts +5 -0
- package/dist/constants/ps-postal-code-formats.js +13 -0
- package/dist/constants/ps-text-format-config.d.ts +6 -0
- package/dist/constants/ps-text-format-config.js +9 -0
- package/dist/esm/components/ps-amount-input.js +10 -0
- package/dist/esm/components/ps-calendar-header.js +5 -0
- package/dist/esm/components/ps-cascader-input.js +48 -0
- package/dist/esm/components/ps-checkbox.js +7 -0
- package/dist/esm/components/ps-color-input.js +16 -0
- package/dist/esm/components/ps-color-picker.js +60 -0
- package/dist/esm/components/ps-combobox-input.js +10 -0
- package/dist/esm/components/ps-date-input.js +38 -0
- package/dist/esm/components/ps-dropdown.js +3 -0
- package/dist/esm/components/ps-email-input.js +13 -0
- package/dist/esm/components/ps-field-skeleton.js +3 -0
- package/dist/esm/components/ps-file-picker.js +27 -0
- package/dist/esm/components/ps-form-control.js +3 -0
- package/dist/esm/components/ps-multi-select-input.js +23 -0
- package/dist/esm/components/ps-percent-input.js +12 -0
- package/dist/esm/components/ps-phone-input.js +51 -0
- package/dist/esm/components/ps-postal-code-input.js +10 -0
- package/dist/esm/components/ps-required-mark.js +2 -0
- package/dist/esm/components/ps-select-input.js +25 -0
- package/dist/esm/components/ps-select-option-item.js +4 -0
- package/dist/esm/components/ps-text-area-input.js +5 -0
- package/dist/esm/components/ps-text-input.js +15 -0
- package/dist/esm/components/ps-toggle.js +3 -0
- package/dist/esm/components/ps-url-input.js +13 -0
- package/dist/esm/constants/ps-amount-input-config.js +7 -0
- package/dist/esm/constants/ps-color-input-config.js +13 -0
- package/dist/esm/constants/ps-date-input-config.js +8 -0
- package/dist/esm/constants/ps-phone-input-config.js +10 -0
- package/dist/esm/constants/ps-postal-code-formats.js +10 -0
- package/dist/esm/constants/ps-text-format-config.js +6 -0
- package/dist/esm/index.js +35 -0
- package/dist/esm/interfaces/color-hsv.js +1 -0
- package/dist/esm/interfaces/ps-highlight-part.js +1 -0
- package/dist/esm/interfaces/ps-phone-country-option.js +1 -0
- package/dist/esm/interfaces/ps-phone-display-data.js +1 -0
- package/dist/esm/interfaces/ps-postal-code-format.js +1 -0
- package/dist/esm/services/ps-amount-input-service.js +25 -0
- package/dist/esm/services/ps-cascader-service.js +22 -0
- package/dist/esm/services/ps-color-service.js +73 -0
- package/dist/esm/services/ps-combobox-input-service.js +6 -0
- package/dist/esm/services/ps-date-input-service.js +24 -0
- package/dist/esm/services/ps-phone-input-service.js +77 -0
- package/dist/esm/services/ps-postal-code-service.js +28 -0
- package/dist/esm/services/ps-select-input-service.js +8 -0
- package/dist/esm/services/ps-text-format-service.js +12 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/interfaces/ps-highlight-part.d.ts +4 -0
- package/dist/interfaces/ps-highlight-part.js +2 -0
- package/dist/interfaces/ps-phone-country-option.d.ts +5 -0
- package/dist/interfaces/ps-phone-country-option.js +2 -0
- package/dist/interfaces/ps-phone-display-data.d.ts +4 -0
- package/dist/interfaces/ps-phone-display-data.js +2 -0
- package/dist/interfaces/ps-postal-code-format.d.ts +5 -0
- package/dist/interfaces/ps-postal-code-format.js +2 -0
- package/dist/services/ps-phone-input-service.d.ts +15 -0
- package/dist/services/ps-phone-input-service.js +81 -0
- package/dist/services/ps-postal-code-service.d.ts +4 -0
- package/dist/services/ps-postal-code-service.js +32 -0
- package/dist/services/ps-text-format-service.d.ts +4 -0
- package/dist/services/ps-text-format-service.js +16 -0
- package/package.json +6 -2
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
3
|
+
import { PsDateInputService } from '../services/ps-date-input-service';
|
|
4
|
+
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
|
+
export const PsCalendarHeader = ({ decreaseMonth, increaseMonth, monthDate, nextMonthButtonDisabled, prevMonthButtonDisabled, }) => (_jsxs("div", { className: 'flex items-center justify-between gap-2 px-1', children: [_jsx("button", { "aria-label": 'Previous month', className: navigationButtonClassName, disabled: prevMonthButtonDisabled, onClick: decreaseMonth, type: 'button', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4 rotate-180', iconKey: PsIconKey.ChevronRight }) }), _jsx("p", { "aria-live": 'polite', className: 'text-sm font-semibold text-ink', children: PsDateInputService.monthLabel(monthDate) }), _jsx("button", { "aria-label": 'Next month', className: navigationButtonClassName, disabled: nextMonthButtonDisabled, onClick: increaseMonth, type: 'button', children: _jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.ChevronRight }) })] }));
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
5
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
6
|
+
import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
|
|
7
|
+
import { PsCascaderService } from '../services/ps-cascader-service';
|
|
8
|
+
import { PsFieldSkeleton } from './ps-field-skeleton';
|
|
9
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
10
|
+
export const PsCascaderInput = ({ className, disabled, groups, helperText, hideLabel, isLoading = false, label, onChange, placeholder = 'Select an option', required, searchPlaceholder = 'Search', value, }) => {
|
|
11
|
+
if (isLoading)
|
|
12
|
+
return _jsx(PsFieldSkeleton, { className: className, hideLabel: hideLabel, label: label });
|
|
13
|
+
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
14
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
15
|
+
const [query, setQuery] = useState('');
|
|
16
|
+
const [activeGroupKey, setActiveGroupKey] = useState(null);
|
|
17
|
+
const selected = useMemo(() => PsCascaderService.findSelected(groups, value), [groups, value]);
|
|
18
|
+
const matches = useMemo(() => PsCascaderService.search(groups, query), [groups, query]);
|
|
19
|
+
const activeGroup = useMemo(() => {
|
|
20
|
+
const key = activeGroupKey
|
|
21
|
+
? activeGroupKey
|
|
22
|
+
: selected
|
|
23
|
+
? selected.group.key
|
|
24
|
+
: groups.length > 0
|
|
25
|
+
? groups[0].key
|
|
26
|
+
: null;
|
|
27
|
+
return groups.find((group) => group.key === key) ?? null;
|
|
28
|
+
}, [activeGroupKey, groups, selected]);
|
|
29
|
+
const open = (nextOpen) => {
|
|
30
|
+
setIsOpen(nextOpen);
|
|
31
|
+
if (!nextOpen)
|
|
32
|
+
return;
|
|
33
|
+
setQuery('');
|
|
34
|
+
setActiveGroupKey(selected ? selected.group.key : null);
|
|
35
|
+
};
|
|
36
|
+
const select = (nextValue, close) => {
|
|
37
|
+
onChange(nextValue);
|
|
38
|
+
close();
|
|
39
|
+
};
|
|
40
|
+
const renderOption = (option, groupLabel, close) => (_jsxs("button", { "aria-selected": option.value === value, className: PallasoftClassNameService.merge('ps-focus-ring flex w-full items-start gap-2 rounded-ps-control px-3 py-2.5 text-left text-sm font-medium transition-colors', option.value === value
|
|
41
|
+
? 'bg-primary/10 text-ink'
|
|
42
|
+
: 'text-ink/80 hover:bg-mist/60 hover:text-ink'), 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 truncate', children: option.label }), groupLabel ? (_jsx("span", { className: 'mt-0.5 block truncate text-xs font-normal text-ink/50', children: groupLabel })) : null, option.description ? (_jsx("span", { className: 'mt-0.5 block truncate text-xs font-normal text-ink/55', children: option.description })) : null] })] }, option.value));
|
|
43
|
+
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 }) => (_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
|
|
44
|
+
? 'bg-mist text-ink'
|
|
45
|
+
: '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
|
|
46
|
+
? activeGroup.options.map((option) => renderOption(option, null, close))
|
|
47
|
+
: null] })] })) : (_jsx("div", { className: 'ps-scrollbar-hidden max-h-72 overflow-y-auto p-1.5', role: 'listbox', children: matches.length === 0 ? (_jsx("p", { className: 'px-3 py-6 text-center text-sm text-ink/55', children: "No matches." })) : (matches.map((match) => renderOption(match.option, match.group.label, close))) }))] })) }) })] }), helperText ? _jsx("span", { className: 'ps-input-helper mt-2 block', children: helperText }) : null] }));
|
|
48
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
4
|
+
import { Checkbox } from 'react-aria-components';
|
|
5
|
+
export const PsCheckbox = ({ children, className, isDisabled, isIndeterminate, isSelected, label, name, onChange, value, }) => (_jsx(Checkbox, { "aria-label": children ? undefined : label, className: PallasoftClassNameService.merge('group inline-flex cursor-pointer items-center gap-3 text-sm text-ink outline-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-45', className), isDisabled: isDisabled, isIndeterminate: isIndeterminate, isSelected: isSelected, name: name, onChange: onChange, value: value, children: ({ isIndeterminate: showDash, isSelected: showCheck }) => (_jsxs(_Fragment, { children: [_jsx("span", { className: PallasoftClassNameService.merge('flex h-[1.125rem] w-[1.125rem] shrink-0 items-center justify-center rounded-[0.35rem] border transition-colors duration-150 group-data-[focus-visible]:outline group-data-[focus-visible]:outline-2 group-data-[focus-visible]:outline-offset-2 group-data-[focus-visible]:outline-[rgb(var(--ps-color-primary-rgb))]', showCheck || showDash
|
|
6
|
+
? 'border-primary bg-primary text-[color:var(--ps-color-primary-contrast)]'
|
|
7
|
+
: 'border-line bg-surface group-hover:border-primary/60 dark:border-white/20'), children: showDash ? (_jsx("span", { className: 'block h-0.5 w-2.5 rounded-full bg-current' })) : showCheck ? (_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-3.5 w-3.5', iconKey: PsIconKey.Check, strokeWidth: 3 })) : null }), children] })) }));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
5
|
+
import { PsColorInputConfig } from '../constants/ps-color-input-config';
|
|
6
|
+
import { PsColorPicker } from './ps-color-picker';
|
|
7
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
8
|
+
export const PsColorInput = ({ className, helperText, label, onChange, required, swatches = [...PsColorInputConfig.swatches], value, }) => {
|
|
9
|
+
const labelId = useId();
|
|
10
|
+
const swatchesId = useId();
|
|
11
|
+
const normalised = value.toUpperCase();
|
|
12
|
+
return (_jsxs("div", { className: PallasoftClassNameService.merge('flex flex-col gap-2', className), children: [_jsxs("span", { className: 'ps-input-label', id: labelId, children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsxs("div", { className: 'flex flex-col gap-4 rounded-ps-control border border-line bg-surface p-4', children: [_jsx(PsColorPicker, { hideLabel: true, label: `${label} picker`, onChange: onChange, swatches: swatches, value: value }), _jsxs("div", { className: 'flex flex-col gap-2 border-t border-line pt-4', children: [_jsx("span", { className: 'ps-input-helper', id: swatchesId, children: "Presets" }), _jsx("div", { "aria-labelledby": swatchesId, className: 'flex flex-wrap gap-2', role: 'radiogroup', children: swatches.map((swatch) => {
|
|
13
|
+
const isSelected = swatch.toUpperCase() === normalised;
|
|
14
|
+
return (_jsx("button", { "aria-checked": isSelected, "aria-label": swatch, className: PallasoftClassNameService.merge('ps-focus-ring flex h-8 w-8 items-center justify-center rounded-full text-white ring-offset-2 ring-offset-surface transition-shadow', isSelected ? 'ring-2 ring-ink/70' : 'hover:ring-2 hover:ring-ink/25'), onClick: () => onChange(swatch.toUpperCase()), role: 'radio', style: { backgroundColor: swatch }, type: 'button', children: isSelected ? (_jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-4 w-4', iconKey: PsIconKey.Check })) : null }, swatch));
|
|
15
|
+
}) })] })] }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
|
|
16
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState, } from 'react';
|
|
3
|
+
import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
5
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
6
|
+
import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
|
|
7
|
+
import { PsColorInputConfig } from '../constants/ps-color-input-config';
|
|
8
|
+
import { PsColorService } from '../services/ps-color-service';
|
|
9
|
+
export const PsColorPicker = ({ className, disabled, helperText, hideLabel, label, onChange, swatches = [...PsColorInputConfig.swatches], value, }) => {
|
|
10
|
+
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
11
|
+
const areaRef = useRef(null);
|
|
12
|
+
const [draft, setDraft] = useState(value);
|
|
13
|
+
const hsv = useMemo(() => PsColorService.toHsv(value), [value]);
|
|
14
|
+
const swatchColor = PsColorService.normaliseHex(value);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setDraft(value);
|
|
17
|
+
}, [value]);
|
|
18
|
+
const commitHex = (next) => {
|
|
19
|
+
const hex = PsColorService.normaliseHex(next);
|
|
20
|
+
if (hex)
|
|
21
|
+
onChange(hex);
|
|
22
|
+
else
|
|
23
|
+
setDraft(value);
|
|
24
|
+
};
|
|
25
|
+
const pickFromArea = (event) => {
|
|
26
|
+
const area = areaRef.current;
|
|
27
|
+
if (!area)
|
|
28
|
+
return;
|
|
29
|
+
const bounds = area.getBoundingClientRect();
|
|
30
|
+
const x = Math.min(Math.max(event.clientX - bounds.left, 0), bounds.width);
|
|
31
|
+
const y = Math.min(Math.max(event.clientY - bounds.top, 0), bounds.height);
|
|
32
|
+
onChange(PsColorService.toHex({
|
|
33
|
+
brightness: Math.round(100 - (y / bounds.height) * 100),
|
|
34
|
+
hue: hsv.hue,
|
|
35
|
+
saturation: Math.round((x / bounds.width) * 100),
|
|
36
|
+
}));
|
|
37
|
+
};
|
|
38
|
+
return (_jsxs("div", { className: PallasoftClassNameService.merge('flex flex-col gap-2', className), children: [hideLabel ? null : _jsx("span", { className: 'ps-input-label block', children: label }), _jsxs(DialogTrigger, { children: [_jsxs(Button, { "aria-label": hideLabel ? label : undefined, className: 'ps-input-control ps-focus-ring flex min-h-11 w-full items-center gap-3 text-left text-sm font-semibold disabled:cursor-not-allowed disabled:opacity-60', isDisabled: disabled, children: [_jsx("span", { className: 'h-6 w-6 shrink-0 rounded-md border border-line', style: { backgroundColor: swatchColor ? swatchColor : 'transparent' } }), _jsx("span", { className: 'font-mono text-sm uppercase', children: swatchColor ? swatchColor : 'Pick a colour' }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'ml-auto block h-4 w-4 shrink-0 text-primary', iconKey: PsIconKey.Chevron })] }), _jsx(Popover, { className: 'ps-floating-panel w-[min(calc(100vw-2rem),18rem)] outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsxs(Dialog, { "aria-label": label, className: 'flex flex-col gap-3 p-3 outline-none', children: [_jsx("div", { className: 'relative h-36 w-full cursor-crosshair rounded-ps-control', onPointerDown: (event) => {
|
|
39
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
40
|
+
pickFromArea(event);
|
|
41
|
+
}, onPointerMove: (event) => {
|
|
42
|
+
if (event.buttons === 1)
|
|
43
|
+
pickFromArea(event);
|
|
44
|
+
}, ref: areaRef, role: 'presentation', style: {
|
|
45
|
+
background: `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, hsl(${hsv.hue} 100% 50%))`,
|
|
46
|
+
}, children: _jsx("span", { "aria-hidden": 'true', className: 'pointer-events-none absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow', style: {
|
|
47
|
+
left: `${hsv.saturation}%`,
|
|
48
|
+
top: `${100 - hsv.brightness}%`,
|
|
49
|
+
backgroundColor: swatchColor ? swatchColor : 'transparent',
|
|
50
|
+
} }) }), _jsxs("label", { className: 'flex flex-col gap-1', children: [_jsx("span", { className: 'ps-input-helper', children: "Hue" }), _jsx("input", { className: 'ps-focus-ring h-3 w-full cursor-pointer appearance-none rounded-full', max: 360, min: 0, onChange: (event) => onChange(PsColorService.toHex({ ...hsv, hue: Number(event.target.value) })), step: 1, style: {
|
|
51
|
+
background: 'linear-gradient(to right, #FF0000, #FFFF00, #00FF00, #00FFFF, #0000FF, #FF00FF, #FF0000)',
|
|
52
|
+
}, type: 'range', value: hsv.hue })] }), _jsx("input", { "aria-label": `${label} hex`, className: 'ps-input-control ps-focus-ring h-10 w-full font-mono text-sm uppercase', maxLength: 7, onBlur: (event) => commitHex(event.target.value), onChange: (event) => setDraft(event.target.value), onKeyDown: (event) => {
|
|
53
|
+
if (event.key !== 'Enter')
|
|
54
|
+
return;
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
commitHex(draft);
|
|
57
|
+
}, placeholder: '#000000', value: draft }), _jsx("div", { className: 'flex flex-wrap gap-1.5', children: swatches.map((swatch) => (_jsx("button", { "aria-label": swatch, className: PallasoftClassNameService.merge('ps-focus-ring h-7 w-7 rounded-full ring-offset-2 ring-offset-surface transition-shadow', swatch.toUpperCase() === (swatchColor ? swatchColor : '')
|
|
58
|
+
? 'ring-2 ring-ink/70'
|
|
59
|
+
: 'hover:ring-2 hover:ring-ink/25'), onClick: () => onChange(swatch.toUpperCase()), style: { backgroundColor: swatch }, type: 'button' }, swatch))) })] }) })] }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
|
|
60
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PsSpinner } from '@pallasoft/ps-core-components';
|
|
3
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
|
+
import { ComboBox, Input, Label, ListBox, ListBoxItem, Popover } from 'react-aria-components';
|
|
5
|
+
import { PsComboboxInputService } from '../services/ps-combobox-input-service';
|
|
6
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
7
|
+
export const PsComboboxInput = ({ className, getItemKey, getItemLabel, isLoading = false, items, label, onInputChange, onSelectionChange, placeholder, renderItem, required, }) => {
|
|
8
|
+
const itemsByKey = new Map(items.map((item) => [getItemKey(item), item]));
|
|
9
|
+
return (_jsxs(ComboBox, { className: PallasoftClassNameService.merge('block', className), defaultFilter: () => true, items: items, onInputChange: onInputChange, onSelectionChange: (key) => onSelectionChange?.(key === null ? null : PsComboboxInputService.getItem(itemsByKey, key)), children: [label ? (_jsxs(Label, { className: 'ps-input-label', children: [label, _jsx(PsRequiredMark, { isRequired: required })] })) : null, _jsx("span", { className: 'relative mt-2 block', children: _jsx(Input, { className: 'ps-input-control ps-focus-ring min-h-11 w-full text-sm font-semibold', placeholder: placeholder }) }), _jsx(Popover, { className: 'w-[--trigger-width] rounded-2xl border border-line bg-white/95 p-1.5 shadow-lg dark:border-white/10 dark:bg-slate-900/95', children: isLoading ? (_jsx("div", { className: 'flex items-center justify-center py-4', children: _jsx(PsSpinner, { label: 'Loading results', size: 'small' }) })) : (_jsx(ListBox, { className: 'max-h-72 overflow-y-auto outline-none', items: items, children: (item) => (_jsx(ListBoxItem, { className: 'ps-focus-ring cursor-pointer rounded-xl px-3 py-2 text-sm font-semibold text-ink selected:bg-primary-soft selected:text-primary hover:bg-slate-50 dark:hover:bg-white/6', id: getItemKey(item), textValue: getItemLabel(item), children: renderItem ? renderItem(item) : getItemLabel(item) })) })) })] }));
|
|
10
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useState } from 'react';
|
|
3
|
+
import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
5
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
6
|
+
import DatePicker from 'react-datepicker';
|
|
7
|
+
import { PsDateInputConfig } from '../constants/ps-date-input-config';
|
|
8
|
+
import { PsDateInputService } from '../services/ps-date-input-service';
|
|
9
|
+
import { PsCalendarHeader } from './ps-calendar-header';
|
|
10
|
+
import { PsFieldSkeleton } from './ps-field-skeleton';
|
|
11
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
12
|
+
export const PsDateInput = ({ className, defaultValue, disabled, helperText, hideLabel, isLoading = false, label, max, min, name, onChange, placeholder = PsDateInputConfig.placeholder, required, value, }) => {
|
|
13
|
+
if (isLoading)
|
|
14
|
+
return _jsx(PsFieldSkeleton, { className: className, hideLabel: hideLabel, label: label });
|
|
15
|
+
const inputId = useId();
|
|
16
|
+
const portalId = `${PsDateInputConfig.portalId}-${inputId.replace(/:/g, '')}`;
|
|
17
|
+
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
18
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue === undefined ? '' : defaultValue);
|
|
19
|
+
const currentValue = value === undefined ? uncontrolledValue : value;
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
const existing = document.getElementById(portalId);
|
|
22
|
+
const container = existing ? existing : document.createElement('div');
|
|
23
|
+
container.id = portalId;
|
|
24
|
+
container.setAttribute(PsDateInputConfig.topLayerAttribute, 'true');
|
|
25
|
+
container.style.position = 'relative';
|
|
26
|
+
container.style.zIndex = String(zIndex);
|
|
27
|
+
if (!existing)
|
|
28
|
+
document.body.appendChild(container);
|
|
29
|
+
return () => container.remove();
|
|
30
|
+
}, [portalId, zIndex]);
|
|
31
|
+
const handleChange = (date) => {
|
|
32
|
+
const nextValue = PsDateInputService.format(date);
|
|
33
|
+
if (value === undefined)
|
|
34
|
+
setUncontrolledValue(nextValue);
|
|
35
|
+
onChange?.(nextValue);
|
|
36
|
+
};
|
|
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: PsDateInputService.parseBound(max), minDate: PsDateInputService.parseBound(min), onChange: handleChange, placeholderText: placeholder, popperClassName: 'ps-datepicker-popper', popperPlacement: 'bottom-start', popperProps: { strategy: 'fixed' }, portalId: portalId, renderCustomHeader: (headerProps) => _jsx(PsCalendarHeader, { ...headerProps }), 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
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsSelectInput } from './ps-select-input';
|
|
3
|
+
export const PsDropdown = ({ className, defaultValue, disabled = false, helperText, label, name, onChange, options, placeholder, value, }) => (_jsx(PsSelectInput, { className: className, defaultValue: defaultValue, disabled: disabled, helperText: helperText, label: label, name: name, onChange: onChange, options: options, placeholder: placeholder, value: value }));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsTextFormatService } from '../services/ps-text-format-service';
|
|
3
|
+
import { PsTextInput } from './ps-text-input';
|
|
4
|
+
export const PsEmailInput = ({ onBlur, onChange, ...props }) => (_jsx(PsTextInput, { ...props, autoCapitalize: 'none', autoComplete: 'email', inputMode: 'email', onBlur: (event) => {
|
|
5
|
+
const formatted = PsTextFormatService.email(event.target.value);
|
|
6
|
+
if (formatted !== event.target.value) {
|
|
7
|
+
event.target.value = formatted;
|
|
8
|
+
if (onChange)
|
|
9
|
+
onChange(event);
|
|
10
|
+
}
|
|
11
|
+
if (onBlur)
|
|
12
|
+
onBlur(event);
|
|
13
|
+
}, onChange: onChange, spellCheck: false, type: 'email' }));
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
export const PsFieldSkeleton = ({ className, hideLabel, label, rows = 1, }) => (_jsxs("div", { "aria-busy": 'true', className: 'block', role: 'status', children: [_jsx("span", { className: 'sr-only', children: label }), hideLabel ? null : _jsx("span", { className: 'ps-skeleton mb-2 block h-2.5 w-24 rounded-full' }), _jsx("span", { className: PallasoftClassNameService.merge('ps-skeleton block w-full rounded-ps-control', rows > 1 ? 'min-h-24' : 'min-h-11', className) })] }));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
4
|
+
import { PsIcon } from '@pallasoft/ps-icons';
|
|
5
|
+
export const PsFilePicker = ({ accept, acceptLabel, className, disabled, dropLabel, maxSizeBytes, maxSizeLabel, onError, onFileLoad, }) => {
|
|
6
|
+
const acceptAttr = useMemo(() => accept.join(','), [accept]);
|
|
7
|
+
const isAccepted = (candidate) => accept.some((entry) => entry.startsWith('.')
|
|
8
|
+
? candidate.name.toLowerCase().endsWith(entry.toLowerCase())
|
|
9
|
+
: entry === candidate.type);
|
|
10
|
+
const onFileChange = (event) => {
|
|
11
|
+
const newFile = event.target.files?.[0];
|
|
12
|
+
if (!newFile || disabled)
|
|
13
|
+
return;
|
|
14
|
+
if (!isAccepted(newFile)) {
|
|
15
|
+
onError?.('Invalid file type. Please select a supported file.');
|
|
16
|
+
event.target.value = '';
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (maxSizeBytes && newFile.size > maxSizeBytes) {
|
|
20
|
+
onError?.(maxSizeLabel ? `File is too large. ${maxSizeLabel}.` : 'File is too large.');
|
|
21
|
+
event.target.value = '';
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
onFileLoad(newFile);
|
|
25
|
+
};
|
|
26
|
+
return (_jsxs("div", { className: PallasoftClassNameService.merge('relative flex h-48 w-full flex-col items-center justify-center rounded-lg border-2 border-dashed border-line p-4 transition-all hover:border-primary/50 hover:bg-primary/5 sm:h-64', className), children: [_jsxs("div", { className: 'flex flex-col items-center gap-3 text-center', children: [_jsx(PsIcon, { iconKey: 'upload', className: 'h-8 w-8 text-primary' }), _jsxs("div", { children: [_jsx("p", { className: 'text-body-2 font-medium text-ink sm:text-body-1', children: dropLabel }), _jsx("p", { className: 'mt-1 text-body-3 text-ink/60 sm:text-body-2', children: "or click to browse your files" }), maxSizeLabel ? _jsx("p", { className: 'mt-3 text-body-3 text-ink/60', children: maxSizeLabel }) : null, _jsxs("p", { className: 'text-body-3 text-ink/60', children: ["Accepted formats: ", acceptLabel] })] })] }), _jsx("input", { type: 'file', accept: acceptAttr, className: 'absolute inset-0 h-full w-full cursor-pointer opacity-0', "aria-label": 'File upload', onChange: onFileChange, disabled: disabled, "aria-disabled": disabled })] }));
|
|
27
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
export const PsFormControl = ({ children, className, errorText, helperText, label, }) => (_jsxs("div", { className: PallasoftClassNameService.merge('grid gap-2', className), children: [label ? _jsx("span", { className: 'ps-input-label-compact', children: label }) : null, children, errorText ? (_jsx("p", { className: 'text-xs font-semibold text-danger', children: errorText })) : helperText ? (_jsx("p", { className: 'ps-input-helper', children: helperText })) : null] }));
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
5
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
6
|
+
import { Button, Label, ListBox, Popover, Select, SelectValue, Text, } from 'react-aria-components';
|
|
7
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
8
|
+
import { PsSelectOptionItem } from './ps-select-option-item';
|
|
9
|
+
export const PsMultiSelectInput = ({ className, defaultValue, disabled, helperText, hideLabel, label, name, onChange, options, placeholder = 'Select options', required, value, }) => {
|
|
10
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue === undefined ? [] : defaultValue);
|
|
11
|
+
const currentValue = value === undefined ? uncontrolledValue : value;
|
|
12
|
+
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
13
|
+
const labelByValue = new Map(options.map((option) => [option.value, option.label]));
|
|
14
|
+
const handleChange = (keys) => {
|
|
15
|
+
const nextValue = keys.map((key) => String(key));
|
|
16
|
+
if (value === undefined)
|
|
17
|
+
setUncontrolledValue(nextValue);
|
|
18
|
+
onChange?.(nextValue);
|
|
19
|
+
};
|
|
20
|
+
return (_jsxs(Select, { "aria-label": hideLabel ? label : undefined, className: PallasoftClassNameService.merge('block', className), isDisabled: disabled, isRequired: required, name: name, onChange: handleChange, placeholder: placeholder, selectionMode: 'multiple', value: currentValue.filter((selected) => labelByValue.has(selected)), children: [hideLabel ? null : (_jsxs(Label, { className: 'ps-input-label block', children: [label, _jsx(PsRequiredMark, { isRequired: required })] })), _jsxs(Button, { className: PallasoftClassNameService.merge('ps-input-control ps-focus-ring relative flex min-h-11 w-full items-center py-1.5 pr-10 text-left text-sm font-semibold disabled:cursor-not-allowed disabled:opacity-60', hideLabel ? '' : 'mt-2'), children: [_jsx(SelectValue, { className: 'flex min-w-0 flex-1 flex-wrap gap-1.5', children: ({ isPlaceholder }) => isPlaceholder ? (_jsx("span", { className: 'font-medium text-ink/45', children: placeholder })) : (currentValue
|
|
21
|
+
.filter((selected) => labelByValue.has(selected))
|
|
22
|
+
.map((selected) => (_jsx("span", { className: 'inline-flex max-w-full items-center rounded-full bg-primary/12 px-2.5 py-0.5 text-xs font-semibold text-ink', children: _jsx("span", { className: 'truncate', children: labelByValue.get(selected) }) }, selected)))) }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-primary', iconKey: PsIconKey.Chevron })] }), helperText ? (_jsx(Text, { className: 'ps-input-helper', slot: 'description', children: helperText })) : null, _jsx(Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-72 min-w-[var(--trigger-width)] overflow-y-auto p-1.5 outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(ListBox, { className: 'outline-none', items: options.map((option) => ({ ...option, id: option.value })), children: (option) => _jsx(PsSelectOptionItem, { option: option }) }) })] }));
|
|
23
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsTextFormatConfig } from '../constants/ps-text-format-config';
|
|
4
|
+
import { PsAmountInputService } from '../services/ps-amount-input-service';
|
|
5
|
+
import { PsTextInput } from './ps-text-input';
|
|
6
|
+
export const PsPercentInput = ({ className, onChange, ...props }) => (_jsxs("div", { className: 'relative', children: [_jsx(PsTextInput, { ...props, autoComplete: 'off', className: PallasoftClassNameService.merge('pr-8', className), inputMode: 'decimal', onChange: (event) => {
|
|
7
|
+
const normalised = PsAmountInputService.normalise(event.target.value);
|
|
8
|
+
if (normalised !== event.target.value)
|
|
9
|
+
event.target.value = normalised;
|
|
10
|
+
if (onChange)
|
|
11
|
+
onChange(event);
|
|
12
|
+
}, type: 'text' }), _jsx("span", { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 bottom-3 text-sm text-ink/50', children: PsTextFormatConfig.percentSuffix })] }));
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useMemo, useState } from 'react';
|
|
3
|
+
import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
5
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
6
|
+
import { Button, Dialog, DialogTrigger, Popover } from 'react-aria-components';
|
|
7
|
+
import { PsPhoneInputConfig } from '../constants/ps-phone-input-config';
|
|
8
|
+
import { PsPhoneInputService } from '../services/ps-phone-input-service';
|
|
9
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
10
|
+
export const PsPhoneInput = ({ className, defaultCountry = PsPhoneInputConfig.defaultCountry, disabled, helperText, hideLabel, label, name, onBlur, onChange, required, value, }) => {
|
|
11
|
+
const inputId = useId();
|
|
12
|
+
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
13
|
+
const initial = PsPhoneInputService.display(value, defaultCountry);
|
|
14
|
+
const [country, setCountry] = useState(initial.country);
|
|
15
|
+
const [national, setNational] = useState(initial.national);
|
|
16
|
+
const [isTouched, setIsTouched] = useState(false);
|
|
17
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
18
|
+
const [query, setQuery] = useState('');
|
|
19
|
+
const countries = useMemo(() => PsPhoneInputService.countries(defaultCountry), [defaultCountry]);
|
|
20
|
+
const matches = useMemo(() => PsPhoneInputService.search(countries, query), [countries, query]);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (PsPhoneInputService.toStored(national, country) === value)
|
|
23
|
+
return;
|
|
24
|
+
const next = PsPhoneInputService.display(value, country);
|
|
25
|
+
setCountry(next.country);
|
|
26
|
+
setNational(next.national);
|
|
27
|
+
}, [value]);
|
|
28
|
+
const emit = (nextNational, nextCountry) => onChange(PsPhoneInputService.toStored(nextNational, nextCountry));
|
|
29
|
+
const isInvalid = isTouched && !PsPhoneInputService.isValid(national, country);
|
|
30
|
+
const choose = (code) => {
|
|
31
|
+
setCountry(code);
|
|
32
|
+
setIsOpen(false);
|
|
33
|
+
setQuery('');
|
|
34
|
+
emit(national, code);
|
|
35
|
+
document.getElementById(inputId)?.focus();
|
|
36
|
+
};
|
|
37
|
+
return (_jsxs("div", { className: PallasoftClassNameService.merge('block', className), children: [_jsxs("label", { className: hideLabel ? 'sr-only' : 'ps-input-label', htmlFor: inputId, children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsxs("div", { className: PallasoftClassNameService.merge('ps-input-control flex min-h-11 items-stretch overflow-hidden p-0 focus-within:border-primary', hideLabel ? '' : 'mt-2', isInvalid ? 'border-danger bg-danger/5' : '', disabled ? 'opacity-60' : ''), children: [_jsxs(DialogTrigger, { isOpen: isOpen, onOpenChange: setIsOpen, children: [_jsxs(Button, { "aria-label": PsPhoneInputConfig.countryLabel, className: 'ps-focus-ring flex shrink-0 items-center gap-1.5 border-r border-line px-3 text-sm font-semibold text-ink', isDisabled: disabled, children: [country, _jsx(PsIcon, { "aria-hidden": 'true', className: 'block h-3.5 w-3.5 text-ink/45', iconKey: PsIconKey.Chevron })] }), _jsx(Popover, { className: 'ps-floating-panel w-72 outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsxs(Dialog, { "aria-label": PsPhoneInputConfig.countryLabel, className: 'flex flex-col outline-none', children: [_jsx("div", { className: 'border-b border-line p-2', children: _jsx("input", { "aria-label": PsPhoneInputConfig.searchPlaceholder, autoFocus: true, className: 'ps-input-control ps-focus-ring h-10 w-full text-sm', onChange: (event) => setQuery(event.target.value), placeholder: PsPhoneInputConfig.searchPlaceholder, type: 'search', value: query }) }), _jsx("ul", { className: 'ps-scrollbar-hidden max-h-64 overflow-y-auto p-1.5', role: 'listbox', children: matches.length === 0 ? (_jsx("li", { className: 'px-3 py-2.5 text-sm text-ink/50', children: PsPhoneInputConfig.emptyMessage })) : (matches.map((option) => (_jsx("li", { role: 'none', children: _jsxs("button", { "aria-selected": option.code === country, className: PallasoftClassNameService.merge('ps-focus-ring flex w-full items-center justify-between gap-3 rounded-ps-control px-3 py-2.5 text-left text-sm', option.code === country
|
|
38
|
+
? 'bg-primary/10 text-ink'
|
|
39
|
+
: 'text-ink/80 hover:bg-mist/60 hover:text-ink'), onClick: () => choose(option.code), role: 'option', type: 'button', children: [_jsx("span", { className: 'min-w-0 truncate', children: PsPhoneInputService.highlight(option.label, query).map((part, index) => part.isMatch ? (_jsx("strong", { className: 'font-semibold text-ink', children: part.text }, index)) : (_jsx("span", { children: part.text }, index))) }), _jsxs("span", { className: 'shrink-0 text-xs text-ink/50', children: ["+", option.callingCode] })] }) }, option.code)))) })] }) })] }), _jsxs("span", { "aria-hidden": 'true', className: 'flex items-center pl-3 text-sm text-ink/45', children: [PsPhoneInputConfig.plus, PsPhoneInputService.callingCode(country)] }), _jsx("input", { "aria-invalid": isInvalid, autoComplete: 'tel-national', className: 'min-w-0 flex-1 bg-transparent px-2 text-sm text-ink outline-none', disabled: disabled, id: inputId, inputMode: 'tel', name: name, onBlur: () => {
|
|
40
|
+
setIsTouched(true);
|
|
41
|
+
if (onBlur)
|
|
42
|
+
onBlur();
|
|
43
|
+
}, onChange: (event) => {
|
|
44
|
+
const isDeleting = event.target.value.length < national.length;
|
|
45
|
+
const next = isDeleting
|
|
46
|
+
? event.target.value
|
|
47
|
+
: PsPhoneInputService.format(event.target.value, country);
|
|
48
|
+
setNational(next);
|
|
49
|
+
emit(next, country);
|
|
50
|
+
}, required: required, type: 'tel', value: national })] }), isInvalid ? (_jsx("span", { className: 'ps-input-helper text-danger', children: PsPhoneInputConfig.invalidMessage })) : helperText ? (_jsx("span", { className: 'ps-input-helper', children: helperText })) : null] }));
|
|
51
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsPostalCodeService } from '../services/ps-postal-code-service';
|
|
3
|
+
import { PsTextInput } from './ps-text-input';
|
|
4
|
+
export const PsPostalCodeInput = ({ country, onChange, ...props }) => (_jsx(PsTextInput, { ...props, autoComplete: 'postal-code', autoCapitalize: 'characters', maxLength: PsPostalCodeService.maxLength(country), onChange: (event) => {
|
|
5
|
+
const formatted = PsPostalCodeService.format(event.target.value, country);
|
|
6
|
+
if (formatted !== event.target.value)
|
|
7
|
+
event.target.value = formatted;
|
|
8
|
+
if (onChange)
|
|
9
|
+
onChange(event);
|
|
10
|
+
}, type: 'text' }));
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { usePsLayerZIndex } from '@pallasoft/ps-core-components';
|
|
4
|
+
import { PallasoftClassNameService, PallasoftZIndex } from '@pallasoft/ps-design-system';
|
|
5
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
6
|
+
import { Button, Label, ListBox, Popover, Select, SelectValue, Text, } from 'react-aria-components';
|
|
7
|
+
import { PsSelectInputService } from '../services/ps-select-input-service';
|
|
8
|
+
import { PsFieldSkeleton } from './ps-field-skeleton';
|
|
9
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
10
|
+
import { PsSelectOptionItem } from './ps-select-option-item';
|
|
11
|
+
export const PsSelectInput = ({ className, defaultValue, disabled, helperText, hideLabel, isLoading = false, label, name, onChange, options, placeholder = 'Select an option', required, value, }) => {
|
|
12
|
+
if (isLoading)
|
|
13
|
+
return _jsx(PsFieldSkeleton, { className: className, hideLabel: hideLabel, label: label });
|
|
14
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(() => PsSelectInputService.getDefaultValue(defaultValue, options));
|
|
15
|
+
const currentValue = value === undefined ? uncontrolledValue : value;
|
|
16
|
+
const zIndex = usePsLayerZIndex(PallasoftZIndex.Dropdown);
|
|
17
|
+
const hasOption = options.some((option) => option.value === currentValue);
|
|
18
|
+
const handleChange = (key) => {
|
|
19
|
+
const nextValue = key === null ? '' : String(key);
|
|
20
|
+
if (value === undefined)
|
|
21
|
+
setUncontrolledValue(nextValue);
|
|
22
|
+
onChange?.(nextValue);
|
|
23
|
+
};
|
|
24
|
+
return (_jsxs(Select, { "aria-label": hideLabel ? label : undefined, className: PallasoftClassNameService.merge('block', className), isDisabled: disabled, isRequired: required, name: name, onChange: handleChange, placeholder: placeholder, value: hasOption ? currentValue : null, children: [hideLabel ? null : (_jsxs(Label, { className: 'ps-input-label block', children: [label, _jsx(PsRequiredMark, { isRequired: required })] })), _jsxs(Button, { 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'), children: [_jsx(SelectValue, { className: 'min-w-0 flex-1 truncate', children: ({ isPlaceholder, selectedText }) => isPlaceholder ? (_jsx("span", { className: 'font-medium text-ink/45', children: placeholder })) : (selectedText) }), _jsx(PsIcon, { "aria-hidden": 'true', className: 'pointer-events-none absolute right-3 top-1/2 h-4 w-4 -translate-y-1/2 text-primary', iconKey: PsIconKey.Chevron })] }), helperText ? (_jsx(Text, { className: 'ps-input-helper', slot: 'description', children: helperText })) : null, _jsx(Popover, { className: 'ps-floating-panel ps-scrollbar-hidden max-h-72 min-w-[var(--trigger-width)] overflow-y-auto p-1.5 outline-none', offset: 6, placement: 'bottom start', style: { zIndex }, children: _jsx(ListBox, { className: 'outline-none', items: options.map((option) => ({ ...option, id: option.value })), children: (option) => _jsx(PsSelectOptionItem, { option: option }) }) })] }));
|
|
25
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { PsIcon, PsIconKey } from '@pallasoft/ps-icons';
|
|
3
|
+
import { ListBoxItem } from 'react-aria-components';
|
|
4
|
+
export const PsSelectOptionItem = ({ option }) => (_jsx(ListBoxItem, { className: 'ps-floating-option text-ink', id: option.value, textValue: option.label, children: ({ isSelected }) => (_jsxs(_Fragment, { children: [_jsx("span", { className: 'flex h-4 w-4 shrink-0 items-center justify-center text-primary', children: isSelected ? (_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 truncate', children: option.label }), option.description ? (_jsx("span", { className: 'mt-0.5 block text-xs font-normal text-ink/55', children: option.description })) : null] })] })) }));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsFieldSkeleton } from './ps-field-skeleton';
|
|
4
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
5
|
+
export const PsTextAreaInput = ({ className, helperText, hideLabel, isLoading = false, label, required, rows = 4, ...props }) => isLoading ? (_jsx(PsFieldSkeleton, { className: className, hideLabel: hideLabel, label: label, rows: 3 })) : (_jsxs("label", { className: 'block', children: [_jsxs("span", { className: hideLabel ? 'sr-only' : 'ps-input-label', children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsx("textarea", { className: PallasoftClassNameService.merge(hideLabel ? 'ps-input-control' : 'ps-input-control mt-2', 'min-h-24 resize-y leading-6', className), required: required, rows: rows, ...props }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
import { PsDateInput } from './ps-date-input';
|
|
4
|
+
import { PsFieldSkeleton } from './ps-field-skeleton';
|
|
5
|
+
import { PsRequiredMark } from './ps-required-mark';
|
|
6
|
+
export const PsTextInput = ({ className, defaultValue, disabled, helperText, hideLabel, isLoading = false, label, name, onChange, required, type, value, ...props }) => {
|
|
7
|
+
if (isLoading)
|
|
8
|
+
return _jsx(PsFieldSkeleton, { className: className, hideLabel: hideLabel, label: label });
|
|
9
|
+
if (type === 'date') {
|
|
10
|
+
return (_jsx(PsDateInput, { className: className, defaultValue: typeof defaultValue === 'string' ? defaultValue : undefined, disabled: disabled, helperText: helperText, hideLabel: hideLabel, label: label, name: name, onChange: (nextValue) => onChange?.({
|
|
11
|
+
target: { name, value: nextValue },
|
|
12
|
+
}), required: required, value: typeof value === 'string' ? value : undefined }));
|
|
13
|
+
}
|
|
14
|
+
return (_jsxs("label", { className: 'block', children: [_jsxs("span", { className: hideLabel ? 'sr-only' : 'ps-input-label', children: [label, _jsx(PsRequiredMark, { isRequired: required })] }), _jsx("input", { className: PallasoftClassNameService.merge(hideLabel ? 'ps-input-control' : 'ps-input-control mt-2', className), defaultValue: defaultValue, disabled: disabled, name: name, onChange: onChange, required: required, type: type, value: value, ...props }), helperText ? _jsx("span", { className: 'ps-input-helper', children: helperText }) : null] }));
|
|
15
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PallasoftClassNameService } from '@pallasoft/ps-design-system';
|
|
3
|
+
export const PsToggle = ({ description, isChecked, label, name, onChange }) => (_jsxs("label", { className: 'ps-focus-ring flex cursor-pointer items-center justify-between gap-4 rounded-2xl border border-line bg-white/78 px-4 py-3 text-ink transition-colors hover:border-primary/35 dark:border-white/10 dark:bg-white/6 dark:text-white', children: [_jsxs("span", { children: [_jsx("span", { className: 'block text-sm font-semibold text-current', children: label }), description ? (_jsx("span", { className: 'mt-1 block text-xs leading-5 text-slate-500 dark:text-slate-400', children: description })) : null] }), _jsx("input", { checked: isChecked, className: 'sr-only', name: name, onChange: (event) => onChange?.(event.target.checked), type: 'checkbox' }), _jsx("span", { className: PallasoftClassNameService.merge('flex h-7 w-12 items-center rounded-full p-1 transition', isChecked ? 'bg-primary' : 'bg-slate-200 dark:bg-slate-700'), children: _jsx("span", { className: PallasoftClassNameService.merge('h-5 w-5 rounded-full bg-white ring-1 ring-black/5 transition dark:ring-white/10', isChecked && 'translate-x-5') }) })] }));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { PsTextFormatService } from '../services/ps-text-format-service';
|
|
3
|
+
import { PsTextInput } from './ps-text-input';
|
|
4
|
+
export const PsUrlInput = ({ onBlur, onChange, ...props }) => (_jsx(PsTextInput, { ...props, autoCapitalize: 'none', autoComplete: 'url', inputMode: 'url', onBlur: (event) => {
|
|
5
|
+
const formatted = PsTextFormatService.url(event.target.value);
|
|
6
|
+
if (formatted !== event.target.value) {
|
|
7
|
+
event.target.value = formatted;
|
|
8
|
+
if (onChange)
|
|
9
|
+
onChange(event);
|
|
10
|
+
}
|
|
11
|
+
if (onBlur)
|
|
12
|
+
onBlur(event);
|
|
13
|
+
}, onChange: onChange, spellCheck: false, type: 'text' }));
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const PsDateInputConfig = {
|
|
2
|
+
inputFormats: ['MMM d, yyyy', 'yyyy-MM-dd', 'MM/dd/yyyy', 'd MMM yyyy'],
|
|
3
|
+
monthLabelFormat: 'MMMM YYYY',
|
|
4
|
+
valueFormat: 'YYYY-MM-DD',
|
|
5
|
+
placeholder: 'Select a date',
|
|
6
|
+
portalId: 'ps-floating-root',
|
|
7
|
+
topLayerAttribute: 'data-react-aria-top-layer',
|
|
8
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const PsPhoneInputConfig = {
|
|
2
|
+
countryLabel: 'Country',
|
|
3
|
+
defaultCountry: 'CA',
|
|
4
|
+
displayNamesType: 'region',
|
|
5
|
+
emptyMessage: 'No country matches that',
|
|
6
|
+
invalidMessage: 'Check this number for the country chosen.',
|
|
7
|
+
locale: 'en',
|
|
8
|
+
plus: '+',
|
|
9
|
+
searchPlaceholder: 'Search for a country',
|
|
10
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const PsPostalCodeFormats = {
|
|
2
|
+
CA: { allowed: /[^A-Z0-9]/g, groups: [3, 3], separator: ' ' },
|
|
3
|
+
GB: { allowed: /[^A-Z0-9]/g, groups: [], separator: ' ' },
|
|
4
|
+
IN: { allowed: /[^0-9]/g, groups: [6], separator: '' },
|
|
5
|
+
NL: { allowed: /[^A-Z0-9]/g, groups: [4, 2], separator: ' ' },
|
|
6
|
+
US: { allowed: /[^0-9]/g, groups: [5, 4], separator: '-' },
|
|
7
|
+
};
|
|
8
|
+
export const PsPostalCodeConfig = {
|
|
9
|
+
fallbackAllowed: /[^A-Z0-9 -]/g,
|
|
10
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export * from './components/ps-cascader-input';
|
|
2
|
+
export * from './components/ps-amount-input';
|
|
3
|
+
export * from './components/ps-checkbox';
|
|
4
|
+
export * from './components/ps-color-input';
|
|
5
|
+
export * from './components/ps-color-picker';
|
|
6
|
+
export * from './components/ps-combobox-input';
|
|
7
|
+
export * from './components/ps-date-input';
|
|
8
|
+
export * from './components/ps-dropdown';
|
|
9
|
+
export * from './components/ps-file-picker';
|
|
10
|
+
export * from './components/ps-field-skeleton';
|
|
11
|
+
export * from './components/ps-form-control';
|
|
12
|
+
export * from './components/ps-multi-select-input';
|
|
13
|
+
export * from './components/ps-select-input';
|
|
14
|
+
export * from './components/ps-text-area-input';
|
|
15
|
+
export * from './components/ps-text-input';
|
|
16
|
+
export * from './components/ps-toggle';
|
|
17
|
+
export * from './services/ps-amount-input-service';
|
|
18
|
+
export * from './services/ps-color-service';
|
|
19
|
+
export * from './interfaces/color-hsv';
|
|
20
|
+
export * from './components/ps-email-input';
|
|
21
|
+
export * from './components/ps-percent-input';
|
|
22
|
+
export * from './components/ps-phone-input';
|
|
23
|
+
export * from './components/ps-postal-code-input';
|
|
24
|
+
export * from './components/ps-url-input';
|
|
25
|
+
export * from './constants/ps-phone-input-config';
|
|
26
|
+
export * from './constants/ps-postal-code-formats';
|
|
27
|
+
export * from './constants/ps-text-format-config';
|
|
28
|
+
export * from './interfaces/ps-highlight-part';
|
|
29
|
+
export * from './interfaces/ps-phone-country-option';
|
|
30
|
+
export * from './interfaces/ps-phone-display-data';
|
|
31
|
+
export * from './interfaces/ps-postal-code-format';
|
|
32
|
+
export * from './services/ps-phone-input-service';
|
|
33
|
+
export * from './services/ps-postal-code-service';
|
|
34
|
+
export * from './services/ps-text-format-service';
|
|
35
|
+
export * from './components/ps-required-mark';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|