feat: enhance dropdown components by integrating useClickOutside hook for improved user experience and accessibility in NewWorkshopDropdown and WorkshopTabs

This commit is contained in:
Julien Froidefond
2026-02-18 08:25:08 +01:00
parent d50a8a0266
commit ee13f8ba99
9 changed files with 189 additions and 197 deletions

View File

@@ -7,6 +7,7 @@ import { Input } from '@/components/ui/Input';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Badge';
import { Avatar } from '@/components/ui/Avatar';
import { Select } from '@/components/ui/Select';
import { getTeamMembersForShare, type TeamWithMembers } from '@/lib/share-utils';
import type { ShareRole } from '@prisma/client';
@@ -41,8 +42,10 @@ interface ShareModalProps {
helpText?: React.ReactNode;
}
const SELECT_STYLE =
'appearance-none rounded-lg border border-border bg-card px-3 py-2.5 pr-10 text-sm text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20';
const ROLE_OPTIONS = [
{ value: 'EDITOR', label: 'Éditeur' },
{ value: 'VIEWER', label: 'Lecteur' },
] as const;
export function ShareModal({
isOpen,
@@ -151,10 +154,12 @@ export function ShareModal({
className="flex-1"
required
/>
<select value={role} onChange={(e) => setRole(e.target.value as ShareRole)} className={SELECT_STYLE}>
<option value="EDITOR">Éditeur</option>
<option value="VIEWER">Lecteur</option>
</select>
<Select
value={role}
onChange={(e) => setRole(e.target.value as ShareRole)}
options={[...ROLE_OPTIONS]}
wrapperClassName="w-auto shrink-0 min-w-[7rem]"
/>
</div>
)}
@@ -171,30 +176,25 @@ export function ShareModal({
</p>
) : (
<div className="flex gap-2">
<div className="relative flex-1">
<select
value={selectedMemberId}
onChange={(e) => setSelectedMemberId(e.target.value)}
className={`w-full ${SELECT_STYLE}`}
required
>
<option value="">Sélectionner un membre</option>
{teamMembers.map((m) => (
<option key={m.id} value={m.id}>
{m.name || m.email} {m.name && `(${m.email})`}
</option>
))}
</select>
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2">
<svg className="h-4 w-4 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
<select value={role} onChange={(e) => setRole(e.target.value as ShareRole)} className={SELECT_STYLE}>
<option value="EDITOR">Éditeur</option>
<option value="VIEWER">Lecteur</option>
</select>
<Select
value={selectedMemberId}
onChange={(e) => setSelectedMemberId(e.target.value)}
options={[
{ value: '', label: 'Sélectionner un membre', disabled: true },
...teamMembers.map((m) => ({
value: m.id,
label: m.name ? `${m.name} (${m.email})` : m.email,
})),
]}
wrapperClassName="flex-1 min-w-0"
required
/>
<Select
value={role}
onChange={(e) => setRole(e.target.value as ShareRole)}
options={[...ROLE_OPTIONS]}
wrapperClassName="w-auto shrink-0 min-w-[7rem]"
/>
</div>
)}
</div>
@@ -211,39 +211,27 @@ export function ShareModal({
.
</p>
) : (
<>
<div className="relative">
<select
value={teamId}
onChange={(e) => setTeamId(e.target.value)}
className={`w-full ${SELECT_STYLE}`}
required
>
<option value="">Sélectionner une équipe</option>
{userTeams.map((team) => (
<option key={team.id} value={team.id}>
{team.name} {team.userRole === 'ADMIN' && '(Admin)'}
</option>
))}
</select>
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2">
<svg className="h-4 w-4 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
<div className="relative">
<select value={role} onChange={(e) => setRole(e.target.value as ShareRole)} className={`w-full ${SELECT_STYLE}`}>
<option value="EDITOR">Éditeur</option>
<option value="VIEWER">Lecteur</option>
</select>
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2">
<svg className="h-4 w-4 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
</>
<div className="flex gap-2">
<Select
value={teamId}
onChange={(e) => setTeamId(e.target.value)}
options={[
{ value: '', label: 'Sélectionner une équipe', disabled: true },
...userTeams.map((team) => ({
value: team.id,
label: `${team.name}${team.userRole === 'ADMIN' ? ' (Admin)' : ''}`,
})),
]}
wrapperClassName="flex-1 min-w-0"
required
/>
<Select
value={role}
onChange={(e) => setRole(e.target.value as ShareRole)}
options={[...ROLE_OPTIONS]}
wrapperClassName="w-auto shrink-0 min-w-[7rem]"
/>
</div>
)}
</div>
)}