@campxdev/react-blueprint 3.0.0-alpha.7 → 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/index.d.ts CHANGED
@@ -1236,7 +1236,7 @@ interface CardProps extends Omit<React$1.ComponentProps<typeof Card$1>, 'title'
1236
1236
  declare const Card: React$1.ForwardRefExoticComponent<Omit<CardProps, "ref"> & React$1.RefAttributes<HTMLDivElement>>;
1237
1237
 
1238
1238
  declare const badgeVariants: (props?: ({
1239
- variant?: "default" | "secondary" | "destructive" | "outline" | null | undefined;
1239
+ variant?: "default" | "destructive" | "outline" | "secondary" | null | undefined;
1240
1240
  } & class_variance_authority_dist_types.ClassProp) | undefined) => string;
1241
1241
  declare function Badge({ className, variant, asChild, ...props }: React$1.ComponentProps<'span'> & VariantProps<typeof badgeVariants> & {
1242
1242
  asChild?: boolean;
@@ -2024,7 +2024,7 @@ type TooltipProps = {
2024
2024
  declare const Tooltip: ({ children, title, placement, open, defaultOpen, onOpenChange, delayDuration, }: TooltipProps) => react_jsx_runtime.JSX.Element;
2025
2025
 
2026
2026
  declare const buttonVariants: (props?: ({
2027
- variant?: "input" | "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
2027
+ variant?: "input" | "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
2028
2028
  size?: "default" | "sm" | "lg" | "icon" | "icon-sm" | "icon-lg" | null | undefined;
2029
2029
  } & class_variance_authority_dist_types.ClassProp) | undefined) => string;
2030
2030
 
package/dist/styles.css CHANGED
@@ -2573,6 +2573,12 @@
2573
2573
  border-color: var(--ring);
2574
2574
  }
2575
2575
  }
2576
+ .focus-visible\:ring-2 {
2577
+ &:focus-visible {
2578
+ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
2579
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
2580
+ }
2581
+ }
2576
2582
  .focus-visible\:ring-\[3px\] {
2577
2583
  &:focus-visible {
2578
2584
  --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
@@ -2600,6 +2606,12 @@
2600
2606
  }
2601
2607
  }
2602
2608
  }
2609
+ .focus-visible\:ring-offset-2 {
2610
+ &:focus-visible {
2611
+ --tw-ring-offset-width: 2px;
2612
+ --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
2613
+ }
2614
+ }
2603
2615
  .focus-visible\:outline-1 {
2604
2616
  &:focus-visible {
2605
2617
  outline-style: var(--tw-outline-style);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@campxdev/react-blueprint",
3
- "version": "3.0.0-alpha.7",
3
+ "version": "3.0.0-alpha.8",
4
4
  "description": "React UI component library for CampX applications",
5
5
  "author": "CampX",
6
6
  "license": "MIT",
@@ -181,7 +181,7 @@ export const DatePickerInput = ({
181
181
  {/* Helper Text / Error */}
182
182
  {(helperText || error) && (
183
183
  <Typography
184
- variant="small"
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="small"
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}
@@ -129,7 +129,10 @@ export const PasswordField = ({
129
129
  label={label}
130
130
  required={required}
131
131
  name={name}
132
- containerProps={containerProps}
132
+ containerProps={{
133
+ ...(fullWidth && { style: { width: '100%' } }),
134
+ ...containerProps,
135
+ }}
133
136
  >
134
137
  {content}
135
138
  </LabelWrapper>
@@ -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 > 0) {
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
- // Calculate positions initially
66
- calculatePositions();
103
+ // Use requestAnimationFrame to ensure DOM is ready
104
+ const animationFrameId = requestAnimationFrame(() => {
105
+ calculatePositions();
106
+ });
67
107
 
68
- // Recalculate on window resize
108
+ // Also recalculate on window resize
69
109
  window.addEventListener('resize', calculatePositions);
70
110
 
71
- // Cleanup listener on unmount
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
- <Button
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
- </Button>
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="flex items-start justify-between relative">
159
- {steps.map((step, index) => {
160
- const state = getStepState(index, activeStep);
161
- const isClickable = allowNavigation && onStepClick;
162
-
163
- return (
164
- <div key={index} className="flex items-start flex-1">
165
- <div className="flex flex-col items-center">
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
- <Button
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-4 h-4" />
217
+ <Check className="w-5 h-5" />
177
218
  ) : (
178
219
  index + 1
179
220
  )}
180
- </Button>
221
+ </StepIndicator>
181
222
 
182
- {/* Step Content */}
183
- <div className="flex flex-col gap-1 mt-3 text-center px-2">
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
- </div>
205
- );
206
- })}
207
-
208
- {/* Connectors - Positioned absolutely based on calculated positions */}
209
- {connectorPositions.map((position, index) => (
210
- <div
211
- key={`connector-${index}`}
212
- className={cn(
213
- 'absolute h-0.5 rounded-full transition-colors duration-200',
214
- getConnectorClasses(index, activeStep),
215
- )}
216
- style={{
217
- left: `${position.left}px`,
218
- width: `${position.width}px`,
219
- top: `${position.top}px`,
220
- }}
221
- aria-hidden="true"
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
- <Button
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
- </Button>
89
+ </StepIndicator>
50
90
 
51
91
  {/* Vertical Connector */}
52
92
  {!isLast && (