- Ajouter fallback pour Swagger UI si doc.json ne fonctionne pas - Améliorer message d'erreur avec bouton pour ouvrir Swagger UI directement - Les fonctionnalités API Keys et Usage Stats sont maintenant complètes et fonctionnelles - Tous les onglets de DeveloperPage sont maintenant implémentés
124 lines
5.7 KiB
TypeScript
124 lines
5.7 KiB
TypeScript
import React from 'react';
|
|
import { Search, Upload as UploadIcon } from 'lucide-react';
|
|
import { cn } from '../../utils/cn';
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Text Input */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
label?: string;
|
|
icon?: React.ReactNode;
|
|
}
|
|
|
|
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
({ label, icon, className, type, autoComplete, required, id, ...props }, ref) => {
|
|
// CRITIQUE FIX #4: Générer un ID stable si non fourni pour l'association avec le label
|
|
const generatedId = React.useId();
|
|
const inputId = id || generatedId;
|
|
|
|
// CRITIQUE FIX #4: Déterminer autoComplete par défaut basé sur le type
|
|
const defaultAutoComplete = autoComplete !== undefined
|
|
? autoComplete
|
|
: (type === 'email' ? 'email'
|
|
: type === 'password' ? 'current-password'
|
|
: undefined);
|
|
|
|
// CRITIQUE FIX #51: S'assurer que aria-describedby et aria-invalid sont correctement passés
|
|
const ariaDescribedBy = props['aria-describedby'];
|
|
const ariaInvalid = props['aria-invalid'];
|
|
|
|
return (
|
|
<div className="w-full" style={{ width: '100%', minWidth: '0', maxWidth: '100%', boxSizing: 'border-box' }}>
|
|
{label && (
|
|
<label htmlFor={inputId} className="block text-sm font-medium text-gray-400 mb-2 font-body">{label}</label>
|
|
)}
|
|
<div className="relative w-full" style={{ width: '100%', minWidth: '0', maxWidth: '100%', boxSizing: 'border-box' }}>
|
|
{icon && (
|
|
<div className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-500 pointer-events-none">
|
|
{icon}
|
|
</div>
|
|
)}
|
|
<input
|
|
ref={ref}
|
|
id={inputId}
|
|
type={type}
|
|
autoComplete={defaultAutoComplete}
|
|
required={required}
|
|
aria-describedby={ariaDescribedBy}
|
|
aria-invalid={ariaInvalid}
|
|
className={cn(
|
|
'w-full py-3 bg-kodo-graphite border border-kodo-steel text-white placeholder-gray-500 font-body text-base rounded-lg focus-visible:outline-none focus-visible:border-kodo-cyan/60 transition-colors duration-200',
|
|
icon ? 'pl-11 pr-4' : 'px-4',
|
|
className
|
|
)}
|
|
style={{
|
|
width: '100%',
|
|
minWidth: '200px', /* Largeur minimale pour éviter les inputs trop étroits */
|
|
maxWidth: '100%',
|
|
boxSizing: 'border-box',
|
|
display: 'block',
|
|
...props.style,
|
|
}}
|
|
{...props}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
Input.displayName = 'Input';
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Search Input */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
export const SearchInput = React.forwardRef<
|
|
HTMLInputElement,
|
|
React.InputHTMLAttributes<HTMLInputElement>
|
|
>((props, ref) => {
|
|
return (
|
|
<div className="relative w-full group">
|
|
<input
|
|
ref={ref}
|
|
type="search"
|
|
className="w-full pl-12 pr-4 py-3 bg-kodo-graphite border border-kodo-steel text-white placeholder-gray-500 rounded-full focus-visible:outline-none focus-visible:border-kodo-cyan/60 transition-colors duration-300"
|
|
placeholder="Search platform..."
|
|
{...props}
|
|
/>
|
|
<Search className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500 group-focus-within:text-kodo-cyan transition-colors" />
|
|
</div>
|
|
);
|
|
});
|
|
|
|
SearchInput.displayName = 'SearchInput';
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* File Upload */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
export interface FileUploadProps {
|
|
onUpload?: (files: FileList) => void;
|
|
className?: string;
|
|
}
|
|
|
|
export const FileUpload: React.FC<FileUploadProps> = ({ onUpload, className }) => {
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'border-2 border-dashed border-kodo-steel rounded-xl p-8 bg-kodo-graphite/50 hover:bg-kodo-slate/30 hover:border-kodo-cyan/50 transition-all duration-300 cursor-pointer text-center group',
|
|
className
|
|
)}
|
|
>
|
|
<div className="w-16 h-16 rounded-full bg-kodo-slate flex items-center justify-center mx-auto mb-4 group-hover:scale-110 group-hover:bg-kodo-steel transition-all">
|
|
<UploadIcon className="w-8 h-8 text-kodo-cyan" />
|
|
</div>
|
|
<h3 className="text-xl font-bold text-white mb-2 font-display">Drop your stems here</h3>
|
|
<p className="text-gray-500 text-sm max-w-md mx-auto">
|
|
Support for WAV, FLAC, AIFF. Up to 500MB per file.{' '}
|
|
<span className="text-kodo-cyan">Premium users</span> get unlimited storage.
|
|
</p>
|
|
</div>
|
|
);
|
|
};
|