Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,12 @@ export const AccessTokenList = ({ searchString = '', onDeleteSuccess }: AccessTo
{filteredTokens?.map((x) => {
return (
<TableRow key={x.token_alias}>
<TableCell className="w-36 max-w-36">
<TableCell className="max-w-32 lg:max-w-40">
<p className="truncate" title={x.name}>
{x.name}
</p>
</TableCell>
<TableCell className="max-w-96">
<TableCell className="max-w-36 lg:max-w-80">
<p className="font-mono text-foreground-light truncate">{x.token_alias}</p>
</TableCell>
<TableCell className="min-w-32">
Expand Down
114 changes: 60 additions & 54 deletions apps/studio/components/interfaces/Database/Extensions/ExtensionRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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()
Expand Down Expand Up @@ -118,58 +118,66 @@ const ExtensionRow = ({ extension }: ExtensionRowProps) => {
)}
</TableCell>

<TableCell className="flex gap-2 items-center">
{extensionMeta?.github_url && (
<Button asChild type="default" icon={<Github />} className="rounded-full">
<a
target="_blank"
rel="noreferrer"
href={extensionMeta.github_url}
className="font-mono tracking-tighter"
>
{extensionMeta.github_url.split('/').slice(-2).join('/')}
</a>
</Button>
)}
{docsUrl !== undefined && (
<Button asChild type="default" icon={<Book />} className="rounded-full">
<a
target="_blank"
rel="noreferrer"
className="font-mono tracking-tighter"
href={docsUrl}
>
Docs
</a>
</Button>
)}
<TableCell>
<div className="flex gap-2 items-center">
{extensionMeta?.github_url && (
<Button asChild type="default" icon={<Github />} className="rounded-full">
<a
target="_blank"
rel="noreferrer"
href={extensionMeta.github_url}
className="font-mono tracking-tighter"
>
{extensionMeta.github_url.split('/').slice(-2).join('/')}
</a>
</Button>
)}
{docsUrl !== undefined && (
<Button asChild type="default" icon={<Book />} className="rounded-full">
<a
target="_blank"
rel="noreferrer"
className="font-mono tracking-tighter"
href={docsUrl}
>
Docs
</a>
</Button>
)}
</div>
</TableCell>

<TableCell className="w-20 sticky bg-surface-100 border-l right-0">
{isDisabling ? (
<Loader2 className="animate-spin" size={16} />
) : (
<Tooltip>
<TooltipTrigger>
<Switch
disabled={disabled}
checked={isOn}
onCheckedChange={() =>
isOn ? setIsDisableModalOpen(true) : setShowConfirmEnableModal(true)
}
/>
</TooltipTrigger>
{disabled && (
<TooltipContent side="bottom">
{!canUpdateExtensions
? 'You need additional permissions to toggle extensions'
: orioleDbCheck
? 'Project is using OrioleDB and cannot be disabled'
: null}
</TooltipContent>
)}
</Tooltip>
)}
{/*
[Joshen] The div child here and all these classes is to properly add a left border
to make the sticky column more distinct
*/}
<TableCell className="w-20 sticky bg-surface-100 right-0 relative">
<div className="absolute top-0 right-0 left-0 bottom-0 flex items-center justify-center border-l">
{isDisabling ? (
<Loader2 className="animate-spin" size={16} />
) : (
<Tooltip>
<TooltipTrigger>
<Switch
disabled={disabled}
checked={isOn}
onCheckedChange={() =>
isOn ? setIsDisableModalOpen(true) : setShowConfirmEnableModal(true)
}
/>
</TooltipTrigger>
{disabled && (
<TooltipContent side="bottom">
{!canUpdateExtensions
? 'You need additional permissions to toggle extensions'
: orioleDbCheck
? 'Project is using OrioleDB and cannot be disabled'
: null}
</TooltipContent>
)}
</Tooltip>
)}
</div>
</TableCell>
</TableRow>

Expand Down Expand Up @@ -202,5 +210,3 @@ const ExtensionRow = ({ extension }: ExtensionRowProps) => {
</>
)
}

export default ExtensionRow
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>('')
Expand Down Expand Up @@ -74,8 +84,8 @@ const Extensions = () => {
{isLoading ? (
<GenericSkeletonLoader />
) : (
<div className="w-full overflow-hidden overflow-x-auto">
<Card>
<Card>
<ShadowScrollArea stickyLastColumn>
<Table>
<TableHeader>
<TableRow>
Expand All @@ -85,11 +95,16 @@ const Extensions = () => {
<TableHead key="description">Description</TableHead>
<TableHead key="used-by">Used by</TableHead>
<TableHead key="links">Links</TableHead>
<TableHead
key="enabled"
className="w-20 bg-background-200 border-l sticky right-0"
>
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
*/}
<TableHead key="enabled" className="px-0">
<div className="!bg-200 px-4 w-full h-full flex items-center border-l">
Enabled
</div>
</TableHead>
</TableRow>
</TableHeader>
Expand All @@ -110,11 +125,9 @@ const Extensions = () => {
)}
</TableBody>
</Table>
</Card>
</div>
</ShadowScrollArea>
</Card>
)}
</>
)
}

export default Extensions
2 changes: 0 additions & 2 deletions apps/studio/components/interfaces/Database/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
4 changes: 2 additions & 2 deletions apps/studio/pages/project/[ref]/database/extensions.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
1 change: 1 addition & 0 deletions packages/ui/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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_,
Expand Down
72 changes: 72 additions & 0 deletions packages/ui/src/components/ShadowScrollArea/index.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> {
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<HTMLDivElement, ShadowScrollAreaProps>(
({ className, children, stickyLastColumn, ...props }, ref) => {
const containerRef = React.useRef<HTMLDivElement>(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 (
<div className="relative">
<div
className={cn(
'absolute inset-0 pointer-events-none z-50',
'before:absolute before:top-0 before:right-0 before:bottom-0 before:w-6 before:bg-gradient-to-l before:from-black/5 dark:before:from-black/20 before:to-transparent before:opacity-0 before:transition-all before:duration-400 before:easing-[0.24, 0.25, 0.05, 1]',
'after:absolute after:top-0 after:left-0 after:bottom-0 after:w-6 after:bg-gradient-to-r after:from-black/5 dark:after:from-black/20 after:to-transparent after:opacity-0 after:transition-all after:duration-400 after:easing-[0.24, 0.25, 0.05, 1]',
hasHorizontalScroll && 'hover:before:opacity-100 hover:after:opacity-100',
canScrollRight && 'before:opacity-100',
canScrollLeft && 'after:opacity-100'
)}
/>
<div
ref={containerRef}
className={cn(
'w-full overflow-auto',
stickyLastColumn && [
'[&_tr>*: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}
</div>
</div>
)
}
)

ShadowScrollArea.displayName = 'ShadowScrollArea'

export { ShadowScrollArea }
47 changes: 47 additions & 0 deletions packages/ui/src/components/hooks/use-horizontal-scroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as React from 'react'

export const useHorizontalScroll = (ref: React.RefObject<HTMLDivElement>) => {
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 }
}
Loading
Loading