Added Stepper component
This commit is contained in:
1206
apps/frontend/src/components/ui/stepper.tsx
Normal file
1206
apps/frontend/src/components/ui/stepper.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,15 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from '@/components/ui/select';
|
} from '@/components/ui/select';
|
||||||
|
import {
|
||||||
|
Stepper,
|
||||||
|
StepperIndicator,
|
||||||
|
StepperItem,
|
||||||
|
StepperList,
|
||||||
|
StepperSeparator,
|
||||||
|
StepperTitle,
|
||||||
|
StepperTrigger,
|
||||||
|
} from '@/components/ui/stepper';
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
import { ApiClientError, apiClient } from '@/lib/api-client';
|
import { ApiClientError, apiClient } from '@/lib/api-client';
|
||||||
import { useTheme } from '@/lib/theme';
|
import { useTheme } from '@/lib/theme';
|
||||||
@@ -265,49 +274,43 @@ function PlayzerBrand({ compact = false }: { compact?: boolean }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ProgressSteps({ currentStep, mobile = false }: { currentStep: number; mobile?: boolean }) {
|
function ProgressSteps({ currentStep, mobile = false }: { currentStep: number; mobile?: boolean }) {
|
||||||
const labels = ['Elegi cancha', 'Selecciona horario', 'Tus datos', 'Confirmacion'];
|
const steps = [
|
||||||
|
{ value: 'court', label: 'Elegi cancha' },
|
||||||
|
{ value: 'time', label: 'Selecciona horario' },
|
||||||
|
{ value: 'details', label: 'Tus datos' },
|
||||||
|
{ value: 'confirm', label: 'Confirmacion' },
|
||||||
|
];
|
||||||
|
const activeStep = steps[Math.max(0, Math.min(currentStep - 1, steps.length - 1))]?.value;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Stepper
|
||||||
className={
|
value={activeStep}
|
||||||
mobile
|
nonInteractive
|
||||||
? 'mx-auto grid w-full max-w-[230px] grid-cols-[1fr_1fr_1fr_1fr] items-center'
|
className={mobile ? 'mx-auto w-full max-w-[240px]' : 'max-w-[760px]'}
|
||||||
: 'flex items-center gap-5'
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{labels.map((label, index) => {
|
<StepperList className="items-center gap-0">
|
||||||
const step = index + 1;
|
{steps.map((step, index) => (
|
||||||
const isActive = step <= currentStep;
|
<StepperItem
|
||||||
|
key={step.value}
|
||||||
return (
|
value={step.value}
|
||||||
<div key={label} className="flex min-w-0 items-center">
|
completed={index < currentStep - 1}
|
||||||
<div
|
className="flex min-w-0 flex-1 items-center last:flex-none"
|
||||||
className={`flex size-7 shrink-0 items-center justify-center rounded-full text-xs font-semibold ${
|
|
||||||
isActive
|
|
||||||
? 'bg-emerald-500 text-white shadow-lg shadow-emerald-500/25'
|
|
||||||
: 'bg-white/10 text-white/65'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{step}
|
<StepperTrigger className="gap-2 rounded-full p-0 text-white/60 data-[state=active]:text-white data-[state=completed]:text-white">
|
||||||
</div>
|
<StepperIndicator className="size-7 border-0 bg-white/10 text-xs font-bold text-white/62 shadow-none data-[state=active]:bg-emerald-500 data-[state=active]:text-white data-[state=completed]:bg-emerald-500 data-[state=completed]:text-white">
|
||||||
|
{index + 1}
|
||||||
|
</StepperIndicator>
|
||||||
{!mobile && (
|
{!mobile && (
|
||||||
<span
|
<StepperTitle className="whitespace-nowrap text-xs font-semibold text-inherit">
|
||||||
className={`ml-3 text-xs ${isActive ? 'font-semibold text-white' : 'text-white/65'}`}
|
{step.label}
|
||||||
>
|
</StepperTitle>
|
||||||
{label}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
{index < labels.length - 1 && (
|
</StepperTrigger>
|
||||||
<div
|
<StepperSeparator className="mx-3 h-px min-w-6 flex-1 bg-white/10 data-[state=completed]:bg-emerald-500/80" />
|
||||||
className={`mx-2 h-px flex-1 ${isActive ? 'bg-emerald-500/70' : 'bg-white/10'} ${
|
</StepperItem>
|
||||||
mobile ? 'min-w-9' : 'w-8'
|
))}
|
||||||
}`}
|
</StepperList>
|
||||||
/>
|
</Stepper>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
61
apps/frontend/src/lib/compose-refs.ts
Normal file
61
apps/frontend/src/lib/compose-refs.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
type PossibleRef<T> = React.Ref<T> | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a given ref to a given value
|
||||||
|
* This utility takes care of different types of refs: callback refs and RefObject(s)
|
||||||
|
*/
|
||||||
|
function setRef<T>(ref: PossibleRef<T>, value: T) {
|
||||||
|
if (typeof ref === 'function') {
|
||||||
|
return ref(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref !== null && ref !== undefined) {
|
||||||
|
ref.current = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility to compose multiple refs together
|
||||||
|
* Accepts callback refs and RefObject(s)
|
||||||
|
*/
|
||||||
|
function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
||||||
|
return (node) => {
|
||||||
|
let hasCleanup = false;
|
||||||
|
const cleanups = refs.map((ref) => {
|
||||||
|
const cleanup = setRef(ref, node);
|
||||||
|
if (!hasCleanup && typeof cleanup === 'function') {
|
||||||
|
hasCleanup = true;
|
||||||
|
}
|
||||||
|
return cleanup;
|
||||||
|
});
|
||||||
|
|
||||||
|
// React <19 will log an error to the console if a callback ref returns a
|
||||||
|
// value. We don't use ref cleanups internally so this will only happen if a
|
||||||
|
// user's ref callback returns a value, which we only expect if they are
|
||||||
|
// using the cleanup functionality added in React 19.
|
||||||
|
if (hasCleanup) {
|
||||||
|
return () => {
|
||||||
|
for (let i = 0; i < cleanups.length; i++) {
|
||||||
|
const cleanup = cleanups[i];
|
||||||
|
if (typeof cleanup === 'function') {
|
||||||
|
cleanup();
|
||||||
|
} else {
|
||||||
|
setRef(refs[i], null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom hook that composes multiple refs
|
||||||
|
* Accepts callback refs and RefObject(s)
|
||||||
|
*/
|
||||||
|
function useComposedRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
||||||
|
return React.useCallback(composeRefs(...refs), refs);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { composeRefs, useComposedRefs };
|
||||||
Reference in New Issue
Block a user