Skip to content

Commit 83ebe98

Browse files
committed
Feat: SearchBar component
1 parent 2217824 commit 83ebe98

File tree

3 files changed

+125
-0
lines changed

3 files changed

+125
-0
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@
136136
"./Summary": "./dist/Summary.js",
137137
"./Stepper": "./dist/Stepper.js",
138138
"./SkipLinks": "./dist/SkipLinks.js",
139+
"./SearchBar": "./dist/SearchBar.js",
139140
"./Quote": "./dist/Quote.js",
140141
"./Pagination": "./dist/Pagination.js",
141142
"./Notice": "./dist/Notice.js",

src/SearchBar.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, { memo, forwardRef, useId } from "react";
2+
import type { InputHTMLAttributes } from "react";
3+
import { symToStr } from "tsafe/symToStr";
4+
import { assert } from "tsafe/assert";
5+
import { createComponentI18nApi } from "./i18n";
6+
import type { Equals } from "tsafe";
7+
import { cx } from "./tools/cx";
8+
import { fr } from "./fr";
9+
10+
export type SearchBarProps = {
11+
className?: string;
12+
/** Default: "Rechercher" (or translation) */
13+
label?: string;
14+
/** Props forwarded to the underlying <input /> element */
15+
nativeInputProps?: InputHTMLAttributes<HTMLInputElement>;
16+
/** Default: false */
17+
big?: boolean;
18+
classes?: Partial<Record<"root" | "label" | "input", string>>;
19+
};
20+
21+
/**
22+
* @see <https://react-dsfr-components.etalab.studio/?path=/docs/components-input>
23+
* */
24+
export const SearchBar = memo(
25+
forwardRef<HTMLDivElement, SearchBarProps>((props, ref) => {
26+
const {
27+
className,
28+
label: label_props,
29+
nativeInputProps = {},
30+
big = false,
31+
classes = {},
32+
...rest
33+
} = props;
34+
35+
assert<Equals<keyof typeof rest, never>>();
36+
37+
const { t } = useTranslation();
38+
39+
const label = label_props ?? t("label");
40+
41+
const inputId = `search-${useId()}-input`;
42+
43+
return (
44+
<div
45+
className={cx(
46+
fr.cx("fr-search-bar", big && "fr-search-bar--lg"),
47+
classes.root,
48+
className
49+
)}
50+
role="search"
51+
ref={ref}
52+
{...rest}
53+
>
54+
<label className={cx(fr.cx("fr-label"), classes.label)} htmlFor={inputId}>
55+
{label}
56+
</label>
57+
<input
58+
{...nativeInputProps}
59+
className={cx(fr.cx("fr-input"), classes.input, nativeInputProps.className)}
60+
placeholder={label}
61+
type="search"
62+
id={inputId}
63+
/>
64+
<button className="fr-btn" title="Rechercher">
65+
{label}
66+
</button>
67+
</div>
68+
);
69+
})
70+
);
71+
72+
SearchBar.displayName = symToStr({ SearchBar });
73+
74+
export default SearchBar;
75+
76+
const { useTranslation, addSearchBarTranslations } = createComponentI18nApi({
77+
"componentName": symToStr({ SearchBar }),
78+
"frMessages": {
79+
/* spell-checker: disable */
80+
"label": "Rechercher"
81+
/* spell-checker: enable */
82+
}
83+
});
84+
85+
addSearchBarTranslations({
86+
"lang": "en",
87+
"messages": {
88+
"label": "Search"
89+
}
90+
});
91+
92+
export { addSearchBarTranslations };

stories/SearchBar.stories.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { SearchBar } from "../dist/SearchBar";
2+
import { sectionName } from "./sectionName";
3+
import { getStoryFactory } from "./getStory";
4+
5+
const { meta, getStory } = getStoryFactory({
6+
sectionName,
7+
"wrappedComponent": { SearchBar },
8+
"description": `
9+
- [See DSFR documentation](https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/barre-de-recherche)
10+
- [See source code](https://github.com/codegouvfr/react-dsfr/blob/main/src/SearchBar.tsx)`,
11+
"argTypes": {
12+
"big": {
13+
"description": "Use the big variant if you have space to spare",
14+
"control": { "type": "boolean" }
15+
},
16+
"label": {
17+
"description": "Default: 'Rechercher' (or translation)",
18+
"control": { "type": "text" }
19+
},
20+
"nativeInputProps": {
21+
"description": `An object that is forwarded as props to te underlying native \`<input />\` element.
22+
This is where you pass the \`name\` prop or \`onChange\` for example.`,
23+
"control": { "type": null }
24+
}
25+
}
26+
});
27+
28+
export default meta;
29+
30+
export const Default = getStory({
31+
"label": undefined
32+
});

0 commit comments

Comments
 (0)