-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2756d47
commit 4a320b7
Showing
4 changed files
with
256 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import type { Meta, StoryObj } from '@storybook/react'; | ||
|
||
import { SearchResults } from './SearchResults'; | ||
|
||
const meta = { | ||
title: 'Commons/SearchResults', | ||
component: SearchResults, | ||
tags: ['autodocs'], | ||
args: { | ||
data: [ | ||
{ | ||
id: '6', | ||
type: 'type-3', | ||
value: 'value-3-1', | ||
}, | ||
{ | ||
id: '4', | ||
type: 'type-2', | ||
value: 'value-2-2', | ||
}, | ||
{ | ||
id: '1', | ||
type: 'type-1', | ||
value: 'value-1-1', | ||
}, | ||
{ | ||
id: '2', | ||
type: 'type-1', | ||
value: 'value-1-2', | ||
}, | ||
{ | ||
id: '3', | ||
type: 'type-2', | ||
value: 'value-2-1', | ||
}, | ||
{ | ||
id: '5', | ||
type: 'type-2', | ||
value: 'value-2-3', | ||
}, | ||
], | ||
type: 'single', | ||
}, | ||
} satisfies Meta<typeof SearchResults>; | ||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
export const NoExtras: Story = {}; | ||
|
||
export const Stylized: Story = { | ||
args: { | ||
getLabel: ({ category }) => ( | ||
<h1 className="w-full bg-slate-400 font-4xl font-bold italic"> | ||
{category} | ||
</h1> | ||
), | ||
getContent: ({ results }) => ( | ||
<div className="flex flex-row gap-2"> | ||
{results.map((result) => ( | ||
<p key={result.id} className="rounded-md bg-slate-400 p-2"> | ||
{result.value} | ||
</p> | ||
))} | ||
</div> | ||
), | ||
}, | ||
}; | ||
|
||
export const Grouped: Story = { | ||
args: { | ||
groupBy: (item) => item.value.at(-1) ?? 'undefined', | ||
}, | ||
}; | ||
|
||
export const Sorted: Story = { | ||
args: { | ||
sortBy: (a, b) => a[1].length - b[1].length, | ||
}, | ||
}; | ||
|
||
export const NoResults: Story = { | ||
args: { | ||
data: [], | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { | ||
type ComponentPropsWithoutRef, | ||
type ElementRef, | ||
type ReactElement, | ||
type ReactNode, | ||
forwardRef, | ||
} from 'react'; | ||
|
||
import { | ||
Accordion, | ||
AccordionContent, | ||
AccordionItem, | ||
AccordionTrigger, | ||
} from '@/lib/components/ui/accordion'; | ||
import type { SearchResult } from '@/types'; | ||
|
||
export interface SearchResultsProps<Category extends string> { | ||
data: SearchResult[]; | ||
groupBy?: (item: SearchResult) => Category; | ||
getLabel?: ({ | ||
category, | ||
results, | ||
}: { | ||
category: Category; | ||
results: SearchResult[]; | ||
}) => ReactNode; | ||
getContent?: ({ | ||
category, | ||
results, | ||
}: { | ||
category: Category; | ||
results: SearchResult[]; | ||
}) => ReactNode; | ||
sortBy?: ( | ||
a: [Category, SearchResult[]], | ||
b: [Category, SearchResult[]], | ||
) => number; | ||
onNoResults?: () => ReactNode; | ||
} | ||
|
||
export const SearchResultsNoForwardRef = <T extends string>( | ||
{ | ||
data, | ||
groupBy = (item) => item.type as T, | ||
getLabel = ({ category }) => ( | ||
<h1 className="font-bold text-xl">{category}</h1> | ||
), | ||
getContent = ({ results }) => ( | ||
<div className="flex flex-wrap"> | ||
{results.map((result) => ( | ||
<p key={result.id} className="w-full"> | ||
{result.value} | ||
</p> | ||
))} | ||
</div> | ||
), | ||
onNoResults = () => ( | ||
<h1 className="w-full text-center font-bold">No results</h1> | ||
), | ||
sortBy = (a, b) => a[0].localeCompare(b[0]), | ||
...props | ||
}: SearchResultsProps<T> & ComponentPropsWithoutRef<typeof Accordion>, | ||
ref: React.Ref<ElementRef<typeof Accordion>>, | ||
) => { | ||
const categorizedResults: Map<T, SearchResult[]> = new Map(); | ||
|
||
for (const item of data) { | ||
const category = groupBy(item); | ||
|
||
if (!categorizedResults.has(category)) { | ||
categorizedResults.set(category, []); | ||
} | ||
categorizedResults.get(category)?.push(item); | ||
} | ||
|
||
if (data.length === 0) return onNoResults(); | ||
|
||
return ( | ||
<Accordion {...props} ref={ref}> | ||
{categorizedResults | ||
.entries() | ||
.toArray() | ||
.sort(sortBy) | ||
.map(([category, results], index) => ( | ||
<AccordionItem key={`item-${category}`} value={category}> | ||
<AccordionTrigger> | ||
{getLabel({ category, results })} | ||
</AccordionTrigger> | ||
<AccordionContent> | ||
{getContent({ category, results })} | ||
</AccordionContent> | ||
</AccordionItem> | ||
))} | ||
</Accordion> | ||
); | ||
}; | ||
|
||
export const SearchResults = forwardRef(SearchResultsNoForwardRef) as < | ||
Category extends string, | ||
>( | ||
props: SearchResultsProps<Category> & | ||
ComponentPropsWithoutRef<typeof Accordion>, | ||
ref: ElementRef<typeof Accordion>, | ||
) => ReactElement; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import * as React from 'react'; | ||
|
||
import { ChevronDown } from 'lucide-react'; | ||
|
||
import * as AccordionPrimitive from '@radix-ui/react-accordion'; | ||
|
||
import { cn } from '@/lib/utils'; | ||
|
||
const Accordion = AccordionPrimitive.Root; | ||
|
||
const AccordionItem = React.forwardRef< | ||
React.ElementRef<typeof AccordionPrimitive.Item>, | ||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> | ||
>(({ className, ...props }, ref) => ( | ||
<AccordionPrimitive.Item | ||
ref={ref} | ||
className={cn('border-b', className)} | ||
{...props} | ||
/> | ||
)); | ||
AccordionItem.displayName = 'AccordionItem'; | ||
|
||
const AccordionTrigger = React.forwardRef< | ||
React.ElementRef<typeof AccordionPrimitive.Trigger>, | ||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> | ||
>(({ className, children, ...props }, ref) => ( | ||
<AccordionPrimitive.Header className="flex"> | ||
<AccordionPrimitive.Trigger | ||
ref={ref} | ||
className={cn( | ||
'flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180', | ||
className, | ||
)} | ||
{...props} | ||
> | ||
{children} | ||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" /> | ||
</AccordionPrimitive.Trigger> | ||
</AccordionPrimitive.Header> | ||
)); | ||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName; | ||
|
||
const AccordionContent = React.forwardRef< | ||
React.ElementRef<typeof AccordionPrimitive.Content>, | ||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content> | ||
>(({ className, children, ...props }, ref) => ( | ||
<AccordionPrimitive.Content | ||
ref={ref} | ||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down" | ||
{...props} | ||
> | ||
<div className={cn('pt-0 pb-4', className)}>{children}</div> | ||
</AccordionPrimitive.Content> | ||
)); | ||
|
||
AccordionContent.displayName = AccordionPrimitive.Content.displayName; | ||
|
||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters