Skip to content

Commit

Permalink
feat: Search forms now accepts custom search actions
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-crowell committed Jun 19, 2024
1 parent ee7cb81 commit 6f0c02e
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 32 deletions.
14 changes: 8 additions & 6 deletions packages/ui/src/components/Navigation/NavigationExtended.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,28 @@ export function NavigationExtended(props: NavigationProps) {
return (
<Navbar
position={props.position}
height="8rem"
className={clmg(clsx(props.color && colors, 'relative border-b-1 border-b-foreground-200/50', props.className))}
height="10rem"
isBlurred={props.isBlurred}
maxWidth={props.maxWidth}
className={clmg(clsx(props.color && colors, 'relative overflow-hidden border-b-1 border-b-foreground/10', props.className))}

>
<NavbarContent justify="start" className="md:items-start md:pt-2">
<NavbarContent justify="start" className="lg:items-start lg:pt-2">
<div className="relative z-10">
<NavigationPart_Brand base={props} />
</div>
</NavbarContent>

<NavbarContent justify="start" className="absolute hidden items-end md:flex">
<NavbarContent justify="start" className="absolute hidden items-end lg:flex">
<NavigationPart_Links base={props} />
</NavbarContent>

<NavbarContent justify="end" className="items-start pt-2">
<div className="hidden max-w-64 p-2 md:block">
<div className="hidden max-w-64 p-2 lg:block">
<NavigationPart_Actions base={props} />
</div>
<NavigationPart_MenuToggle
className="md:hidden"
className="lg:hidden"
/>
</NavbarContent>

Expand Down
3 changes: 2 additions & 1 deletion packages/ui/src/components/Navigation/NavigationIsland.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export function NavigationIsland(props: NavigationProps) {
return (
<Navbar
position={props.position}
isBlurred={false}
isBlurred={props.isBlurred ?? false}
maxWidth={props.maxWidth}
className="items-center justify-center bg-transparent bg-none"
height="5rem"
>
Expand Down
4 changes: 3 additions & 1 deletion packages/ui/src/components/Navigation/NavigationStandard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export function NavigationStandard(props: NavigationProps) {
<Navbar
position={props.position}
height="4rem"
className={clmg(clsx(props.color && colors, 'border-b-1 border-b-foreground-200/50', props.className))}
maxWidth={props.maxWidth}
isBlurred={props.isBlurred}
className={clmg(clsx(props.color && colors, 'border-b-1 border-b-foreground/10', props.className))}
>
<NavbarContent justify="start">
<NavigationPart_Brand base={props} />
Expand Down
10 changes: 10 additions & 0 deletions packages/ui/src/components/Navigation/data/NavigationProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ export interface NavigationProps {
* @default 40
*/
logoSize?: number;

/**
* Max width of the navigation content.
*/
maxWidth?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full';

/**
* If the navigation should be blurred
*/
isBlurred?: boolean;

/**
* The theme color of the navigation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function NavigationPart_Brand({ base: {
height={0}
loading="eager"
className={classNames?.logo}
style={{ width: 'auto', height: logoSize ?? 40 }}
style={{ width: logoSize ?? 40, height: logoSize ?? 40 }}
/>
) : null}
<h1 className={clsx('hidden text-3xl tracking-tight md:inline', classNames?.title)}>{title}</h1>
Expand Down
32 changes: 32 additions & 0 deletions packages/ui/src/components/SearchButton/SearchButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Meta, StoryObj } from '@storybook/react';

import { SearchButton } from './SearchButton';

const meta = {
component: SearchButton,
} satisfies Meta<typeof SearchButton>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
action: async (_, payload) => (payload.query.length > 1 ? [
{
id: '1',
title: 'Title',
description: 'Description',
url: '#',
thumbnail: 'https://example.com/image.jpg',
},
{
id: '2',
title: 'Title',
description: 'Description',
url: '#',
thumbnail: 'https://example.com/image.jpg',
}
] : []),
}
};
15 changes: 10 additions & 5 deletions packages/ui/src/components/SearchButton/SearchButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

import { MagnifyingGlassIcon } from '@heroicons/react/24/solid';
import { Button, ButtonProps, Modal, ModalHeader, ModalBody, useDisclosure, ModalContent } from '@nextui-org/react';
import { SearchAction, search } from '@do-ob/ui/actions';
import { SearchForm } from '../SearchForm/SearchForm';
import { SearchAction } from '@do-ob/ui/types';
import React from 'react';

/**
* Navigation Brand properties
Expand All @@ -21,11 +22,12 @@ export interface SearchButtonProps extends ButtonProps {
* Navigation Search component
*/
export function SearchButton({
action = search,
action = async () => [],
...props
}: SearchButtonProps) {

const { isOpen, onOpen, onOpenChange } = useDisclosure();
const [ results, resultsSet ] = React.useState<Awaited<ReturnType<SearchAction>>>([]);

return (<>
<Button isIconOnly onPress={onOpen} aria-label="Search website" {...props}>
Expand All @@ -35,15 +37,18 @@ export function SearchButton({
isOpen={isOpen}
onOpenChange={onOpenChange}
title="Search"
size="lg"
size="xl"
hideCloseButton
>
<ModalContent>
<ModalHeader className="p-1">
<SearchForm action={action} />
<SearchForm action={action} onResult={resultsSet} />
</ModalHeader>
<ModalBody className="p-2">
&nbsp;
{results.map((result, index) => (
<div key={index}>{result.title}</div>
))}
{results.length === 0 && <p className="p-8 text-foreground/60">Use the input above to build a search query. Results from the query will display here.</p>}
</ModalBody>
</ModalContent>
</Modal>
Expand Down
27 changes: 23 additions & 4 deletions packages/ui/src/components/SearchForm/SearchForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
'use client';

import { SearchAction, search } from '@do-ob/ui/actions';
import { SearchAction } from '@do-ob/ui/types';
import { SearchFormInputs } from './SearchFormInputs';
import { useActionState } from '@do-ob/ui/hooks';
import { Form } from '@do-ob/ui/components';
import { nop } from '@do-ob/core';

/**
* Search Form properties
Expand All @@ -12,17 +13,35 @@ export interface SearchFormProps {
/**
* The search form action URL.
*/
action?: SearchAction;
action: SearchAction;

/**
* Callback when the action state is updated.
*/
onResult?: (state: Awaited<ReturnType<SearchAction>> ) => void;

}

/**
* Search Form component
*/
export function SearchForm({
action = search,
action = async () => [],
onResult = nop,
}: SearchFormProps) {

const [ , formAction ] = useActionState(action, []);
const [ state, formAction ] = useActionState(
async (state: [], payload: FormData) => {
const query = payload.get('query') as string;
return await action(state, { query });
},
[]
);

// Call the onResult callback when the state is updated
if (state !== undefined) {
onResult(state);
}

return (
<Form
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SearchState } from '@do-ob/ui/actions';
import { SearchState } from '@do-ob/ui/types';

/**
* Search Form Results properties
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@

export * from './types/actions';
export * from './types/locale';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export type FormAction<S = any, P = any> = (state: S, payload: P) => Promise<S>;

/**
* Search State type
*/
Expand Down Expand Up @@ -31,25 +34,14 @@ export type SearchState = Array<{
/**
* Search Payload type
*/
export type SearchPayload = FormData & {
export type SearchPayload = {
/**
* The search query
*/
query: string;
};

/**
* Search action type.
*/
export type SearchAction = (state: SearchState, payload: FormData) => Promise<SearchState>;

/**
* Search action type
*/
export const search: SearchAction = async (state, payload) => {
const query = payload.get('query');

console.log(`Searching for: ${query}`);

return state;
};
export type SearchAction = FormAction<SearchState, SearchPayload>;

0 comments on commit 6f0c02e

Please sign in to comment.