Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 | 8x 8x 6x 6x 6x 6x 6x 6x 1x 6x 3x 1x | import {
type CSSProperties,
Fragment,
type InputHTMLAttributes,
type SyntheticEvent,
useMemo,
} from 'react';
import { Dropdown } from './dropdown-button';
import Button from './button';
import type { FranklinStyle } from '../types/common';
import '../styles/components/main-search.scss';
interface InputInternalStyle extends CSSProperties {
'--input-padding': string;
}
export type MainSearchProps = {
/**
* Action performed upon search submission
*/
onSubmit: (e: SyntheticEvent) => void;
/**
* What happens when text is typed in the search box
*/
onTextChange: (selectedValue: string) => void;
/**
* The search term
*/
searchTerm?: string;
/**
* An object representing the different namespace options
*/
namespaces?: Record<string, string>;
/**
* What happens when a namespace is changed in the dropdown
*/
onNamespaceChange?: (namespace: string) => void;
/**
* The selected namespace in the dropdown
*/
selectedNamespace?: string;
/**
* A list of objects representing secondary buttons and their actions
*/
secondaryButtons?: { label: string; action: () => void }[];
} & InputHTMLAttributes<HTMLInputElement>;
const countCharacters = (items: string[]) =>
items.reduce((prev, curr) => prev + curr.length, 0);
const MainSearch = ({
searchTerm,
namespaces = {},
onTextChange,
onSubmit,
onNamespaceChange,
selectedNamespace,
secondaryButtons,
...props
}: MainSearchProps) => {
const style = useMemo<FranklinStyle>(
() => ({
'--main-button-color': `var(--fr--color-${selectedNamespace}, var(--fr--color-sea-blue))`,
}),
[selectedNamespace]
);
const inputStyle = useMemo<InputInternalStyle | undefined>(() => {
const count =
secondaryButtons &&
countCharacters(secondaryButtons.map(({ label }) => label));
return count ? { '--input-padding': `${count}ch` } : undefined;
}, [secondaryButtons]);
const visibleElement = (onClick: () => unknown) => (
<Button variant="primary" style={style} onClick={onClick}>
{
namespaces[
// Pick the first item if nothing defined
selectedNamespace || Object.keys(namespaces)[0]
]
}
</Button>
);
return (
<form onSubmit={onSubmit} aria-label="Main search" className="main-search">
{Object.keys(namespaces).length > 0 && onNamespaceChange && (
<Dropdown
visibleElement={visibleElement}
propChangeToClose={selectedNamespace}
>
<ul className="no-bullet">
{Object.keys(namespaces).map((key) => (
<li key={key}>
<Button
variant="tertiary"
onClick={() => {
onNamespaceChange(key);
}}
>
{namespaces[key]}
</Button>
</li>
))}
</ul>
</Dropdown>
)}
<div className="main-search__input-container">
<input
type="search"
aria-label={`Text query${
selectedNamespace ? ` in ${selectedNamespace}` : ''
}`}
onChange={(e) => onTextChange(e.target.value)}
value={searchTerm}
style={inputStyle}
{...props}
/>
{secondaryButtons && (
<div className="main-search__secondary-container">
{secondaryButtons.map(({ label, action }, index) => (
<Fragment key={index}>
{index > 0 && <small> | </small>}
<button
type="button"
onClick={action}
key={label}
className="main-search--secondary"
>
<small>{label}</small>
</button>
</Fragment>
))}
</div>
)}
</div>
<Button type="submit">Search</Button>
</form>
);
};
export default MainSearch;
|