mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-27 22:45:03 +00:00
feat(ara-projects): unroll the permission detail instead of popping it (#413)
The sub-row's wrapper is a one-row grid whose track animates 0fr → 1fr over 160ms, so the detail unrolls to whatever height its own content needs — no measuring, no ref, and no effect to start it: mounting runs the animation. Rows below slide down with it rather than jumping. motion-reduce:animate-none respects a reduced-motion preference. Adds the `disclosure-open` keyframe to tailwind.config.js — namespaced and additive, affecting nothing else. tailwindcss-animate is installed but not registered in `plugins`, so the animate-in/slide-in-from-* utilities the shadcn components reference generate no CSS; enabling that plugin would change animation behaviour across every dialog and tooltip in the app, which is not this ticket's call to make. Collapsing is still immediate — React unmounts the row, so there is no exit animation without holding it mounted through one.
This commit is contained in:
parent
2fe4b5ea17
commit
feee863010
2 changed files with 87 additions and 54 deletions
|
|
@ -142,7 +142,8 @@ export function ContractorPermissionsTable({
|
|||
? { kind: "blocked", reason }
|
||||
: { kind: "needs-confirmation", reason };
|
||||
}
|
||||
if (!res.ok) throw new Error(body.error ?? "Couldn't remove the contractor");
|
||||
if (!res.ok)
|
||||
throw new Error(body.error ?? "Couldn't remove the contractor");
|
||||
return { kind: "removed" };
|
||||
},
|
||||
onSuccess: (outcome, { contractor }) => {
|
||||
|
|
@ -321,7 +322,9 @@ export function ContractorPermissionsTable({
|
|||
<td className="px-4 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full bg-indigo-50 text-sm font-bold text-indigo-700">
|
||||
{contractor.organisationName.charAt(0).toUpperCase()}
|
||||
{contractor.organisationName
|
||||
.charAt(0)
|
||||
.toUpperCase()}
|
||||
</span>
|
||||
<span className="text-sm font-semibold text-gray-900">
|
||||
{contractor.organisationName}
|
||||
|
|
@ -391,59 +394,71 @@ export function ContractorPermissionsTable({
|
|||
data-testid={`contractor-permissions-${contractor.organisationId}`}
|
||||
>
|
||||
{/* Full-bleed: the detail table *is* the expanded
|
||||
space, spanning every column of the row above. */}
|
||||
space, spanning every column of the row above.
|
||||
|
||||
The wrapper is a one-row grid whose track animates
|
||||
0fr → 1fr, so the detail unrolls to whatever height
|
||||
its own content needs — no measuring, and no effect
|
||||
to run the animation: mounting starts it. */}
|
||||
<td colSpan={4} className="p-0">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 bg-gray-100/60 text-xs text-gray-600">
|
||||
<th className="px-4 py-2 text-left font-semibold">
|
||||
Workstream
|
||||
</th>
|
||||
{PERMISSION_COLUMNS.map((column) => (
|
||||
<th
|
||||
key={column.key}
|
||||
className="w-40 px-4 py-2 text-left font-semibold"
|
||||
>
|
||||
{column.header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{contractor.assignments.map((assignment) => (
|
||||
<tr
|
||||
key={assignment.id}
|
||||
className="border-b border-gray-100 last:border-b-0"
|
||||
data-testid={`assignment-row-${assignment.id}`}
|
||||
>
|
||||
<td className="px-4 py-2.5">
|
||||
<span className="text-sm font-medium text-gray-800">
|
||||
{assignment.workstreamName}
|
||||
</span>
|
||||
{assignment.workOrders > 0 && (
|
||||
<span className="ml-2 text-xs text-gray-400">
|
||||
{assignment.workOrders}{" "}
|
||||
{assignment.workOrders === 1
|
||||
? "work order"
|
||||
: "work orders"}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
{PERMISSION_COLUMNS.map((column) => (
|
||||
<td key={column.key} className="px-4 py-2.5">
|
||||
<PermissionState
|
||||
granted={assignment[column.key]}
|
||||
label={column.header}
|
||||
workstreamName={
|
||||
assignment.workstreamName
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
<div className="grid animate-disclosure-open motion-reduce:animate-none">
|
||||
<div className="min-h-0 overflow-hidden">
|
||||
<table className="w-full border-collapse">
|
||||
<thead>
|
||||
<tr className="border-b border-gray-200 bg-gray-100/60 text-xs text-gray-600">
|
||||
<th className="px-4 py-2 text-left font-semibold">
|
||||
Workstream
|
||||
</th>
|
||||
{PERMISSION_COLUMNS.map((column) => (
|
||||
<th
|
||||
key={column.key}
|
||||
className="w-40 px-4 py-2 text-left font-semibold"
|
||||
>
|
||||
{column.header}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{contractor.assignments.map((assignment) => (
|
||||
<tr
|
||||
key={assignment.id}
|
||||
className="border-b border-gray-100 last:border-b-0"
|
||||
data-testid={`assignment-row-${assignment.id}`}
|
||||
>
|
||||
<td className="px-4 py-2.5">
|
||||
<span className="text-sm font-medium text-gray-800">
|
||||
{assignment.workstreamName}
|
||||
</span>
|
||||
{assignment.workOrders > 0 && (
|
||||
<span className="ml-2 text-xs text-gray-400">
|
||||
{assignment.workOrders}{" "}
|
||||
{assignment.workOrders === 1
|
||||
? "work order"
|
||||
: "work orders"}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
{PERMISSION_COLUMNS.map((column) => (
|
||||
<td
|
||||
key={column.key}
|
||||
className="px-4 py-2.5"
|
||||
>
|
||||
<PermissionState
|
||||
granted={assignment[column.key]}
|
||||
label={column.header}
|
||||
workstreamName={
|
||||
assignment.workstreamName
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
|
@ -467,7 +482,10 @@ export function ContractorPermissionsTable({
|
|||
Work orders can only be imported once every workstream has both a
|
||||
stage ladder and a contractor.
|
||||
</p>
|
||||
<ul className="mt-3 space-y-1.5" data-testid="contractors-readiness-list">
|
||||
<ul
|
||||
className="mt-3 space-y-1.5"
|
||||
data-testid="contractors-readiness-list"
|
||||
>
|
||||
{readiness.incomplete.map((workstream) => (
|
||||
<li
|
||||
key={workstream.name}
|
||||
|
|
|
|||
|
|
@ -190,12 +190,27 @@ module.exports = {
|
|||
height: "0",
|
||||
},
|
||||
},
|
||||
// A disclosure row unrolling to its own content height, without that
|
||||
// height having to be measured: the wrapper is a one-row grid and the
|
||||
// track animates from 0fr to 1fr. Used by the contractor permissions
|
||||
// sub-row (#413) — see `animation["disclosure-open"]`.
|
||||
"disclosure-open": {
|
||||
from: {
|
||||
gridTemplateRows: "0fr",
|
||||
opacity: "0",
|
||||
},
|
||||
to: {
|
||||
gridTemplateRows: "1fr",
|
||||
opacity: "1",
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
"disclosure-open": "disclosure-open 160ms ease-out",
|
||||
},
|
||||
maxWidth: {
|
||||
"8xl": "90rem",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue