diff --git a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteContent/CommandPaletteContent.test.tsx b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteContent/CommandPaletteContent.test.tsx
index 24ff12352c..4cb111d3ce 100644
--- a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteContent/CommandPaletteContent.test.tsx
+++ b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteContent/CommandPaletteContent.test.tsx
@@ -17,8 +17,40 @@ import { CommandPaletteContent } from './CommandPaletteContent'
const schema: SchemaProviderValue = {
current: {
tables: {
- users: aTable({ name: 'users' }),
- posts: aTable({ name: 'posts' }),
+ users: aTable({
+ name: 'users',
+ columns: {
+ id: {
+ name: 'id',
+ type: 'bigint',
+ default: null,
+ check: null,
+ notNull: true,
+ comment: null,
+ },
+ name: {
+ name: 'name',
+ type: 'text',
+ default: null,
+ check: null,
+ notNull: true,
+ comment: null,
+ },
+ },
+ }),
+ posts: aTable({
+ name: 'posts',
+ columns: {
+ created_at: {
+ name: 'created_at',
+ type: 'timestamp',
+ default: null,
+ check: null,
+ notNull: true,
+ comment: null,
+ },
+ },
+ }),
follows: aTable({ name: 'follows' }),
user_settings: aTable({ name: 'user_settings' }),
},
@@ -175,6 +207,41 @@ describe('input mode and filters', () => {
).toBeInTheDocument()
})
})
+
+ describe('table input mode', () => {
+ it('renders selected table option and its column options', async () => {
+ const user = userEvent.setup()
+ render(, { wrapper })
+
+ // switch to "table" input mode of the "users" table
+ await user.keyboard('users')
+ await user.keyboard('{Tab}')
+
+ // the "users" table option is always displayed on the top of the options list
+ expect(screen.getByRole('option', { name: 'users' })).toBeInTheDocument()
+ // column options of the "users" table
+ expect(screen.getByRole('option', { name: 'id' })).toBeInTheDocument()
+ expect(screen.getByRole('option', { name: 'name' })).toBeInTheDocument()
+ })
+
+ it('filters column options by user input in the combobox while keeping the selected table option visible', async () => {
+ const user = userEvent.setup()
+ render(, { wrapper })
+
+ // switch to "table" input mode of the "users" table
+ await user.keyboard('users')
+ await user.keyboard('{Tab}')
+ // filter the column options by typing "name"
+ await user.keyboard('name')
+
+ expect(screen.getByRole('option', { name: 'users' })).toBeInTheDocument()
+ expect(screen.getByRole('option', { name: 'name' })).toBeInTheDocument()
+ // the "id" column option is filtered out
+ expect(
+ screen.queryByRole('option', { name: 'id' }),
+ ).not.toBeInTheDocument()
+ })
+ })
})
describe('preview', () => {
@@ -192,6 +259,29 @@ describe('preview', () => {
await user.hover(screen.getByRole('option', { name: 'posts' }))
expect(within(previewContainer).getByText('posts')).toBeInTheDocument()
})
+
+ it('renders a table preview when a column option is selected', async () => {
+ const user = userEvent.setup()
+ render(, { wrapper })
+ const previewContainer = screen.getByTestId('CommandPalettePreview')
+
+ // renders the "users" preview when the "users/name" option is selected
+ await user.hover(screen.getByRole('option', { name: 'users' }))
+ // go to the "table" input mode of the "users" table
+ await user.keyboard('{Tab}')
+ await user.hover(screen.getByRole('option', { name: 'name' }))
+ expect(within(previewContainer).getByText('users')).toBeInTheDocument()
+
+ // back to the "default" input mode
+ await user.keyboard('{Backspace}')
+
+ // renders the "posts" preview when the "posts/created_at" option is selected
+ await user.hover(screen.getByRole('option', { name: 'posts' }))
+ // go to the "table" input mode of the "posts" table
+ await user.keyboard('{Tab}')
+ await user.hover(screen.getByRole('option', { name: 'created_at' }))
+ expect(within(previewContainer).getByText('posts')).toBeInTheDocument()
+ })
})
describe('command preview', () => {
diff --git a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteContent/CommandPaletteContent.tsx b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteContent/CommandPaletteContent.tsx
index 7808c05445..0591b442d0 100644
--- a/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteContent/CommandPaletteContent.tsx
+++ b/frontend/packages/erd-core/src/features/erd/components/ERDRenderer/CommandPalette/CommandPaletteContent/CommandPaletteContent.tsx
@@ -1,31 +1,68 @@
import { Button } from '@liam-hq/ui'
import { DialogClose } from '@radix-ui/react-dialog'
-import { Command, defaultFilter } from 'cmdk'
+import { Command, defaultFilter as cmdkBaseFilter } from 'cmdk'
import { type FC, useMemo, useState } from 'react'
import {
CommandPaletteCommandOptions,
TableOptions,
} from '../CommandPaletteOptions'
+import { TableColumnOptions } from '../CommandPaletteOptions/TableColumnOptions'
import { CommandPreview, TablePreview } from '../CommandPalettePreview'
import { CommandPaletteSearchInput } from '../CommandPaletteSearchInput'
import type { CommandPaletteInputMode } from '../types'
import { textToSuggestion } from '../utils'
import styles from './CommandPaletteContent.module.css'
-const commandPaletteFilter: typeof defaultFilter = (value, ...rest) => {
+const defaultCommandPaletteFilter: typeof cmdkBaseFilter = (value, ...rest) => {
const suggestion = textToSuggestion(value)
// if the value is inappropriate for suggestion, it returns 0 and the options is hidden
// https://github.com/pacocoursey/cmdk/blob/d6fde235386414196bf80d9b9fa91e2cf89a72ea/cmdk/src/index.tsx#L91-L95
if (!suggestion) return 0
- return defaultFilter(suggestion.name, ...rest)
+ // displays 'table' and 'command' type suggestions in the "default" input mode
+ if (suggestion.type === 'table' || suggestion.type === 'command') {
+ return cmdkBaseFilter(suggestion.name, ...rest)
+ }
+
+ return 0
+}
+
+const tableInputModeFilter: typeof cmdkBaseFilter = (value, ...rest) => {
+ const suggestion = textToSuggestion(value)
+
+ // if the value is inappropriate for suggestion, it returns 0 and the options is hidden
+ // https://github.com/pacocoursey/cmdk/blob/d6fde235386414196bf80d9b9fa91e2cf89a72ea/cmdk/src/index.tsx#L91-L95
+ if (!suggestion) return 0
+
+ // always display the table itself on the top of the options list
+ if (suggestion.type === 'table') return 1
+ if (suggestion.type === 'column')
+ return cmdkBaseFilter(suggestion.columnName, ...rest)
+
+ // it displays only 'table' and 'column' type suggestions in the "table" input mode
+ return 0
}
-export const CommandPaletteContent: FC = () => {
+type Props = {
+ isTableModeActivatable?: boolean
+}
+
+export const CommandPaletteContent: FC = ({
+ isTableModeActivatable = false,
+}) => {
const [inputMode, setInputMode] = useState({
type: 'default',
})
+ const filter = useMemo(() => {
+ switch (inputMode.type) {
+ case 'default':
+ case 'command':
+ return defaultCommandPaletteFilter
+ case 'table':
+ return tableInputModeFilter
+ }
+ }, [inputMode.type])
const [suggestionText, setSuggestionText] = useState('')
const suggestion = useMemo(
@@ -37,7 +74,7 @@ export const CommandPaletteContent: FC = () => {
setSuggestionText(v)}
- filter={commandPaletteFilter}
+ filter={filter}
>
{
suggestion={suggestion}
setMode={setInputMode}
onBlur={(event) => event.target.focus()}
+ isTableModeActivatable={isTableModeActivatable}
/>