Spaces:
Sleeping
Sleeping
File size: 3,896 Bytes
0cfd364 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | import React, { forwardRef } from 'react';
import { ChevronDown } from 'lucide-react';
import { classNames } from '@/utils/helpers';
interface SelectOption {
value: string;
label: string;
disabled?: boolean;
}
interface SelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, 'children'> {
label?: string;
error?: string;
hint?: string;
options: SelectOption[];
placeholder?: string;
}
export const Select = forwardRef<HTMLSelectElement, SelectProps>(
({ label, error, hint, options, placeholder, className, id, ...props }, ref) => {
const selectId = id || `select-${Math.random().toString(36).slice(2, 9)}`;
return (
<div className="w-full">
{label && (
<label
htmlFor={selectId}
className="block text-sm font-medium text-dark-300 mb-1.5"
>
{label}
</label>
)}
<div className="relative">
<select
ref={ref}
id={selectId}
className={classNames(
'w-full px-3 py-2 pr-10 bg-dark-900 border border-dark-600 rounded-lg',
'text-dark-100 appearance-none cursor-pointer',
'focus:outline-none focus:border-accent-primary focus:ring-1 focus:ring-accent-primary/50',
'transition-colors duration-200',
error && 'border-red-500 focus:border-red-500 focus:ring-red-500/50',
className
)}
{...props}
>
{placeholder && (
<option value="" disabled>
{placeholder}
</option>
)}
{options.map((option) => (
<option
key={option.value}
value={option.value}
disabled={option.disabled}
>
{option.label}
</option>
))}
</select>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none text-dark-500">
<ChevronDown className="w-4 h-4" />
</div>
</div>
{(error || hint) && (
<p
className={classNames(
'mt-1.5 text-sm',
error ? 'text-red-400' : 'text-dark-500'
)}
>
{error || hint}
</p>
)}
</div>
);
}
);
Select.displayName = 'Select';
interface ToggleProps {
label?: string;
description?: string;
checked: boolean;
onChange: (checked: boolean) => void;
disabled?: boolean;
}
export const Toggle: React.FC<ToggleProps> = ({
label,
description,
checked,
onChange,
disabled = false,
}) => {
return (
<label
className={classNames(
'flex items-center justify-between gap-4 cursor-pointer',
disabled && 'opacity-50 cursor-not-allowed'
)}
>
<div>
{label && (
<span className="block text-sm font-medium text-dark-200">
{label}
</span>
)}
{description && (
<span className="block text-sm text-dark-500 mt-0.5">
{description}
</span>
)}
</div>
<button
type="button"
role="switch"
aria-checked={checked}
disabled={disabled}
onClick={() => !disabled && onChange(!checked)}
className={classNames(
'relative inline-flex h-6 w-11 items-center rounded-full transition-colors',
'focus:outline-none focus:ring-2 focus:ring-accent-primary/50',
checked ? 'bg-accent-primary' : 'bg-dark-600'
)}
>
<span
className={classNames(
'inline-block h-4 w-4 transform rounded-full bg-white transition-transform',
checked ? 'translate-x-6' : 'translate-x-1'
)}
/>
</button>
</label>
);
};
export default Select;
|