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:
@@ -131,7 +131,6 @@ const WrapperDynamicColumns = ({
|
||||
value={column.type}
|
||||
enumTypes={[]}
|
||||
onOptionSelect={(value) => onUpdateValue(column.id, 'type', value)}
|
||||
layout="vertical"
|
||||
className="[&_label]:!p-0"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user