diff --git a/apps/studio/components/interfaces/Account/AccessTokens/AccessTokenList.tsx b/apps/studio/components/interfaces/Account/AccessTokens/AccessTokenList.tsx index cb0ad93a87cff..e553af1fdf11c 100644 --- a/apps/studio/components/interfaces/Account/AccessTokens/AccessTokenList.tsx +++ b/apps/studio/components/interfaces/Account/AccessTokens/AccessTokenList.tsx @@ -152,12 +152,12 @@ export const AccessTokenList = ({ searchString = '', onDeleteSuccess }: AccessTo {filteredTokens?.map((x) => { return ( - +

{x.name}

- +

{x.token_alias}

diff --git a/apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx b/apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx index 1f456082cfb8f..17e78b1368f8f 100644 --- a/apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx +++ b/apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx @@ -10,7 +10,7 @@ import { DatabaseExtension } from 'data/database-extensions/database-extensions- import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useIsOrioleDb, useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { extensions } from 'shared-data' -import { Button, Switch, Tooltip, TooltipContent, TooltipTrigger, TableRow, TableCell } from 'ui' +import { Button, Switch, TableCell, TableRow, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { Admonition } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import EnableExtensionModal from './EnableExtensionModal' @@ -20,7 +20,7 @@ interface ExtensionRowProps { extension: DatabaseExtension } -const ExtensionRow = ({ extension }: ExtensionRowProps) => { +export const ExtensionRow = ({ extension }: ExtensionRowProps) => { const { data: project } = useSelectedProjectQuery() const isOn = extension.installed_version !== null const isOrioleDb = useIsOrioleDb() @@ -118,58 +118,66 @@ const ExtensionRow = ({ extension }: ExtensionRowProps) => { )} - - {extensionMeta?.github_url && ( - - )} - {docsUrl !== undefined && ( - - )} + +
+ {extensionMeta?.github_url && ( + + )} + {docsUrl !== undefined && ( + + )} +
- - {isDisabling ? ( - - ) : ( - - - - isOn ? setIsDisableModalOpen(true) : setShowConfirmEnableModal(true) - } - /> - - {disabled && ( - - {!canUpdateExtensions - ? 'You need additional permissions to toggle extensions' - : orioleDbCheck - ? 'Project is using OrioleDB and cannot be disabled' - : null} - - )} - - )} + {/* + [Joshen] The div child here and all these classes is to properly add a left border + to make the sticky column more distinct + */} + +
+ {isDisabling ? ( + + ) : ( + + + + isOn ? setIsDisableModalOpen(true) : setShowConfirmEnableModal(true) + } + /> + + {disabled && ( + + {!canUpdateExtensions + ? 'You need additional permissions to toggle extensions' + : orioleDbCheck + ? 'Project is using OrioleDB and cannot be disabled' + : null} + + )} + + )} +
@@ -202,5 +210,3 @@ const ExtensionRow = ({ extension }: ExtensionRowProps) => { ) } - -export default ExtensionRow diff --git a/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx b/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx index 4aba0c0fa6156..3e170bcd7f03a 100644 --- a/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx +++ b/apps/studio/components/interfaces/Database/Extensions/Extensions.tsx @@ -11,11 +11,21 @@ import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { Card, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from 'ui' -import ExtensionRow from './ExtensionRow' +import { + Card, + Input, + ShadowScrollArea, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from 'ui' +import { ExtensionRow } from './ExtensionRow' import { HIDDEN_EXTENSIONS, SEARCH_TERMS } from './Extensions.constants' -const Extensions = () => { +export const Extensions = () => { const { filter } = useParams() const { data: project } = useSelectedProjectQuery() const [filterString, setFilterString] = useState('') @@ -74,8 +84,8 @@ const Extensions = () => { {isLoading ? ( ) : ( -
- + + @@ -85,11 +95,16 @@ const Extensions = () => { Description Used by Links - - Enabled + {/* + [Joshen] All these classes are just to make the last column sticky + I reckon we can pull these out into the Table component where we can declare + sticky columns via props, but we can do that if we start to have more tables + in the dashboard with sticky columns + */} + +
+ Enabled +
@@ -110,11 +125,9 @@ const Extensions = () => { )}
-
-
+ + )} ) } - -export default Extensions diff --git a/apps/studio/components/interfaces/Database/index.ts b/apps/studio/components/interfaces/Database/index.ts index 0269df1734528..372316bfb5bc9 100644 --- a/apps/studio/components/interfaces/Database/index.ts +++ b/apps/studio/components/interfaces/Database/index.ts @@ -1,7 +1,5 @@ export { default as RolesList } from './Roles/RolesList' -export { default as Extensions } from './Extensions/Extensions' - export { default as BackupsList } from './Backups/BackupsList' export { default as CreateFunction } from './Functions/CreateFunction' diff --git a/apps/studio/pages/project/[ref]/database/extensions.tsx b/apps/studio/pages/project/[ref]/database/extensions.tsx index 4239872f36ade..01bc3ba3e841c 100644 --- a/apps/studio/pages/project/[ref]/database/extensions.tsx +++ b/apps/studio/pages/project/[ref]/database/extensions.tsx @@ -1,10 +1,10 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' -import { Extensions } from 'components/interfaces/Database' +import { Extensions } from 'components/interfaces/Database/Extensions/Extensions' import DatabaseLayout from 'components/layouts/DatabaseLayout/DatabaseLayout' import DefaultLayout from 'components/layouts/DefaultLayout' -import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 788a01590ca00..c922b73f62fbd 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -204,6 +204,7 @@ export * from './src/components/shadcn/ui/hover-card' export * from './src/components/shadcn/ui/aspect-ratio' export * from './src/components/shadcn/ui/table' +export * from './src/components/ShadowScrollArea' export { Collapsible as Collapsible_Shadcn_, diff --git a/packages/ui/src/components/ShadowScrollArea/index.tsx b/packages/ui/src/components/ShadowScrollArea/index.tsx new file mode 100644 index 0000000000000..9625fc42d7c56 --- /dev/null +++ b/packages/ui/src/components/ShadowScrollArea/index.tsx @@ -0,0 +1,72 @@ +import * as React from 'react' +import { cn } from '../../lib/utils/cn' +import { useHorizontalScroll } from '../hooks/use-horizontal-scroll' + +interface ShadowScrollAreaProps extends React.HTMLAttributes { + children: React.ReactNode + stickyLastColumn?: boolean +} + +/** + * [Joshen] Leaving feedback here to address in the future + We should pull out all the sticky column logic, shift them to Table component where we can declare + sticky columns via a prop. ShadowScrollArea here should purely handle the shadow styling + */ + +const ShadowScrollArea = React.forwardRef( + ({ className, children, stickyLastColumn, ...props }, ref) => { + const containerRef = React.useRef(null) + const { hasHorizontalScroll, canScrollLeft, canScrollRight } = useHorizontalScroll(containerRef) + + const stickyColumnShadow = cn( + '[&_td:last-child]:before:absolute [&_td:last-child]:before:top-0 [&_td:last-child]:before:-left-6', + '[&_td:last-child]:before:bottom-0 [&_td:last-child]:before:w-6 [&_td:last-child]:before:bg-gradient-to-l', + '[&_td:last-child]:before:from-black/5 dark:[&_td:last-child]:before:from-black/20 [&_td:last-child]:before:to-transparent', + '[&_td:last-child]:before:opacity-0 [&_td:last-child]:before:transition-all [&_td:last-child]:before:duration-400', + '[&_td:last-child]:before:easing-[0.24, 0.25, 0.05, 1] [&_td:last-child]:before:z-[60]', + '[&_th:last-child]:before:absolute [&_th:last-child]:before:top-0 [&_th:last-child]:before:-left-6', + '[&_th:last-child]:before:bottom-0 [&_th:last-child]:before:w-6 [&_th:last-child]:before:bg-gradient-to-l', + '[&_th:last-child]:before:from-black/5 dark:[&_th:last-child]:before:from-black/20 [&_th:last-child]:before:to-transparent', + '[&_th:last-child]:before:opacity-0 [&_th:last-child]:before:transition-all [&_th:last-child]:before:duration-400', + '[&_th:last-child]:before:easing-[0.24, 0.25, 0.05, 1] [&_th:last-child]:before:z-[60]' + ) + + return ( +
+
+
*:last-child]:sticky [&_tr>*:last-child]:z-50 [&_tr>*:last-child]:right-0', + '[&_tr:hover>*:last-child]:bg-transparent', + '[&_th>*:last-child]:bg-surface-100', + stickyColumnShadow, + ], + hasHorizontalScroll && '[&_tr:hover>td:last-child]:!bg-surface-200', + canScrollRight && + '[&_td]:before:opacity-100 [&_tr>*:last-child]:before:opacity-100 [&_th:last-child]:before:opacity-100', + className + )} + {...props} + > + {children} +
+
+ ) + } +) + +ShadowScrollArea.displayName = 'ShadowScrollArea' + +export { ShadowScrollArea } diff --git a/packages/ui/src/components/hooks/use-horizontal-scroll.ts b/packages/ui/src/components/hooks/use-horizontal-scroll.ts new file mode 100644 index 0000000000000..d4a300d38c4d9 --- /dev/null +++ b/packages/ui/src/components/hooks/use-horizontal-scroll.ts @@ -0,0 +1,47 @@ +import * as React from 'react' + +export const useHorizontalScroll = (ref: React.RefObject) => { + const [hasHorizontalScroll, setHasHorizontalScroll] = React.useState(false) + const [canScrollLeft, setCanScrollLeft] = React.useState(false) + const [canScrollRight, setCanScrollRight] = React.useState(false) + + React.useEffect(() => { + const element = ref.current + if (!element) return + + const checkScroll = () => { + const hasScroll = element.scrollWidth > element.clientWidth + setHasHorizontalScroll(hasScroll) + + if (hasScroll) { + const canScrollLeft = element.scrollLeft > 0 + const canScrollRight = element.scrollLeft < element.scrollWidth - element.clientWidth + setCanScrollLeft(canScrollLeft) + setCanScrollRight(canScrollRight) + } else { + setCanScrollLeft(false) + setCanScrollRight(false) + } + } + + const handleScroll = () => { + if (hasHorizontalScroll) { + const canScrollLeft = element.scrollLeft > 0 + const canScrollRight = element.scrollLeft < element.scrollWidth - element.clientWidth + setCanScrollLeft(canScrollLeft) + setCanScrollRight(canScrollRight) + } + } + + checkScroll() + element.addEventListener('scroll', handleScroll) + window.addEventListener('resize', checkScroll) + + return () => { + element.removeEventListener('scroll', handleScroll) + window.removeEventListener('resize', checkScroll) + } + }, [ref, hasHorizontalScroll]) + + return { hasHorizontalScroll, canScrollLeft, canScrollRight } +} diff --git a/packages/ui/src/components/shadcn/ui/table.tsx b/packages/ui/src/components/shadcn/ui/table.tsx index ef716e89d09ea..49810340a41d9 100644 --- a/packages/ui/src/components/shadcn/ui/table.tsx +++ b/packages/ui/src/components/shadcn/ui/table.tsx @@ -1,17 +1,20 @@ import * as React from 'react' import { cn } from '../../../lib/utils/cn' +import { ShadowScrollArea } from '../../ShadowScrollArea' interface TableProps extends React.HTMLAttributes { containerProps?: React.HTMLAttributes } const Table = React.forwardRef( - ({ className, containerProps, ...props }, ref) => ( -
- - - ) + ({ className, containerProps, ...props }, ref) => { + return ( + +
+ + ) + } ) Table.displayName = 'Table' @@ -48,7 +51,7 @@ const TableRow = React.forwardReftd]:hover:bg-surface-200 data-[state=selected]:bg-muted', className )} {...props} @@ -78,7 +81,7 @@ const TableCell = React.forwardRef< >(({ className, ...props }, ref) => (
))