Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 @@ -3,6 +3,7 @@
import type { ComponentType } from 'react'
import { memo } from 'react'
import { Command } from 'cmdk'
import { ChevronRight } from 'lucide-react'
import { File, Workflow } from '@/components/emcn/icons'
import { cn } from '@/lib/core/utils/cn'
import type { CommandItemProps } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils'
Expand Down Expand Up @@ -328,6 +329,43 @@ export const MemoizedPageItem = memo(
prev.query === next.query
)

export const MemoizedCategoryItem = memo(
function CategoryItem({
value,
onSelect,
icon: Icon,
name,
count,
query,
}: {
value: string
onSelect: () => void
icon: ComponentType<{ className?: string }>
name: string
count: number
query?: string
}) {
return (
<Command.Item value={value} onSelect={onSelect} className={COMMAND_ITEM_CLASSNAME}>
<Icon className='size-[16px] flex-shrink-0 text-[var(--text-icon)]' />
<span className='truncate text-[var(--text-body)]'>
<HighlightedText text={name} query={query} />
</span>
<span className='ml-auto flex flex-shrink-0 items-center gap-1.5 text-[var(--text-subtle)] text-small'>
{count}
<ChevronRight className='size-[14px]' />
</span>
</Command.Item>
)
},
(prev, next) =>
prev.value === next.value &&
prev.icon === next.icon &&
prev.name === next.name &&
prev.count === next.count &&
prev.query === next.query
)

export const MemoizedIconItem = memo(
function IconItem({
value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,29 @@
import type { ComponentType } from 'react'
import { memo } from 'react'
import { Command } from 'cmdk'
import {
Activity,
BarChart3,
Blocks,
FileText,
GitBranch,
LifeBuoy,
type LucideIcon,
Mail,
Megaphone,
MessageCircle,
Search as SearchIcon,
Shield,
ShoppingCart,
Sparkles,
TrendingUp,
Users,
Zap,
} from 'lucide-react'
import { Database, Table } from '@/components/emcn/icons'
import {
MemoizedActionItem,
MemoizedCategoryItem,
MemoizedCommandItem,
MemoizedFileItem,
MemoizedIconItem,
Expand All @@ -26,10 +46,33 @@ import type {
import { GROUP_HEADING_CLASSNAME } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/utils'
import type {
SearchBlockItem,
SearchCategory,
SearchDocItem,
SearchToolOperationItem,
} from '@/stores/modals/search/types'

/** Icon for each browsable category, keyed by {@link SearchCategory.id}. */
const CATEGORY_ICONS: Record<string, LucideIcon> = {
blocks: Blocks,
triggers: Zap,
ai: Sparkles,
analytics: BarChart3,
commerce: ShoppingCart,
communication: MessageCircle,
databases: Database,
devops: GitBranch,
documents: FileText,
email: Mail,
hr: Users,
marketing: Megaphone,
observability: Activity,
productivity: Blocks,
sales: TrendingUp,
search: SearchIcon,
security: Shield,
support: LifeBuoy,
}

export const ActionsGroup = memo(function ActionsGroup({
items,
onSelect,
Expand Down Expand Up @@ -57,18 +100,72 @@ export const ActionsGroup = memo(function ActionsGroup({
)
})

/** A recent selection resolved to a renderable row by the modal. */
export interface RecentRenderItem {
id: string
label: string
icon: ComponentType<{ className?: string }>
bgColor: string
onSelect: () => void
}

export const RecentsGroup = memo(function RecentsGroup({ items }: { items: RecentRenderItem[] }) {
if (items.length === 0) return null
return (
<Command.Group heading='Recent' className={GROUP_HEADING_CLASSNAME}>
Comment on lines +114 to +115

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 productivity maps to the same Blocks icon as the blocks category

Both 'blocks' and 'productivity' resolve to the Blocks icon, so users would see identical icons for two different browse categories. A more fitting choice for productivity might be Briefcase, LayoutDashboard, or CheckSquare from Lucide.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

{items.map((item) => (
<MemoizedCommandItem
key={item.id}
value={`${item.label} recent-${item.id}`}
onSelect={item.onSelect}
icon={item.icon}
bgColor={item.bgColor}
showColoredIcon
label={item.label}
/>
))}
</Command.Group>
)
})

export const BrowseGroup = memo(function BrowseGroup({
items,
onSelect,
}: {
items: SearchCategory[]
onSelect: (category: SearchCategory) => void
}) {
if (items.length === 0) return null
return (
<Command.Group heading='Browse' className={GROUP_HEADING_CLASSNAME}>
{items.map((category) => (
<MemoizedCategoryItem
key={category.id}
value={`${category.label} category-${category.id}`}
onSelect={() => onSelect(category)}
icon={CATEGORY_ICONS[category.id] ?? Blocks}
name={category.label}
count={category.count}
/>
))}
</Command.Group>
)
})

export const BlocksGroup = memo(function BlocksGroup({
items,
onSelect,
query,
heading = 'Blocks',
}: {
items: SearchBlockItem[]
onSelect: (block: SearchBlockItem) => void
query?: string
heading?: string
}) {
if (items.length === 0) return null
return (
<Command.Group heading='Blocks' className={GROUP_HEADING_CLASSNAME}>
<Command.Group heading={heading} className={GROUP_HEADING_CLASSNAME}>
{items.map((block) => (
<MemoizedCommandItem
key={block.id}
Expand All @@ -89,14 +186,16 @@ export const ToolsGroup = memo(function ToolsGroup({
items,
onSelect,
query,
heading = 'Tools',
}: {
items: SearchBlockItem[]
onSelect: (tool: SearchBlockItem) => void
query?: string
heading?: string
}) {
if (items.length === 0) return null
return (
<Command.Group heading='Tools' className={GROUP_HEADING_CLASSNAME}>
<Command.Group heading={heading} className={GROUP_HEADING_CLASSNAME}>
{items.map((tool) => (
<MemoizedCommandItem
key={tool.id}
Expand All @@ -117,14 +216,16 @@ export const TriggersGroup = memo(function TriggersGroup({
items,
onSelect,
query,
heading = 'Triggers',
}: {
items: SearchBlockItem[]
onSelect: (trigger: SearchBlockItem) => void
query?: string
heading?: string
}) {
if (items.length === 0) return null
return (
<Command.Group heading='Triggers' className={GROUP_HEADING_CLASSNAME}>
<Command.Group heading={heading} className={GROUP_HEADING_CLASSNAME}>
{items.map((trigger) => (
<MemoizedCommandItem
key={trigger.id}
Expand Down
Loading
Loading