@campxdev/react-blueprint 3.0.0-alpha.7 → 3.0.0-alpha.9
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/Assets/Icons/IconComponents/CampxFullLogoIcon.d.ts +3 -1
- package/dist/cjs/types/src/components/Charts/TreeMap/TreeMap.d.ts +1 -2
- package/dist/cjs/types/src/components/DataDisplay/DataTable/components/TableHeaders/TableActionHeader.d.ts +2 -1
- package/dist/cjs/types/src/components/Input/MultiSelect/MultiSelect.d.ts +5 -1
- package/dist/cjs/types/src/components/Input/MultiSelect/components/MultiSelectInput.d.ts +1 -1
- package/dist/cjs/types/src/components/Input/SingleSelect/SingleSelect.d.ts +5 -1
- package/dist/cjs/types/src/components/Input/SingleSelect/components/SingleInput.d.ts +1 -1
- package/dist/cjs/types/src/components/Layout/AppLayout/AppLayout.d.ts +0 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/types/src/components/Assets/Icons/IconComponents/CampxFullLogoIcon.d.ts +3 -1
- package/dist/esm/types/src/components/Charts/TreeMap/TreeMap.d.ts +1 -2
- package/dist/esm/types/src/components/DataDisplay/DataTable/components/TableHeaders/TableActionHeader.d.ts +2 -1
- package/dist/esm/types/src/components/Input/MultiSelect/MultiSelect.d.ts +5 -1
- package/dist/esm/types/src/components/Input/MultiSelect/components/MultiSelectInput.d.ts +1 -1
- package/dist/esm/types/src/components/Input/SingleSelect/SingleSelect.d.ts +5 -1
- package/dist/esm/types/src/components/Input/SingleSelect/components/SingleInput.d.ts +1 -1
- package/dist/esm/types/src/components/Layout/AppLayout/AppLayout.d.ts +0 -1
- package/dist/index.d.ts +63 -4
- package/dist/styles.css +15 -0
- package/package.json +1 -1
- package/src/components/Assets/Icons/IconComponents/CampxFullLogoIcon.tsx +134 -19
- package/src/components/Charts/TreeMap/TreeMap.tsx +1 -3
- package/src/components/DataDisplay/DataTable/DataTable.tsx +1 -0
- package/src/components/DataDisplay/DataTable/components/CardsView.tsx +1 -1
- package/src/components/DataDisplay/DataTable/components/TableHeaders/TableActionHeader.tsx +21 -18
- package/src/components/Input/DatePicker/components/DatePickerInput.tsx +1 -1
- package/src/components/Input/DateTimePicker/components/DateTimePickerInput.tsx +2 -4
- package/src/components/Input/MultiSelect/MultiSelect.tsx +18 -8
- package/src/components/Input/MultiSelect/components/MultiSelectInput.tsx +2 -3
- package/src/components/Input/PasswordField/PasswordField.tsx +4 -1
- package/src/components/Input/SingleSelect/SingleSelect.tsx +23 -12
- package/src/components/Input/SingleSelect/components/SingleInput.tsx +2 -1
- package/src/components/Layout/AppLayout/AppLayout.tsx +0 -4
- package/src/components/Layout/PageContent/PageContent.tsx +1 -1
- package/src/components/Navigation/Dialog/Dialog.tsx +1 -0
- package/src/components/Navigation/DialogButton/DialogButton.tsx +2 -1
- package/src/components/Navigation/Stepper/HorizontalStepper.tsx +94 -51
- package/src/components/Navigation/Stepper/VerticalStepper.tsx +44 -4
|
@@ -20,6 +20,7 @@ type TableActionHeaderProps = {
|
|
|
20
20
|
type: string;
|
|
21
21
|
axios: Axios;
|
|
22
22
|
};
|
|
23
|
+
hasCardView?: boolean;
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
const TableActionHeader = ({
|
|
@@ -28,6 +29,7 @@ const TableActionHeader = ({
|
|
|
28
29
|
uniqueId,
|
|
29
30
|
columns,
|
|
30
31
|
viewsProps,
|
|
32
|
+
hasCardView = false,
|
|
31
33
|
}: TableActionHeaderProps) => {
|
|
32
34
|
const { data: views, isLoading } = useQuery(
|
|
33
35
|
viewsProps.type,
|
|
@@ -97,30 +99,31 @@ const TableActionHeader = ({
|
|
|
97
99
|
<div className="flex items-center gap-2">
|
|
98
100
|
{layoutMode === 'table' && (
|
|
99
101
|
<>
|
|
100
|
-
{' '}
|
|
101
102
|
{/* Density Selector */}
|
|
102
103
|
<DensitySelector uniqueId={uniqueId} />
|
|
103
104
|
{/* Column Selector */}
|
|
104
105
|
<ColumnSelector columns={columns} uniqueId={uniqueId} />
|
|
105
106
|
</>
|
|
106
107
|
)}
|
|
107
|
-
{/* Layout Switcher */}
|
|
108
|
-
|
|
109
|
-
<
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
108
|
+
{/* Layout Switcher - Only show if card view is available */}
|
|
109
|
+
{hasCardView && (
|
|
110
|
+
<div className="flex gap-1 border rounded-md flex-shrink-0 bg-background">
|
|
111
|
+
<Button
|
|
112
|
+
variant={layoutMode === 'table' ? 'default' : 'ghost'}
|
|
113
|
+
size="sm"
|
|
114
|
+
onClick={() => onLayoutChange('table')}
|
|
115
|
+
>
|
|
116
|
+
<Table />
|
|
117
|
+
</Button>
|
|
118
|
+
<Button
|
|
119
|
+
variant={layoutMode === 'card' ? 'default' : 'ghost'}
|
|
120
|
+
size="sm"
|
|
121
|
+
onClick={() => onLayoutChange('card')}
|
|
122
|
+
>
|
|
123
|
+
<LayoutGrid />
|
|
124
|
+
</Button>
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
124
127
|
</div>
|
|
125
128
|
</Card>
|
|
126
129
|
);
|
|
@@ -181,7 +181,7 @@ export const DatePickerInput = ({
|
|
|
181
181
|
{/* Helper Text / Error */}
|
|
182
182
|
{(helperText || error) && (
|
|
183
183
|
<Typography
|
|
184
|
-
variant="
|
|
184
|
+
variant="muted"
|
|
185
185
|
className={cn('ml-1 mt-1', error && 'text-destructive')}
|
|
186
186
|
>
|
|
187
187
|
{typeof error === 'string' ? error : error?.message || helperText}
|
|
@@ -196,9 +196,7 @@ export const DateTimePickerInput = ({
|
|
|
196
196
|
disabled={disabled}
|
|
197
197
|
{...rest}
|
|
198
198
|
>
|
|
199
|
-
<span>
|
|
200
|
-
{value ? formatDateTimeString(value) : placeholder}
|
|
201
|
-
</span>
|
|
199
|
+
<span>{value ? formatDateTimeString(value) : placeholder}</span>
|
|
202
200
|
{Icon &&
|
|
203
201
|
cloneElement(Icon as React.ReactElement, {
|
|
204
202
|
className: 'ml-2 h-4 w-4 ',
|
|
@@ -286,7 +284,7 @@ export const DateTimePickerInput = ({
|
|
|
286
284
|
{/* Helper Text / Error */}
|
|
287
285
|
{(helperText || error) && (
|
|
288
286
|
<Typography
|
|
289
|
-
variant="
|
|
287
|
+
variant="muted"
|
|
290
288
|
className={cn('ml-1 mt-1', error && 'text-destructive')}
|
|
291
289
|
>
|
|
292
290
|
{typeof error === 'string' ? error : error?.message || helperText}
|
|
@@ -22,6 +22,8 @@ export type MultiSelectProps = {
|
|
|
22
22
|
name?: string;
|
|
23
23
|
getValue?: (option: any) => any;
|
|
24
24
|
value?: any[];
|
|
25
|
+
defaultValue?: any[];
|
|
26
|
+
placeholder?: string;
|
|
25
27
|
dbValueProps?: {
|
|
26
28
|
valueKey: string;
|
|
27
29
|
isObjectId?: boolean;
|
|
@@ -70,6 +72,8 @@ export type MultiSelectProps = {
|
|
|
70
72
|
* @param {boolean} [props.loading] - Whether options are loading
|
|
71
73
|
* @param {boolean} [props.disableClear] - Whether to hide the clear button
|
|
72
74
|
* @param {boolean} [props.fullWidth] - Whether the select takes full width
|
|
75
|
+
* @param {any[]} [props.defaultValue] - Default values when component is uncontrolled (overridden by value prop)
|
|
76
|
+
* @param {string} [props.placeholder] - Placeholder text shown when no values are selected
|
|
73
77
|
* @param {'input' | 'filter'} [props.type='input'] - Display type
|
|
74
78
|
* @param {(event: SyntheticEvent) => void} [props.onOpen] - Callback when menu opens
|
|
75
79
|
* @param {(event: SyntheticEvent, reason: string) => void} [props.onClose] - Callback when menu closes
|
|
@@ -92,7 +96,9 @@ export const MultiSelect = ({
|
|
|
92
96
|
optionsApiEndpointParams,
|
|
93
97
|
externalAxios,
|
|
94
98
|
getValue,
|
|
95
|
-
value
|
|
99
|
+
value,
|
|
100
|
+
defaultValue,
|
|
101
|
+
placeholder,
|
|
96
102
|
onChange,
|
|
97
103
|
dbValueProps = {
|
|
98
104
|
valueKey: 'id',
|
|
@@ -110,16 +116,18 @@ export const MultiSelect = ({
|
|
|
110
116
|
type = 'input',
|
|
111
117
|
...restProps
|
|
112
118
|
}: MultiSelectProps) => {
|
|
119
|
+
// Prioritize value over defaultValue (value takes precedence if both exist)
|
|
120
|
+
const effectiveValue = value !== undefined ? value : defaultValue ?? [];
|
|
113
121
|
const initialState: MultiSelectState = {
|
|
114
122
|
open: false,
|
|
115
123
|
loadingInternalOptions: false,
|
|
116
124
|
loadingInitialInternalOptions: false,
|
|
117
125
|
internalOptions: options ?? [],
|
|
118
126
|
internalOptionsMap: generateOptionsMap(options ?? [], getValue),
|
|
119
|
-
selectedValues:
|
|
127
|
+
selectedValues: effectiveValue,
|
|
120
128
|
selectedOptionsMap: generateSelectedOptionsMap(
|
|
121
129
|
options ?? [],
|
|
122
|
-
|
|
130
|
+
effectiveValue,
|
|
123
131
|
getValue,
|
|
124
132
|
),
|
|
125
133
|
limit: 10,
|
|
@@ -415,7 +423,7 @@ export const MultiSelect = ({
|
|
|
415
423
|
...(dbValueProps.isObjectId && { isObjectId: true }),
|
|
416
424
|
...(dbValueProps.isInt && { isInt: true }),
|
|
417
425
|
...(dbValueProps.isFloat && { isFloat: true }),
|
|
418
|
-
selectedValueData:
|
|
426
|
+
selectedValueData: effectiveValue,
|
|
419
427
|
filterBySelectedValues: true,
|
|
420
428
|
},
|
|
421
429
|
dbLabelProps,
|
|
@@ -429,7 +437,7 @@ export const MultiSelect = ({
|
|
|
429
437
|
internalOptionsMap: generateOptionsMap(res.data, getValue),
|
|
430
438
|
selectedOptionsMap: generateSelectedOptionsMap(
|
|
431
439
|
res.data,
|
|
432
|
-
|
|
440
|
+
effectiveValue,
|
|
433
441
|
getValue,
|
|
434
442
|
),
|
|
435
443
|
},
|
|
@@ -444,7 +452,7 @@ export const MultiSelect = ({
|
|
|
444
452
|
};
|
|
445
453
|
|
|
446
454
|
useEffect(() => {
|
|
447
|
-
if (
|
|
455
|
+
if (effectiveValue && effectiveValue.length > 0 && optionsApiEndPoint) {
|
|
448
456
|
loadSelectedOptions().finally(() => {
|
|
449
457
|
dispatch({
|
|
450
458
|
actionType: MultiSelectActionsTypes.LOAD_SELECTED_OPTIONS_END,
|
|
@@ -470,7 +478,8 @@ export const MultiSelect = ({
|
|
|
470
478
|
state={state}
|
|
471
479
|
optionsApiEndPoint={optionsApiEndPoint}
|
|
472
480
|
getValue={getValue}
|
|
473
|
-
value={
|
|
481
|
+
value={effectiveValue}
|
|
482
|
+
placeholder={placeholder}
|
|
474
483
|
dbLabelProps={dbLabelProps}
|
|
475
484
|
{...restProps}
|
|
476
485
|
/>
|
|
@@ -488,7 +497,8 @@ export const MultiSelect = ({
|
|
|
488
497
|
state={state}
|
|
489
498
|
optionsApiEndPoint={optionsApiEndPoint}
|
|
490
499
|
getValue={getValue}
|
|
491
|
-
value={
|
|
500
|
+
value={effectiveValue}
|
|
501
|
+
placeholder={placeholder}
|
|
492
502
|
dbLabelProps={dbLabelProps}
|
|
493
503
|
{...restProps}
|
|
494
504
|
/>
|
|
@@ -43,6 +43,7 @@ export const MultiSelectInput = ({
|
|
|
43
43
|
name,
|
|
44
44
|
getValue,
|
|
45
45
|
value = [],
|
|
46
|
+
placeholder = '',
|
|
46
47
|
onChange,
|
|
47
48
|
error,
|
|
48
49
|
helperText,
|
|
@@ -155,9 +156,7 @@ export const MultiSelectInput = ({
|
|
|
155
156
|
>
|
|
156
157
|
<div className="flex items-center gap-2 flex-1 overflow-hidden">
|
|
157
158
|
{selectedChips.length === 0 ? (
|
|
158
|
-
<span className="text-muted-foreground">
|
|
159
|
-
Select options...
|
|
160
|
-
</span>
|
|
159
|
+
<span className="text-muted-foreground">{placeholder}</span>
|
|
161
160
|
) : (
|
|
162
161
|
<>
|
|
163
162
|
<Badge variant={'default'}>{firstChip.label}</Badge>
|
|
@@ -129,7 +129,10 @@ export const PasswordField = ({
|
|
|
129
129
|
label={label}
|
|
130
130
|
required={required}
|
|
131
131
|
name={name}
|
|
132
|
-
containerProps={
|
|
132
|
+
containerProps={{
|
|
133
|
+
...(fullWidth && { style: { width: '100%' } }),
|
|
134
|
+
...containerProps,
|
|
135
|
+
}}
|
|
133
136
|
>
|
|
134
137
|
{content}
|
|
135
138
|
</LabelWrapper>
|
|
@@ -30,6 +30,8 @@ export type SingleSelectProps = {
|
|
|
30
30
|
name?: string;
|
|
31
31
|
getValue?: (option: any) => any;
|
|
32
32
|
value?: any;
|
|
33
|
+
defaultValue?: any;
|
|
34
|
+
placeholder?: string;
|
|
33
35
|
dbValueProps?: {
|
|
34
36
|
valueKey: string;
|
|
35
37
|
isObjectId?: boolean;
|
|
@@ -78,6 +80,8 @@ export type SingleSelectProps = {
|
|
|
78
80
|
* @param {boolean} [props.loading] - Whether options are loading
|
|
79
81
|
* @param {boolean} [props.disableClear] - Whether to hide the clear button
|
|
80
82
|
* @param {boolean} [props.fullWidth] - Whether the select takes full width
|
|
83
|
+
* @param {any} [props.defaultValue] - Default value when component is uncontrolled (overridden by value prop)
|
|
84
|
+
* @param {string} [props.placeholder] - Placeholder text shown when no value is selected
|
|
81
85
|
* @param {'input' | 'filter'} [props.type='input'] - Display type
|
|
82
86
|
* @param {(event: SyntheticEvent) => void} [props.onOpen] - Callback when menu opens
|
|
83
87
|
* @param {(event: SyntheticEvent, reason: string) => void} [props.onClose] - Callback when menu closes
|
|
@@ -101,6 +105,8 @@ export const SingleSelect = ({
|
|
|
101
105
|
externalAxios,
|
|
102
106
|
getValue,
|
|
103
107
|
value,
|
|
108
|
+
defaultValue,
|
|
109
|
+
placeholder,
|
|
104
110
|
onChange,
|
|
105
111
|
dbValueProps = {
|
|
106
112
|
valueKey: 'id',
|
|
@@ -139,6 +145,9 @@ export const SingleSelect = ({
|
|
|
139
145
|
|
|
140
146
|
const internalAxios = externalAxios || axios;
|
|
141
147
|
|
|
148
|
+
// Prioritize value over defaultValue (value takes precedence if both exist)
|
|
149
|
+
const effectiveValue = value !== undefined ? value : defaultValue;
|
|
150
|
+
|
|
142
151
|
const handleOpen = async (event: SyntheticEvent) => {
|
|
143
152
|
dispatch({
|
|
144
153
|
actionType: SingleSelectActionsTypes.OPEN,
|
|
@@ -211,10 +220,10 @@ export const SingleSelect = ({
|
|
|
211
220
|
actionType: SingleSelectActionsTypes.CLEAR_SEARCH,
|
|
212
221
|
stateChanges: {
|
|
213
222
|
internalOptions: internalOptions.filter(
|
|
214
|
-
(o: any) => o.value ===
|
|
223
|
+
(o: any) => o.value === effectiveValue,
|
|
215
224
|
),
|
|
216
225
|
internalOptionsMap: generateOptionsMap(
|
|
217
|
-
internalOptions.filter((o: any) => o.value ===
|
|
226
|
+
internalOptions.filter((o: any) => o.value === effectiveValue),
|
|
218
227
|
),
|
|
219
228
|
},
|
|
220
229
|
});
|
|
@@ -246,7 +255,7 @@ export const SingleSelect = ({
|
|
|
246
255
|
...(dbValueProps.isObjectId && { isObjectId: true }),
|
|
247
256
|
...(dbValueProps.isInt && { isInt: true }),
|
|
248
257
|
...(dbValueProps.isFloat && { isFloat: true }),
|
|
249
|
-
selectedValueData:
|
|
258
|
+
selectedValueData: effectiveValue,
|
|
250
259
|
},
|
|
251
260
|
dbLabelProps: {
|
|
252
261
|
search: search,
|
|
@@ -284,10 +293,10 @@ export const SingleSelect = ({
|
|
|
284
293
|
actionType: SingleSelectActionsTypes.CLEAR_SEARCH,
|
|
285
294
|
stateChanges: {
|
|
286
295
|
internalOptions: internalOptions.filter(
|
|
287
|
-
(o: any) => o.value ===
|
|
296
|
+
(o: any) => o.value === effectiveValue,
|
|
288
297
|
),
|
|
289
298
|
internalOptionsMap: generateOptionsMap(
|
|
290
|
-
internalOptions.filter((o: any) => o.value ===
|
|
299
|
+
internalOptions.filter((o: any) => o.value === effectiveValue),
|
|
291
300
|
),
|
|
292
301
|
},
|
|
293
302
|
});
|
|
@@ -307,7 +316,7 @@ export const SingleSelect = ({
|
|
|
307
316
|
...(dbValueProps.isObjectId && { isObjectId: true }),
|
|
308
317
|
...(dbValueProps.isInt && { isInt: true }),
|
|
309
318
|
...(dbValueProps.isFloat && { isFloat: true }),
|
|
310
|
-
selectedValueData:
|
|
319
|
+
selectedValueData: effectiveValue,
|
|
311
320
|
},
|
|
312
321
|
dbLabelProps: {
|
|
313
322
|
search: searchValue,
|
|
@@ -324,11 +333,11 @@ export const SingleSelect = ({
|
|
|
324
333
|
stateChanges: {
|
|
325
334
|
internalOptions: [
|
|
326
335
|
...options,
|
|
327
|
-
...internalOptions.filter((o: any) => o.value ===
|
|
336
|
+
...internalOptions.filter((o: any) => o.value === effectiveValue),
|
|
328
337
|
],
|
|
329
338
|
internalOptionsMap: generateOptionsMap([
|
|
330
339
|
...options,
|
|
331
|
-
...internalOptions.filter((o: any) => o.value ===
|
|
340
|
+
...internalOptions.filter((o: any) => o.value === effectiveValue),
|
|
332
341
|
]),
|
|
333
342
|
},
|
|
334
343
|
});
|
|
@@ -360,7 +369,7 @@ export const SingleSelect = ({
|
|
|
360
369
|
...(dbValueProps.isObjectId && { isObjectId: true }),
|
|
361
370
|
...(dbValueProps.isInt && { isInt: true }),
|
|
362
371
|
...(dbValueProps.isFloat && { isFloat: true }),
|
|
363
|
-
selectedValueData:
|
|
372
|
+
selectedValueData: effectiveValue,
|
|
364
373
|
filterBySelectedValues: true,
|
|
365
374
|
},
|
|
366
375
|
dbLabelProps,
|
|
@@ -384,7 +393,7 @@ export const SingleSelect = ({
|
|
|
384
393
|
};
|
|
385
394
|
|
|
386
395
|
useEffect(() => {
|
|
387
|
-
if (
|
|
396
|
+
if (effectiveValue && optionsApiEndPoint) {
|
|
388
397
|
loadSelectedOptions().finally(() => {
|
|
389
398
|
dispatch({
|
|
390
399
|
actionType: SingleSelectActionsTypes.LOAD_SELECTED_OPTIONS_END,
|
|
@@ -408,7 +417,8 @@ export const SingleSelect = ({
|
|
|
408
417
|
state={state}
|
|
409
418
|
optionsApiEndPoint={optionsApiEndPoint}
|
|
410
419
|
getValue={getValue}
|
|
411
|
-
value={
|
|
420
|
+
value={effectiveValue}
|
|
421
|
+
placeholder={placeholder}
|
|
412
422
|
dbLabelProps={dbLabelProps}
|
|
413
423
|
{...restProps}
|
|
414
424
|
/>
|
|
@@ -424,7 +434,8 @@ export const SingleSelect = ({
|
|
|
424
434
|
state={state}
|
|
425
435
|
optionsApiEndPoint={optionsApiEndPoint}
|
|
426
436
|
getValue={getValue}
|
|
427
|
-
value={
|
|
437
|
+
value={effectiveValue}
|
|
438
|
+
placeholder={placeholder}
|
|
428
439
|
dbLabelProps={dbLabelProps}
|
|
429
440
|
{...restProps}
|
|
430
441
|
/>
|
|
@@ -39,6 +39,7 @@ export const SingleInput = ({
|
|
|
39
39
|
name,
|
|
40
40
|
getValue,
|
|
41
41
|
value,
|
|
42
|
+
placeholder,
|
|
42
43
|
onChange,
|
|
43
44
|
error,
|
|
44
45
|
helperText,
|
|
@@ -140,7 +141,7 @@ export const SingleInput = ({
|
|
|
140
141
|
'border-destructive focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40',
|
|
141
142
|
)}
|
|
142
143
|
>
|
|
143
|
-
{selectedOption?.label || 'Select an option...'}
|
|
144
|
+
{selectedOption?.label || placeholder || 'Select an option...'}
|
|
144
145
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
145
146
|
</Button>
|
|
146
147
|
</span>
|
|
@@ -15,7 +15,6 @@ import { AppLayoutProps } from './types';
|
|
|
15
15
|
* @param {SideMenuItemProps[]} menu - Navigation menu items for the sidebar
|
|
16
16
|
* @param {ReactNode | ((params: { collapsed: boolean }) => ReactNode)} [actions] - Optional action elements or function that receives collapsed state
|
|
17
17
|
* @param {string} [mainContainerClassName=''] - Additional CSS classes for the main content container
|
|
18
|
-
* @param {HelpDocsConfig} [helpDocsConfig] - Configuration for context-based help documentation
|
|
19
18
|
* @param {boolean} [initialCollapsed] - Initial state of sidebar collapse (auto-determined on small screens)
|
|
20
19
|
* @param {UserProfilePopupProps} [userProfileParams] - Configuration for user profile popup/menu
|
|
21
20
|
*
|
|
@@ -41,7 +40,6 @@ export const AppLayout: React.FC<AppLayoutProps> = ({
|
|
|
41
40
|
menu,
|
|
42
41
|
actions,
|
|
43
42
|
mainContainerClassName = '',
|
|
44
|
-
helpDocsConfig,
|
|
45
43
|
initialCollapsed,
|
|
46
44
|
userProfileParams,
|
|
47
45
|
}) => {
|
|
@@ -53,7 +51,6 @@ export const AppLayout: React.FC<AppLayoutProps> = ({
|
|
|
53
51
|
menu={menu}
|
|
54
52
|
actions={actions}
|
|
55
53
|
mainContainerClassName={mainContainerClassName}
|
|
56
|
-
helpDocsConfig={helpDocsConfig}
|
|
57
54
|
userProfileParams={userProfileParams}
|
|
58
55
|
>
|
|
59
56
|
{children}
|
|
@@ -67,7 +64,6 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({
|
|
|
67
64
|
menu,
|
|
68
65
|
actions,
|
|
69
66
|
mainContainerClassName = '',
|
|
70
|
-
helpDocsConfig,
|
|
71
67
|
userProfileParams,
|
|
72
68
|
}) => {
|
|
73
69
|
const { collapsed, setCollapsed } = useSidebar();
|
|
@@ -37,7 +37,7 @@ const PageContent = ({ children, className, styles }: PageContentProps) => {
|
|
|
37
37
|
className,
|
|
38
38
|
)}
|
|
39
39
|
style={{
|
|
40
|
-
height: isSmallScreen ? 'calc(100vh -
|
|
40
|
+
height: isSmallScreen ? 'calc(100vh - 149px)' : `calc(100vh - 85px)`,
|
|
41
41
|
overflowY: 'auto',
|
|
42
42
|
...styles,
|
|
43
43
|
}}
|
|
@@ -102,6 +102,7 @@ export const DialogButton = ({
|
|
|
102
102
|
lg: 'max-w-lg',
|
|
103
103
|
xl: 'max-w-xl',
|
|
104
104
|
'2xl': 'max-w-2xl',
|
|
105
|
+
'3xl': 'max-w-3xl',
|
|
105
106
|
};
|
|
106
107
|
|
|
107
108
|
const onClose = () => {
|
|
@@ -134,7 +135,7 @@ export const DialogButton = ({
|
|
|
134
135
|
showCloseButton={showCloseButton}
|
|
135
136
|
>
|
|
136
137
|
{title && (
|
|
137
|
-
<DialogHeader className="pl-4 pt-4">
|
|
138
|
+
<DialogHeader className="pl-4 pt-4 border-b">
|
|
138
139
|
<ShadcnDialogTitle>{title}</ShadcnDialogTitle>
|
|
139
140
|
</DialogHeader>
|
|
140
141
|
)}
|
|
@@ -10,6 +10,48 @@ import {
|
|
|
10
10
|
handleStepClick as handleStepClickUtil,
|
|
11
11
|
} from './utils';
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Native HTML button component for step indicators
|
|
15
|
+
* Uses forwardRef to properly expose DOM node for positioning calculations
|
|
16
|
+
*/
|
|
17
|
+
interface StepIndicatorProps
|
|
18
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
19
|
+
variant?: 'default' | 'secondary' | 'inactive';
|
|
20
|
+
children: React.ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const StepIndicator = React.forwardRef<HTMLButtonElement, StepIndicatorProps>(
|
|
24
|
+
function StepIndicator(
|
|
25
|
+
{ variant = 'default', className, children, ...props },
|
|
26
|
+
ref,
|
|
27
|
+
) {
|
|
28
|
+
const baseStyles =
|
|
29
|
+
'inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 outline-none focus-visible:ring-2 focus-visible:ring-offset-2 h-10 w-10 rounded-full duration-200';
|
|
30
|
+
|
|
31
|
+
const variantStyles = {
|
|
32
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
33
|
+
secondary:
|
|
34
|
+
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
35
|
+
inactive: 'bg-muted text-muted-foreground hover:bg-muted/80',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<button
|
|
40
|
+
ref={ref}
|
|
41
|
+
className={cn(
|
|
42
|
+
baseStyles,
|
|
43
|
+
variantStyles[variant],
|
|
44
|
+
'cursor-pointer',
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
{children}
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
13
55
|
const HorizontalStepper: React.FC<StepperVariantProps> = ({
|
|
14
56
|
activeStep,
|
|
15
57
|
steps,
|
|
@@ -19,6 +61,7 @@ const HorizontalStepper: React.FC<StepperVariantProps> = ({
|
|
|
19
61
|
}) => {
|
|
20
62
|
// Refs to track indicator positions
|
|
21
63
|
const indicatorRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
64
|
+
const stepsContainerRef = useRef<HTMLDivElement>(null);
|
|
22
65
|
const [connectorPositions, setConnectorPositions] = useState<
|
|
23
66
|
{ left: number; width: number; top: number }[]
|
|
24
67
|
>([]);
|
|
@@ -30,8 +73,9 @@ const HorizontalStepper: React.FC<StepperVariantProps> = ({
|
|
|
30
73
|
// Calculate connector positions based on indicator refs
|
|
31
74
|
useEffect(() => {
|
|
32
75
|
const calculatePositions = () => {
|
|
33
|
-
if (indicatorRefs.current.length >
|
|
76
|
+
if (indicatorRefs.current.length > 1 && stepsContainerRef.current) {
|
|
34
77
|
const spacing = 6; // Spacing in pixels on each side of the connector
|
|
78
|
+
const containerRect = stepsContainerRef.current.getBoundingClientRect();
|
|
35
79
|
|
|
36
80
|
const positions = indicatorRefs.current
|
|
37
81
|
.slice(0, -1)
|
|
@@ -43,12 +87,6 @@ const HorizontalStepper: React.FC<StepperVariantProps> = ({
|
|
|
43
87
|
const currentRect = indicator.getBoundingClientRect();
|
|
44
88
|
const nextRect =
|
|
45
89
|
indicatorRefs.current[index + 1]!.getBoundingClientRect();
|
|
46
|
-
const containerRect =
|
|
47
|
-
indicator.offsetParent?.getBoundingClientRect();
|
|
48
|
-
|
|
49
|
-
if (!containerRect) {
|
|
50
|
-
return { left: 0, width: 0, top: 0 };
|
|
51
|
-
}
|
|
52
90
|
|
|
53
91
|
const left = currentRect.right - containerRect.left + spacing;
|
|
54
92
|
const width = nextRect.left - currentRect.right - spacing * 2;
|
|
@@ -62,14 +100,17 @@ const HorizontalStepper: React.FC<StepperVariantProps> = ({
|
|
|
62
100
|
}
|
|
63
101
|
};
|
|
64
102
|
|
|
65
|
-
//
|
|
66
|
-
|
|
103
|
+
// Use requestAnimationFrame to ensure DOM is ready
|
|
104
|
+
const animationFrameId = requestAnimationFrame(() => {
|
|
105
|
+
calculatePositions();
|
|
106
|
+
});
|
|
67
107
|
|
|
68
|
-
//
|
|
108
|
+
// Also recalculate on window resize
|
|
69
109
|
window.addEventListener('resize', calculatePositions);
|
|
70
110
|
|
|
71
|
-
// Cleanup
|
|
111
|
+
// Cleanup listeners on unmount
|
|
72
112
|
return () => {
|
|
113
|
+
cancelAnimationFrame(animationFrameId);
|
|
73
114
|
window.removeEventListener('resize', calculatePositions);
|
|
74
115
|
};
|
|
75
116
|
}, [steps, activeStep]);
|
|
@@ -109,12 +150,9 @@ const HorizontalStepper: React.FC<StepperVariantProps> = ({
|
|
|
109
150
|
<ChevronLeft className="w-5 h-5" />
|
|
110
151
|
</Button>
|
|
111
152
|
{/* Step Indicator */}
|
|
112
|
-
<
|
|
113
|
-
variant={currentState === 'inactive' ? 'secondary' : 'default'}
|
|
114
|
-
className="h-8 w-8 rounded-full duration-200"
|
|
115
|
-
>
|
|
153
|
+
<StepIndicator variant={currentState === 'inactive' ? 'secondary' : 'default'}>
|
|
116
154
|
{activeStep + 1}
|
|
117
|
-
</
|
|
155
|
+
</StepIndicator>
|
|
118
156
|
|
|
119
157
|
{/* Next Button */}
|
|
120
158
|
<Button
|
|
@@ -154,33 +192,36 @@ const HorizontalStepper: React.FC<StepperVariantProps> = ({
|
|
|
154
192
|
</div>
|
|
155
193
|
|
|
156
194
|
{/* Desktop View - All steps with connectors (md breakpoint and above) */}
|
|
157
|
-
<div className="hidden md:block">
|
|
158
|
-
<div className="
|
|
159
|
-
{
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
195
|
+
<div className="hidden md:block w-full">
|
|
196
|
+
<div className="relative w-full">
|
|
197
|
+
{/* Steps Row - flex-shrink-0 prevents content expansion */}
|
|
198
|
+
<div className="flex justify-between items-start gap-2 relative px-1" ref={stepsContainerRef}>
|
|
199
|
+
{steps.map((step, index) => {
|
|
200
|
+
const state = getStepState(index, activeStep);
|
|
201
|
+
const isClickable = allowNavigation && onStepClick;
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<div
|
|
205
|
+
key={index}
|
|
206
|
+
className="flex flex-col items-center flex-shrink-0"
|
|
207
|
+
>
|
|
166
208
|
{/* Step Indicator */}
|
|
167
|
-
<
|
|
209
|
+
<StepIndicator
|
|
168
210
|
ref={(el) => (indicatorRefs.current[index] = el)}
|
|
169
211
|
onClick={
|
|
170
212
|
isClickable ? () => handleStepClick(index) : () => {}
|
|
171
213
|
}
|
|
172
214
|
variant={state === 'inactive' ? 'secondary' : 'default'}
|
|
173
|
-
className="h-8 w-8 rounded-full duration-200"
|
|
174
215
|
>
|
|
175
216
|
{state === 'completed' ? (
|
|
176
|
-
<Check className="w-
|
|
217
|
+
<Check className="w-5 h-5" />
|
|
177
218
|
) : (
|
|
178
219
|
index + 1
|
|
179
220
|
)}
|
|
180
|
-
</
|
|
221
|
+
</StepIndicator>
|
|
181
222
|
|
|
182
|
-
{/* Step Content */}
|
|
183
|
-
<div className="flex flex-col gap-1 mt-3 text-center
|
|
223
|
+
{/* Step Content - Centered below indicator */}
|
|
224
|
+
<div className="flex flex-col gap-1 mt-3 text-center whitespace-nowrap">
|
|
184
225
|
<Typography
|
|
185
226
|
variant={state === 'inactive' ? 'muted' : 'small'}
|
|
186
227
|
>
|
|
@@ -201,26 +242,28 @@ const HorizontalStepper: React.FC<StepperVariantProps> = ({
|
|
|
201
242
|
)}
|
|
202
243
|
</div>
|
|
203
244
|
</div>
|
|
204
|
-
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
{/* Connectors - Positioned absolutely based on calculated positions */}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
245
|
+
);
|
|
246
|
+
})}
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
{/* Connectors - Positioned absolutely based on calculated indicator positions */}
|
|
250
|
+
<div className="absolute top-0 left-0 right-0 w-full h-full pointer-events-none">
|
|
251
|
+
{connectorPositions.map((position, index) => (
|
|
252
|
+
<div
|
|
253
|
+
key={`connector-${index}`}
|
|
254
|
+
className={cn(
|
|
255
|
+
'absolute h-0.5 rounded-full transition-colors duration-200',
|
|
256
|
+
getConnectorClasses(index, activeStep),
|
|
257
|
+
)}
|
|
258
|
+
style={{
|
|
259
|
+
left: `${position.left}px`,
|
|
260
|
+
width: `${position.width}px`,
|
|
261
|
+
top: `${position.top}px`,
|
|
262
|
+
}}
|
|
263
|
+
aria-hidden="true"
|
|
264
|
+
/>
|
|
265
|
+
))}
|
|
266
|
+
</div>
|
|
224
267
|
</div>
|
|
225
268
|
</div>
|
|
226
269
|
</div>
|