@campxdev/react-blueprint 3.0.0-alpha.6 → 3.0.0-alpha.8
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/DataDisplay/DataTable/DataTable.d.ts +3 -3
- package/dist/cjs/types/src/components/DataDisplay/DataTable/components/CardsView.d.ts +14 -2
- package/dist/cjs/types/src/components/Layout/PageContent/PageContent.d.ts +2 -1
- package/dist/cjs/types/src/components/Navigation/TabsContainer/TabsContainer.d.ts +5 -1
- package/dist/cjs/types/src/shadcn-components/Input/Button/Button.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/types/src/components/DataDisplay/DataTable/DataTable.d.ts +3 -3
- package/dist/esm/types/src/components/DataDisplay/DataTable/components/CardsView.d.ts +14 -2
- package/dist/esm/types/src/components/Layout/PageContent/PageContent.d.ts +2 -1
- package/dist/esm/types/src/components/Navigation/TabsContainer/TabsContainer.d.ts +5 -1
- package/dist/esm/types/src/shadcn-components/Input/Button/Button.d.ts +1 -1
- package/dist/index.d.ts +12 -8
- package/dist/styles.css +12 -72
- package/package.json +1 -1
- package/src/components/DataDisplay/DataTable/DataTable.tsx +16 -4
- package/src/components/DataDisplay/DataTable/components/CardsView.tsx +28 -13
- package/src/components/DataDisplay/DataTable/components/ColumnSelector/ColumnSelector.tsx +22 -4
- package/src/components/DataDisplay/DataTable/components/TableHeaders/TableActionHeader.tsx +9 -4
- package/src/components/DataDisplay/DataTable/components/TableView.tsx +5 -1
- package/src/components/Input/DatePicker/components/DatePickerInput.tsx +1 -1
- package/src/components/Input/DateTimePicker/components/DateTimePickerInput.tsx +2 -4
- package/src/components/Input/PasswordField/PasswordField.tsx +4 -1
- package/src/components/Layout/AppLayout/AppLayout.tsx +1 -10
- package/src/components/Layout/AppLayout/components/UserProfilePopup.tsx +1 -1
- package/src/components/Layout/PageContent/PageContent.tsx +4 -3
- package/src/components/Layout/PageHeader/PageHeader.tsx +22 -7
- package/src/components/Navigation/Breadcrumbs/Breadcrumbs.tsx +20 -4
- package/src/components/Navigation/Dialog/Dialog.tsx +7 -2
- package/src/components/Navigation/Stepper/HorizontalStepper.tsx +94 -51
- package/src/components/Navigation/Stepper/VerticalStepper.tsx +44 -4
- package/src/components/Navigation/TabsContainer/TabsContainer.tsx +6 -1
|
@@ -5,6 +5,7 @@ import { ReactNode } from 'react';
|
|
|
5
5
|
interface PageContentProps {
|
|
6
6
|
children: ReactNode;
|
|
7
7
|
className?: string;
|
|
8
|
+
styles?: React.CSSProperties;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -26,7 +27,7 @@ interface PageContentProps {
|
|
|
26
27
|
* </PageContent>
|
|
27
28
|
* ```
|
|
28
29
|
*/
|
|
29
|
-
const PageContent = ({ children, className }: PageContentProps) => {
|
|
30
|
+
const PageContent = ({ children, className, styles }: PageContentProps) => {
|
|
30
31
|
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
|
31
32
|
|
|
32
33
|
return (
|
|
@@ -36,9 +37,9 @@ const PageContent = ({ children, className }: PageContentProps) => {
|
|
|
36
37
|
className,
|
|
37
38
|
)}
|
|
38
39
|
style={{
|
|
39
|
-
height: isSmallScreen ? 'calc(100vh - 101px)' : `calc(100vh -
|
|
40
|
-
maxHeight: isSmallScreen ? 'calc(100vh - 101px)' : `calc(100vh - 90px)`,
|
|
40
|
+
height: isSmallScreen ? 'calc(100vh - 101px)' : `calc(100vh - 80px)`,
|
|
41
41
|
overflowY: 'auto',
|
|
42
|
+
...styles,
|
|
42
43
|
}}
|
|
43
44
|
>
|
|
44
45
|
{children}
|
|
@@ -9,7 +9,10 @@ import { cn } from '@/lib/utils';
|
|
|
9
9
|
import { EllipsisVertical, Funnel, Plus } from 'lucide-react';
|
|
10
10
|
import { cloneElement, ReactElement, ReactNode } from 'react';
|
|
11
11
|
import { useDispatch } from 'react-redux';
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
resetStateForUniqueId,
|
|
14
|
+
setFilterByNameForUniqueId,
|
|
15
|
+
} from '../../../redux/slices/pageHeaderSlice';
|
|
13
16
|
import { SearchBarProps } from '../../Input/SearchBar/SearchBar';
|
|
14
17
|
import { Breadcrumbs } from '../../Navigation/Breadcrumbs/Breadcrumbs';
|
|
15
18
|
import { PageHeaderSearchBar } from './components/SearchBar';
|
|
@@ -144,12 +147,24 @@ const PageHeader = ({
|
|
|
144
147
|
useMenuSlot={true}
|
|
145
148
|
menuSlot={() => (
|
|
146
149
|
<div className="flex flex-col gap-2 p-1 w-80">
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
<div className="flex justify-between">
|
|
151
|
+
<Typography
|
|
152
|
+
variant={'lead'}
|
|
153
|
+
className="flex gap-1 items-center p-1 pb-0 text-sm"
|
|
154
|
+
>
|
|
155
|
+
<Funnel size={16} /> Advanced Filters
|
|
156
|
+
</Typography>
|
|
157
|
+
<Button
|
|
158
|
+
variant={'link'}
|
|
159
|
+
size={'sm'}
|
|
160
|
+
className="text-destructive hover:no-underline"
|
|
161
|
+
onClick={() =>
|
|
162
|
+
dispatch(resetStateForUniqueId({ uniqueId }))
|
|
163
|
+
}
|
|
164
|
+
>
|
|
165
|
+
Clear
|
|
166
|
+
</Button>
|
|
167
|
+
</div>
|
|
153
168
|
<Separator />
|
|
154
169
|
{filterButtonProps.components.map((component, index) => (
|
|
155
170
|
<div key={index} className="w-full ">
|
|
@@ -91,11 +91,22 @@ export const Breadcrumbs = ({
|
|
|
91
91
|
return breadcrumbItems.map((item, index) => (
|
|
92
92
|
<BreadcrumbItem key={index}>
|
|
93
93
|
{item.isLast ? (
|
|
94
|
-
<BreadcrumbPage>
|
|
94
|
+
<BreadcrumbPage>
|
|
95
|
+
<Typography variant={'small'} className="text-semibold">
|
|
96
|
+
{item.label}
|
|
97
|
+
</Typography>
|
|
98
|
+
</BreadcrumbPage>
|
|
95
99
|
) : (
|
|
96
100
|
<>
|
|
97
101
|
<BreadcrumbLink asChild>
|
|
98
|
-
<Link to={item.path}>
|
|
102
|
+
<Link to={item.path}>
|
|
103
|
+
<Typography
|
|
104
|
+
variant={'small'}
|
|
105
|
+
className="text-semibold text-muted-foreground"
|
|
106
|
+
>
|
|
107
|
+
{item.label}
|
|
108
|
+
</Typography>
|
|
109
|
+
</Link>
|
|
99
110
|
</BreadcrumbLink>
|
|
100
111
|
<BreadcrumbSeparator />
|
|
101
112
|
</>
|
|
@@ -113,7 +124,10 @@ export const Breadcrumbs = ({
|
|
|
113
124
|
<BreadcrumbItem>
|
|
114
125
|
<BreadcrumbLink asChild>
|
|
115
126
|
<Link to={firstItem.path}>
|
|
116
|
-
<Typography
|
|
127
|
+
<Typography
|
|
128
|
+
variant={'small'}
|
|
129
|
+
className="text-semibold text-muted-foreground"
|
|
130
|
+
>
|
|
117
131
|
{firstItem.label}
|
|
118
132
|
</Typography>
|
|
119
133
|
</Link>
|
|
@@ -137,7 +151,9 @@ export const Breadcrumbs = ({
|
|
|
137
151
|
</BreadcrumbItem>
|
|
138
152
|
<BreadcrumbItem>
|
|
139
153
|
<BreadcrumbPage>
|
|
140
|
-
<Typography variant={'small'}>
|
|
154
|
+
<Typography variant={'small'} className="text-semibold">
|
|
155
|
+
{lastItem.label}
|
|
156
|
+
</Typography>
|
|
141
157
|
</BreadcrumbPage>
|
|
142
158
|
</BreadcrumbItem>
|
|
143
159
|
</>
|
|
@@ -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 } from 'react';
|
|
3
8
|
|
|
4
9
|
/**
|
|
@@ -100,7 +105,7 @@ const Dialog = ({
|
|
|
100
105
|
showCloseButton={showCloseButton}
|
|
101
106
|
>
|
|
102
107
|
{title && (
|
|
103
|
-
<DialogHeader className="pl-4
|
|
108
|
+
<DialogHeader className="pl-4 py-4 border-b">
|
|
104
109
|
<ShadcnDialogTitle>{title}</ShadcnDialogTitle>
|
|
105
110
|
</DialogHeader>
|
|
106
111
|
)}
|
|
@@ -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>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cn } from '@/lib/utils';
|
|
2
2
|
import { Typography } from '@/shadcn-components/DataDisplay/Typography/Typography';
|
|
3
|
-
import { Button } from '@/shadcn-components/Input/Button/Button';
|
|
4
3
|
import { Check } from 'lucide-react';
|
|
5
4
|
import React from 'react';
|
|
6
5
|
import { StepperVariantProps } from './Stepper';
|
|
@@ -10,6 +9,48 @@ import {
|
|
|
10
9
|
handleStepClick as handleStepClickUtil,
|
|
11
10
|
} from './utils';
|
|
12
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Native HTML button component for step indicators
|
|
14
|
+
* Uses forwardRef to properly expose DOM node for positioning calculations
|
|
15
|
+
*/
|
|
16
|
+
interface StepIndicatorProps
|
|
17
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
18
|
+
variant?: 'default' | 'secondary' | 'inactive';
|
|
19
|
+
children: React.ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const StepIndicator = React.forwardRef<HTMLButtonElement, StepIndicatorProps>(
|
|
23
|
+
function StepIndicator(
|
|
24
|
+
{ variant = 'default', className, children, ...props },
|
|
25
|
+
ref,
|
|
26
|
+
) {
|
|
27
|
+
const baseStyles =
|
|
28
|
+
'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-8 w-8 rounded-full duration-200';
|
|
29
|
+
|
|
30
|
+
const variantStyles = {
|
|
31
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
32
|
+
secondary:
|
|
33
|
+
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
34
|
+
inactive: 'bg-muted text-muted-foreground hover:bg-muted/80',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<button
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={cn(
|
|
41
|
+
baseStyles,
|
|
42
|
+
variantStyles[variant],
|
|
43
|
+
'cursor-pointer',
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
{children}
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
13
54
|
const VerticalStepper: React.FC<StepperVariantProps> = ({
|
|
14
55
|
activeStep,
|
|
15
56
|
steps,
|
|
@@ -34,19 +75,18 @@ const VerticalStepper: React.FC<StepperVariantProps> = ({
|
|
|
34
75
|
{/* Left side: Indicator + Connector */}
|
|
35
76
|
<div className="flex flex-col items-center">
|
|
36
77
|
{/* Step Indicator */}
|
|
37
|
-
<
|
|
78
|
+
<StepIndicator
|
|
38
79
|
onClick={
|
|
39
80
|
isClickable ? () => handleStepClick(index) : () => {}
|
|
40
81
|
}
|
|
41
82
|
variant={state === 'inactive' ? 'secondary' : 'default'}
|
|
42
|
-
className="h-8 w-8 rounded-full duration-200"
|
|
43
83
|
>
|
|
44
84
|
{state === 'completed' ? (
|
|
45
85
|
<Check className="w-4 h-4" />
|
|
46
86
|
) : (
|
|
47
87
|
index + 1
|
|
48
88
|
)}
|
|
49
|
-
</
|
|
89
|
+
</StepIndicator>
|
|
50
90
|
|
|
51
91
|
{/* Vertical Connector */}
|
|
52
92
|
{!isLast && (
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
TabsList,
|
|
7
7
|
TabsTrigger,
|
|
8
8
|
} from '@/shadcn-components/Navigation/Tabs/Tabs';
|
|
9
|
-
import { useEffect, useState } from 'react';
|
|
9
|
+
import { CSSProperties, useEffect, useState } from 'react';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Configuration for an individual tab in TabsContainer
|
|
@@ -42,6 +42,8 @@ export interface TabsContainerProps {
|
|
|
42
42
|
tabsListClassName?: string;
|
|
43
43
|
/** Optional CSS class names for each TabsTrigger element */
|
|
44
44
|
tabsTriggerClassName?: string;
|
|
45
|
+
/** Optional inline styles for the TabsContent container */
|
|
46
|
+
tabsContainerStyles?: CSSProperties;
|
|
45
47
|
/** Layout variant - 'fixed' uses viewport height, 'dynamic' uses content height. Defaults to 'fixed' */
|
|
46
48
|
variant?: 'fixed' | 'dynamic';
|
|
47
49
|
}
|
|
@@ -61,6 +63,7 @@ export interface TabsContainerProps {
|
|
|
61
63
|
* @param {string} [props.className] - CSS classes for the Tabs root
|
|
62
64
|
* @param {string} [props.tabsListClassName] - CSS classes for the TabsList
|
|
63
65
|
* @param {string} [props.tabsTriggerClassName] - CSS classes for TabsTrigger elements
|
|
66
|
+
* @param {CSSProperties} [props.tabsContainerStyles] - Inline styles for TabsContent
|
|
64
67
|
* @param {'fixed' | 'dynamic'} [props.variant='fixed'] - Layout mode for tab content
|
|
65
68
|
*
|
|
66
69
|
* @returns {React.ReactElement} A Tabs component with tab list and content area
|
|
@@ -114,6 +117,7 @@ export const TabsContainer = ({
|
|
|
114
117
|
className,
|
|
115
118
|
tabsListClassName,
|
|
116
119
|
tabsTriggerClassName,
|
|
120
|
+
tabsContainerStyles,
|
|
117
121
|
variant = 'fixed',
|
|
118
122
|
}: TabsContainerProps) => {
|
|
119
123
|
const isSmallScreen = useMediaQuery('(max-width: 768px)');
|
|
@@ -168,6 +172,7 @@ export const TabsContainer = ({
|
|
|
168
172
|
? `calc(100vh - 98px)`
|
|
169
173
|
: `calc(100vh - 80px)`
|
|
170
174
|
: 'max-content',
|
|
175
|
+
...tabsContainerStyles,
|
|
171
176
|
}}
|
|
172
177
|
>
|
|
173
178
|
{component}
|