From ffd0de4661d33b5e9920edd250699cd8f22d9e32 Mon Sep 17 00:00:00 2001 From: somebody1234 Date: Thu, 12 Dec 2024 09:57:17 +1000 Subject: [PATCH] Sticky headers for settings tables (#11826) - https://github.com/enso-org/cloud-v2/issues/1574 - Make header rows sticky - Use solid background on header rows User Groups and Members tables in settings to avoid overlapping text - https://github.com/enso-org/cloud-v2/issues/895 - Make "name" column sticky in assets table # Important Notes None --- .../components/dashboard/AssetRow.tsx | 4 +- .../dashboard/column/columnUtils.ts | 2 +- .../columnHeading/NameColumnHeading.tsx | 2 +- app/gui/src/dashboard/hooks/scrollHooks.ts | 8 +- app/gui/src/dashboard/layouts/AssetsTable.tsx | 6 +- .../KeyboardShortcutsSettingsSection.tsx | 2 +- .../Settings/MembersSettingsSection.tsx | 134 +++++++++--------- .../layouts/Settings/MembersTable.tsx | 6 +- .../dashboard/layouts/Settings/Section.tsx | 4 +- .../src/dashboard/layouts/Settings/Tab.tsx | 2 +- .../Settings/UserGroupsSettingsSection.tsx | 6 +- .../src/dashboard/layouts/Settings/data.tsx | 4 +- 12 files changed, 82 insertions(+), 98 deletions(-) diff --git a/app/gui/src/dashboard/components/dashboard/AssetRow.tsx b/app/gui/src/dashboard/components/dashboard/AssetRow.tsx index 6ed1f98910..98e42717cf 100644 --- a/app/gui/src/dashboard/components/dashboard/AssetRow.tsx +++ b/app/gui/src/dashboard/components/dashboard/AssetRow.tsx @@ -2,6 +2,7 @@ import * as React from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import invariant from 'tiny-invariant' import { useStore } from 'zustand' import BlankIcon from '#/assets/blank.svg' @@ -33,6 +34,7 @@ import * as localBackend from '#/services/LocalBackend' import * as backendModule from '#/services/Backend' import { Text } from '#/components/AriaComponents' +import { IndefiniteSpinner } from '#/components/Spinner' import type { AssetEvent } from '#/events/assetEvent' import { useCutAndPaste } from '#/events/assetListEvent' import { @@ -56,8 +58,6 @@ import * as permissions from '#/utilities/permissions' import * as set from '#/utilities/set' import * as tailwindMerge from '#/utilities/tailwindMerge' import Visibility from '#/utilities/Visibility' -import invariant from 'tiny-invariant' -import { IndefiniteSpinner } from '../Spinner' // ================= // === Constants === diff --git a/app/gui/src/dashboard/components/dashboard/column/columnUtils.ts b/app/gui/src/dashboard/components/dashboard/column/columnUtils.ts index d1dc4039e8..7c78549b32 100644 --- a/app/gui/src/dashboard/components/dashboard/column/columnUtils.ts +++ b/app/gui/src/dashboard/components/dashboard/column/columnUtils.ts @@ -69,7 +69,7 @@ const NORMAL_COLUMN_CSS_CLASSES = `px-cell-x py ${COLUMN_CSS_CLASSES}` /** CSS classes for every column. */ export const COLUMN_CSS_CLASS: Readonly> = { - [Column.name]: `rounded-rows-skip-level min-w-drive-name-column h-full p-0 border-l-0 ${COLUMN_CSS_CLASSES}`, + [Column.name]: `z-10 sticky left-0 bg-dashboard rounded-rows-skip-level min-w-drive-name-column h-full p-0 border-l-0 ${COLUMN_CSS_CLASSES}`, [Column.modified]: `min-w-drive-modified-column rounded-rows-have-level ${NORMAL_COLUMN_CSS_CLASSES}`, [Column.sharedWith]: `min-w-drive-shared-with-column rounded-rows-have-level ${NORMAL_COLUMN_CSS_CLASSES}`, [Column.labels]: `min-w-drive-labels-column rounded-rows-have-level ${NORMAL_COLUMN_CSS_CLASSES}`, diff --git a/app/gui/src/dashboard/components/dashboard/columnHeading/NameColumnHeading.tsx b/app/gui/src/dashboard/components/dashboard/columnHeading/NameColumnHeading.tsx index 04f7c5dc12..dc4e3d5597 100644 --- a/app/gui/src/dashboard/components/dashboard/columnHeading/NameColumnHeading.tsx +++ b/app/gui/src/dashboard/components/dashboard/columnHeading/NameColumnHeading.tsx @@ -41,7 +41,7 @@ export default function NameColumnHeading(props: AssetColumnHeadingProps) { getText('stopSortingByName') : getText('sortByNameDescending') } - className="group flex h-table-row w-full items-center justify-start gap-icon-with-text px-name-column-x" + className="group sticky left-0 flex h-table-row w-full items-center justify-start gap-icon-with-text bg-dashboard px-name-column-x" onPress={cycleSortDirection} > {getText('nameColumnName')} diff --git a/app/gui/src/dashboard/hooks/scrollHooks.ts b/app/gui/src/dashboard/hooks/scrollHooks.ts index 76b7d0110f..5891df1b57 100644 --- a/app/gui/src/dashboard/hooks/scrollHooks.ts +++ b/app/gui/src/dashboard/hooks/scrollHooks.ts @@ -3,7 +3,6 @@ import { useRef, useState, type MutableRefObject, type RefObject } from 'react' import { useSyncRef } from '#/hooks/syncRefHooks' import useOnScroll from '#/hooks/useOnScroll' -import { unsafeWriteValue } from '#/utilities/write' /** Options for the {@link useStickyTableHeaderOnScroll} hook. */ interface UseStickyTableHeaderOnScrollOptions { @@ -29,12 +28,7 @@ export function useStickyTableHeaderOnScroll( const trackShadowClassRef = useSyncRef(trackShadowClass) const [shadowClassName, setShadowClass] = useState('') const onScroll = useOnScroll(() => { - if (rootRef.current != null && bodyRef.current != null) { - unsafeWriteValue( - bodyRef.current.style, - 'clipPath', - `inset(${rootRef.current.scrollTop}px 0 0 0)`, - ) + if (rootRef.current && bodyRef.current) { if (trackShadowClassRef.current) { const isAtTop = rootRef.current.scrollTop === 0 const isAtBottom = diff --git a/app/gui/src/dashboard/layouts/AssetsTable.tsx b/app/gui/src/dashboard/layouts/AssetsTable.tsx index ffb6d2e0d5..7e7d0bf705 100644 --- a/app/gui/src/dashboard/layouts/AssetsTable.tsx +++ b/app/gui/src/dashboard/layouts/AssetsTable.tsx @@ -44,6 +44,7 @@ import Label from '#/components/dashboard/Label' import { ErrorDisplay } from '#/components/ErrorBoundary' import { IsolateLayout } from '#/components/IsolateLayout' import SelectionBrush from '#/components/SelectionBrush' +import { IndefiniteSpinner } from '#/components/Spinner' import FocusArea from '#/components/styled/FocusArea' import SvgMask from '#/components/SvgMask' import { ASSETS_MIME_TYPE } from '#/data/mimeTypes' @@ -140,7 +141,6 @@ import { EMPTY_SET, setPresence, withPresence } from '#/utilities/set' import type { SortInfo } from '#/utilities/sorting' import { twJoin, twMerge } from '#/utilities/tailwindMerge' import Visibility from '#/utilities/Visibility' -import { IndefiniteSpinner } from '../components/Spinner' declare module '#/utilities/LocalStorage' { /** */ @@ -1806,8 +1806,8 @@ function AssetsTable(props: AssetsTableProps) { } }} > - - {headerRow} +
+ {headerRow} {itemRows} diff --git a/app/gui/src/dashboard/layouts/Settings/KeyboardShortcutsSettingsSection.tsx b/app/gui/src/dashboard/layouts/Settings/KeyboardShortcutsSettingsSection.tsx index d34a69d4ed..e7ae0c6b5d 100644 --- a/app/gui/src/dashboard/layouts/Settings/KeyboardShortcutsSettingsSection.tsx +++ b/app/gui/src/dashboard/layouts/Settings/KeyboardShortcutsSettingsSection.tsx @@ -73,7 +73,7 @@ export default function KeyboardShortcutsSettingsSection() { })} >
- + diff --git a/app/gui/src/dashboard/layouts/Settings/MembersSettingsSection.tsx b/app/gui/src/dashboard/layouts/Settings/MembersSettingsSection.tsx index a047d7462a..b16c069555 100644 --- a/app/gui/src/dashboard/layouts/Settings/MembersSettingsSection.tsx +++ b/app/gui/src/dashboard/layouts/Settings/MembersSettingsSection.tsx @@ -16,16 +16,8 @@ import InviteUsersModal from '#/modals/InviteUsersModal' import type * as backendModule from '#/services/Backend' import type RemoteBackend from '#/services/RemoteBackend' -// ================= -// === Constants === -// ================= - const LIST_USERS_STALE_TIME_MS = 60_000 -// ============================== -// === MembersSettingsSection === -// ============================== - /** Settings tab for viewing and editing organization members. */ export default function MembersSettingsSection() { const { getText } = textProvider.useText() @@ -87,71 +79,73 @@ export default function MembersSettingsSection() { )} -
{/* Icon */} {getText('name')}
- - - - - - - - {members.map((member) => ( - - - +
+
- {getText('name')} - - {getText('status')} -
- - {member.email} - - - {member.name} - - -
- {getText('active')} - {member.email !== user.email && isAdmin && ( - - - - )} -
-
+ + + + - ))} - {invitations.invitations.map((invitation) => ( - - - + + {members.map((member) => ( + + + + + ))} + {invitations.invitations.map((invitation) => ( + + + - - ))} - -
+ {getText('name')} + + {getText('status')} +
- {invitation.userEmail} - -
- {getText('pendingInvitation')} - {isAdmin && ( - - - {getText('copyInviteLink')} - +
+ + {member.email} + + + {member.name} + + +
+ {getText('active')} + {member.email !== user.email && isAdmin && ( + + + + )} +
+
+ {invitation.userEmail} + +
+ {getText('pendingInvitation')} + {isAdmin && ( + + + {getText('copyInviteLink')} + - + - - - )} -
-
+ + + )} + + + + ))} + + + ) } diff --git a/app/gui/src/dashboard/layouts/Settings/MembersTable.tsx b/app/gui/src/dashboard/layouts/Settings/MembersTable.tsx index 170b689061..012c05ab35 100644 --- a/app/gui/src/dashboard/layouts/Settings/MembersTable.tsx +++ b/app/gui/src/dashboard/layouts/Settings/MembersTable.tsx @@ -21,10 +21,6 @@ import { UserId, type User } from '#/services/Backend' import { twMerge } from '#/utilities/tailwindMerge' import UserRow from './UserRow' -// ==================== -// === MembersTable === -// ==================== - /** Props for a {@link MembersTable}. */ export interface MembersTableProps { readonly backend: Backend @@ -129,7 +125,7 @@ export default function MembersTable(props: MembersTableProps) { className="w-settings-main-section max-w-full table-fixed self-start rounded-rows" {...(draggable ? { dragAndDropHooks } : {})} > - + +
{!heading ? null : ( {getText(nameId)} )} -
+
{entries.map((entry, i) => ( ))} diff --git a/app/gui/src/dashboard/layouts/Settings/Tab.tsx b/app/gui/src/dashboard/layouts/Settings/Tab.tsx index abeedbf67a..29872623b0 100644 --- a/app/gui/src/dashboard/layouts/Settings/Tab.tsx +++ b/app/gui/src/dashboard/layouts/Settings/Tab.tsx @@ -77,7 +77,7 @@ export default function SettingsTab(props: SettingsTabProps) { {...contentProps} > {columns.map((sectionsInColumn, i) => ( -
+
{sectionsInColumn.map((section) => ( ))} diff --git a/app/gui/src/dashboard/layouts/Settings/UserGroupsSettingsSection.tsx b/app/gui/src/dashboard/layouts/Settings/UserGroupsSettingsSection.tsx index 541e4d32de..10ef6e5934 100644 --- a/app/gui/src/dashboard/layouts/Settings/UserGroupsSettingsSection.tsx +++ b/app/gui/src/dashboard/layouts/Settings/UserGroupsSettingsSection.tsx @@ -139,7 +139,7 @@ export default function UserGroupsSettingsSection(props: UserGroupsSettingsSecti return ( <> {isAdmin && ( - + {shouldDisplayPaywall && ( - +