| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- 'use client'
- import { cn, CommandList } from 'ui'
- import { ShimmeringLoader } from 'ui-patterns'
- import { TextHighlighter } from 'ui-patterns/CommandMenu'
- import { CommandMenuGroup } from 'ui-patterns/CommandMenu/internal/CommandMenuGroup'
- import { CommandMenuItem } from 'ui-patterns/CommandMenu/internal/CommandMenuItem'
- import type { IActionCommand, IRouteCommand } from 'ui-patterns/CommandMenu/internal/types'
- export interface SearchResult {
- id: string
- name: string
- description?: string
- }
- export function SkeletonResults() {
- return (
- <div className="p-2 space-y-1">
- {[0, 1, 2, 3].map((i) => (
- <div key={i} className="flex items-center gap-3 px-2 py-2">
- <ShimmeringLoader className="w-4! h-4! py-0! rounded-sm" delayIndex={i} />
- <div className="flex-1 space-y-1">
- <ShimmeringLoader className="w-32! py-1.5!" delayIndex={i} />
- <ShimmeringLoader className="w-48! py-1!" delayIndex={i + 1} />
- </div>
- </div>
- ))}
- </div>
- )
- }
- interface EmptyStateProps {
- icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
- label: string
- query: string
- }
- export function EmptyState({ icon: Icon, label, query }: EmptyStateProps) {
- return (
- <div className="h-full flex flex-col items-center justify-center py-12 px-4 gap-4 text-center text-foreground-lighter">
- <Icon className="h-6 w-6" strokeWidth={1.5} />
- <p className="text-sm">
- {query ? `No results found for "${query}"` : `Type to search in ${label}`}
- </p>
- </div>
- )
- }
- interface ResultsListProps {
- results: SearchResult[]
- icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
- getIcon?: (result: SearchResult) => React.ComponentType<React.SVGProps<SVGSVGElement>>
- onResultClick?: (result: SearchResult) => void
- getRoute?: (result: SearchResult) => `/${string}` | `http${string}`
- className?: string
- }
- export function ResultsList({
- results,
- icon: Icon,
- getIcon,
- onResultClick,
- getRoute,
- className,
- }: ResultsListProps) {
- const commands = results.map((result): IRouteCommand | IActionCommand => {
- const ResultIcon = getIcon ? getIcon(result) : Icon
- const baseCommand = {
- id: result.id,
- name: result.name,
- value: result.description ? `${result.name} ${result.description}` : result.name,
- icon: () => <ResultIcon className="h-4 w-4" strokeWidth={1.5} />,
- }
- if (getRoute) {
- return {
- ...baseCommand,
- route: getRoute(result),
- } as IRouteCommand
- }
- return {
- ...baseCommand,
- action: () => onResultClick?.(result),
- } as IActionCommand
- })
- return (
- <CommandList
- className={cn(
- 'max-h-full! flex-1 min-h-0 overflow-y-auto overflow-x-hidden bg-transparent',
- className
- )}
- >
- <CommandMenuGroup>
- {commands.map((command) => (
- <CommandMenuItem key={command.id} command={command}>
- <div className="flex flex-col min-w-0 text-foreground-light">
- <TextHighlighter>{command.name}</TextHighlighter>
- {command.value && command.value !== command.name && (
- <p className="text-xs text-foreground-lighter/70 truncate mt-0.5">
- {command.value.replace(command.name, '').trim()}
- </p>
- )}
- </div>
- </CommandMenuItem>
- ))}
- </CommandMenuGroup>
- </CommandList>
- )
- }
|