Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const CommandPalette: FC = () => {
A search-based interface that allows quick access to various
commands and features within the application.
</DialogDescription>
<CommandPaletteContent />
<CommandPaletteContent isTableModeActivatable />
</DialogContent>
</DialogPortal>
</Dialog>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' }),
},
Expand Down Expand Up @@ -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(<CommandPaletteContent isTableModeActivatable />, { 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(<CommandPaletteContent isTableModeActivatable />, { 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', () => {
Expand All @@ -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(<CommandPaletteContent isTableModeActivatable />, { 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', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Props> = ({
isTableModeActivatable = false,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isTableModeActivatable will be false by default, so it won't be displayed for the users yet.

}) => {
const [inputMode, setInputMode] = useState<CommandPaletteInputMode>({
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(
Expand All @@ -37,14 +74,15 @@ export const CommandPaletteContent: FC = () => {
<Command
value={suggestionText}
onValueChange={(v) => setSuggestionText(v)}
filter={commandPaletteFilter}
filter={filter}
>
<div className={styles.searchContainer}>
<CommandPaletteSearchInput
mode={inputMode}
suggestion={suggestion}
setMode={setInputMode}
onBlur={(event) => event.target.focus()}
isTableModeActivatable={isTableModeActivatable}
/>
<DialogClose asChild>
<Button
Expand All @@ -62,6 +100,12 @@ export const CommandPaletteContent: FC = () => {
{inputMode.type === 'default' && (
<TableOptions suggestion={suggestion} />
)}
{inputMode.type === 'table' && (
<TableColumnOptions
tableName={inputMode.tableName}
suggestion={suggestion}
/>
)}
{(inputMode.type === 'default' || inputMode.type === 'command') && (
<CommandPaletteCommandOptions />
)}
Expand All @@ -73,6 +117,9 @@ export const CommandPaletteContent: FC = () => {
{suggestion?.type === 'table' && (
<TablePreview tableName={suggestion.name} />
)}
{suggestion?.type === 'column' && (
<TablePreview tableName={suggestion.tableName} />
)}
{suggestion?.type === 'command' && (
<CommandPreview commandName={suggestion.name} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@
border-radius: var(--border-radius-base);
}

.item.indent {
padding-left: var(--spacing-6);
position: relative;
}

.item.indent:before {
content: '';
position: absolute;
top: calc(var(--spacing-1) / 2);
left: var(--spacing-4);
width: 1px;
height: calc(100% - var(--spacing-1));
background-color: var(--overlay-10);
}

.item .itemText {
flex-grow: 1;
font-size: var(--font-size-5);
Expand Down
Loading