@company-manager/docs

Composants UI

Documentation des composants UI de Company Manager.

Composants UI

Documentation complète des composants UI de Company Manager.

Composants de Base

Button

// components/ui/Button.tsx
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
  size?: 'sm' | 'md' | 'lg';
  loading?: boolean;
  icon?: React.ReactNode;
}

export const Button: React.FC<ButtonProps> = ({
  variant = 'primary',
  size = 'md',
  loading,
  icon,
  children,
  ...props
}) => {
  return (
    <button
      className={cn(
        'inline-flex items-center justify-center',
        'rounded-md font-medium transition-colors',
        'focus:outline-none focus:ring-2',
        'disabled:opacity-50 disabled:pointer-events-none',
        variants[variant],
        sizes[size]
      )}
      disabled={loading || props.disabled}
      {...props}
    >
      {loading && <Spinner className="mr-2" />}
      {icon && !loading && <span className="mr-2">{icon}</span>}
      {children}
    </button>
  );
};

Input

// components/ui/Input.tsx
interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  label?: string;
  error?: string;
  hint?: string;
}

export const Input: React.FC<InputProps> = ({
  label,
  error,
  hint,
  ...props
}) => {
  return (
    <div className="space-y-1">
      {label && (
        <label className="text-sm font-medium">
          {label}
        </label>
      )}
      <input
        className={cn(
          'w-full rounded-md border',
          'px-3 py-2',
          'focus:outline-none focus:ring-2',
          error ? 'border-red-500' : 'border-gray-300'
        )}
        {...props}
      />
      {error && (
        <p className="text-sm text-red-500">{error}</p>
      )}
      {hint && !error && (
        <p className="text-sm text-gray-500">{hint}</p>
      )}
    </div>
  );
};

Composants de Données

DataTable

// components/ui/DataTable.tsx
interface DataTableProps<T> {
  data: T[];
  columns: Column<T>[];
  loading?: boolean;
  pagination?: {
    page: number;
    pageSize: number;
    total: number;
    onPageChange: (page: number) => void;
  };
  sorting?: {
    sortBy: string;
    sortOrder: 'asc' | 'desc';
    onSort: (field: string) => void;
  };
}

export const DataTable = <T extends {}>({
  data,
  columns,
  loading,
  pagination,
  sorting,
}: DataTableProps<T>) => {
  return (
    <div className="overflow-x-auto">
      <table className="w-full">
        <thead>
          <tr>
            {columns.map(column => (
              <th
                key={column.key}
                onClick={() => sorting?.onSort(column.key)}
                className={cn(
                  'px-4 py-2 text-left',
                  sorting && 'cursor-pointer'
                )}
              >
                {column.title}
                {sorting?.sortBy === column.key && (
                  <SortIcon order={sorting.sortOrder} />
                )}
              </th>
            ))}
          </tr>
        </thead>
        <tbody>
          {loading ? (
            <tr>
              <td colSpan={columns.length}>
                <LoadingState />
              </td>
            </tr>
          ) : (
            data.map((row, index) => (
              <tr key={index}>
                {columns.map(column => (
                  <td key={column.key} className="px-4 py-2">
                    {column.render
                      ? column.render(row)
                      : row[column.key]}
                  </td>
                ))}
              </tr>
            ))
          )}
        </tbody>
      </table>
      {pagination && (
        <Pagination
          page={pagination.page}
          pageSize={pagination.pageSize}
          total={pagination.total}
          onChange={pagination.onPageChange}
        />
      )}
    </div>
  );
};

Form

// components/ui/Form.tsx
interface FormProps<T> extends UseFormReturn<T> {
  onSubmit: SubmitHandler<T>;
  children: React.ReactNode;
}

export const Form = <T extends {}>({
  onSubmit,
  children,
  ...form
}: FormProps<T>) => {
  return (
    <FormProvider {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)}>
        {children}
      </form>
    </FormProvider>
  );
};

// Exemple d'utilisation
const UserForm = () => {
  const form = useForm<User>({
    defaultValues: {
      name: '',
      email: '',
      role: 'USER',
    },
  });

  const onSubmit = async (data: User) => {
    await createUser(data);
  };

  return (
    <Form {...form} onSubmit={onSubmit}>
      <Input name="name" label="Nom" />
      <Input name="email" label="Email" type="email" />
      <Select
        name="role"
        label="Rôle"
        options={[
          { value: 'USER', label: 'Utilisateur' },
          { value: 'ADMIN', label: 'Administrateur' },
        ]}
      />
      <Button type="submit">Créer</Button>
    </Form>
  );
};

Composants de Feedback

Toast

// components/ui/Toast.tsx
import { toast } from 'sonner';

interface ToastProps {
  title: string;
  description?: string;
  type?: 'success' | 'error' | 'warning' | 'info';
}

export const showToast = ({
  title,
  description,
  type = 'info',
}: ToastProps) => {
  toast[type](title, {
    description,
    duration: 5000,
  });
};

// Exemple d'utilisation
const handleSave = async () => {
  try {
    await saveData();
    showToast({
      type: 'success',
      title: 'Sauvegardé',
      description: 'Les modifications ont été enregistrées.',
    });
  } catch (error) {
    showToast({
      type: 'error',
      title: 'Erreur',
      description: error.message,
    });
  }
};

Dialog

// components/ui/Dialog.tsx
interface DialogProps {
  open: boolean;
  onClose: () => void;
  title: string;
  description?: string;
  children: React.ReactNode;
}

export const Dialog: React.FC<DialogProps> = ({
  open,
  onClose,
  title,
  description,
  children,
}) => {
  return (
    <DialogPrimitive.Root open={open} onOpenChange={onClose}>
      <DialogPrimitive.Portal>
        <DialogPrimitive.Overlay className="fixed inset-0 bg-black/50" />
        <DialogPrimitive.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
          <div className="bg-white rounded-lg shadow-lg p-6 w-[32rem] max-w-full">
            <DialogPrimitive.Title className="text-lg font-medium">
              {title}
            </DialogPrimitive.Title>
            {description && (
              <DialogPrimitive.Description className="mt-2 text-gray-500">
                {description}
              </DialogPrimitive.Description>
            )}
            <div className="mt-4">{children}</div>
          </div>
        </DialogPrimitive.Content>
      </DialogPrimitive.Portal>
    </DialogPrimitive.Root>
  );
};

Composants de Navigation

Tabs

// components/ui/Tabs.tsx
interface Tab {
  key: string;
  title: string;
  content: React.ReactNode;
}

interface TabsProps {
  tabs: Tab[];
  defaultTab?: string;
  onChange?: (key: string) => void;
}

export const Tabs: React.FC<TabsProps> = ({
  tabs,
  defaultTab,
  onChange,
}) => {
  const [activeTab, setActiveTab] = useState(defaultTab || tabs[0].key);

  const handleChange = (key: string) => {
    setActiveTab(key);
    onChange?.(key);
  };

  return (
    <div>
      <div className="border-b border-gray-200">
        <nav className="-mb-px flex space-x-8">
          {tabs.map(tab => (
            <button
              key={tab.key}
              onClick={() => handleChange(tab.key)}
              className={cn(
                'py-4 px-1 border-b-2 font-medium text-sm',
                activeTab === tab.key
                  ? 'border-primary-500 text-primary-600'
                  : 'border-transparent text-gray-500 hover:text-gray-700'
              )}
            >
              {tab.title}
            </button>
          ))}
        </nav>
      </div>
      <div className="mt-4">
        {tabs.find(tab => tab.key === activeTab)?.content}
      </div>
    </div>
  );
};

Bonnes Pratiques

Accessibilité

  1. Utilisation des attributs ARIA appropriés
  2. Support du clavier
  3. Contraste des couleurs suffisant
  4. Messages d'erreur explicites

Performance

  1. Code splitting des composants
  2. Lazy loading des images
  3. Optimisation des re-renders
  4. Utilisation de memo quand nécessaire

Maintenance

  1. Documentation des props
  2. Tests des composants
  3. Storybook pour le développement
  4. Thèmes et styles cohérents