From 75acf7207b151f0227600be555bfa5e14dfa8dd1 Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 24 Jul 2026 13:37:21 +0200 Subject: [PATCH] fix: pin table actions column so it stays visible on narrow screens (#10301) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: pin table actions column so it stays visible on narrow screens Wide DataTables (folders, variables, resources) scroll horizontally on small screens, pushing the trailing per-row actions column (the ⋯ menu, Edit/Delete, and folders' "Publish to Hub") off the right edge where it was effectively unreachable. Add an opt-in `stickyEnd` prop to Cell that pins a column to the right of the scroll container with an opaque background and a left divider, and apply it to the actions column (header + body) on the folders, variables, and resources pages. The background is opaque (bg-surface / bg-surface-secondary) rather than the row's translucent hover tint, so cells sliding under the pinned column are occluded instead of bleeding through. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: keep resources actions cell as table-cell so sticky pins correctly The workspace resources actions cell used class="flex justify-end" on the Cell, which forces the to display:flex. A flex box inside a table row is wrapped in an anonymous table-cell, so position:sticky on it is constrained to that wrapper and no longer pins to the scrollport — the header stayed pinned while the row actions scrolled away. Move the flex layout to an inner
so the keeps display:table-cell and the stickyEnd pin works. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: address review nits on pinned actions column - resources Workspace table: add `last` to the body actions cell so its right padding (sm:pr-6) matches the header and the other tables. - variables table: isolate the refresh-error ping indicator's stacking context so its z-50 can't paint over a sticky-pinned actions column scrolling past it. Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- frontend/src/lib/components/table/Cell.svelte | 7 + .../(root)/(logged)/folders/+page.svelte | 4 +- .../(root)/(logged)/resources/+page.svelte | 152 +++++++++--------- .../(root)/(logged)/variables/+page.svelte | 10 +- 4 files changed, 93 insertions(+), 80 deletions(-) diff --git a/frontend/src/lib/components/table/Cell.svelte b/frontend/src/lib/components/table/Cell.svelte index c14e9e02c6..94aac6db7a 100644 --- a/frontend/src/lib/components/table/Cell.svelte +++ b/frontend/src/lib/components/table/Cell.svelte @@ -11,6 +11,7 @@ shouldStopPropagation?: boolean selected?: boolean sticky?: boolean + stickyEnd?: boolean wrap?: boolean children?: import('svelte').Snippet [key: string]: any @@ -24,6 +25,7 @@ shouldStopPropagation = false, selected = false, sticky = false, + stickyEnd = false, wrap = false, children, ...rest @@ -53,6 +55,11 @@ last && size === 'xs' ? 'sm:pr-3' : '', numeric ? 'text-right' : '', + // Pin an actions column to the right so it stays visible when a wide table + // scrolls horizontally. The background must be opaque so cells sliding under it + // are occluded — the row's hover tint is translucent and would bleed through. + stickyEnd ? 'sticky right-0 border-l' : '', + stickyEnd ? (head ? 'bg-surface-secondary' : 'bg-surface') : '', sticky ? `!p-0 sticky ${first ? 'left-0' : 'right-0'}` : 'px-2 py-2', size === 'sm' ? 'px-1.5 py-2.5' : '', size === 'lg' ? 'px-3 py-4' : '', diff --git a/frontend/src/routes/(root)/(logged)/folders/+page.svelte b/frontend/src/routes/(root)/(logged)/folders/+page.svelte index 343e7a3e48..0df2235212 100644 --- a/frontend/src/routes/(root)/(logged)/folders/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/folders/+page.svelte @@ -180,7 +180,7 @@ Variables Resources Participants - + @@ -242,7 +242,7 @@ - + Resource type Description - + @@ -1176,83 +1176,85 @@ {/if}
- - {#if path && assetCanBeExplored({ kind: 'resource', path }, { resource_type }) && !$userStore?.operator} - - {/if} - { - shareModal?.openDrawer?.(path, 'resource') - } - }, - { - displayName: 'Edit', - icon: Pen, - disabled: !canWrite || !showCreateButtons, - action: () => { - resourceEditor?.initEdit?.(path) - } - }, - ...(!ws_specific && isDeployable('resource', path, deployUiSettings) - ? [ - { - displayName: 'Deploy to prod/staging', - icon: FileUp, - action: () => { - deploymentDrawer?.openDrawer(path, 'resource') + +
+ {#if path && assetCanBeExplored({ kind: 'resource', path }, { resource_type }) && !$userStore?.operator} + + {/if} + { + shareModal?.openDrawer?.(path, 'resource') + } + }, + { + displayName: 'Edit', + icon: Pen, + disabled: !canWrite || !showCreateButtons, + action: () => { + resourceEditor?.initEdit?.(path) + } + }, + ...(!ws_specific && isDeployable('resource', path, deployUiSettings) + ? [ + { + displayName: 'Deploy to prod/staging', + icon: FileUp, + action: () => { + deploymentDrawer?.openDrawer(path, 'resource') + } } - } - ] - : []), - { - displayName: 'Delete', - disabled: !canWrite || !showCreateButtons, - icon: Trash, - type: 'delete', - action: (event) => { - // TODO - // @ts-ignore - if (event?.shiftKey) { - deleteResource(path, account) - } else { - deleteIsLinked = is_linked ?? false - deleteConfirmedCallback = () => { + ] + : []), + { + displayName: 'Delete', + disabled: !canWrite || !showCreateButtons, + icon: Trash, + type: 'delete', + action: (event) => { + // TODO + // @ts-ignore + if (event?.shiftKey) { deleteResource(path, account) + } else { + deleteIsLinked = is_linked ?? false + deleteConfirmedCallback = () => { + deleteResource(path, account) + } } } - } - }, - ...(account != undefined - ? [ - { - displayName: 'Refresh token', - icon: RotateCw, - action: async () => { - await OauthService.refreshToken({ - workspace: $workspaceStore ?? '', - id: account ?? 0, - requestBody: { - path - } - }) - sendUserToast('Token refreshed') - loadResources() + }, + ...(account != undefined + ? [ + { + displayName: 'Refresh token', + icon: RotateCw, + action: async () => { + await OauthService.refreshToken({ + workspace: $workspaceStore ?? '', + id: account ?? 0, + requestBody: { + path + } + }) + sendUserToast('Token refreshed') + loadResources() + } } - } - ] - : []) - ]} - /> - + ] + : []) + ]} + /> +
{/each} {/if} @@ -1280,7 +1282,7 @@ Name Description - + @@ -1316,7 +1318,7 @@ {removeMarkdown(truncate(description ?? '', 200))}
- + {#if !canWrite} Shared globally diff --git a/frontend/src/routes/(root)/(logged)/variables/+page.svelte b/frontend/src/routes/(root)/(logged)/variables/+page.svelte index af2184e7f5..ed01a56ac6 100644 --- a/frontend/src/routes/(root)/(logged)/variables/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/variables/+page.svelte @@ -362,7 +362,7 @@ Value Description - + @@ -465,7 +465,11 @@
{#if refresh_error} -
+ +
- + { let owner = isOwner(path, $userStore, $workspaceStore)