@shipfox/react-ui 0.18.0 → 0.19.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/calendar/calendar.js +12 -12
- package/dist/components/dashboard/components/kpi-card.js +4 -1
- package/dist/components/date-picker/date-picker.d.ts +1 -0
- package/dist/components/date-picker/date-picker.js +20 -4
- package/dist/components/date-picker/date-picker.stories.js +16 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.d.ts +1 -0
- package/dist/components/date-time-range-picker/date-time-range-picker.js +51 -23
- package/dist/components/dropdown-input/dropdown-input.d.ts +25 -0
- package/dist/components/dropdown-input/dropdown-input.js +188 -0
- package/dist/components/dropdown-input/dropdown-input.stories.js +240 -0
- package/dist/components/dropdown-input/index.d.ts +2 -0
- package/dist/components/dropdown-input/index.js +3 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/input/input.d.ts +6 -3
- package/dist/components/input/input.js +27 -11
- package/dist/components/input/input.stories.js +66 -0
- package/dist/styles.css +1 -1
- package/package.json +4 -4
|
@@ -5,34 +5,34 @@ import { cn } from '../../utils/cn.js';
|
|
|
5
5
|
export function Calendar({ className, classNames, showOutsideDays = true, ...props }) {
|
|
6
6
|
return /*#__PURE__*/ _jsx(DayPicker, {
|
|
7
7
|
showOutsideDays: showOutsideDays,
|
|
8
|
-
className: cn('
|
|
8
|
+
className: cn('transition-colors', className),
|
|
9
9
|
classNames: {
|
|
10
|
-
months: 'flex flex-col sm:flex-row gap-
|
|
11
|
-
month: 'space-y-16 relative',
|
|
12
|
-
month_caption: 'flex items-center justify-center mb-8 px-4 relative h-32',
|
|
10
|
+
months: 'flex flex-col sm:flex-row gap-0',
|
|
11
|
+
month: 'space-y-16 relative p-16 border-r border-border-neutral-base-component last:border-r-0',
|
|
12
|
+
month_caption: 'flex items-center justify-center mb-8 px-4 relative h-32 bg-background-field-base rounded-8 shadow-tooltip',
|
|
13
13
|
caption_label: 'text-sm font-medium text-foreground-neutral-base',
|
|
14
14
|
nav: 'flex items-center gap-4 fixed left-0 top-16 w-full z-10',
|
|
15
|
-
button_previous: cn('size-32 bg-transparent p-0 absolute left-16 top-0', 'inline-flex items-center justify-center rounded-6', 'text-foreground-neutral-
|
|
16
|
-
button_next: cn('size-32 bg-transparent p-0 absolute right-16 top-0', 'inline-flex items-center justify-center rounded-6', 'text-foreground-neutral-
|
|
15
|
+
button_previous: cn('size-32 bg-transparent p-0 absolute left-16 top-0 cursor-pointer', 'inline-flex items-center justify-center rounded-6', 'text-foreground-neutral-muted', 'hover:bg-transparent hover:text-foreground-neutral-subtle', 'active:bg-transparent', 'transition-colors outline-none', 'focus-visible:shadow-border-interactive-with-active', 'disabled:pointer-events-none disabled:opacity-50'),
|
|
16
|
+
button_next: cn('size-32 bg-transparent p-0 absolute right-16 top-0 cursor-pointer', 'inline-flex items-center justify-center rounded-6', 'text-foreground-neutral-muted', 'hover:bg-transparent hover:text-foreground-neutral-subtle', 'active:bg-transparent', 'transition-colors outline-none', 'focus-visible:shadow-border-interactive-with-active', 'disabled:pointer-events-none disabled:opacity-50'),
|
|
17
17
|
month_grid: 'w-full border-collapse mt-8',
|
|
18
|
-
weekdays: 'flex
|
|
18
|
+
weekdays: 'flex gap-8',
|
|
19
19
|
weekday: 'text-foreground-neutral-subtle text-xs font-medium w-36 h-32 flex items-center justify-center',
|
|
20
|
-
week: 'flex mt-
|
|
20
|
+
week: 'flex mt-8 gap-8',
|
|
21
21
|
day: cn('relative text-center size-36 p-0 text-sm font-normal [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none', '[&:last-child[data-selected=true]_button]:rounded-r-6', props.showWeekNumber ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-6' : '[&:first-child[data-selected=true]_button]:rounded-l-6'),
|
|
22
22
|
day_button: cn('size-36 p-0 text-sm font-normal rounded-6', 'inline-flex items-center justify-center', 'hover:bg-background-button-transparent-hover', 'focus-visible:shadow-border-interactive-with-active', 'transition-colors outline-none', 'aria-selected:opacity-100'),
|
|
23
23
|
range_start: 'day-range-start rounded-6',
|
|
24
24
|
range_end: 'day-range-end rounded-6',
|
|
25
|
-
selected: cn('bg-foreground-highlight-interactive/80 !text-foreground-neutral-
|
|
26
|
-
today: cn('
|
|
25
|
+
selected: cn('bg-foreground-highlight-interactive/80 !text-foreground-neutral-on-inverted font-medium rounded-6', 'hover:bg-foreground-highlight-interactive-hover/80', 'focus:bg-foreground-highlight-interactive/80'),
|
|
26
|
+
today: cn('relative font-medium rounded-6', 'after:absolute after:bottom-[6px] after:left-1/2 after:-translate-x-1/2', 'after:size-[3px] after:rounded-full after:bg-[var(--color-primary-400)]'),
|
|
27
27
|
outside: 'day-outside text-foreground-neutral-muted',
|
|
28
28
|
disabled: 'text-foreground-neutral-disabled opacity-30 cursor-not-allowed',
|
|
29
|
-
range_middle: cn('aria-selected:bg-
|
|
29
|
+
range_middle: cn('aria-selected:bg-background-highlight-base aria-selected:!text-foreground-highlight-interactive'),
|
|
30
30
|
hidden: 'invisible',
|
|
31
31
|
...classNames
|
|
32
32
|
},
|
|
33
33
|
components: {
|
|
34
34
|
Chevron: ({ orientation })=>{
|
|
35
|
-
const iconName = orientation === 'left' ? '
|
|
35
|
+
const iconName = orientation === 'left' ? 'arrowLeftSFill' : 'arrowRightSFill';
|
|
36
36
|
return /*#__PURE__*/ _jsx(Icon, {
|
|
37
37
|
name: iconName,
|
|
38
38
|
className: "size-20"
|
|
@@ -29,10 +29,13 @@ export function KpiCard({ label, value, variant = 'neutral', isLoading, classNam
|
|
|
29
29
|
}),
|
|
30
30
|
isLoading ? /*#__PURE__*/ _jsx(Skeleton, {
|
|
31
31
|
className: "w-48 h-20 rounded-4"
|
|
32
|
-
}) : /*#__PURE__*/ _jsx(Text, {
|
|
32
|
+
}) : typeof value === 'string' || typeof value === 'number' ? /*#__PURE__*/ _jsx(Text, {
|
|
33
33
|
size: "sm",
|
|
34
34
|
className: "font-medium text-foreground-neutral-base",
|
|
35
35
|
children: value
|
|
36
|
+
}) : /*#__PURE__*/ _jsx("div", {
|
|
37
|
+
className: "text-sm font-medium text-foreground-neutral-base",
|
|
38
|
+
children: value
|
|
36
39
|
})
|
|
37
40
|
]
|
|
38
41
|
})
|
|
@@ -14,6 +14,7 @@ export type DatePickerProps = Omit<ComponentProps<'input'>, 'size' | 'type'> & V
|
|
|
14
14
|
rightIcon?: ReactNode;
|
|
15
15
|
onClear?: () => void;
|
|
16
16
|
closeOnSelect?: boolean;
|
|
17
|
+
maxDisabledOffsetDays?: number;
|
|
17
18
|
};
|
|
18
19
|
export declare const DatePicker: import("react").ForwardRefExoticComponent<Omit<DatePickerProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
19
20
|
//# sourceMappingURL=date-picker.d.ts.map
|
|
@@ -3,8 +3,8 @@ import { cva } from 'class-variance-authority';
|
|
|
3
3
|
import { Calendar } from '../../components/calendar/index.js';
|
|
4
4
|
import { Icon } from '../../components/icon/index.js';
|
|
5
5
|
import { Popover, PopoverContent, PopoverTrigger } from '../../components/popover/index.js';
|
|
6
|
-
import { format } from 'date-fns';
|
|
7
|
-
import { forwardRef, useState } from 'react';
|
|
6
|
+
import { addDays, format, subDays } from 'date-fns';
|
|
7
|
+
import { forwardRef, useMemo, useState } from 'react';
|
|
8
8
|
import { cn } from '../../utils/cn.js';
|
|
9
9
|
export const datePickerVariants = cva('relative flex items-center rounded-6 shadow-button-neutral transition-[background-color,box-shadow] outline-none', {
|
|
10
10
|
variants: {
|
|
@@ -28,10 +28,22 @@ export const datePickerVariants = cva('relative flex items-center rounded-6 shad
|
|
|
28
28
|
state: 'default'
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
|
-
export const DatePicker = /*#__PURE__*/ forwardRef(({ className, variant, size, state, date, onDateSelect, placeholder = 'DD/MM/YYYY', dateFormat = 'dd/MM/yyyy', leftIcon, rightIcon, onClear, disabled, closeOnSelect = false, ...props }, ref)=>{
|
|
31
|
+
export const DatePicker = /*#__PURE__*/ forwardRef(({ className, variant, size, state, date, onDateSelect, placeholder = 'DD/MM/YYYY', dateFormat = 'dd/MM/yyyy', leftIcon, rightIcon, onClear, disabled, closeOnSelect = false, maxDisabledOffsetDays, ...props }, ref)=>{
|
|
32
32
|
const [open, setOpen] = useState(false);
|
|
33
33
|
const isDisabled = disabled || state === 'disabled';
|
|
34
34
|
const displayValue = date ? format(date, dateFormat) : '';
|
|
35
|
+
// Disable dates beyond maxDisabledDays before and after today
|
|
36
|
+
const disabledDates = useMemo(()=>{
|
|
37
|
+
if (!maxDisabledOffsetDays) return undefined;
|
|
38
|
+
const today = new Date();
|
|
39
|
+
const minDate = subDays(today, maxDisabledOffsetDays);
|
|
40
|
+
const maxDate = addDays(today, maxDisabledOffsetDays);
|
|
41
|
+
return (date)=>{
|
|
42
|
+
return date < minDate || date > maxDate;
|
|
43
|
+
};
|
|
44
|
+
}, [
|
|
45
|
+
maxDisabledOffsetDays
|
|
46
|
+
]);
|
|
35
47
|
const handleSelect = (selectedDate)=>{
|
|
36
48
|
onDateSelect?.(selectedDate);
|
|
37
49
|
if (closeOnSelect) {
|
|
@@ -103,7 +115,11 @@ export const DatePicker = /*#__PURE__*/ forwardRef(({ className, variant, size,
|
|
|
103
115
|
children: /*#__PURE__*/ _jsx(Calendar, {
|
|
104
116
|
mode: "single",
|
|
105
117
|
selected: date,
|
|
106
|
-
onSelect: handleSelect
|
|
118
|
+
onSelect: handleSelect,
|
|
119
|
+
disabled: disabledDates,
|
|
120
|
+
formatters: {
|
|
121
|
+
formatWeekdayName: (date)=>format(date, 'EEEEE')
|
|
122
|
+
}
|
|
107
123
|
})
|
|
108
124
|
})
|
|
109
125
|
]
|
|
@@ -51,6 +51,22 @@ export const DatePickerStory = {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
};
|
|
54
|
+
export const DatePickerWithThresholdStory = {
|
|
55
|
+
play: (ctx)=>openCalendarAndScreenshot(ctx, 'DatePicker With Threshold Calendar Open'),
|
|
56
|
+
render: ()=>{
|
|
57
|
+
const [date, setDate] = useState(new Date());
|
|
58
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
59
|
+
className: "relative flex h-600 w-500 items-center justify-center rounded-16 bg-background-subtle-base shadow-tooltip overflow-visible",
|
|
60
|
+
children: /*#__PURE__*/ _jsx(DatePicker, {
|
|
61
|
+
date: date,
|
|
62
|
+
onDateSelect: setDate,
|
|
63
|
+
onClear: ()=>setDate(undefined),
|
|
64
|
+
placeholder: "DD/MM/YYYY",
|
|
65
|
+
maxDisabledOffsetDays: 30
|
|
66
|
+
})
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
};
|
|
54
70
|
export const DateRangePickerStory = {
|
|
55
71
|
play: (ctx)=>openCalendarAndScreenshot(ctx, 'DateRangePicker Calendar Open'),
|
|
56
72
|
render: ()=>{
|
|
@@ -19,6 +19,7 @@ export type DateTimeRangePickerProps = Omit<ComponentProps<'input'>, 'size' | 't
|
|
|
19
19
|
onClear?: () => void;
|
|
20
20
|
numberOfMonths?: number;
|
|
21
21
|
closeOnSelect?: boolean;
|
|
22
|
+
maxRangeDays?: number;
|
|
22
23
|
};
|
|
23
24
|
export declare const DateTimeRangePicker: import("react").ForwardRefExoticComponent<Omit<DateTimeRangePickerProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
|
|
24
25
|
//# sourceMappingURL=date-time-range-picker.d.ts.map
|
|
@@ -3,8 +3,8 @@ import { cva } from 'class-variance-authority';
|
|
|
3
3
|
import { Calendar } from '../../components/calendar/index.js';
|
|
4
4
|
import { Icon } from '../../components/icon/index.js';
|
|
5
5
|
import { Popover, PopoverContent, PopoverTrigger } from '../../components/popover/index.js';
|
|
6
|
-
import { format } from 'date-fns';
|
|
7
|
-
import { forwardRef, useState } from 'react';
|
|
6
|
+
import { addDays, differenceInDays, format } from 'date-fns';
|
|
7
|
+
import { forwardRef, useMemo, useState } from 'react';
|
|
8
8
|
import { cn } from '../../utils/cn.js';
|
|
9
9
|
export const dateTimeRangePickerVariants = cva('min-w-240 relative flex items-center rounded-6 shadow-button-neutral transition-[background-color,box-shadow] outline-none', {
|
|
10
10
|
variants: {
|
|
@@ -28,28 +28,52 @@ export const dateTimeRangePickerVariants = cva('min-w-240 relative flex items-ce
|
|
|
28
28
|
state: 'default'
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
|
-
export const DateTimeRangePicker = /*#__PURE__*/ forwardRef(({ className, variant, size, state, dateRange, onDateRangeSelect, placeholder = 'DD/MM/YYYY - DD/MM/YYYY', dateFormat = 'dd/MM/yyyy', leftIcon, rightIcon, onClear, disabled, numberOfMonths = 2, closeOnSelect = false, ...props }, ref)=>{
|
|
31
|
+
export const DateTimeRangePicker = /*#__PURE__*/ forwardRef(({ className, variant, size, state, dateRange, onDateRangeSelect, placeholder = 'DD/MM/YYYY - DD/MM/YYYY', dateFormat = 'dd/MM/yyyy', leftIcon, rightIcon, onClear, disabled, numberOfMonths = 2, closeOnSelect = false, maxRangeDays, ...props }, ref)=>{
|
|
32
32
|
const [open, setOpen] = useState(false);
|
|
33
33
|
const isDisabled = disabled || state === 'disabled';
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
const startDate = dateRange?.start;
|
|
35
|
+
const endDate = dateRange?.end;
|
|
36
|
+
const hasRange = Boolean(startDate && endDate);
|
|
37
|
+
// Format display value
|
|
38
|
+
const displayValue = hasRange && startDate && endDate ? `${format(startDate, dateFormat)} - ${format(endDate, dateFormat)}` : '';
|
|
39
|
+
// Convert to react-day-picker format
|
|
40
|
+
const dayPickerRange = startDate || endDate ? {
|
|
41
|
+
from: startDate,
|
|
42
|
+
to: endDate
|
|
40
43
|
} : undefined;
|
|
44
|
+
// Get default month for calendar (prioritize selected start date, then today)
|
|
45
|
+
const defaultMonth = startDate ?? new Date();
|
|
46
|
+
// Disable dates beyond maxRangeDays (only if maxRangeDays is provided)
|
|
47
|
+
const disabledDates = useMemo(()=>{
|
|
48
|
+
if (!startDate || maxRangeDays === undefined) return undefined;
|
|
49
|
+
return (date)=>{
|
|
50
|
+
const daysFromStart = differenceInDays(date, startDate);
|
|
51
|
+
return daysFromStart > maxRangeDays;
|
|
52
|
+
};
|
|
53
|
+
}, [
|
|
54
|
+
startDate,
|
|
55
|
+
maxRangeDays
|
|
56
|
+
]);
|
|
41
57
|
const handleSelect = (selectedRange)=>{
|
|
42
|
-
if (selectedRange) {
|
|
43
|
-
onDateRangeSelect?.({
|
|
44
|
-
start: selectedRange.from,
|
|
45
|
-
end: selectedRange.to
|
|
46
|
-
});
|
|
47
|
-
// Only close if both dates are selected and closeOnSelect is true
|
|
48
|
-
if (closeOnSelect && selectedRange.from && selectedRange.to) {
|
|
49
|
-
setOpen(false);
|
|
50
|
-
}
|
|
51
|
-
} else {
|
|
58
|
+
if (!selectedRange) {
|
|
52
59
|
onDateRangeSelect?.(undefined);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const { from, to } = selectedRange;
|
|
63
|
+
let finalEndDate = to;
|
|
64
|
+
// Cap end date to maxRangeDays if both dates are selected and maxRangeDays is provided
|
|
65
|
+
if (from && to && maxRangeDays !== undefined) {
|
|
66
|
+
const daysFromStart = differenceInDays(to, from);
|
|
67
|
+
if (daysFromStart > maxRangeDays) {
|
|
68
|
+
finalEndDate = addDays(from, maxRangeDays);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
onDateRangeSelect?.({
|
|
72
|
+
start: from,
|
|
73
|
+
end: finalEndDate
|
|
74
|
+
});
|
|
75
|
+
if (closeOnSelect && from && finalEndDate) {
|
|
76
|
+
setOpen(false);
|
|
53
77
|
}
|
|
54
78
|
};
|
|
55
79
|
const handleClear = (e)=>{
|
|
@@ -98,11 +122,11 @@ export const DateTimeRangePicker = /*#__PURE__*/ forwardRef(({ className, varian
|
|
|
98
122
|
/*#__PURE__*/ _jsx("button", {
|
|
99
123
|
type: "button",
|
|
100
124
|
onClick: handleClear,
|
|
101
|
-
className: cn('flex items-center justify-center shrink-0 transition-colors hover:text-foreground-neutral-base', size === 'small' ? 'size-28' : 'size-32', hasRange && onClear && !isDisabled ? 'visible' : 'invisible'),
|
|
125
|
+
className: cn('flex items-center justify-center shrink-0 transition-colors hover:text-foreground-neutral-base cursor-pointer', size === 'small' ? 'size-28' : 'size-32', hasRange && onClear && !isDisabled ? 'visible' : 'invisible'),
|
|
102
126
|
"aria-label": "Clear date range",
|
|
103
127
|
children: /*#__PURE__*/ _jsx(Icon, {
|
|
104
128
|
name: "closeLine",
|
|
105
|
-
className: "size-16 text-foreground-neutral-muted"
|
|
129
|
+
className: "size-16 text-foreground-neutral-muted hover:text-foreground-neutral-subtle transition-colors"
|
|
106
130
|
})
|
|
107
131
|
}),
|
|
108
132
|
rightIcon && !hasRange && /*#__PURE__*/ _jsx("div", {
|
|
@@ -116,10 +140,14 @@ export const DateTimeRangePicker = /*#__PURE__*/ forwardRef(({ className, varian
|
|
|
116
140
|
align: "start",
|
|
117
141
|
children: /*#__PURE__*/ _jsx(Calendar, {
|
|
118
142
|
mode: "range",
|
|
119
|
-
defaultMonth:
|
|
143
|
+
defaultMonth: defaultMonth,
|
|
120
144
|
selected: dayPickerRange,
|
|
121
145
|
onSelect: handleSelect,
|
|
122
|
-
numberOfMonths: numberOfMonths
|
|
146
|
+
numberOfMonths: numberOfMonths,
|
|
147
|
+
disabled: disabledDates,
|
|
148
|
+
formatters: {
|
|
149
|
+
formatWeekdayName: (date)=>format(date, 'EEEEE')
|
|
150
|
+
}
|
|
123
151
|
})
|
|
124
152
|
})
|
|
125
153
|
]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Input } from '../../components/input';
|
|
2
|
+
import type { ComponentProps } from 'react';
|
|
3
|
+
import { type ReactNode } from 'react';
|
|
4
|
+
export type DropdownInputItem<T = unknown> = {
|
|
5
|
+
id: string | number;
|
|
6
|
+
label: string;
|
|
7
|
+
value: T;
|
|
8
|
+
};
|
|
9
|
+
type InputBaseProps = Omit<ComponentProps<typeof Input>, 'value' | 'onChange' | 'onSelect'>;
|
|
10
|
+
export type DropdownInputProps<T = unknown> = InputBaseProps & {
|
|
11
|
+
value: string;
|
|
12
|
+
onValueChange: (value: string) => void;
|
|
13
|
+
onSelect?: (item: DropdownInputItem<T>) => void;
|
|
14
|
+
items: DropdownInputItem<T>[];
|
|
15
|
+
emptyPlaceholder?: string | ReactNode;
|
|
16
|
+
open: boolean;
|
|
17
|
+
onOpenChange: (open: boolean) => void;
|
|
18
|
+
focusedIndex: number;
|
|
19
|
+
onFocusedIndexChange: (index: number) => void;
|
|
20
|
+
selectedItem?: DropdownInputItem<T> | null;
|
|
21
|
+
dropdownClassName?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function DropdownInput<T = unknown>({ value, onValueChange, onSelect, items, emptyPlaceholder, open, onOpenChange, focusedIndex, onFocusedIndexChange, selectedItem, dropdownClassName, className, ...inputProps }: DropdownInputProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=dropdown-input.d.ts.map
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from '../../components/button/index.js';
|
|
3
|
+
import { Input } from '../../components/input/index.js';
|
|
4
|
+
import { Popover, PopoverContent, PopoverTrigger } from '../../components/popover/index.js';
|
|
5
|
+
import { useCallback, useEffect, useRef } from 'react';
|
|
6
|
+
import { cn } from '../../utils/cn.js';
|
|
7
|
+
export function DropdownInput({ value, onValueChange, onSelect, items = [], emptyPlaceholder, open, onOpenChange, focusedIndex, onFocusedIndexChange, selectedItem, dropdownClassName, className, ...inputProps }) {
|
|
8
|
+
const inputRef = useRef(null);
|
|
9
|
+
const blurTimeoutRef = useRef(null);
|
|
10
|
+
const popoverContentRef = useRef(null);
|
|
11
|
+
const itemRefs = useRef([]);
|
|
12
|
+
const isDisabled = Boolean(inputProps.disabled);
|
|
13
|
+
const hasResults = items.length > 0;
|
|
14
|
+
const shouldShowDropdown = open && !isDisabled;
|
|
15
|
+
const handleOpenChange = useCallback((nextOpen)=>{
|
|
16
|
+
if (isDisabled) {
|
|
17
|
+
if (!nextOpen) onOpenChange(false);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
onOpenChange(nextOpen);
|
|
21
|
+
}, [
|
|
22
|
+
isDisabled,
|
|
23
|
+
onOpenChange
|
|
24
|
+
]);
|
|
25
|
+
const handleSelect = useCallback((item)=>{
|
|
26
|
+
onValueChange(item.label);
|
|
27
|
+
onSelect?.(item);
|
|
28
|
+
onOpenChange(false);
|
|
29
|
+
inputRef.current?.blur();
|
|
30
|
+
}, [
|
|
31
|
+
onValueChange,
|
|
32
|
+
onOpenChange,
|
|
33
|
+
onSelect
|
|
34
|
+
]);
|
|
35
|
+
const handleInputChange = useCallback((e)=>{
|
|
36
|
+
onValueChange(e.target.value);
|
|
37
|
+
onFocusedIndexChange(-1);
|
|
38
|
+
if (!open && !isDisabled) {
|
|
39
|
+
onOpenChange(true);
|
|
40
|
+
}
|
|
41
|
+
}, [
|
|
42
|
+
onValueChange,
|
|
43
|
+
onFocusedIndexChange,
|
|
44
|
+
open,
|
|
45
|
+
isDisabled,
|
|
46
|
+
onOpenChange
|
|
47
|
+
]);
|
|
48
|
+
const handleInputKeyDown = useCallback((e)=>{
|
|
49
|
+
if (!shouldShowDropdown || items.length === 0) return;
|
|
50
|
+
if (e.key === 'ArrowDown') {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
onFocusedIndexChange(focusedIndex < items.length - 1 ? focusedIndex + 1 : 0);
|
|
53
|
+
} else if (e.key === 'ArrowUp') {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
onFocusedIndexChange(focusedIndex > 0 ? focusedIndex - 1 : items.length - 1);
|
|
56
|
+
} else if (e.key === 'Enter' && focusedIndex >= 0) {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
const item = items[focusedIndex];
|
|
59
|
+
if (item) {
|
|
60
|
+
handleSelect(item);
|
|
61
|
+
}
|
|
62
|
+
} else if (e.key === 'Escape') {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
onOpenChange(false);
|
|
65
|
+
}
|
|
66
|
+
}, [
|
|
67
|
+
shouldShowDropdown,
|
|
68
|
+
items,
|
|
69
|
+
focusedIndex,
|
|
70
|
+
onFocusedIndexChange,
|
|
71
|
+
handleSelect,
|
|
72
|
+
onOpenChange
|
|
73
|
+
]);
|
|
74
|
+
const handleInputBlur = useCallback(()=>{
|
|
75
|
+
if (blurTimeoutRef.current) {
|
|
76
|
+
clearTimeout(blurTimeoutRef.current);
|
|
77
|
+
}
|
|
78
|
+
blurTimeoutRef.current = setTimeout(()=>{
|
|
79
|
+
const activeElement = document.activeElement;
|
|
80
|
+
const popoverContent = activeElement?.closest('[data-radix-popper-content-wrapper]');
|
|
81
|
+
if (!popoverContent) {
|
|
82
|
+
onOpenChange(false);
|
|
83
|
+
}
|
|
84
|
+
}, 200);
|
|
85
|
+
}, [
|
|
86
|
+
onOpenChange
|
|
87
|
+
]);
|
|
88
|
+
useEffect(()=>{
|
|
89
|
+
return ()=>{
|
|
90
|
+
if (blurTimeoutRef.current) {
|
|
91
|
+
clearTimeout(blurTimeoutRef.current);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}, []);
|
|
95
|
+
useEffect(()=>{
|
|
96
|
+
if (focusedIndex >= 0 && itemRefs.current[focusedIndex]) {
|
|
97
|
+
itemRefs.current[focusedIndex]?.scrollIntoView({
|
|
98
|
+
block: 'nearest'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}, [
|
|
102
|
+
focusedIndex
|
|
103
|
+
]);
|
|
104
|
+
const handlePointerDownOutside = useCallback((e)=>{
|
|
105
|
+
const target = e.target;
|
|
106
|
+
if (target && popoverContentRef.current?.contains(target)) {
|
|
107
|
+
e.preventDefault();
|
|
108
|
+
}
|
|
109
|
+
}, []);
|
|
110
|
+
return /*#__PURE__*/ _jsxs(Popover, {
|
|
111
|
+
open: shouldShowDropdown,
|
|
112
|
+
onOpenChange: handleOpenChange,
|
|
113
|
+
children: [
|
|
114
|
+
/*#__PURE__*/ _jsx(PopoverTrigger, {
|
|
115
|
+
asChild: true,
|
|
116
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
117
|
+
className: "w-full",
|
|
118
|
+
children: /*#__PURE__*/ _jsx(Input, {
|
|
119
|
+
ref: inputRef,
|
|
120
|
+
value: value,
|
|
121
|
+
onChange: handleInputChange,
|
|
122
|
+
onKeyDown: handleInputKeyDown,
|
|
123
|
+
onBlur: handleInputBlur,
|
|
124
|
+
onFocus: ()=>{
|
|
125
|
+
if (blurTimeoutRef.current) {
|
|
126
|
+
clearTimeout(blurTimeoutRef.current);
|
|
127
|
+
blurTimeoutRef.current = null;
|
|
128
|
+
}
|
|
129
|
+
if (!open && !isDisabled && value.length > 0 && hasResults) {
|
|
130
|
+
onOpenChange(true);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
className: className,
|
|
134
|
+
...inputProps
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
}),
|
|
138
|
+
/*#__PURE__*/ _jsx(PopoverContent, {
|
|
139
|
+
align: "start",
|
|
140
|
+
sideOffset: 8,
|
|
141
|
+
className: cn('w-(--radix-popover-trigger-width) rounded-8 bg-background-components-base p-4 shadow-tooltip', dropdownClassName),
|
|
142
|
+
onOpenAutoFocus: (e)=>e.preventDefault(),
|
|
143
|
+
onPointerDownOutside: handlePointerDownOutside,
|
|
144
|
+
onInteractOutside: handlePointerDownOutside,
|
|
145
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
146
|
+
ref: popoverContentRef,
|
|
147
|
+
className: "max-h-200 overflow-y-auto overscroll-contain scrollbar",
|
|
148
|
+
onWheel: (e)=>e.stopPropagation(),
|
|
149
|
+
onTouchStart: (e)=>e.stopPropagation(),
|
|
150
|
+
onTouchMove: (e)=>e.stopPropagation(),
|
|
151
|
+
children: hasResults ? /*#__PURE__*/ _jsxs("div", {
|
|
152
|
+
className: "flex flex-col gap-4 p-4",
|
|
153
|
+
children: [
|
|
154
|
+
/*#__PURE__*/ _jsx("div", {
|
|
155
|
+
className: "p-4 text-xs leading-20 text-foreground-neutral-muted",
|
|
156
|
+
children: "Select a repository"
|
|
157
|
+
}),
|
|
158
|
+
items.map((item, index)=>{
|
|
159
|
+
const isSelected = selectedItem?.id === item.id;
|
|
160
|
+
const isFocused = focusedIndex === index;
|
|
161
|
+
return /*#__PURE__*/ _jsx(Button, {
|
|
162
|
+
type: "button",
|
|
163
|
+
variant: "transparent",
|
|
164
|
+
ref: (el)=>{
|
|
165
|
+
itemRefs.current[index] = el;
|
|
166
|
+
},
|
|
167
|
+
onClick: ()=>handleSelect(item),
|
|
168
|
+
onMouseDown: (e)=>e.preventDefault(),
|
|
169
|
+
onMouseEnter: ()=>onFocusedIndexChange(index),
|
|
170
|
+
className: cn('!px-8 w-full text-left text-foreground-neutral-subtle', (isSelected || isFocused) && 'bg-background-components-hover text-foreground-neutral-base'),
|
|
171
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
172
|
+
className: "flex-1 truncate",
|
|
173
|
+
children: item.label
|
|
174
|
+
})
|
|
175
|
+
}, item.id);
|
|
176
|
+
})
|
|
177
|
+
]
|
|
178
|
+
}) : /*#__PURE__*/ _jsx("div", {
|
|
179
|
+
className: "p-4 text-xs leading-20 text-foreground-neutral-muted",
|
|
180
|
+
children: emptyPlaceholder ? emptyPlaceholder : null
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
})
|
|
184
|
+
]
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
//# sourceMappingURL=dropdown-input.js.map
|