because I got bored of customising my CV for every job
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

chore(ui): add StatusDot, update Progress, fix zod/cn imports

+104 -32
+1 -1
packages/ui/package.json
··· 32 32 "class-variance-authority": "^0.7.0", 33 33 "clsx": "^2.1.1", 34 34 "tailwind-merge": "^2.5.3", 35 - "zod": "^3.25.76" 35 + "zod": "^4.3.6" 36 36 }, 37 37 "devDependencies": { 38 38 "@tailwindcss/cli": "^4.1.16",
+62 -21
packages/ui/src/components/Progress.tsx
··· 8 8 useContext, 9 9 useState, 10 10 } from "react"; 11 - import { z } from "zod"; 11 + import { z } from "zod/v4"; 12 12 13 13 interface ProgressContextValue { 14 14 currentStep: number; ··· 32 32 "completed", 33 33 "pending", 34 34 "error", 35 + "disabled", 35 36 ]); 36 37 37 38 export const stepPropsSchema = z.object({ ··· 39 40 children: z.any().optional(), 40 41 index: z.number().int().nonnegative().optional(), 41 42 state: stepStateSchema.optional(), 43 + disabled: z.boolean().optional(), 42 44 }); 43 45 44 46 export type StepProps = z.infer<typeof stepPropsSchema>; ··· 65 67 return validateStepProps(element.props); 66 68 }; 67 69 68 - const stepContainerVariants = cva("flex items-center space-x-2", { 70 + const stepContainerVariants = cva("flex items-center space-x-2 transition-colors duration-200", { 69 71 variants: { 70 72 state: { 71 73 selected: "text-ctp-blue", 72 74 completed: "text-ctp-green", 73 75 pending: "text-ctp-subtext0", 74 76 error: "text-ctp-red", 77 + disabled: "text-ctp-overlay0 opacity-50", 75 78 }, 76 79 }, 77 80 defaultVariants: { ··· 80 83 }); 81 84 82 85 const stepIndicatorVariants = cva( 83 - "h-8 w-8 rounded-full flex items-center justify-center", 86 + "h-8 w-8 rounded-full flex items-center justify-center transition-colors duration-200", 84 87 { 85 88 variants: { 86 89 state: { ··· 88 91 completed: "bg-ctp-green text-ctp-base", 89 92 pending: "bg-ctp-surface0", 90 93 error: "bg-ctp-red text-ctp-base", 94 + disabled: "bg-ctp-surface0 opacity-50", 91 95 }, 92 96 }, 93 97 defaultVariants: { ··· 99 103 interface StepComponentProps { 100 104 name: string; 101 105 children?: ReactNode; 106 + state?: StepState; 107 + disabled?: boolean; 102 108 } 103 109 104 110 interface StepRenderProps { 105 111 name: string; 106 112 index: number; 107 113 state: StepState; 114 + clickable: boolean; 115 + onClick?: () => void; 108 116 } 109 117 110 118 const Step = (_props: StepComponentProps) => { ··· 113 121 114 122 Step.displayName = "Progress.Step"; 115 123 116 - const StepRenderer = ({ name, index, state }: StepRenderProps) => { 124 + const StepRenderer = ({ 125 + name, 126 + index, 127 + state, 128 + clickable, 129 + onClick, 130 + }: StepRenderProps) => { 117 131 const stepNumber = index + 1; 132 + const isClickable = clickable && state !== "disabled"; 118 133 119 134 return ( 120 - <div className={stepContainerVariants({ state })}> 135 + <button 136 + type="button" 137 + className={`${stepContainerVariants({ state })} ${isClickable ? "cursor-pointer" : "cursor-default"}`} 138 + onClick={isClickable ? onClick : undefined} 139 + disabled={!isClickable} 140 + > 121 141 <div className={stepIndicatorVariants({ state })}>{stepNumber}</div> 122 142 <span className="font-medium">{name}</span> 123 - </div> 143 + </button> 124 144 ); 125 145 }; 126 146 127 147 const ConnectorLine = ({ isCompleted }: { isCompleted: boolean }) => ( 128 148 <div 129 - className={`h-1 w-16 ${isCompleted ? "bg-ctp-green" : "bg-ctp-surface0"}`} 149 + className={`h-1 w-16 transition-colors duration-200 ${isCompleted ? "bg-ctp-green" : "bg-ctp-surface0"}`} 130 150 /> 131 151 ); 132 152 133 153 interface ProgressProps { 134 154 initialStep?: number; 155 + currentStep?: number; 156 + onStepChange?: (step: number) => void; 135 157 onNext?: () => void; 136 158 onBack?: () => void; 137 159 children: ReactNode; ··· 139 161 140 162 export const Progress = ({ 141 163 initialStep = 0, 164 + currentStep: controlledStep, 165 + onStepChange, 142 166 onNext, 143 167 onBack, 144 168 children, 145 169 }: ProgressProps) => { 146 - const [currentStep, setCurrentStep] = useState(initialStep); 170 + const [internalStep, setInternalStep] = useState(initialStep); 171 + 172 + const isControlled = controlledStep !== undefined; 173 + const currentStep = isControlled ? controlledStep : internalStep; 147 174 148 175 const steps = Children.toArray(children).filter( 149 176 (child): child is ReactElement => ··· 157 184 const canGoNext = currentStep < totalSteps - 1; 158 185 const canGoBack = currentStep > 0; 159 186 187 + const goToStep = (step: number) => { 188 + if (isControlled) { 189 + onStepChange?.(step); 190 + } else { 191 + setInternalStep(step); 192 + } 193 + }; 194 + 160 195 const handleNext = () => { 161 - if (!canGoNext) { 162 - return; 163 - } 164 - setCurrentStep((prev) => prev + 1); 196 + if (!canGoNext) return; 197 + goToStep(currentStep + 1); 165 198 onNext?.(); 166 199 }; 167 200 168 201 const handleBack = () => { 169 - if (!canGoBack) { 170 - return; 171 - } 172 - setCurrentStep((prev) => prev - 1); 202 + if (!canGoBack) return; 203 + goToStep(currentStep - 1); 173 204 onBack?.(); 174 205 }; 175 206 ··· 198 229 return null; 199 230 } 200 231 232 + const isDisabled = stepProps.disabled === true; 201 233 const isCurrent = index === currentStep; 202 234 const isCompleted = index < currentStep; 203 - const stepState: StepState = isCurrent 204 - ? "selected" 205 - : isCompleted 206 - ? "completed" 207 - : "pending"; 235 + 236 + const stepState: StepState = isDisabled 237 + ? "disabled" 238 + : stepProps.state 239 + ? stepProps.state 240 + : isCurrent 241 + ? "selected" 242 + : isCompleted 243 + ? "completed" 244 + : "pending"; 245 + 246 + const clickable = !isDisabled && onStepChange !== undefined; 208 247 209 248 return ( 210 249 <div ··· 215 254 name={stepProps.name} 216 255 index={index} 217 256 state={stepState} 257 + clickable={clickable} 258 + onClick={() => goToStep(index)} 218 259 /> 219 260 {index < totalSteps - 1 && ( 220 261 <ConnectorLine isCompleted={isCompleted} />
+1 -1
packages/ui/src/components/SearchableSelect.tsx
··· 133 133 }; 134 134 135 135 return ( 136 - <div className="space-y-2 relative"> 136 + <div className="space-y-1 relative"> 137 137 {label && ( 138 138 <label htmlFor={selectId} className="text-sm font-medium text-ctp-text"> 139 139 {label}
+32
packages/ui/src/components/StatusDot.tsx
··· 1 + import { cva, type VariantProps } from "class-variance-authority"; 2 + import { cn } from "../lib/cn"; 3 + 4 + const statusDotVariants = cva("inline-block shrink-0 rounded-full", { 5 + variants: { 6 + color: { 7 + green: "bg-ctp-green", 8 + red: "bg-ctp-red", 9 + yellow: "bg-ctp-yellow", 10 + gray: "bg-ctp-overlay0", 11 + }, 12 + size: { 13 + sm: "h-2 w-2", 14 + md: "h-2.5 w-2.5", 15 + lg: "h-3 w-3", 16 + }, 17 + }, 18 + defaultVariants: { 19 + color: "green", 20 + size: "md", 21 + }, 22 + }); 23 + 24 + type StatusDotVariants = VariantProps<typeof statusDotVariants>; 25 + 26 + interface StatusDotProps extends StatusDotVariants { 27 + className?: string; 28 + } 29 + 30 + export const StatusDot = ({ color, size, className }: StatusDotProps) => ( 31 + <span className={cn(statusDotVariants({ color, size }), className)} /> 32 + );
+1 -1
packages/ui/src/components/TextInput.tsx
··· 64 64 const inputState = error ? "error" : state; 65 65 66 66 return ( 67 - <div className="space-y-2"> 67 + <div className="space-y-1"> 68 68 {label && ( 69 69 <label htmlFor={inputId} className="text-sm font-medium text-ctp-text"> 70 70 {label}
+1 -1
packages/ui/src/components/Textarea.tsx
··· 50 50 const textareaState = error ? "error" : state; 51 51 52 52 return ( 53 - <div className="space-y-2"> 53 + <div className="space-y-1"> 54 54 {label && ( 55 55 <label 56 56 htmlFor={textareaId}
+4 -4
packages/ui/src/components/Toast.tsx
··· 29 29 { 30 30 variants: { 31 31 level: { 32 - success: "bg-ctp-green/10 border-ctp-green/30 text-ctp-green", 33 - warning: "bg-ctp-yellow/10 border-ctp-yellow/30 text-ctp-yellow", 34 - info: "bg-ctp-blue/10 border-ctp-blue/30 text-ctp-blue", 35 - error: "bg-ctp-red/10 border-ctp-red/30 text-ctp-red", 32 + success: "bg-ctp-green/80 border-ctp-green/50 text-ctp-base", 33 + warning: "bg-ctp-yellow/80 border-ctp-yellow/50 text-ctp-base", 34 + info: "bg-ctp-blue/80 border-ctp-blue/50 text-ctp-base", 35 + error: "bg-ctp-red/80 border-ctp-red/50 text-ctp-base", 36 36 }, 37 37 visibility: { 38 38 visible: "translate-x-0 opacity-100",
+1
packages/ui/src/index.ts
··· 40 40 export { SearchableSelect } from "./components/SearchableSelect"; 41 41 export { Select } from "./components/Select"; 42 42 export { StatusBadge } from "./components/StatusBadge"; 43 + export { StatusDot } from "./components/StatusDot"; 43 44 export { 44 45 Table, 45 46 TableBody,
+1 -3
packages/ui/src/lib/cn.ts
··· 5 5 * Utility function to merge Tailwind CSS classes 6 6 * Combines clsx for conditional classes and tailwind-merge to resolve conflicts 7 7 */ 8 - export const cn = (...inputs: ClassValue[]) => { 9 - return twMerge(clsx(inputs)); 10 - }; 8 + export const cn = (...inputs: ClassValue[]) => twMerge(clsx(inputs));