fix(tickets): dropdowns ne sortent plus du viewport en bas d'écran (#3)
Remplace les <select> natifs de la vue Tickets (statut, priorité, tri, lien, agent assigné/assistant, sprint) par TicketViewportSelect, qui repositionne intelligemment son overlay selon l'espace disponible dans la fenêtre au lieu de toujours ouvrir vers le bas. QA vert : 80/80 (frontend/src/features/tickets), tsc --noEmit propre. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@ -17,6 +17,7 @@ import { useTicketDetail } from "./useTicketDetail";
|
||||
import { useProjectAgents } from "./useProjectAgents";
|
||||
import { TicketAssistantPanel } from "./TicketAssistantPanel";
|
||||
import { TicketPicker } from "./TicketPicker";
|
||||
import { TicketViewportSelect } from "./TicketViewportSelect";
|
||||
import {
|
||||
PriorityBadge,
|
||||
StatusBadge,
|
||||
@ -30,12 +31,6 @@ import {
|
||||
statusLabel,
|
||||
} from "./ticketMeta";
|
||||
|
||||
const selectClass = cn(
|
||||
"h-9 rounded-md bg-raised px-3 text-sm text-content",
|
||||
"border border-border outline-none transition-colors",
|
||||
"focus:border-primary disabled:cursor-not-allowed disabled:opacity-50",
|
||||
);
|
||||
|
||||
const textareaClass = cn(
|
||||
"w-full rounded-md bg-raised p-3 text-sm text-content",
|
||||
"border border-border outline-none transition-colors",
|
||||
@ -320,44 +315,38 @@ export function TicketDetail({
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<label className="flex items-center gap-2 text-xs text-muted">
|
||||
Status
|
||||
<select
|
||||
<TicketViewportSelect
|
||||
aria-label="ticket status"
|
||||
className={selectClass}
|
||||
value={t.status}
|
||||
disabled={vm.busy}
|
||||
onChange={(e) =>
|
||||
options={TICKET_STATUSES.map((status) => ({
|
||||
value: status,
|
||||
label: statusLabel(status),
|
||||
}))}
|
||||
onChange={(next) =>
|
||||
void vm.updateFields({
|
||||
status: e.target.value as (typeof TICKET_STATUSES)[number],
|
||||
status: next as (typeof TICKET_STATUSES)[number],
|
||||
})
|
||||
}
|
||||
>
|
||||
{TICKET_STATUSES.map((s) => (
|
||||
<option key={s} value={s}>
|
||||
{statusLabel(s)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 text-xs text-muted">
|
||||
Priority
|
||||
<select
|
||||
<TicketViewportSelect
|
||||
aria-label="ticket priority"
|
||||
className={selectClass}
|
||||
value={t.priority}
|
||||
disabled={vm.busy}
|
||||
onChange={(e) =>
|
||||
options={TICKET_PRIORITIES.map((priority) => ({
|
||||
value: priority,
|
||||
label: priorityLabel(priority),
|
||||
}))}
|
||||
onChange={(next) =>
|
||||
void vm.updateFields({
|
||||
priority:
|
||||
e.target.value as (typeof TICKET_PRIORITIES)[number],
|
||||
next as (typeof TICKET_PRIORITIES)[number],
|
||||
})
|
||||
}
|
||||
>
|
||||
{TICKET_PRIORITIES.map((p) => (
|
||||
<option key={p} value={p}>
|
||||
{priorityLabel(p)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</Section>
|
||||
@ -426,19 +415,16 @@ export function TicketDetail({
|
||||
{/* Add a link via the TicketPicker popup (#17): the kind is chosen
|
||||
here, the target ticket is selected in the popup (no manual #id). */}
|
||||
<div className="mt-1 flex flex-wrap items-center gap-2">
|
||||
<select
|
||||
<TicketViewportSelect
|
||||
aria-label="link kind"
|
||||
className={selectClass}
|
||||
value={linkKind}
|
||||
disabled={vm.busy}
|
||||
onChange={(e) => setLinkKind(e.target.value as TicketLinkKind)}
|
||||
>
|
||||
{TICKET_LINK_KINDS.map((k) => (
|
||||
<option key={k} value={k}>
|
||||
{linkKindLabel(k)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
options={TICKET_LINK_KINDS.map((kind) => ({
|
||||
value: kind,
|
||||
label: linkKindLabel(kind),
|
||||
}))}
|
||||
onChange={(next) => setLinkKind(next as TicketLinkKind)}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
aria-label="add link"
|
||||
@ -479,24 +465,25 @@ export function TicketDetail({
|
||||
</ul>
|
||||
)}
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<select
|
||||
<TicketViewportSelect
|
||||
aria-label="assign agent"
|
||||
className={selectClass}
|
||||
value={assignPick}
|
||||
disabled={vm.busy || assignable.length === 0}
|
||||
onChange={(e) => setAssignPick(e.target.value)}
|
||||
>
|
||||
<option value="">
|
||||
{assignable.length === 0
|
||||
? "No more agents"
|
||||
: "Select an agent…"}
|
||||
</option>
|
||||
{assignable.map((a) => (
|
||||
<option key={a.id} value={a.id}>
|
||||
{a.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
options={[
|
||||
{
|
||||
value: "",
|
||||
label:
|
||||
assignable.length === 0
|
||||
? "No more agents"
|
||||
: "Select an agent…",
|
||||
},
|
||||
...assignable.map((agent) => ({
|
||||
value: agent.id,
|
||||
label: agent.name,
|
||||
})),
|
||||
]}
|
||||
onChange={setAssignPick}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
disabled={!assignPick || vm.busy}
|
||||
|
||||
Reference in New Issue
Block a user