@shipfox/react-ui 0.22.0 → 0.24.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/dashboard/context/dashboard-context.d.ts +12 -8
- package/dist/components/dashboard/context/dashboard-context.js +42 -7
- package/dist/components/dashboard/context/index.d.ts +2 -2
- package/dist/components/dashboard/context/index.js +1 -1
- package/dist/components/dashboard/context/types.d.ts +9 -7
- package/dist/components/dashboard/index.d.ts +2 -4
- package/dist/components/dashboard/index.js +1 -2
- package/dist/components/dashboard/pages/analytics-page.js +2 -9
- package/dist/components/dashboard/pages/jobs-page.js +2 -4
- package/dist/components/dashboard/toolbar/filter-button.d.ts +6 -10
- package/dist/components/dashboard/toolbar/filter-button.js +109 -76
- package/dist/components/dashboard/toolbar/page-toolbar.d.ts +7 -19
- package/dist/components/dashboard/toolbar/page-toolbar.js +11 -99
- package/dist/components/dashboard/toolbar/toolbar-actions.js +3 -3
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/interval-selector/hooks/index.d.ts +4 -0
- package/dist/components/interval-selector/hooks/index.js +5 -0
- package/dist/components/interval-selector/hooks/use-interval-selector-input.d.ts +30 -0
- package/dist/components/interval-selector/hooks/use-interval-selector-input.js +125 -0
- package/dist/components/interval-selector/hooks/use-interval-selector-navigation.d.ts +21 -0
- package/dist/components/interval-selector/hooks/use-interval-selector-navigation.js +58 -0
- package/dist/components/interval-selector/hooks/use-interval-selector.d.ts +25 -0
- package/dist/components/interval-selector/hooks/use-interval-selector.js +75 -0
- package/dist/components/interval-selector/index.d.ts +3 -0
- package/dist/components/interval-selector/index.js +4 -0
- package/dist/components/interval-selector/interval-selector-calendar.d.ts +7 -0
- package/dist/components/interval-selector/interval-selector-calendar.js +47 -0
- package/dist/components/interval-selector/interval-selector-input.d.ts +8 -0
- package/dist/components/interval-selector/interval-selector-input.js +34 -0
- package/dist/components/interval-selector/interval-selector-suggestions.d.ts +11 -0
- package/dist/components/interval-selector/interval-selector-suggestions.js +107 -0
- package/dist/components/interval-selector/interval-selector.d.ts +12 -0
- package/dist/components/interval-selector/interval-selector.js +56 -0
- package/dist/components/interval-selector/interval-selector.stories.js +232 -0
- package/dist/components/interval-selector/types.d.ts +19 -0
- package/dist/components/interval-selector/types.js +3 -0
- package/dist/components/interval-selector/utils/constants.d.ts +24 -0
- package/dist/components/interval-selector/utils/constants.js +129 -0
- package/dist/components/interval-selector/utils/format.d.ts +16 -0
- package/dist/components/interval-selector/utils/format.js +23 -0
- package/dist/components/interval-selector/utils/index.d.ts +3 -0
- package/dist/components/interval-selector/utils/index.js +4 -0
- package/dist/components/modal/modal.d.ts +10 -3
- package/dist/components/modal/modal.js +30 -2
- package/dist/components/popover/popover.d.ts +3 -1
- package/dist/components/popover/popover.js +2 -1
- package/dist/styles.css +1 -1
- package/dist/utils/date.js +130 -22
- package/dist/utils/format/date.d.ts +1 -0
- package/dist/utils/format/date.js +11 -4
- package/package.json +3 -2
- package/dist/components/dashboard/filters/expression-filter-bar.d.ts +0 -42
- package/dist/components/dashboard/filters/expression-filter-bar.js +0 -80
- package/dist/components/dashboard/filters/index.d.ts +0 -6
- package/dist/components/dashboard/filters/index.js +0 -5
|
@@ -7,26 +7,26 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
7
7
|
*/ import { Button } from '../../../components/button/index.js';
|
|
8
8
|
import { ButtonGroup, ButtonGroupSeparator } from '../../../components/button-group/index.js';
|
|
9
9
|
import { Icon } from '../../../components/icon/index.js';
|
|
10
|
-
import {
|
|
11
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../../components/select/index.js';
|
|
10
|
+
import { IntervalSelector } from '../../../components/interval-selector/index.js';
|
|
12
11
|
import { Header, Text } from '../../../components/typography/index.js';
|
|
13
12
|
import { cn } from '../../../utils/cn.js';
|
|
13
|
+
import { useDashboardContext } from '../context/index.js';
|
|
14
14
|
/**
|
|
15
15
|
* Generic Page Toolbar
|
|
16
16
|
*
|
|
17
17
|
* A flexible toolbar component that can be used across different dashboard pages.
|
|
18
|
-
* Supports title customization, time
|
|
18
|
+
* Supports title customization, time interval selection, refresh indicator, and playback controls.
|
|
19
|
+
* Uses DashboardContext for interval state management.
|
|
19
20
|
*
|
|
20
21
|
* @example
|
|
21
22
|
* ```tsx
|
|
22
23
|
* <PageToolbar
|
|
23
24
|
* title="Analytics"
|
|
24
|
-
* timePeriod="2days"
|
|
25
|
-
* onTimePeriodChange={setTimePeriod}
|
|
26
25
|
* onRefresh={handleRefresh}
|
|
27
26
|
* />
|
|
28
27
|
* ```
|
|
29
|
-
*/ export function PageToolbar({ title,
|
|
28
|
+
*/ export function PageToolbar({ title, onRefresh, showPlaybackControls = true, onRewind, onPlay, onSpeed, actions, children, intervalSelectorContainer, className, ...props }) {
|
|
29
|
+
const { selection, setSelection, lastUpdated } = useDashboardContext();
|
|
30
30
|
return /*#__PURE__*/ _jsxs("div", {
|
|
31
31
|
className: cn('flex flex-wrap gap-12 items-center justify-between px-12 py-20 md:px-24 md:py-40', className),
|
|
32
32
|
...props,
|
|
@@ -66,99 +66,11 @@ import { cn } from '../../../utils/cn.js';
|
|
|
66
66
|
]
|
|
67
67
|
})
|
|
68
68
|
}),
|
|
69
|
-
/*#__PURE__*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
className: "w-full md:w-280",
|
|
75
|
-
children: /*#__PURE__*/ _jsx("div", {
|
|
76
|
-
className: "flex items-center gap-8 flex-1 min-w-0",
|
|
77
|
-
children: /*#__PURE__*/ _jsx(SelectValue, {
|
|
78
|
-
placeholder: "Select time range"
|
|
79
|
-
})
|
|
80
|
-
})
|
|
81
|
-
}),
|
|
82
|
-
/*#__PURE__*/ _jsxs(SelectContent, {
|
|
83
|
-
children: [
|
|
84
|
-
/*#__PURE__*/ _jsx(SelectItem, {
|
|
85
|
-
value: "1hour",
|
|
86
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
87
|
-
className: "flex items-center gap-8",
|
|
88
|
-
children: [
|
|
89
|
-
/*#__PURE__*/ _jsx(Kbd, {
|
|
90
|
-
className: "h-16",
|
|
91
|
-
children: "1h"
|
|
92
|
-
}),
|
|
93
|
-
/*#__PURE__*/ _jsx("span", {
|
|
94
|
-
children: "Past 1 Hour"
|
|
95
|
-
})
|
|
96
|
-
]
|
|
97
|
-
})
|
|
98
|
-
}),
|
|
99
|
-
/*#__PURE__*/ _jsx(SelectItem, {
|
|
100
|
-
value: "1day",
|
|
101
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
102
|
-
className: "flex items-center gap-8",
|
|
103
|
-
children: [
|
|
104
|
-
/*#__PURE__*/ _jsx(Kbd, {
|
|
105
|
-
className: "h-16",
|
|
106
|
-
children: "1d"
|
|
107
|
-
}),
|
|
108
|
-
/*#__PURE__*/ _jsx("span", {
|
|
109
|
-
children: "Past 1 Day"
|
|
110
|
-
})
|
|
111
|
-
]
|
|
112
|
-
})
|
|
113
|
-
}),
|
|
114
|
-
/*#__PURE__*/ _jsx(SelectItem, {
|
|
115
|
-
value: "2days",
|
|
116
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
117
|
-
className: "flex items-center gap-8",
|
|
118
|
-
children: [
|
|
119
|
-
/*#__PURE__*/ _jsx(Kbd, {
|
|
120
|
-
className: "h-16",
|
|
121
|
-
children: "2d"
|
|
122
|
-
}),
|
|
123
|
-
/*#__PURE__*/ _jsx("span", {
|
|
124
|
-
children: "Past 2 Days"
|
|
125
|
-
})
|
|
126
|
-
]
|
|
127
|
-
})
|
|
128
|
-
}),
|
|
129
|
-
/*#__PURE__*/ _jsx(SelectItem, {
|
|
130
|
-
value: "7days",
|
|
131
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
132
|
-
className: "flex items-center gap-8",
|
|
133
|
-
children: [
|
|
134
|
-
/*#__PURE__*/ _jsx(Kbd, {
|
|
135
|
-
className: "h-16",
|
|
136
|
-
children: "7d"
|
|
137
|
-
}),
|
|
138
|
-
/*#__PURE__*/ _jsx("span", {
|
|
139
|
-
children: "Past 7 Days"
|
|
140
|
-
})
|
|
141
|
-
]
|
|
142
|
-
})
|
|
143
|
-
}),
|
|
144
|
-
/*#__PURE__*/ _jsx(SelectItem, {
|
|
145
|
-
value: "30days",
|
|
146
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
147
|
-
className: "flex items-center gap-8",
|
|
148
|
-
children: [
|
|
149
|
-
/*#__PURE__*/ _jsx(Kbd, {
|
|
150
|
-
className: "h-16",
|
|
151
|
-
children: "30d"
|
|
152
|
-
}),
|
|
153
|
-
/*#__PURE__*/ _jsx("span", {
|
|
154
|
-
children: "Past 30 Days"
|
|
155
|
-
})
|
|
156
|
-
]
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
]
|
|
160
|
-
})
|
|
161
|
-
]
|
|
69
|
+
/*#__PURE__*/ _jsx(IntervalSelector, {
|
|
70
|
+
selection: selection,
|
|
71
|
+
onSelectionChange: setSelection,
|
|
72
|
+
container: intervalSelectorContainer,
|
|
73
|
+
className: "w-[75vw] md:w-350"
|
|
162
74
|
}),
|
|
163
75
|
showPlaybackControls && /*#__PURE__*/ _jsxs(ButtonGroup, {
|
|
164
76
|
"aria-label": "Playback controls",
|
|
@@ -26,14 +26,14 @@ import { ViewDropdown } from './view-dropdown.js';
|
|
|
26
26
|
* </ToolbarActions>
|
|
27
27
|
* ```
|
|
28
28
|
*/ export function ToolbarActions({ showFilter = true, showSearch = true, showView = true, searchPlaceholder = 'Try: job name, status, pipeline...', className, children, ...props }) {
|
|
29
|
-
const { setSearchQuery,
|
|
29
|
+
const { setSearchQuery, resourceType, setResourceType, columns, setColumns } = useDashboardContext();
|
|
30
30
|
return /*#__PURE__*/ _jsxs("div", {
|
|
31
31
|
className: cn('flex items-start md:items-center gap-8 md:gap-12', className),
|
|
32
32
|
...props,
|
|
33
33
|
children: [
|
|
34
34
|
showFilter && /*#__PURE__*/ _jsx(FilterButton, {
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
value: resourceType,
|
|
36
|
+
onValueChange: setResourceType
|
|
37
37
|
}),
|
|
38
38
|
showSearch && /*#__PURE__*/ _jsx(ToolbarSearch, {
|
|
39
39
|
placeholder: searchPlaceholder,
|
package/dist/components/index.js
CHANGED
|
@@ -23,6 +23,7 @@ export * from './form/index.js';
|
|
|
23
23
|
export * from './icon/index.js';
|
|
24
24
|
export * from './inline-tips/index.js';
|
|
25
25
|
export * from './input/index.js';
|
|
26
|
+
export * from './interval-selector/index.js';
|
|
26
27
|
export * from './item/index.js';
|
|
27
28
|
export * from './kbd/index.js';
|
|
28
29
|
export * from './label/index.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IntervalSelection } from '../types';
|
|
2
|
+
export interface UseNewIntervalSelectorInputProps {
|
|
3
|
+
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
4
|
+
onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
5
|
+
onFocus?: () => void;
|
|
6
|
+
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
7
|
+
onSelect: (selection: IntervalSelection) => void;
|
|
8
|
+
selection: IntervalSelection;
|
|
9
|
+
isNavigating: boolean;
|
|
10
|
+
inputRef: React.RefObject<HTMLInputElement | null>;
|
|
11
|
+
isFocused: boolean;
|
|
12
|
+
setIsFocused: (isFocused: boolean) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare function useIntervalSelectorInput({ onChange: onChangeProp, onKeyDown: onKeyDownProp, onFocus: onFocusProp, onBlur: onBlurProp, onSelect, selection, isNavigating, inputRef, isFocused, setIsFocused, }: UseNewIntervalSelectorInputProps): {
|
|
15
|
+
value: string;
|
|
16
|
+
shouldShake: boolean;
|
|
17
|
+
isFocused: boolean;
|
|
18
|
+
isInvalid: boolean;
|
|
19
|
+
displayValue: string;
|
|
20
|
+
shortcutValue: string;
|
|
21
|
+
setValue: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
22
|
+
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
23
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
24
|
+
onFocus: () => void;
|
|
25
|
+
onMouseDown: () => void;
|
|
26
|
+
onMouseUp: () => void;
|
|
27
|
+
onBlur: (e: React.FocusEvent<HTMLInputElement>) => void;
|
|
28
|
+
inputRef: import("react").RefObject<HTMLInputElement | null>;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=use-interval-selector-input.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import { parseTextDurationShortcut, parseTextInterval } from '../../../utils/date.js';
|
|
3
|
+
import { formatSelection, formatSelectionForInput, formatShortcut } from '../utils/format.js';
|
|
4
|
+
export function useIntervalSelectorInput({ onChange: onChangeProp, onKeyDown: onKeyDownProp, onFocus: onFocusProp, onBlur: onBlurProp, onSelect, selection, isNavigating, inputRef, isFocused, setIsFocused }) {
|
|
5
|
+
const [value, setValue] = useState('');
|
|
6
|
+
const [isInvalid, setIsInvalid] = useState(false);
|
|
7
|
+
const [shouldShake, setShouldShake] = useState(false);
|
|
8
|
+
const shakeTimeoutRef = useRef(null);
|
|
9
|
+
const isMouseDownOnInputRef = useRef(false);
|
|
10
|
+
const onChange = useCallback((e)=>{
|
|
11
|
+
const newValue = e.target.value;
|
|
12
|
+
setValue(newValue);
|
|
13
|
+
const shortcut = parseTextDurationShortcut(newValue);
|
|
14
|
+
const interval = parseTextInterval(newValue);
|
|
15
|
+
const isValid = shortcut || interval || newValue.trim() === '';
|
|
16
|
+
setIsInvalid(!isValid);
|
|
17
|
+
onChangeProp?.(e);
|
|
18
|
+
}, [
|
|
19
|
+
onChangeProp
|
|
20
|
+
]);
|
|
21
|
+
const triggerShakeAnimation = useCallback(()=>{
|
|
22
|
+
if (shakeTimeoutRef.current) clearTimeout(shakeTimeoutRef.current);
|
|
23
|
+
setIsInvalid(true);
|
|
24
|
+
setShouldShake(true);
|
|
25
|
+
shakeTimeoutRef.current = setTimeout(()=>{
|
|
26
|
+
setShouldShake(false);
|
|
27
|
+
shakeTimeoutRef.current = null;
|
|
28
|
+
}, 500);
|
|
29
|
+
}, []);
|
|
30
|
+
const handleConfirmInput = useCallback(()=>{
|
|
31
|
+
const shortcut = parseTextDurationShortcut(value);
|
|
32
|
+
if (shortcut) return onSelect({
|
|
33
|
+
type: 'relative',
|
|
34
|
+
duration: shortcut
|
|
35
|
+
});
|
|
36
|
+
const interval = parseTextInterval(value);
|
|
37
|
+
if (interval) return onSelect({
|
|
38
|
+
type: 'interval',
|
|
39
|
+
interval
|
|
40
|
+
});
|
|
41
|
+
triggerShakeAnimation();
|
|
42
|
+
}, [
|
|
43
|
+
value,
|
|
44
|
+
onSelect,
|
|
45
|
+
triggerShakeAnimation
|
|
46
|
+
]);
|
|
47
|
+
const onKeyDown = useCallback((e)=>{
|
|
48
|
+
if (!isNavigating && e.key === 'Enter') {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
handleConfirmInput();
|
|
51
|
+
}
|
|
52
|
+
onKeyDownProp?.(e);
|
|
53
|
+
}, [
|
|
54
|
+
isNavigating,
|
|
55
|
+
handleConfirmInput,
|
|
56
|
+
onKeyDownProp
|
|
57
|
+
]);
|
|
58
|
+
const onFocus = useCallback(()=>{
|
|
59
|
+
setIsFocused(true);
|
|
60
|
+
setValue(formatSelectionForInput(selection));
|
|
61
|
+
setIsInvalid(false);
|
|
62
|
+
requestAnimationFrame(()=>{
|
|
63
|
+
inputRef.current?.select();
|
|
64
|
+
});
|
|
65
|
+
onFocusProp?.();
|
|
66
|
+
}, [
|
|
67
|
+
selection,
|
|
68
|
+
onFocusProp,
|
|
69
|
+
inputRef,
|
|
70
|
+
setIsFocused
|
|
71
|
+
]);
|
|
72
|
+
const onMouseDown = useCallback(()=>{
|
|
73
|
+
isMouseDownOnInputRef.current = true;
|
|
74
|
+
}, []);
|
|
75
|
+
const onMouseUp = useCallback(()=>{
|
|
76
|
+
setTimeout(()=>{
|
|
77
|
+
isMouseDownOnInputRef.current = false;
|
|
78
|
+
});
|
|
79
|
+
}, []);
|
|
80
|
+
const onBlur = useCallback((e)=>{
|
|
81
|
+
const relatedTarget = e.relatedTarget;
|
|
82
|
+
if (relatedTarget?.closest('[role="dialog"]')) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (isMouseDownOnInputRef.current) {
|
|
86
|
+
requestAnimationFrame(()=>{
|
|
87
|
+
inputRef.current?.focus();
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
setIsFocused(false);
|
|
91
|
+
onBlurProp?.(e);
|
|
92
|
+
}, [
|
|
93
|
+
onBlurProp,
|
|
94
|
+
inputRef,
|
|
95
|
+
setIsFocused
|
|
96
|
+
]);
|
|
97
|
+
const displayValue = formatSelection({
|
|
98
|
+
selection,
|
|
99
|
+
isFocused: isFocused,
|
|
100
|
+
inputValue: value
|
|
101
|
+
});
|
|
102
|
+
const shortcutValue = formatShortcut({
|
|
103
|
+
selection,
|
|
104
|
+
inputValue: value,
|
|
105
|
+
isFocused: isFocused
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
value,
|
|
109
|
+
shouldShake,
|
|
110
|
+
isFocused,
|
|
111
|
+
isInvalid,
|
|
112
|
+
displayValue,
|
|
113
|
+
shortcutValue,
|
|
114
|
+
setValue,
|
|
115
|
+
onChange,
|
|
116
|
+
onKeyDown,
|
|
117
|
+
onFocus,
|
|
118
|
+
onMouseDown,
|
|
119
|
+
onMouseUp,
|
|
120
|
+
onBlur,
|
|
121
|
+
inputRef
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=use-interval-selector-input.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IntervalSelection, IntervalSuggestion, RelativeSuggestion } from '../types';
|
|
2
|
+
interface UseIntervalSelectorNavigationProps {
|
|
3
|
+
relativeSuggestions: RelativeSuggestion[];
|
|
4
|
+
intervalSuggestions: IntervalSuggestion[];
|
|
5
|
+
highlightedIndex: number;
|
|
6
|
+
setHighlightedIndex: (index: number) => void;
|
|
7
|
+
popoverOpen: boolean;
|
|
8
|
+
calendarOpen: boolean;
|
|
9
|
+
onOpenCalendar: () => void;
|
|
10
|
+
onSelect: (selection: IntervalSelection) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare function useIntervalSelectorNavigation({ relativeSuggestions, intervalSuggestions, highlightedIndex, setHighlightedIndex, popoverOpen, calendarOpen, onOpenCalendar, onSelect, }: UseIntervalSelectorNavigationProps): {
|
|
13
|
+
allNavigableItems: (RelativeSuggestion | IntervalSuggestion | {
|
|
14
|
+
type: "calendar";
|
|
15
|
+
label: string;
|
|
16
|
+
})[];
|
|
17
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
18
|
+
isNavigating: boolean;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=use-interval-selector-navigation.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { useCallback, useMemo } from 'react';
|
|
2
|
+
export function useIntervalSelectorNavigation({ relativeSuggestions, intervalSuggestions, highlightedIndex, setHighlightedIndex, popoverOpen, calendarOpen, onOpenCalendar, onSelect }) {
|
|
3
|
+
const allNavigableItems = useMemo(()=>[
|
|
4
|
+
...relativeSuggestions,
|
|
5
|
+
...intervalSuggestions,
|
|
6
|
+
{
|
|
7
|
+
type: 'calendar',
|
|
8
|
+
label: 'Select from calendar'
|
|
9
|
+
}
|
|
10
|
+
], [
|
|
11
|
+
relativeSuggestions,
|
|
12
|
+
intervalSuggestions
|
|
13
|
+
]);
|
|
14
|
+
const canNavigate = popoverOpen && !calendarOpen;
|
|
15
|
+
const currentIndex = highlightedIndex;
|
|
16
|
+
const isNavigating = canNavigate && currentIndex >= 0 && currentIndex < allNavigableItems.length;
|
|
17
|
+
const onKeyDown = useCallback((e)=>{
|
|
18
|
+
if (!canNavigate) return;
|
|
19
|
+
const items = allNavigableItems;
|
|
20
|
+
if (e.key === 'ArrowDown') {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
23
|
+
setHighlightedIndex(nextIndex);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (e.key === 'ArrowUp') {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
const nextIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
29
|
+
setHighlightedIndex(nextIndex);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (e.key === 'Enter' && isNavigating) {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
const item = items[currentIndex];
|
|
35
|
+
if (item.type === 'calendar') {
|
|
36
|
+
onOpenCalendar();
|
|
37
|
+
} else {
|
|
38
|
+
onSelect(item);
|
|
39
|
+
}
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}, [
|
|
43
|
+
canNavigate,
|
|
44
|
+
isNavigating,
|
|
45
|
+
currentIndex,
|
|
46
|
+
allNavigableItems,
|
|
47
|
+
setHighlightedIndex,
|
|
48
|
+
onOpenCalendar,
|
|
49
|
+
onSelect
|
|
50
|
+
]);
|
|
51
|
+
return {
|
|
52
|
+
allNavigableItems,
|
|
53
|
+
onKeyDown,
|
|
54
|
+
isNavigating
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=use-interval-selector-navigation.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DismissableLayerProps } from '@radix-ui/react-dismissable-layer';
|
|
2
|
+
import type { IntervalSelectorProps } from '../interval-selector';
|
|
3
|
+
import type { IntervalSelection, IntervalSuggestion, RelativeSuggestion } from '../types';
|
|
4
|
+
export interface UseIntervalSelectorProps extends Pick<IntervalSelectorProps, 'onSelectionChange'> {
|
|
5
|
+
relativeSuggestions: RelativeSuggestion[];
|
|
6
|
+
intervalSuggestions: IntervalSuggestion[];
|
|
7
|
+
}
|
|
8
|
+
export declare function useIntervalSelector({ onSelectionChange, relativeSuggestions, intervalSuggestions, }: UseIntervalSelectorProps): {
|
|
9
|
+
onSelect: (selection: IntervalSelection) => void;
|
|
10
|
+
popoverOpen: boolean;
|
|
11
|
+
calendarOpen: boolean;
|
|
12
|
+
highlightedIndex: number;
|
|
13
|
+
inputRef: import("react").RefObject<HTMLInputElement | null>;
|
|
14
|
+
onFocus: () => void;
|
|
15
|
+
onBlur: () => void;
|
|
16
|
+
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;
|
|
17
|
+
onOpenCalendar: () => void;
|
|
18
|
+
onChange: () => void;
|
|
19
|
+
onInteractOutside: (e: Parameters<Required<DismissableLayerProps>["onInteractOutside"]>[0]) => void;
|
|
20
|
+
closeAll: () => void;
|
|
21
|
+
isNavigating: boolean;
|
|
22
|
+
isFocused: boolean;
|
|
23
|
+
setIsFocused: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=use-interval-selector.d.ts.map
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback, useRef, useState } from 'react';
|
|
2
|
+
import { useIntervalSelectorNavigation } from './use-interval-selector-navigation.js';
|
|
3
|
+
export function useIntervalSelector({ onSelectionChange, relativeSuggestions, intervalSuggestions }) {
|
|
4
|
+
const [popoverOpen, setPopoverOpen] = useState(false);
|
|
5
|
+
const [calendarOpen, setCalendarOpen] = useState(false);
|
|
6
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
7
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
8
|
+
const isSelectingRef = useRef(false);
|
|
9
|
+
const inputRef = useRef(null);
|
|
10
|
+
const closeAll = useCallback(()=>{
|
|
11
|
+
setPopoverOpen(false);
|
|
12
|
+
inputRef.current?.blur();
|
|
13
|
+
setCalendarOpen(false);
|
|
14
|
+
setHighlightedIndex(-1);
|
|
15
|
+
setIsFocused(false);
|
|
16
|
+
}, []);
|
|
17
|
+
const onSelect = useCallback((selection)=>{
|
|
18
|
+
isSelectingRef.current = true;
|
|
19
|
+
onSelectionChange(selection);
|
|
20
|
+
closeAll();
|
|
21
|
+
}, [
|
|
22
|
+
closeAll,
|
|
23
|
+
onSelectionChange
|
|
24
|
+
]);
|
|
25
|
+
const onFocus = useCallback(()=>{
|
|
26
|
+
setPopoverOpen(true);
|
|
27
|
+
setHighlightedIndex(-1);
|
|
28
|
+
}, []);
|
|
29
|
+
const onBlur = useCallback(()=>{
|
|
30
|
+
if (!calendarOpen) setPopoverOpen(false);
|
|
31
|
+
}, [
|
|
32
|
+
calendarOpen
|
|
33
|
+
]);
|
|
34
|
+
const onOpenCalendar = useCallback(()=>{
|
|
35
|
+
setCalendarOpen(true);
|
|
36
|
+
}, []);
|
|
37
|
+
const { onKeyDown, isNavigating } = useIntervalSelectorNavigation({
|
|
38
|
+
relativeSuggestions,
|
|
39
|
+
intervalSuggestions,
|
|
40
|
+
highlightedIndex,
|
|
41
|
+
setHighlightedIndex,
|
|
42
|
+
popoverOpen,
|
|
43
|
+
calendarOpen,
|
|
44
|
+
onOpenCalendar,
|
|
45
|
+
onSelect
|
|
46
|
+
});
|
|
47
|
+
const onInteractOutside = useCallback((e)=>{
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
const target = e.target;
|
|
50
|
+
const isClickOnPopover = inputRef.current && (inputRef.current.contains(target) || target.closest('[data-radix-popover-trigger]'));
|
|
51
|
+
if (isClickOnPopover) return;
|
|
52
|
+
closeAll();
|
|
53
|
+
}, [
|
|
54
|
+
closeAll
|
|
55
|
+
]);
|
|
56
|
+
return {
|
|
57
|
+
onSelect,
|
|
58
|
+
popoverOpen,
|
|
59
|
+
calendarOpen,
|
|
60
|
+
highlightedIndex,
|
|
61
|
+
inputRef,
|
|
62
|
+
onFocus,
|
|
63
|
+
onBlur,
|
|
64
|
+
onKeyDown,
|
|
65
|
+
onOpenCalendar,
|
|
66
|
+
onChange: ()=>setHighlightedIndex(-1),
|
|
67
|
+
onInteractOutside,
|
|
68
|
+
closeAll,
|
|
69
|
+
isNavigating,
|
|
70
|
+
isFocused,
|
|
71
|
+
setIsFocused
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//# sourceMappingURL=use-interval-selector.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IntervalSelection } from './types';
|
|
2
|
+
interface IntervalSelectorCalendarProps {
|
|
3
|
+
onSelect: (selection: IntervalSelection) => void;
|
|
4
|
+
}
|
|
5
|
+
export declare function IntervalSelectorCalendar({ onSelect }: IntervalSelectorCalendarProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=interval-selector-calendar.d.ts.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Calendar } from '../../components/calendar/index.js';
|
|
3
|
+
import { endOfDay, format, startOfDay } from 'date-fns';
|
|
4
|
+
import { useCallback, useState } from 'react';
|
|
5
|
+
export function IntervalSelectorCalendar({ onSelect }) {
|
|
6
|
+
const [selectedRange, setSelectedRange] = useState(undefined);
|
|
7
|
+
const handleSelect = useCallback((_, selectedDay)=>{
|
|
8
|
+
if (!selectedDay) return setSelectedRange(undefined);
|
|
9
|
+
if (!selectedRange?.from) return setSelectedRange({
|
|
10
|
+
from: selectedDay,
|
|
11
|
+
to: undefined
|
|
12
|
+
});
|
|
13
|
+
const start = selectedDay < selectedRange.from ? startOfDay(selectedDay) : startOfDay(selectedRange.from);
|
|
14
|
+
const end = selectedDay < selectedRange.from ? endOfDay(selectedRange.from) : endOfDay(selectedDay);
|
|
15
|
+
onSelect({
|
|
16
|
+
type: 'interval',
|
|
17
|
+
interval: {
|
|
18
|
+
start,
|
|
19
|
+
end
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return setSelectedRange(undefined);
|
|
23
|
+
}, [
|
|
24
|
+
onSelect,
|
|
25
|
+
selectedRange
|
|
26
|
+
]);
|
|
27
|
+
return /*#__PURE__*/ _jsx(Calendar, {
|
|
28
|
+
mode: "range",
|
|
29
|
+
selected: selectedRange,
|
|
30
|
+
onSelect: handleSelect,
|
|
31
|
+
numberOfMonths: 1,
|
|
32
|
+
formatters: {
|
|
33
|
+
formatWeekdayName: (date)=>format(date, 'EEEEE')
|
|
34
|
+
},
|
|
35
|
+
disabled: {
|
|
36
|
+
after: new Date()
|
|
37
|
+
},
|
|
38
|
+
classNames: {
|
|
39
|
+
root: 'relative w-full flex justify-center items-center',
|
|
40
|
+
months: 'w-full',
|
|
41
|
+
month_grid: 'flex flex-col justify-center items-center',
|
|
42
|
+
nav: 'flex items-center gap-4 absolute top-16 left-0 w-full z-10'
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
//# sourceMappingURL=interval-selector-calendar.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type UseNewIntervalSelectorInputProps } from './hooks/use-interval-selector-input';
|
|
2
|
+
type IntervalSelectorInputProps = {
|
|
3
|
+
className?: string;
|
|
4
|
+
inputClassName?: string;
|
|
5
|
+
} & UseNewIntervalSelectorInputProps;
|
|
6
|
+
export declare function IntervalSelectorInput({ className, inputClassName, ...props }: IntervalSelectorInputProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=interval-selector-input.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Input } from '../../components/input/index.js';
|
|
3
|
+
import { Kbd } from '../../components/kbd/index.js';
|
|
4
|
+
import { PopoverTrigger } from '../../components/popover/index.js';
|
|
5
|
+
import { cn } from '../../utils/cn.js';
|
|
6
|
+
import { useIntervalSelectorInput } from './hooks/use-interval-selector-input.js';
|
|
7
|
+
export function IntervalSelectorInput({ className, inputClassName, ...props }) {
|
|
8
|
+
const { displayValue, shortcutValue, isFocused, isInvalid, shouldShake, onChange, onKeyDown, onFocus, onBlur, onMouseDown, onMouseUp, inputRef } = useIntervalSelectorInput(props);
|
|
9
|
+
return /*#__PURE__*/ _jsx(PopoverTrigger, {
|
|
10
|
+
asChild: true,
|
|
11
|
+
children: /*#__PURE__*/ _jsx("div", {
|
|
12
|
+
className: cn('relative', className, shouldShake && 'animate-shake'),
|
|
13
|
+
children: /*#__PURE__*/ _jsx(Input, {
|
|
14
|
+
ref: inputRef,
|
|
15
|
+
value: displayValue,
|
|
16
|
+
onChange: isFocused ? onChange : undefined,
|
|
17
|
+
onFocus: onFocus,
|
|
18
|
+
onBlur: onBlur,
|
|
19
|
+
onMouseDown: onMouseDown,
|
|
20
|
+
onMouseUp: onMouseUp,
|
|
21
|
+
onKeyDown: onKeyDown,
|
|
22
|
+
readOnly: !isFocused,
|
|
23
|
+
"aria-invalid": isInvalid && isFocused,
|
|
24
|
+
iconLeft: /*#__PURE__*/ _jsx(Kbd, {
|
|
25
|
+
className: "h-16 shrink-0 min-w-36",
|
|
26
|
+
children: shortcutValue
|
|
27
|
+
}),
|
|
28
|
+
className: cn('w-full pl-50', inputClassName)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=interval-selector-input.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IntervalSelection, IntervalSuggestion, RelativeSuggestion } from './types';
|
|
2
|
+
interface IntervalSelectorSuggestionsProps {
|
|
3
|
+
relativeSuggestions: RelativeSuggestion[];
|
|
4
|
+
intervalSuggestions: IntervalSuggestion[];
|
|
5
|
+
onSelect: (selection: IntervalSelection) => void;
|
|
6
|
+
onOpenCalendar: () => void;
|
|
7
|
+
highlightedIndex: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function IntervalSelectorSuggestions({ relativeSuggestions, intervalSuggestions, onSelect, onOpenCalendar, highlightedIndex, }: IntervalSelectorSuggestionsProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=interval-selector-suggestions.d.ts.map
|