Skip to content

Commit 7324f77

Browse files
committed
♻️ move column option logics into useTableOptionSelect
1 parent ae98328 commit 7324f77

File tree

4 files changed

+174
-91
lines changed

4 files changed

+174
-91
lines changed

frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableColumnOptions.tsx

Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,12 @@ import {
77
} from '@liam-hq/ui'
88
import clsx from 'clsx'
99
import { Command } from 'cmdk'
10+
import { type ComponentProps, type FC, useMemo } from 'react'
1011
import {
11-
type ComponentProps,
12-
type FC,
13-
useCallback,
14-
useEffect,
15-
useMemo,
16-
} from 'react'
17-
import {
18-
getTableColumnElementId,
1912
getTableColumnLinkHref,
2013
getTableLinkHref,
2114
} from '../../../../../../features'
2215
import { useSchemaOrThrow } from '../../../../../../stores'
23-
import { useTableSelection } from '../../../../hooks'
24-
import { useCommandPaletteOrThrow } from '../CommandPaletteProvider'
2516
import type { CommandPaletteSuggestion } from '../types'
2617
import { getSuggestionText } from '../utils'
2718
import styles from './CommandPaletteOptions.module.css'
@@ -51,44 +42,8 @@ const ColumnIcon: FC<ComponentProps<'svg'> & { columnType: ColumnType }> = ({
5142

5243
export const TableColumnOptions: FC<Props> = ({ tableName, suggestion }) => {
5344
const schema = useSchemaOrThrow()
54-
const { selectTable } = useTableSelection()
55-
const { setOpen } = useCommandPaletteOrThrow()
56-
57-
const goToERD = useCallback(
58-
(tableName: string, columnName?: string) => {
59-
selectTable({ tableId: tableName, displayArea: 'main' })
60-
setOpen(false)
61-
if (columnName) {
62-
window.location.hash = getTableColumnElementId(tableName, columnName)
63-
}
64-
},
65-
[setOpen, selectTable],
66-
)
67-
68-
// Select option by pressing [Enter] key (with/without ⌘ key)
69-
useEffect(() => {
70-
// It doesn't subscribe a keydown event listener if the suggestion type is not "column"
71-
if (suggestion?.type !== 'column') return
72-
73-
const down = (event: KeyboardEvent) => {
74-
const { tableName, columnName } = suggestion
7545

76-
if (event.key === 'Enter') {
77-
event.preventDefault()
78-
79-
if (event.metaKey || event.ctrlKey) {
80-
window.open(getTableColumnLinkHref(tableName, columnName))
81-
} else {
82-
goToERD(tableName, columnName)
83-
}
84-
}
85-
}
86-
87-
document.addEventListener('keydown', down)
88-
return () => document.removeEventListener('keydown', down)
89-
}, [suggestion, goToERD])
90-
91-
const { tableOptionSelectHandler } = useTableOptionSelect(suggestion)
46+
const { optionSelectHandler } = useTableOptionSelect(suggestion)
9247

9348
const table = schema.current.tables[tableName]
9449
const columnTypeMap = useMemo(
@@ -108,7 +63,7 @@ export const TableColumnOptions: FC<Props> = ({ tableName, suggestion }) => {
10863
<a
10964
className={styles.item}
11065
href={getTableLinkHref(table.name)}
111-
onClick={(event) => tableOptionSelectHandler(event, table.name)}
66+
onClick={(event) => optionSelectHandler(event, table.name)}
11267
>
11368
<Table2 className={styles.itemIcon} />
11469
<span className={styles.itemText}>{table.name}</span>
@@ -126,15 +81,9 @@ export const TableColumnOptions: FC<Props> = ({ tableName, suggestion }) => {
12681
<a
12782
className={clsx(styles.item, styles.indent)}
12883
href={getTableColumnLinkHref(table.name, column.name)}
129-
onClick={(event) => {
130-
// Do not call preventDefault to allow the default link behavior when ⌘ key is pressed
131-
if (event.ctrlKey || event.metaKey) {
132-
return
133-
}
134-
135-
event.preventDefault()
136-
goToERD(table.name, column.name)
137-
}}
84+
onClick={(event) =>
85+
optionSelectHandler(event, table.name, column.name)
86+
}
13887
>
13988
{columnTypeMap[column.name] && (
14089
<ColumnIcon

frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/TableOptions.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ type Props = {
1515
export const TableOptions: FC<Props> = ({ suggestion }) => {
1616
const schema = useSchemaOrThrow()
1717

18-
const { tableOptionSelectHandler } = useTableOptionSelect(suggestion)
18+
const { optionSelectHandler } = useTableOptionSelect(suggestion)
1919

2020
return (
2121
<Command.Group heading="Tables">
@@ -27,7 +27,7 @@ export const TableOptions: FC<Props> = ({ suggestion }) => {
2727
<a
2828
className={styles.item}
2929
href={getTableLinkHref(table.name)}
30-
onClick={(event) => tableOptionSelectHandler(event, table.name)}
30+
onClick={(event) => optionSelectHandler(event, table.name)}
3131
>
3232
<Table2 className={styles.itemIcon} />
3333
<span className={styles.itemText}>{table.name}</span>

frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/hooks/useTableOptionSelect.test.tsx

Lines changed: 128 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ import userEvent from '@testing-library/user-event'
33
import { ReactFlowProvider } from '@xyflow/react'
44
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
55
import type { FC, ReactNode } from 'react'
6-
import { afterEach, describe, expect, it, vi } from 'vitest'
6+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
77
import { UserEditingProvider } from '../../../../../../../stores'
88
import * as UseTableSelection from '../../../../../hooks'
99
import { CommandPaletteProvider } from '../../CommandPaletteProvider'
1010
import * as UseCommandPalette from '../../CommandPaletteProvider/hooks'
1111
import type { CommandPaletteSuggestion } from '../../types'
1212
import { useTableOptionSelect } from './useTableOptionSelect'
1313

14+
beforeEach(() => {
15+
window.location.hash = ''
16+
})
17+
1418
afterEach(() => {
1519
vi.clearAllMocks()
1620
})
@@ -86,9 +90,57 @@ describe('keyboard interactions', () => {
8690
})
8791
})
8892

93+
describe('when suggestion is a column', () => {
94+
it('moves to suggested table column in ERD and closes the dialog on Enter', async () => {
95+
const user = userEvent.setup()
96+
renderHook(
97+
() =>
98+
useTableOptionSelect({
99+
type: 'column',
100+
tableName: 'users',
101+
columnName: 'name',
102+
}),
103+
{ wrapper },
104+
)
105+
106+
await user.keyboard('{Enter}')
107+
108+
expect(mockSelectTable).toHaveBeenCalledWith({
109+
displayArea: 'main',
110+
tableId: 'users',
111+
})
112+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
113+
114+
// other functions are not called
115+
expect(mockWindowOpen).not.toHaveBeenCalled()
116+
})
117+
118+
it('opens suggested table in another tab on ⌘Enter', async () => {
119+
const user = userEvent.setup()
120+
renderHook(
121+
() =>
122+
useTableOptionSelect({
123+
type: 'column',
124+
tableName: 'users',
125+
columnName: 'name',
126+
}),
127+
{ wrapper },
128+
)
129+
130+
await user.keyboard('{Meta>}{Enter}{/Meta}')
131+
132+
expect(mockWindowOpen).toHaveBeenCalledWith(
133+
'?active=users#users__columns__name',
134+
)
135+
136+
// other functions are not called
137+
expect(mockSelectTable).not.toHaveBeenCalled()
138+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
139+
})
140+
})
141+
89142
describe.each<CommandPaletteSuggestion | null>([
90143
{ type: 'command', name: 'copy link' },
91-
{ type: 'column', tableName: 'users', columnName: 'created_at' },
92144
null,
93145
])('when suggestion is other than tables, suggestion = %o', (suggestion) => {
94146
it('does nothing when on Enter', async () => {
@@ -115,48 +167,99 @@ describe('keyboard interactions', () => {
115167
})
116168
})
117169

118-
describe('tableOptionSelectHandler', () => {
119-
// a component for testing the "tableOptionSelectHandler" method of the hook
170+
describe('optionSelectHandler', () => {
171+
// a component for testing the "optionSelectHandler" method of the hook
120172
// in the test cases, we simulate the method clicking a link of a table option
121-
const TableOptionLinkWithSelectHandler: FC<{ tableName: string }> = ({
122-
tableName,
123-
}) => {
124-
const { tableOptionSelectHandler } = useTableOptionSelect(null)
173+
const TableOptionLinkWithSelectHandler: FC<{
174+
tableName: string
175+
columnName: string | undefined
176+
}> = ({ tableName, columnName }) => {
177+
const { optionSelectHandler } = useTableOptionSelect(null)
125178

126179
return (
127180
<a
128181
href="/"
129182
// use the handler by passing the event object and table name
130-
onClick={(event) => tableOptionSelectHandler(event, tableName)}
183+
onClick={(event) => optionSelectHandler(event, tableName, columnName)}
131184
>
132185
table option link
133186
</a>
134187
)
135188
}
136189

137-
it('moves to clicked table in ERD and closes the dialog', async () => {
138-
const user = userEvent.setup()
139-
render(<TableOptionLinkWithSelectHandler tableName="follows" />, {
140-
wrapper,
190+
describe('when passing only tableName', () => {
191+
it('moves to clicked table in ERD and closes the dialog', async () => {
192+
const user = userEvent.setup()
193+
render(
194+
<TableOptionLinkWithSelectHandler
195+
tableName="follows"
196+
columnName={undefined}
197+
/>,
198+
{ wrapper },
199+
)
200+
201+
await user.click(screen.getByRole('link', { name: 'table option link' }))
202+
203+
expect(mockSelectTable).toHaveBeenCalled()
204+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
141205
})
142206

143-
await user.click(screen.getByRole('link', { name: 'table option link' }))
207+
it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
208+
const user = userEvent.setup()
209+
render(
210+
<TableOptionLinkWithSelectHandler
211+
tableName="follows"
212+
columnName={undefined}
213+
/>,
214+
{ wrapper },
215+
)
216+
217+
await user.keyboard('{Meta>}')
218+
await user.click(screen.getByRole('link', { name: 'table option link' }))
219+
await user.keyboard('{/Meta}')
144220

145-
expect(mockSelectTable).toHaveBeenCalled()
146-
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
221+
expect(mockSelectTable).not.toHaveBeenCalled()
222+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
223+
})
147224
})
148225

149-
it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
150-
const user = userEvent.setup()
151-
render(<TableOptionLinkWithSelectHandler tableName="follows" />, {
152-
wrapper,
226+
describe('when passing both tableName and columnName', () => {
227+
it('moves to clicked table in ERD and closes the dialog', async () => {
228+
const user = userEvent.setup()
229+
render(
230+
<TableOptionLinkWithSelectHandler
231+
tableName="follows"
232+
columnName="user_id"
233+
/>,
234+
{ wrapper },
235+
)
236+
237+
await user.click(screen.getByRole('link', { name: 'table option link' }))
238+
239+
expect(mockSelectTable).toHaveBeenCalled()
240+
expect(mockSetCommandPaletteDialogOpen).toHaveBeenCalledWith(false)
241+
expect(window.location.hash).toBe('#follows__columns__user_id')
153242
})
154243

155-
await user.keyboard('{Meta>}')
156-
await user.click(screen.getByRole('link', { name: 'table option link' }))
157-
await user.keyboard('{/Meta}')
244+
it('does nothing with ⌘ + click (default browser action: open in new tab)', async () => {
245+
const user = userEvent.setup()
246+
render(
247+
<TableOptionLinkWithSelectHandler
248+
tableName="follows"
249+
columnName="user_id"
250+
/>,
251+
{
252+
wrapper,
253+
},
254+
)
255+
256+
await user.keyboard('{Meta>}')
257+
await user.click(screen.getByRole('link', { name: 'table option link' }))
258+
await user.keyboard('{/Meta}')
158259

159-
expect(mockSelectTable).not.toHaveBeenCalled()
160-
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
260+
expect(mockSelectTable).not.toHaveBeenCalled()
261+
expect(mockSetCommandPaletteDialogOpen).not.toHaveBeenCalled()
262+
expect(window.location.hash).toBe('')
263+
})
161264
})
162265
})

frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteOptions/hooks/useTableOptionSelect.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { useCallback, useEffect } from 'react'
22
import { useTableSelection } from '../../../../../../erd/hooks'
3-
import { getTableLinkHref } from '../../../../../utils'
3+
import {
4+
getTableColumnElementId,
5+
getTableColumnLinkHref,
6+
getTableLinkHref,
7+
} from '../../../../../utils'
48
import { useCommandPaletteOrThrow } from '../../CommandPaletteProvider'
59
import type { CommandPaletteSuggestion } from '../../types'
610

@@ -11,27 +15,31 @@ export const useTableOptionSelect = (
1115

1216
const { selectTable } = useTableSelection()
1317
const goToERD = useCallback(
14-
(tableName: string) => {
18+
(tableName: string, columnName?: string) => {
1519
selectTable({ tableId: tableName, displayArea: 'main' })
20+
if (columnName) {
21+
window.location.hash = getTableColumnElementId(tableName, columnName)
22+
}
23+
1624
setOpen(false)
1725
},
1826
[selectTable, setOpen],
1927
)
2028

21-
const tableOptionSelectHandler = useCallback(
22-
(event: React.MouseEvent, tableName: string) => {
29+
const optionSelectHandler = useCallback(
30+
(event: React.MouseEvent, tableName: string, columnName?: string) => {
2331
// Do not call preventDefault to allow the default link behavior when ⌘ key is pressed
2432
if (event.ctrlKey || event.metaKey) {
2533
return
2634
}
2735

2836
event.preventDefault()
29-
goToERD(tableName)
37+
goToERD(tableName, columnName)
3038
},
3139
[goToERD],
3240
)
3341

34-
// Select option by pressing [Enter] key (with/without ⌘ key)
42+
// Select table option by pressing [Enter] key (with/without ⌘ key)
3543
useEffect(() => {
3644
// It doesn't subscribe a keydown event listener if the suggestion type is not "table"
3745
if (suggestion?.type !== 'table') return
@@ -54,5 +62,28 @@ export const useTableOptionSelect = (
5462
return () => document.removeEventListener('keydown', down)
5563
}, [suggestion, goToERD])
5664

57-
return { tableOptionSelectHandler }
65+
// Select column option by pressing [Enter] key (with/without ⌘ key)
66+
useEffect(() => {
67+
// It doesn't subscribe a keydown event listener if the suggestion type is not "column"
68+
if (suggestion?.type !== 'column') return
69+
70+
const down = (event: KeyboardEvent) => {
71+
const { tableName, columnName } = suggestion
72+
73+
if (event.key === 'Enter') {
74+
event.preventDefault()
75+
76+
if (event.metaKey || event.ctrlKey) {
77+
window.open(getTableColumnLinkHref(tableName, columnName))
78+
} else {
79+
goToERD(tableName, columnName)
80+
}
81+
}
82+
}
83+
84+
document.addEventListener('keydown', down)
85+
return () => document.removeEventListener('keydown', down)
86+
}, [suggestion, goToERD])
87+
88+
return { optionSelectHandler }
5889
}

0 commit comments

Comments
 (0)