Skip to content

Commit c73d3fd

Browse files
saltcodivasilov
andauthored
Redo column type dropdown (supabase#29534)
* Redo column type dropdown * Fix spacing * Fix tests * Fix tests,2 * Fix the tests. --------- Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
1 parent c2b6bed commit c73d3fd

File tree

5 files changed

+171
-114
lines changed

5 files changed

+171
-114
lines changed

apps/studio/components/interfaces/Database/Wrappers/WrapperDynamicColumns.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ const WrapperDynamicColumns = ({
131131
value={column.type}
132132
enumTypes={[]}
133133
onOptionSelect={(value) => onUpdateValue(column.id, 'type', value)}
134-
layout="vertical"
135134
className="[&_label]:!p-0"
136135
/>
137136
</div>

apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,6 @@ const ColumnEditor = ({
264264
<ColumnType
265265
showRecommendation
266266
value={columnFields?.format ?? ''}
267-
layout="vertical"
268267
enumTypes={enumTypes}
269268
error={errors.format}
270269
description={
@@ -305,7 +304,6 @@ const ColumnEditor = ({
305304
)}
306305
</div>
307306
)}
308-
309307
<ColumnDefaultValue
310308
columnFields={columnFields}
311309
enumTypes={enumTypes}

apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx

Lines changed: 170 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,39 @@
11
import * as Tooltip from '@radix-ui/react-tooltip'
22
import { noop } from 'lodash'
33
import Link from 'next/link'
4-
import { ReactNode } from 'react'
5-
import { Alert, Button, Input, Listbox } from 'ui'
4+
import { ReactNode, useState } from 'react'
5+
import {
6+
AlertDescription_Shadcn_,
7+
AlertTitle_Shadcn_,
8+
Alert_Shadcn_,
9+
Button,
10+
CommandEmpty_Shadcn_,
11+
CommandGroup_Shadcn_,
12+
CommandInput_Shadcn_,
13+
CommandItem_Shadcn_,
14+
CommandList_Shadcn_,
15+
Command_Shadcn_,
16+
CriticalIcon,
17+
Input,
18+
PopoverContent_Shadcn_,
19+
PopoverTrigger_Shadcn_,
20+
Popover_Shadcn_,
21+
ScrollArea,
22+
cn,
23+
} from 'ui'
624

725
import type { EnumeratedType } from 'data/enumerated-types/enumerated-types-query'
8-
import { Calendar, Circle, ExternalLink, Hash, ListPlus, ToggleRight, Type } from 'lucide-react'
26+
import {
27+
Calendar,
28+
Check,
29+
ChevronsUpDown,
30+
ExternalLink,
31+
Hash,
32+
ListPlus,
33+
ToggleRight,
34+
Type,
35+
} from 'lucide-react'
36+
937
import {
1038
POSTGRES_DATA_TYPES,
1139
POSTGRES_DATA_TYPE_OPTIONS,
@@ -16,8 +44,6 @@ import type { PostgresDataTypeOption } from '../SidePanelEditor.types'
1644
interface ColumnTypeProps {
1745
value: string
1846
enumTypes: EnumeratedType[]
19-
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge'
20-
layout?: 'vertical' | 'horizontal'
2147
className?: string
2248
error?: any
2349
disabled?: boolean
@@ -30,9 +56,6 @@ interface ColumnTypeProps {
3056
const ColumnType = ({
3157
value,
3258
enumTypes = [],
33-
className,
34-
size = 'medium',
35-
layout,
3659
error,
3760
disabled = false,
3861
showLabel = true,
@@ -44,25 +67,43 @@ const ColumnType = ({
4467
const availableTypes = POSTGRES_DATA_TYPES.concat(enumTypes.map((type) => type.name))
4568
const isAvailableType = value ? availableTypes.includes(value) : true
4669
const recommendation = RECOMMENDED_ALTERNATIVE_DATA_TYPE[value]
70+
const [open, setOpen] = useState(false)
71+
console.log({ availableTypes })
72+
73+
const getOptionByName = (name: string) => {
74+
// handle built in types
75+
const pgOption = POSTGRES_DATA_TYPE_OPTIONS.find((option) => option.name === name)
76+
if (pgOption) return pgOption
77+
78+
// handle custom enums
79+
const enumType = enumTypes.find((type) => type.name === name)
80+
return enumType ? { ...enumType, type: 'enum' } : undefined
81+
}
4782

4883
const inferIcon = (type: string) => {
4984
switch (type) {
5085
case 'number':
51-
return <Hash size={16} className="text-foreground" strokeWidth={1.5} />
86+
return <Hash size={14} className="text-foreground" strokeWidth={1.5} />
5287
case 'time':
53-
return <Calendar size={16} className="text-foreground" strokeWidth={1.5} />
88+
return <Calendar size={14} className="text-foreground" strokeWidth={1.5} />
5489
case 'text':
55-
return <Type size={16} className="text-foreground" strokeWidth={1.5} />
90+
return <Type size={14} className="text-foreground" strokeWidth={1.5} />
5691
case 'json':
5792
return (
5893
<div className="text-foreground" style={{ padding: '0px 1px' }}>
5994
{'{ }'}
6095
</div>
6196
)
97+
case 'jsonb':
98+
return (
99+
<div className="text-foreground" style={{ padding: '0px 1px' }}>
100+
{'{ }'}
101+
</div>
102+
)
62103
case 'bool':
63-
return <ToggleRight size={16} className="text-foreground" strokeWidth={1.5} />
104+
return <ToggleRight size={14} className="text-foreground" strokeWidth={1.5} />
64105
default:
65-
return <Circle size={16} className="text-foreground p-0.5" strokeWidth={1.5} />
106+
return <ListPlus size={16} className="text-foreground" strokeWidth={1.5} />
66107
}
67108
}
68109

@@ -119,7 +160,6 @@ const ColumnType = ({
119160
layout={showLabel ? 'horizontal' : undefined}
120161
className="md:gap-x-0"
121162
size="small"
122-
icon={inferIcon(POSTGRES_DATA_TYPE_OPTIONS.find((x) => x.name === value)?.type ?? '')}
123163
value={value}
124164
/>
125165
</Tooltip.Trigger>
@@ -143,105 +183,126 @@ const ColumnType = ({
143183
}
144184

145185
return (
146-
<div className="space-y-2">
147-
<Listbox
148-
label={showLabel ? 'Type' : ''}
149-
layout={layout || (showLabel ? 'horizontal' : 'vertical')}
150-
value={value}
151-
size={size}
152-
error={error}
153-
disabled={disabled}
154-
// @ts-ignore
155-
descriptionText={description}
156-
className={`${className} ${disabled ? 'column-type-disabled' : ''} rounded-md`}
157-
onChange={(value: string) => onOptionSelect(value)}
158-
optionsWidth={480}
159-
>
160-
<Listbox.Option key="empty" value="" label="---">
161-
---
162-
</Listbox.Option>
163-
164-
{/*
165-
Weird issue with Listbox here
166-
1. Can't do render conditionally (&&) within Listbox hence why using Fragment
167-
2. Can't wrap these 2 components within a Fragment conditional (enumTypes.length)
168-
as selecting the enumType option will not render it in the Listbox component
169-
*/}
170-
{enumTypes.length > 0 ? (
171-
<Listbox.Option disabled key="header-1" value="header-1" label="header-1">
172-
Other Data Types
173-
</Listbox.Option>
174-
) : (
175-
<></>
176-
)}
177-
178-
{enumTypes.length > 0 ? (
179-
// @ts-ignore
180-
enumTypes.map((enumType: PostgresType) => (
181-
<Listbox.Option
182-
key={enumType.name}
183-
value={enumType.name}
184-
label={enumType.name}
185-
addOnBefore={() => {
186-
return <ListPlus size={16} className="text-foreground" strokeWidth={1.5} />
187-
}}
188-
>
189-
<div className="flex items-center space-x-4">
190-
<p className="text-foreground">{enumType.name}</p>
191-
{enumType.comment !== undefined && (
192-
<p className="text-foreground-lighter">{enumType.comment}</p>
193-
)}
186+
<div>
187+
<Popover_Shadcn_ open={open} onOpenChange={setOpen}>
188+
<PopoverTrigger_Shadcn_ asChild>
189+
<Button
190+
type="default"
191+
role="combobox"
192+
size={'small'}
193+
aria-expanded={open}
194+
className="w-full justify-between"
195+
iconRight={<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />}
196+
>
197+
{value ? (
198+
<div className="flex gap-2 items-center">
199+
<span>{inferIcon(getOptionByName(value)?.type ?? '')}</span>
200+
{value}
194201
</div>
195-
</Listbox.Option>
196-
))
197-
) : (
198-
<></>
199-
)}
202+
) : (
203+
'Choose a column type...'
204+
)}
205+
</Button>
206+
</PopoverTrigger_Shadcn_>
207+
<PopoverContent_Shadcn_ className="w-[460px] p-0" side="bottom" align="center">
208+
<ScrollArea className="h-[335px]">
209+
<Command_Shadcn_>
210+
<CommandInput_Shadcn_ placeholder="Search types..." />
211+
<CommandEmpty_Shadcn_>Type not found.</CommandEmpty_Shadcn_>
200212

201-
<Listbox.Option disabled value="header-2" label="header-2">
202-
PostgreSQL Data Types
203-
</Listbox.Option>
213+
<CommandList_Shadcn_>
214+
<CommandGroup_Shadcn_>
215+
{POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption) => (
216+
<CommandItem_Shadcn_
217+
key={option.name}
218+
value={option.name}
219+
className={cn('relative', option.name === value ? 'bg-surface-200' : '')}
220+
onSelect={(value: string) => {
221+
onOptionSelect(value)
222+
setOpen(false)
223+
}}
224+
>
225+
<div className="flex items-center gap-2 pr-6">
226+
<span>{inferIcon(option.type)}</span>
227+
<span className="text-foreground">{option.name}</span>
228+
<span className="text-foreground-lighter">{option.description}</span>
229+
</div>
230+
<span className="absolute right-3 top-2">
231+
{option.name === value ? (
232+
<Check className="text-brand-500" size={14} />
233+
) : (
234+
''
235+
)}
236+
</span>
237+
</CommandItem_Shadcn_>
238+
))}
239+
</CommandGroup_Shadcn_>
240+
{enumTypes.length > 0 && (
241+
<>
242+
<CommandItem_Shadcn_>Other types</CommandItem_Shadcn_>
243+
<CommandGroup_Shadcn_>
244+
{enumTypes.map((option: any) => (
245+
<CommandItem_Shadcn_
246+
key={option.name}
247+
value={option.name}
248+
className={cn('relative', option.name === value ? 'bg-surface-200' : '')}
249+
onSelect={(value: string) => {
250+
onOptionSelect(value)
251+
setOpen(false)
252+
}}
253+
>
254+
<div className="flex items-center gap-2">
255+
<div>
256+
<ListPlus size={16} className="text-foreground" strokeWidth={1.5} />
257+
</div>
258+
<span className="text-foreground">{option.name}</span>
259+
{option.comment !== undefined && (
260+
<span title={option.comment} className="text-foreground-lighter">
261+
{option.comment}
262+
</span>
263+
)}
264+
<span className="flex items-center gap-1.5">
265+
{option.name === value ? <Check size={13} /> : ''}
266+
</span>
267+
</div>
268+
</CommandItem_Shadcn_>
269+
))}
270+
</CommandGroup_Shadcn_>
271+
</>
272+
)}
273+
</CommandList_Shadcn_>
274+
</Command_Shadcn_>
275+
</ScrollArea>
276+
</PopoverContent_Shadcn_>
277+
</Popover_Shadcn_>
204278

205-
{POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption) => (
206-
<Listbox.Option
207-
key={option.name}
208-
value={option.name}
209-
label={option.name}
210-
addOnBefore={() => inferIcon(option.type)}
211-
>
212-
<div className="flex items-center space-x-4">
213-
<span className="text-foreground">{option.name}</span>
214-
<span className="text-foreground-lighter">{option.description}</span>
215-
</div>
216-
</Listbox.Option>
217-
))}
218-
</Listbox>
219279
{showRecommendation && recommendation !== undefined && (
220-
<Alert
221-
withIcon
222-
variant="warning"
223-
title={
224-
<>
225-
It is recommended to use <code className="text-xs">{recommendation.alternative}</code>{' '}
226-
instead
227-
</>
228-
}
229-
>
230-
<p>
231-
Postgres recommends against using the data type <code className="text-xs">{value}</code>{' '}
232-
unless you have a very specific use case.
233-
</p>
234-
<div className="flex items-center space-x-2 mt-3">
235-
<Button asChild type="default" icon={<ExternalLink />}>
236-
<Link href={recommendation.reference} target="_blank" rel="noreferrer">
237-
Read more
238-
</Link>
239-
</Button>
240-
<Button type="primary" onClick={() => onOptionSelect(recommendation.alternative)}>
241-
Use {recommendation.alternative}
242-
</Button>
243-
</div>
244-
</Alert>
280+
<Alert_Shadcn_ variant="warning">
281+
<CriticalIcon />
282+
<AlertTitle_Shadcn_>
283+
{' '}
284+
It is recommended to use <code className="text-xs">
285+
{recommendation.alternative}
286+
</code>{' '}
287+
insteadn
288+
</AlertTitle_Shadcn_>
289+
<AlertDescription_Shadcn_>
290+
<p>
291+
Postgres recommends against using the data type{' '}
292+
<code className="text-xs">{value}</code> unless you have a very specific use case.
293+
</p>
294+
<div className="flex items-center space-x-2 mt-3">
295+
<Button asChild type="default" icon={<ExternalLink />}>
296+
<Link href={recommendation.reference} target="_blank" rel="noreferrer">
297+
Read more
298+
</Link>
299+
</Button>
300+
<Button type="primary" onClick={() => onOptionSelect(recommendation.alternative)}>
301+
Use {recommendation.alternative}
302+
</Button>
303+
</div>
304+
</AlertDescription_Shadcn_>
305+
</Alert_Shadcn_>
245306
)}
246307
</div>
247308
)

apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,6 @@ const Column = ({
226226
<ColumnType
227227
value={column.format}
228228
enumTypes={enumTypes}
229-
size="small"
230229
showLabel={false}
231230
className="table-editor-column-type lg:gap-0 "
232231
disabled={hasForeignKeys}

playwright-tests/tests/snapshot/spec/table-editor.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test.describe('Table Editor page', () => {
3737
await page.getByRole('button', { name: 'Add column' }).click()
3838
await page.getByRole('textbox', { name: 'column_name' }).click()
3939
await page.getByRole('textbox', { name: 'column_name' }).fill('defaultValueColumn')
40-
await page.getByRole('button', { name: '---' }).click()
40+
await page.locator('button').filter({ hasText: 'Choose a column type...' }).click()
4141
await page.getByText('Signed two-byte integer').click()
4242
await page.getByTestId('defaultValueColumn-default-value').click()
4343
await page.getByTestId('defaultValueColumn-default-value').fill('2')

0 commit comments

Comments
 (0)