@campxdev/react-blueprint 3.0.0-alpha.4 → 3.0.0-alpha.5
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/cjs/index.js +1 -1
- package/dist/cjs/types/src/components/Input/DatePicker/DatePicker.d.ts +2 -1
- package/dist/cjs/types/src/components/Input/DatePicker/components/DatePickerFilter.d.ts +2 -0
- package/dist/cjs/types/src/components/Input/DatePicker/components/DatePickerInput.d.ts +2 -0
- package/dist/cjs/types/src/components/Input/DateTimePicker/DateTimePicker.d.ts +2 -1
- package/dist/cjs/types/src/components/Input/DateTimePicker/components/DateTimePickerFilter.d.ts +2 -0
- package/dist/cjs/types/src/components/Input/DateTimePicker/components/DateTimePickerInput.d.ts +2 -0
- package/dist/cjs/types/src/components/Input/MultiSelect/MultiSelect.d.ts +2 -0
- package/dist/cjs/types/src/components/Input/MultiSelect/components/MultiSelectFilter.d.ts +1 -1
- package/dist/cjs/types/src/components/Input/MultiSelect/components/MultiSelectInput.d.ts +1 -1
- package/dist/cjs/types/src/components/Input/Select/Select.d.ts +2 -1
- package/dist/cjs/types/src/components/Input/SingleSelect/SingleSelect.d.ts +2 -0
- package/dist/cjs/types/src/components/Input/SingleSelect/components/SingleFilter.d.ts +1 -1
- package/dist/cjs/types/src/components/Input/SingleSelect/components/SingleInput.d.ts +1 -1
- package/dist/cjs/types/src/shadcn-components/Input/Select/Select.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/types/src/components/Input/DatePicker/DatePicker.d.ts +2 -1
- package/dist/esm/types/src/components/Input/DatePicker/components/DatePickerFilter.d.ts +2 -0
- package/dist/esm/types/src/components/Input/DatePicker/components/DatePickerInput.d.ts +2 -0
- package/dist/esm/types/src/components/Input/DateTimePicker/DateTimePicker.d.ts +2 -1
- package/dist/esm/types/src/components/Input/DateTimePicker/components/DateTimePickerFilter.d.ts +2 -0
- package/dist/esm/types/src/components/Input/DateTimePicker/components/DateTimePickerInput.d.ts +2 -0
- package/dist/esm/types/src/components/Input/MultiSelect/MultiSelect.d.ts +2 -0
- package/dist/esm/types/src/components/Input/MultiSelect/components/MultiSelectFilter.d.ts +1 -1
- package/dist/esm/types/src/components/Input/MultiSelect/components/MultiSelectInput.d.ts +1 -1
- package/dist/esm/types/src/components/Input/Select/Select.d.ts +2 -1
- package/dist/esm/types/src/components/Input/SingleSelect/SingleSelect.d.ts +2 -0
- package/dist/esm/types/src/components/Input/SingleSelect/components/SingleFilter.d.ts +1 -1
- package/dist/esm/types/src/components/Input/SingleSelect/components/SingleInput.d.ts +1 -1
- package/dist/esm/types/src/shadcn-components/Input/Select/Select.d.ts +2 -2
- package/dist/index.d.ts +12 -5
- package/dist/styles.css +372 -4
- package/package.json +1 -1
- package/src/components/DataDisplay/DataTable/components/TableView.tsx +31 -5
- package/src/components/Feedback/Tooltip/Tooltip.tsx +17 -3
- package/src/components/Input/Button/ButtonLoader.css +2 -2
- package/src/components/Input/DatePicker/DatePicker.tsx +9 -188
- package/src/components/Input/DatePicker/components/DatePickerFilter.tsx +178 -0
- package/src/components/Input/DatePicker/components/DatePickerInput.tsx +192 -0
- package/src/components/Input/DateTimePicker/DateTimePicker.tsx +8 -294
- package/src/components/Input/DateTimePicker/components/DateTimePickerFilter.tsx +292 -0
- package/src/components/Input/DateTimePicker/components/DateTimePickerInput.tsx +297 -0
- package/src/components/Input/MultiSelect/MultiSelect.tsx +2 -0
- package/src/components/Input/MultiSelect/components/MultiSelectFilter.tsx +7 -3
- package/src/components/Input/MultiSelect/components/MultiSelectInput.tsx +8 -3
- package/src/components/Input/Select/Select.tsx +22 -12
- package/src/components/Input/SingleSelect/SingleSelect.tsx +2 -0
- package/src/components/Input/SingleSelect/components/SingleFilter.tsx +7 -3
- package/src/components/Input/SingleSelect/components/SingleInput.tsx +8 -3
- package/src/components/Navigation/DialogButton/DialogButton.tsx +6 -1
- package/src/components/Navigation/DropDownMenu/DropDownMenu.tsx +1 -1
- package/src/components/Navigation/TabsContainer/TabsContainer.tsx +1 -1
- package/src/shadcn-components/DataDisplay/Dialog/Dialog.tsx +2 -2
- package/src/shadcn-components/Input/Popover/Popover.tsx +1 -1
- package/src/shadcn-components/Input/Select/Select.tsx +8 -8
- package/src/shadcn-components/Navigation/DropdownMenu/DropdownMenu.tsx +2 -2
- package/src/styles/globals.css +4 -2
- package/src/styles/index.css +5 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { cn } from '@/lib/utils';
|
|
2
|
+
import { Calendar } from '@/shadcn-components/Input/Calendar/Calendar';
|
|
3
|
+
import { Input } from '@/shadcn-components/Input/Input/Input';
|
|
4
|
+
import {
|
|
5
|
+
Popover,
|
|
6
|
+
PopoverContent,
|
|
7
|
+
PopoverTrigger,
|
|
8
|
+
} from '@/shadcn-components/Input/Popover/Popover';
|
|
9
|
+
import { format as DateFnsFormat } from 'date-fns';
|
|
10
|
+
import { CalendarDays } from 'lucide-react';
|
|
11
|
+
import { cloneElement, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
|
+
import { Typography } from '../../../DataDisplay/Typography/Typography';
|
|
13
|
+
import { Button } from '../../Button/Button';
|
|
14
|
+
import { LabelWrapper } from '../../LabelWrapper/LabelWrapper';
|
|
15
|
+
import { DateTimePickerProps } from '../DateTimePicker';
|
|
16
|
+
|
|
17
|
+
export const DateTimePickerInput = ({
|
|
18
|
+
label,
|
|
19
|
+
name,
|
|
20
|
+
value,
|
|
21
|
+
onChange,
|
|
22
|
+
required = false,
|
|
23
|
+
format = 'dd MMM yyyy hh:mm a',
|
|
24
|
+
views = ['year', 'month', 'day', 'hours', 'minutes', 'seconds'],
|
|
25
|
+
helperText,
|
|
26
|
+
placeholder = 'Pick a date and time',
|
|
27
|
+
shortcutsItems = [],
|
|
28
|
+
openPickerIcon: Icon = <CalendarDays />,
|
|
29
|
+
containerProps,
|
|
30
|
+
error,
|
|
31
|
+
disabled = false,
|
|
32
|
+
minDate,
|
|
33
|
+
maxDate,
|
|
34
|
+
disablePast = false,
|
|
35
|
+
disableFuture = false,
|
|
36
|
+
shouldDisableDate,
|
|
37
|
+
shouldDisableMonth,
|
|
38
|
+
shouldDisableYear,
|
|
39
|
+
className,
|
|
40
|
+
onOpen,
|
|
41
|
+
onClose,
|
|
42
|
+
fullWidth,
|
|
43
|
+
type,
|
|
44
|
+
...rest
|
|
45
|
+
}: DateTimePickerProps) => {
|
|
46
|
+
const [open, setOpen] = useState(false);
|
|
47
|
+
const [timeValue, setTimeValue] = useState<string>('');
|
|
48
|
+
const wrapperRef = useRef<HTMLSpanElement>(null);
|
|
49
|
+
|
|
50
|
+
// Determine view configurations based on views prop
|
|
51
|
+
const viewConfig = useMemo(() => {
|
|
52
|
+
const hasTime =
|
|
53
|
+
views.includes('hours') ||
|
|
54
|
+
views.includes('minutes') ||
|
|
55
|
+
views.includes('seconds');
|
|
56
|
+
const hasDate =
|
|
57
|
+
views.includes('day') ||
|
|
58
|
+
views.includes('month') ||
|
|
59
|
+
views.includes('year');
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
showTime: hasTime,
|
|
63
|
+
showDate: hasDate,
|
|
64
|
+
};
|
|
65
|
+
}, [views]);
|
|
66
|
+
|
|
67
|
+
// Update time value when value changes
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (value && viewConfig.showTime) {
|
|
70
|
+
const hours = value.getHours(),
|
|
71
|
+
minutes = value.getMinutes(),
|
|
72
|
+
seconds = value.getSeconds(),
|
|
73
|
+
timeStr = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(
|
|
74
|
+
2,
|
|
75
|
+
'0',
|
|
76
|
+
)}:${String(seconds).padStart(2, '0')}`;
|
|
77
|
+
setTimeValue(timeStr);
|
|
78
|
+
}
|
|
79
|
+
}, [value, viewConfig.showTime]);
|
|
80
|
+
|
|
81
|
+
const formatDateTimeString = (date: Date | undefined) => {
|
|
82
|
+
if (!date) return '';
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
return DateFnsFormat(date, format);
|
|
86
|
+
} catch {
|
|
87
|
+
return DateFnsFormat(date, 'dd/MM/yyyy hh:mm a');
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleOpenChange = (newOpen: boolean) => {
|
|
92
|
+
setOpen(newOpen);
|
|
93
|
+
if (newOpen && onOpen) {
|
|
94
|
+
onOpen();
|
|
95
|
+
} else if (!newOpen && onClose) {
|
|
96
|
+
onClose();
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const handleDateSelect = (date: Date | undefined) => {
|
|
101
|
+
if (date) {
|
|
102
|
+
const newDateTime = new Date(date);
|
|
103
|
+
// Preserve existing time if we have one
|
|
104
|
+
if (value) {
|
|
105
|
+
newDateTime.setHours(
|
|
106
|
+
value.getHours(),
|
|
107
|
+
value.getMinutes(),
|
|
108
|
+
value.getSeconds(),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
onChange?.(newDateTime);
|
|
112
|
+
} else {
|
|
113
|
+
onChange?.(undefined);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleTimeChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
118
|
+
const timeStr = e.target.value;
|
|
119
|
+
setTimeValue(timeStr);
|
|
120
|
+
|
|
121
|
+
if (timeStr) {
|
|
122
|
+
const [hours, minutes, seconds] = timeStr.split(':').map(Number);
|
|
123
|
+
const newDateTime = value ? new Date(value) : new Date();
|
|
124
|
+
newDateTime.setHours(hours || 0, minutes || 0, seconds || 0);
|
|
125
|
+
onChange?.(newDateTime);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const [, forceUpdate] = useState({});
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
forceUpdate({});
|
|
132
|
+
}, []);
|
|
133
|
+
|
|
134
|
+
// If only time selection (no date), render native time input directly
|
|
135
|
+
if (viewConfig.showTime && !viewConfig.showDate) {
|
|
136
|
+
return (
|
|
137
|
+
<LabelWrapper
|
|
138
|
+
label={label}
|
|
139
|
+
required={required}
|
|
140
|
+
name={name}
|
|
141
|
+
containerProps={containerProps}
|
|
142
|
+
>
|
|
143
|
+
<Input
|
|
144
|
+
type="time"
|
|
145
|
+
step="1"
|
|
146
|
+
value={timeValue}
|
|
147
|
+
onChange={handleTimeChange}
|
|
148
|
+
disabled={disabled}
|
|
149
|
+
placeholder={placeholder}
|
|
150
|
+
className={cn(
|
|
151
|
+
'bg-input-background border-none text-muted-foreground',
|
|
152
|
+
error && 'border-destructive',
|
|
153
|
+
className,
|
|
154
|
+
)}
|
|
155
|
+
{...rest}
|
|
156
|
+
/>
|
|
157
|
+
|
|
158
|
+
{/* Helper Text / Error */}
|
|
159
|
+
{(helperText || error) && (
|
|
160
|
+
<Typography
|
|
161
|
+
variant="small"
|
|
162
|
+
className={cn('ml-1 mt-1', error && 'text-destructive')}
|
|
163
|
+
>
|
|
164
|
+
{typeof error === 'string' ? error : error?.message || helperText}
|
|
165
|
+
</Typography>
|
|
166
|
+
)}
|
|
167
|
+
</LabelWrapper>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<LabelWrapper
|
|
173
|
+
label={label}
|
|
174
|
+
required={required}
|
|
175
|
+
name={name}
|
|
176
|
+
containerProps={{
|
|
177
|
+
...(fullWidth && { style: { width: '100%' } }),
|
|
178
|
+
...containerProps,
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
<Popover open={open} onOpenChange={handleOpenChange}>
|
|
182
|
+
<PopoverTrigger asChild>
|
|
183
|
+
<span
|
|
184
|
+
ref={wrapperRef}
|
|
185
|
+
style={{ display: fullWidth ? 'block' : 'inline-block' }}
|
|
186
|
+
>
|
|
187
|
+
<Button
|
|
188
|
+
variant="input"
|
|
189
|
+
className={cn(
|
|
190
|
+
'w-full justify-between text-left font-normal',
|
|
191
|
+
!value && 'text-muted-foreground',
|
|
192
|
+
error && 'border-destructive',
|
|
193
|
+
fullWidth ? 'w-full' : 'w-auto',
|
|
194
|
+
className,
|
|
195
|
+
)}
|
|
196
|
+
disabled={disabled}
|
|
197
|
+
{...rest}
|
|
198
|
+
>
|
|
199
|
+
<span>
|
|
200
|
+
{value ? formatDateTimeString(value) : placeholder}
|
|
201
|
+
</span>
|
|
202
|
+
{Icon &&
|
|
203
|
+
cloneElement(Icon as React.ReactElement, {
|
|
204
|
+
className: 'ml-2 h-4 w-4 ',
|
|
205
|
+
})}
|
|
206
|
+
</Button>
|
|
207
|
+
</span>
|
|
208
|
+
</PopoverTrigger>
|
|
209
|
+
<PopoverContent className="w-auto p-0" align="start">
|
|
210
|
+
<div className="flex flex-col">
|
|
211
|
+
{/* Shortcuts */}
|
|
212
|
+
{shortcutsItems.length > 0 && (
|
|
213
|
+
<div className="border-b p-2">
|
|
214
|
+
<div className="flex flex-wrap gap-1">
|
|
215
|
+
{shortcutsItems.map((shortcut, index) => (
|
|
216
|
+
<Button
|
|
217
|
+
key={index}
|
|
218
|
+
variant="outline"
|
|
219
|
+
size="sm"
|
|
220
|
+
onClick={() => {
|
|
221
|
+
const newDate = shortcut.getValue();
|
|
222
|
+
onChange?.(newDate);
|
|
223
|
+
setOpen(false);
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
{shortcut.label}
|
|
227
|
+
</Button>
|
|
228
|
+
))}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
|
|
233
|
+
{/* Calendar */}
|
|
234
|
+
{viewConfig.showDate && (
|
|
235
|
+
<Calendar
|
|
236
|
+
mode="single"
|
|
237
|
+
selected={value}
|
|
238
|
+
onSelect={handleDateSelect}
|
|
239
|
+
disabled={(date) => {
|
|
240
|
+
const today = new Date();
|
|
241
|
+
today.setHours(0, 0, 0, 0);
|
|
242
|
+
const compareDate = new Date(date);
|
|
243
|
+
compareDate.setHours(0, 0, 0, 0);
|
|
244
|
+
|
|
245
|
+
if (minDate) {
|
|
246
|
+
const min = new Date(minDate);
|
|
247
|
+
min.setHours(0, 0, 0, 0);
|
|
248
|
+
if (compareDate < min) return true;
|
|
249
|
+
}
|
|
250
|
+
if (maxDate) {
|
|
251
|
+
const max = new Date(maxDate);
|
|
252
|
+
max.setHours(0, 0, 0, 0);
|
|
253
|
+
if (compareDate > max) return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (disablePast && compareDate < today) return true;
|
|
257
|
+
if (disableFuture && compareDate > today) return true;
|
|
258
|
+
if (shouldDisableDate && shouldDisableDate(date)) return true;
|
|
259
|
+
if (shouldDisableMonth && shouldDisableMonth(date))
|
|
260
|
+
return true;
|
|
261
|
+
if (shouldDisableYear && shouldDisableYear(date)) return true;
|
|
262
|
+
|
|
263
|
+
return false;
|
|
264
|
+
}}
|
|
265
|
+
captionLayout={'dropdown'}
|
|
266
|
+
defaultMonth={value}
|
|
267
|
+
/>
|
|
268
|
+
)}
|
|
269
|
+
|
|
270
|
+
{/* Time Picker */}
|
|
271
|
+
{viewConfig.showTime && (
|
|
272
|
+
<div className={cn('p-3', viewConfig.showDate && 'border-t')}>
|
|
273
|
+
<Input
|
|
274
|
+
type="time"
|
|
275
|
+
step="1"
|
|
276
|
+
value={timeValue}
|
|
277
|
+
onChange={handleTimeChange}
|
|
278
|
+
className="bg-input-background border-none text-muted-foreground appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
|
|
279
|
+
/>
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
283
|
+
</PopoverContent>
|
|
284
|
+
</Popover>
|
|
285
|
+
|
|
286
|
+
{/* Helper Text / Error */}
|
|
287
|
+
{(helperText || error) && (
|
|
288
|
+
<Typography
|
|
289
|
+
variant="small"
|
|
290
|
+
className={cn('ml-1 mt-1', error && 'text-destructive')}
|
|
291
|
+
>
|
|
292
|
+
{typeof error === 'string' ? error : error?.message || helperText}
|
|
293
|
+
</Typography>
|
|
294
|
+
)}
|
|
295
|
+
</LabelWrapper>
|
|
296
|
+
);
|
|
297
|
+
};
|
|
@@ -46,6 +46,8 @@ export const MultiSelectFilter = ({
|
|
|
46
46
|
handleClearAll,
|
|
47
47
|
state,
|
|
48
48
|
disableClear = false,
|
|
49
|
+
disabled = false,
|
|
50
|
+
loading = false,
|
|
49
51
|
}: MultiSelectFilterProps) => {
|
|
50
52
|
const {
|
|
51
53
|
open,
|
|
@@ -101,8 +103,10 @@ export const MultiSelectFilter = ({
|
|
|
101
103
|
|
|
102
104
|
return (
|
|
103
105
|
<Popover
|
|
104
|
-
open={open}
|
|
106
|
+
open={open && !disabled}
|
|
105
107
|
onOpenChange={(isOpen) => {
|
|
108
|
+
if (disabled) return;
|
|
109
|
+
|
|
106
110
|
const syntheticEvent = {
|
|
107
111
|
currentTarget: {},
|
|
108
112
|
target: {},
|
|
@@ -115,7 +119,7 @@ export const MultiSelectFilter = ({
|
|
|
115
119
|
}
|
|
116
120
|
}}
|
|
117
121
|
>
|
|
118
|
-
<PopoverTrigger asChild>
|
|
122
|
+
<PopoverTrigger asChild disabled={disabled}>
|
|
119
123
|
<span
|
|
120
124
|
ref={wrapperRef}
|
|
121
125
|
style={{ display: 'inline-block', width: '100%' }}
|
|
@@ -125,7 +129,7 @@ export const MultiSelectFilter = ({
|
|
|
125
129
|
role="combobox"
|
|
126
130
|
aria-expanded={open}
|
|
127
131
|
className="justify-between w-full"
|
|
128
|
-
disabled={loadingInitialInternalOptions}
|
|
132
|
+
disabled={disabled || loading || loadingInitialInternalOptions}
|
|
129
133
|
>
|
|
130
134
|
{displayText || label}
|
|
131
135
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
@@ -55,6 +55,8 @@ export const MultiSelectInput = ({
|
|
|
55
55
|
state,
|
|
56
56
|
disableClear = false,
|
|
57
57
|
fullWidth,
|
|
58
|
+
disabled = false,
|
|
59
|
+
loading = false,
|
|
58
60
|
}: MultiSelectInputProps) => {
|
|
59
61
|
const {
|
|
60
62
|
open,
|
|
@@ -88,7 +90,7 @@ export const MultiSelectInput = ({
|
|
|
88
90
|
.map((val) => internalOptionsMap[val])
|
|
89
91
|
.filter(Boolean);
|
|
90
92
|
|
|
91
|
-
if (loadingInitialInternalOptions) {
|
|
93
|
+
if (loadingInitialInternalOptions || loading) {
|
|
92
94
|
return (
|
|
93
95
|
<TextField
|
|
94
96
|
label={label}
|
|
@@ -117,8 +119,10 @@ export const MultiSelectInput = ({
|
|
|
117
119
|
}}
|
|
118
120
|
>
|
|
119
121
|
<Popover
|
|
120
|
-
open={open}
|
|
122
|
+
open={open && !disabled}
|
|
121
123
|
onOpenChange={(isOpen) => {
|
|
124
|
+
if (disabled) return;
|
|
125
|
+
|
|
122
126
|
const syntheticEvent = {
|
|
123
127
|
currentTarget: {},
|
|
124
128
|
target: {},
|
|
@@ -131,7 +135,7 @@ export const MultiSelectInput = ({
|
|
|
131
135
|
}
|
|
132
136
|
}}
|
|
133
137
|
>
|
|
134
|
-
<PopoverTrigger asChild>
|
|
138
|
+
<PopoverTrigger asChild disabled={disabled}>
|
|
135
139
|
<span
|
|
136
140
|
ref={wrapperRef}
|
|
137
141
|
style={{ display: fullWidth ? 'block' : 'inline-block' }}
|
|
@@ -141,6 +145,7 @@ export const MultiSelectInput = ({
|
|
|
141
145
|
role="combobox"
|
|
142
146
|
aria-expanded={open}
|
|
143
147
|
aria-invalid={!!error}
|
|
148
|
+
disabled={disabled}
|
|
144
149
|
className={cn(
|
|
145
150
|
'justify-between font-normal min-h-[40px] h-auto',
|
|
146
151
|
fullWidth ? 'w-full' : 'w-auto',
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SelectValue,
|
|
7
7
|
Select as ShadcnSelect,
|
|
8
8
|
} from '@/shadcn-components/Input/Select/Select';
|
|
9
|
+
import { Loader2 } from 'lucide-react';
|
|
9
10
|
import * as React from 'react';
|
|
10
11
|
import { useEffect, useRef, useState } from 'react';
|
|
11
12
|
|
|
@@ -29,6 +30,7 @@ export type SelectProps = {
|
|
|
29
30
|
className?: string;
|
|
30
31
|
disabled?: boolean;
|
|
31
32
|
fullWidth?: boolean;
|
|
33
|
+
loading?: boolean;
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
const getColorClasses = (
|
|
@@ -63,6 +65,7 @@ export const Select = ({
|
|
|
63
65
|
defaultValue,
|
|
64
66
|
disabled = false,
|
|
65
67
|
fullWidth = false,
|
|
68
|
+
loading = false,
|
|
66
69
|
}: SelectProps) => {
|
|
67
70
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
68
71
|
|
|
@@ -111,18 +114,25 @@ export const Select = ({
|
|
|
111
114
|
</SelectTrigger>
|
|
112
115
|
</div>
|
|
113
116
|
<SelectContent>
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
<
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
117
|
+
{loading ? (
|
|
118
|
+
<div className="flex flex-col items-center justify-center py-8 px-4">
|
|
119
|
+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground mb-2" />
|
|
120
|
+
<p className="text-sm text-muted-foreground">Loading...</p>
|
|
121
|
+
</div>
|
|
122
|
+
) : (
|
|
123
|
+
options?.map((item, index) => {
|
|
124
|
+
const itemColorClasses = getColorClasses(item.color);
|
|
125
|
+
return (
|
|
126
|
+
<SelectItem
|
|
127
|
+
key={index}
|
|
128
|
+
value={item.value.toString()}
|
|
129
|
+
className={cn(itemColorClasses)}
|
|
130
|
+
>
|
|
131
|
+
{item.label}
|
|
132
|
+
</SelectItem>
|
|
133
|
+
);
|
|
134
|
+
})
|
|
135
|
+
)}
|
|
126
136
|
</SelectContent>
|
|
127
137
|
</ShadcnSelect>
|
|
128
138
|
);
|
|
@@ -43,6 +43,8 @@ export const SingleFilter = ({
|
|
|
43
43
|
state,
|
|
44
44
|
disableClear = false,
|
|
45
45
|
fullWidth,
|
|
46
|
+
disabled = false,
|
|
47
|
+
loading = false,
|
|
46
48
|
}: SingleFilterProps) => {
|
|
47
49
|
const {
|
|
48
50
|
open,
|
|
@@ -82,8 +84,10 @@ export const SingleFilter = ({
|
|
|
82
84
|
|
|
83
85
|
return (
|
|
84
86
|
<Popover
|
|
85
|
-
open={open}
|
|
87
|
+
open={open && !disabled}
|
|
86
88
|
onOpenChange={(isOpen) => {
|
|
89
|
+
if (disabled) return;
|
|
90
|
+
|
|
87
91
|
const syntheticEvent = {
|
|
88
92
|
currentTarget: {},
|
|
89
93
|
target: {},
|
|
@@ -96,7 +100,7 @@ export const SingleFilter = ({
|
|
|
96
100
|
}
|
|
97
101
|
}}
|
|
98
102
|
>
|
|
99
|
-
<PopoverTrigger asChild>
|
|
103
|
+
<PopoverTrigger asChild disabled={disabled}>
|
|
100
104
|
<span
|
|
101
105
|
ref={wrapperRef}
|
|
102
106
|
style={{ display: fullWidth ? 'block' : 'inline-block' }}
|
|
@@ -106,7 +110,7 @@ export const SingleFilter = ({
|
|
|
106
110
|
role="combobox"
|
|
107
111
|
aria-expanded={open}
|
|
108
112
|
className={cn('justify-between', fullWidth ? 'w-full' : '')}
|
|
109
|
-
disabled={loadingInitialInternalOptions}
|
|
113
|
+
disabled={disabled || loading || loadingInitialInternalOptions}
|
|
110
114
|
>
|
|
111
115
|
{selectedOption ? `${label} = ${selectedOption.label}` : label}
|
|
112
116
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
@@ -48,6 +48,8 @@ export const SingleInput = ({
|
|
|
48
48
|
handleScroll,
|
|
49
49
|
state,
|
|
50
50
|
fullWidth,
|
|
51
|
+
disabled = false,
|
|
52
|
+
loading = false,
|
|
51
53
|
}: SingleInputProps) => {
|
|
52
54
|
const {
|
|
53
55
|
open,
|
|
@@ -77,7 +79,7 @@ export const SingleInput = ({
|
|
|
77
79
|
|
|
78
80
|
const selectedOption = internalOptionsMap[value];
|
|
79
81
|
|
|
80
|
-
if (loadingInitialInternalOptions) {
|
|
82
|
+
if (loadingInitialInternalOptions || loading) {
|
|
81
83
|
return (
|
|
82
84
|
<TextField
|
|
83
85
|
label={label}
|
|
@@ -103,8 +105,10 @@ export const SingleInput = ({
|
|
|
103
105
|
}}
|
|
104
106
|
>
|
|
105
107
|
<Popover
|
|
106
|
-
open={open}
|
|
108
|
+
open={open && !disabled}
|
|
107
109
|
onOpenChange={(isOpen) => {
|
|
110
|
+
if (disabled) return;
|
|
111
|
+
|
|
108
112
|
const syntheticEvent = {
|
|
109
113
|
currentTarget: {},
|
|
110
114
|
target: {},
|
|
@@ -117,7 +121,7 @@ export const SingleInput = ({
|
|
|
117
121
|
}
|
|
118
122
|
}}
|
|
119
123
|
>
|
|
120
|
-
<PopoverTrigger asChild>
|
|
124
|
+
<PopoverTrigger asChild disabled={disabled}>
|
|
121
125
|
<span
|
|
122
126
|
ref={wrapperRef}
|
|
123
127
|
style={{ display: fullWidth ? 'block' : 'inline-block' }}
|
|
@@ -127,6 +131,7 @@ export const SingleInput = ({
|
|
|
127
131
|
role="combobox"
|
|
128
132
|
aria-expanded={open}
|
|
129
133
|
aria-invalid={!!error}
|
|
134
|
+
disabled={disabled}
|
|
130
135
|
className={cn(
|
|
131
136
|
'justify-between font-normal',
|
|
132
137
|
fullWidth ? 'w-full' : 'w-auto',
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DialogHeader,
|
|
3
|
+
Dialog as ShadcnDialog,
|
|
4
|
+
DialogContent as ShadcnDialogContent,
|
|
5
|
+
DialogTitle as ShadcnDialogTitle,
|
|
6
|
+
} from '@/shadcn-components/DataDisplay/Dialog/Dialog';
|
|
2
7
|
import { ReactNode, useState } from 'react';
|
|
3
8
|
|
|
4
9
|
type MaxWidthSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
|
@@ -66,7 +66,7 @@ export const DropdownMenu = ({
|
|
|
66
66
|
<DropdownMenuContent
|
|
67
67
|
align={align}
|
|
68
68
|
side={side}
|
|
69
|
-
className={cn('min-w-[200px]
|
|
69
|
+
className={cn('min-w-[200px] ', menuContentClassName)}
|
|
70
70
|
sideOffset={5}
|
|
71
71
|
collisionPadding={8}
|
|
72
72
|
// Force Radix to recalculate position
|
|
@@ -36,7 +36,7 @@ function DialogOverlay({
|
|
|
36
36
|
<DialogPrimitive.Overlay
|
|
37
37
|
data-slot="dialog-overlay"
|
|
38
38
|
className={cn(
|
|
39
|
-
'fixed inset-0
|
|
39
|
+
'fixed inset-0 bg-black/50 data-[state=open]:animate-[fadeIn_100ms_ease-out] data-[state=closed]:animate-[fadeOut_100ms_ease-in]',
|
|
40
40
|
className,
|
|
41
41
|
)}
|
|
42
42
|
{...props}
|
|
@@ -58,7 +58,7 @@ function DialogContent({
|
|
|
58
58
|
<DialogPrimitive.Content
|
|
59
59
|
data-slot="dialog-content"
|
|
60
60
|
className={cn(
|
|
61
|
-
'bg-background fixed top-[50%] left-[50%]
|
|
61
|
+
'bg-background fixed top-[50%] left-[50%] grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg data-[state=open]:animate-[zoomIn_200ms_ease-out] data-[state=closed]:animate-[zoomOut_200ms_ease-in]',
|
|
62
62
|
className,
|
|
63
63
|
)}
|
|
64
64
|
{...props}
|
|
@@ -28,7 +28,7 @@ function PopoverContent({
|
|
|
28
28
|
align={align}
|
|
29
29
|
sideOffset={sideOffset}
|
|
30
30
|
className={cn(
|
|
31
|
-
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2
|
|
31
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden',
|
|
32
32
|
className,
|
|
33
33
|
)}
|
|
34
34
|
{...props}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
1
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
3
2
|
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
|
3
|
+
import * as React from 'react';
|
|
4
4
|
|
|
5
5
|
import { cn } from '@/lib/utils';
|
|
6
6
|
|
|
@@ -73,7 +73,7 @@ const SelectContent = React.forwardRef<
|
|
|
73
73
|
<SelectPrimitive.Content
|
|
74
74
|
ref={ref}
|
|
75
75
|
className={cn(
|
|
76
|
-
'relative
|
|
76
|
+
'relative max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
77
77
|
position === 'popper' &&
|
|
78
78
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
79
79
|
className,
|
|
@@ -145,13 +145,13 @@ SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
|
145
145
|
|
|
146
146
|
export {
|
|
147
147
|
Select,
|
|
148
|
-
SelectGroup,
|
|
149
|
-
SelectValue,
|
|
150
|
-
SelectTrigger,
|
|
151
148
|
SelectContent,
|
|
152
|
-
|
|
149
|
+
SelectGroup,
|
|
153
150
|
SelectItem,
|
|
154
|
-
|
|
155
|
-
SelectScrollUpButton,
|
|
151
|
+
SelectLabel,
|
|
156
152
|
SelectScrollDownButton,
|
|
153
|
+
SelectScrollUpButton,
|
|
154
|
+
SelectSeparator,
|
|
155
|
+
SelectTrigger,
|
|
156
|
+
SelectValue,
|
|
157
157
|
};
|
|
@@ -40,7 +40,7 @@ function DropdownMenuContent({
|
|
|
40
40
|
data-slot="dropdown-menu-content"
|
|
41
41
|
sideOffset={sideOffset}
|
|
42
42
|
className={cn(
|
|
43
|
-
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2
|
|
43
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
|
44
44
|
className,
|
|
45
45
|
)}
|
|
46
46
|
{...props}
|
|
@@ -228,7 +228,7 @@ function DropdownMenuSubContent({
|
|
|
228
228
|
<DropdownMenuPrimitive.SubContent
|
|
229
229
|
data-slot="dropdown-menu-sub-content"
|
|
230
230
|
className={cn(
|
|
231
|
-
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2
|
|
231
|
+
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
|
232
232
|
className,
|
|
233
233
|
)}
|
|
234
234
|
{...props}
|