Redo column type dropdown (#29534)

* Redo column type dropdown

* Fix spacing

* Fix tests

* Fix tests,2

* Fix the tests.

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
Terry Sutton
2024-09-27 11:28:11 +00:00
committed by GitHub
parent c2b6bed9be
commit c73d3fdb25
5 changed files with 173 additions and 116 deletions

View File

@@ -131,7 +131,6 @@ const WrapperDynamicColumns = ({
value={column.type}
enumTypes={[]}
onOptionSelect={(value) => onUpdateValue(column.id, 'type', value)}
layout="vertical"
className="[&_label]:!p-0"
/>
</div>

View File

@@ -264,7 +264,6 @@ const ColumnEditor = ({
<ColumnType
showRecommendation
value={columnFields?.format ?? ''}
layout="vertical"
enumTypes={enumTypes}
error={errors.format}
description={
@@ -305,7 +304,6 @@ const ColumnEditor = ({
)}
</div>
)}
<ColumnDefaultValue
columnFields={columnFields}
enumTypes={enumTypes}

View File

@@ -1,11 +1,39 @@
import * as Tooltip from '@radix-ui/react-tooltip'
import { noop } from 'lodash'
import Link from 'next/link'
import { ReactNode } from 'react'
import { Alert, Button, Input, Listbox } from 'ui'
import { ReactNode, useState } from 'react'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
CommandEmpty_Shadcn_,
CommandGroup_Shadcn_,
CommandInput_Shadcn_,
CommandItem_Shadcn_,
CommandList_Shadcn_,
Command_Shadcn_,
CriticalIcon,
Input,
PopoverContent_Shadcn_,
PopoverTrigger_Shadcn_,
Popover_Shadcn_,
ScrollArea,
cn,
} from 'ui'
import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
import { Calendar, Circle, ExternalLink, Hash, ListPlus, ToggleRight, Type } from 'lucide-react'
import {
Calendar,
Check,
ChevronsUpDown,
ExternalLink,
Hash,
ListPlus,
ToggleRight,
Type,
} from 'lucide-react'
import {
POSTGRES_DATA_TYPES,
POSTGRES_DATA_TYPE_OPTIONS,
@@ -16,8 +44,6 @@ import type { PostgresDataTypeOption } from '../SidePanelEditor.types'
interface ColumnTypeProps {
value: string
enumTypes: EnumeratedType[]
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
layout?: 'vertical' | 'horizontal'
className?: string
error?: any
disabled?: boolean
@@ -30,9 +56,6 @@ interface ColumnTypeProps {
const ColumnType = ({
value,
enumTypes = [],
className,
size = 'medium',
layout,
error,
disabled = false,
showLabel = true,
@@ -44,25 +67,43 @@ const ColumnType = ({
const availableTypes = POSTGRES_DATA_TYPES.concat(enumTypes.map((type) => type.name))
const isAvailableType = value ? availableTypes.includes(value) : true
const recommendation = RECOMMENDED_ALTERNATIVE_DATA_TYPE[value]
const [open, setOpen] = useState(false)
console.log({ availableTypes })
const getOptionByName = (name: string) => {
// handle built in types
const pgOption = POSTGRES_DATA_TYPE_OPTIONS.find((option) => option.name === name)
if (pgOption) return pgOption
// handle custom enums
const enumType = enumTypes.find((type) => type.name === name)
return enumType ? { ...enumType, type: 'enum' } : undefined
}
const inferIcon = (type: string) => {
switch (type) {
case 'number':
return <Hash size={16} className="text-foreground" strokeWidth={1.5} />
return <Hash size={14} className="text-foreground" strokeWidth={1.5} />
case 'time':
return <Calendar size={16} className="text-foreground" strokeWidth={1.5} />
return <Calendar size={14} className="text-foreground" strokeWidth={1.5} />
case 'text':
return <Type size={16} className="text-foreground" strokeWidth={1.5} />
return <Type size={14} className="text-foreground" strokeWidth={1.5} />
case 'json':
return (
<div className="text-foreground" style={{ padding: '0px 1px' }}>
{'{ }'}
</div>
)
case 'jsonb':
return (
<div className="text-foreground" style={{ padding: '0px 1px' }}>
{'{ }'}
</div>
)
case 'bool':
return <ToggleRight size={16} className="text-foreground" strokeWidth={1.5} />
return <ToggleRight size={14} className="text-foreground" strokeWidth={1.5} />
default:
return <Circle size={16} className="text-foreground p-0.5" strokeWidth={1.5} />
return <ListPlus size={16} className="text-foreground" strokeWidth={1.5} />
}
}
@@ -119,7 +160,6 @@ const ColumnType = ({
layout={showLabel ? 'horizontal' : undefined}
className="md:gap-x-0"
size="small"
icon={inferIcon(POSTGRES_DATA_TYPE_OPTIONS.find((x) => x.name === value)?.type ?? '')}
value={value}
/>
</Tooltip.Trigger>
@@ -143,105 +183,126 @@ const ColumnType = ({
}
return (
<div className="space-y-2">
<Listbox
label={showLabel ? 'Type' : ''}
layout={layout || (showLabel ? 'horizontal' : 'vertical')}
value={value}
size={size}
error={error}
disabled={disabled}
// @ts-ignore
descriptionText={description}
className={`${className} ${disabled ? 'column-type-disabled' : ''} rounded-md`}
onChange={(value: string) => onOptionSelect(value)}
optionsWidth={480}
>
<Listbox.Option key="empty" value="" label="---">
---
</Listbox.Option>
{/*
Weird issue with Listbox here
1. Can't do render conditionally (&&) within Listbox hence why using Fragment
2. Can't wrap these 2 components within a Fragment conditional (enumTypes.length)
as selecting the enumType option will not render it in the Listbox component
*/}
{enumTypes.length > 0 ? (
<Listbox.Option disabled key="header-1" value="header-1" label="header-1">
Other Data Types
</Listbox.Option>
) : (
<></>
)}
{enumTypes.length > 0 ? (
// @ts-ignore
enumTypes.map((enumType: PostgresType) => (
<Listbox.Option
key={enumType.name}
value={enumType.name}
label={enumType.name}
addOnBefore={() => {
return <ListPlus size={16} className="text-foreground" strokeWidth={1.5} />
}}
>
<div className="flex items-center space-x-4">
<p className="text-foreground">{enumType.name}</p>
{enumType.comment !== undefined && (
<p className="text-foreground-lighter">{enumType.comment}</p>
)}
</div>
</Listbox.Option>
))
) : (
<></>
)}
<Listbox.Option disabled value="header-2" label="header-2">
PostgreSQL Data Types
</Listbox.Option>
{POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption) => (
<Listbox.Option
key={option.name}
value={option.name}
label={option.name}
addOnBefore={() => inferIcon(option.type)}
<div>
<Popover_Shadcn_ open={open} onOpenChange={setOpen}>
<PopoverTrigger_Shadcn_ asChild>
<Button
type="default"
role="combobox"
size={'small'}
aria-expanded={open}
className="w-full justify-between"
iconRight={<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />}
>
<div className="flex items-center space-x-4">
<span className="text-foreground">{option.name}</span>
<span className="text-foreground-lighter">{option.description}</span>
</div>
</Listbox.Option>
))}
</Listbox>
{value ? (
<div className="flex gap-2 items-center">
<span>{inferIcon(getOptionByName(value)?.type ?? '')}</span>
{value}
</div>
) : (
'Choose a column type...'
)}
</Button>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_ className="w-[460px] p-0" side="bottom" align="center">
<ScrollArea className="h-[335px]">
<Command_Shadcn_>
<CommandInput_Shadcn_ placeholder="Search types..." />
<CommandEmpty_Shadcn_>Type not found.</CommandEmpty_Shadcn_>
<CommandList_Shadcn_>
<CommandGroup_Shadcn_>
{POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption) => (
<CommandItem_Shadcn_
key={option.name}
value={option.name}
className={cn('relative', option.name === value ? 'bg-surface-200' : '')}
onSelect={(value: string) => {
onOptionSelect(value)
setOpen(false)
}}
>
<div className="flex items-center gap-2 pr-6">
<span>{inferIcon(option.type)}</span>
<span className="text-foreground">{option.name}</span>
<span className="text-foreground-lighter">{option.description}</span>
</div>
<span className="absolute right-3 top-2">
{option.name === value ? (
<Check className="text-brand-500" size={14} />
) : (
''
)}
</span>
</CommandItem_Shadcn_>
))}
</CommandGroup_Shadcn_>
{enumTypes.length > 0 && (
<>
<CommandItem_Shadcn_>Other types</CommandItem_Shadcn_>
<CommandGroup_Shadcn_>
{enumTypes.map((option: any) => (
<CommandItem_Shadcn_
key={option.name}
value={option.name}
className={cn('relative', option.name === value ? 'bg-surface-200' : '')}
onSelect={(value: string) => {
onOptionSelect(value)
setOpen(false)
}}
>
<div className="flex items-center gap-2">
<div>
<ListPlus size={16} className="text-foreground" strokeWidth={1.5} />
</div>
<span className="text-foreground">{option.name}</span>
{option.comment !== undefined && (
<span title={option.comment} className="text-foreground-lighter">
{option.comment}
</span>
)}
<span className="flex items-center gap-1.5">
{option.name === value ? <Check size={13} /> : ''}
</span>
</div>
</CommandItem_Shadcn_>
))}
</CommandGroup_Shadcn_>
</>
)}
</CommandList_Shadcn_>
</Command_Shadcn_>
</ScrollArea>
</PopoverContent_Shadcn_>
</Popover_Shadcn_>
{showRecommendation && recommendation !== undefined && (
<Alert
withIcon
variant="warning"
title={
<>
It is recommended to use <code className="text-xs">{recommendation.alternative}</code>{' '}
instead
</>
}
>
<p>
Postgres recommends against using the data type <code className="text-xs">{value}</code>{' '}
unless you have a very specific use case.
</p>
<div className="flex items-center space-x-2 mt-3">
<Button asChild type="default" icon={<ExternalLink />}>
<Link href={recommendation.reference} target="_blank" rel="noreferrer">
Read more
</Link>
</Button>
<Button type="primary" onClick={() => onOptionSelect(recommendation.alternative)}>
Use {recommendation.alternative}
</Button>
</div>
</Alert>
<Alert_Shadcn_ variant="warning">
<CriticalIcon />
<AlertTitle_Shadcn_>
{' '}
It is recommended to use <code className="text-xs">
{recommendation.alternative}
</code>{' '}
insteadn
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
<p>
Postgres recommends against using the data type{' '}
<code className="text-xs">{value}</code> unless you have a very specific use case.
</p>
<div className="flex items-center space-x-2 mt-3">
<Button asChild type="default" icon={<ExternalLink />}>
<Link href={recommendation.reference} target="_blank" rel="noreferrer">
Read more
</Link>
</Button>
<Button type="primary" onClick={() => onOptionSelect(recommendation.alternative)}>
Use {recommendation.alternative}
</Button>
</div>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
</div>
)

View File

@@ -226,7 +226,6 @@ const Column = ({
<ColumnType
value={column.format}
enumTypes={enumTypes}
size="small"
showLabel={false}
className="table-editor-column-type lg:gap-0 "
disabled={hasForeignKeys}

View File

@@ -37,7 +37,7 @@ test.describe('Table Editor page', () => {
await page.getByRole('button', { name: 'Add column' }).click()
await page.getByRole('textbox', { name: 'column_name' }).click()
await page.getByRole('textbox', { name: 'column_name' }).fill('defaultValueColumn')
await page.getByRole('button', { name: '---' }).click()
await page.locator('button').filter({ hasText: 'Choose a column type...' }).click()
await page.getByText('Signed two-byte integer').click()
await page.getByTestId('defaultValueColumn-default-value').click()
await page.getByTestId('defaultValueColumn-default-value').fill('2')