Skip to content

Commit 7e0bf74

Browse files
authored
Merge pull request #36 from buildo/3147480-implement_searchbar
#3147480: Implement SearchBar
2 parents cc8d88a + edc92b6 commit 7e0bf74

File tree

9 files changed

+188
-9
lines changed

9 files changed

+188
-9
lines changed

Diff for: src/Field/InputConfig.ts

+7
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,10 @@ export type InputConfig = {
88
radius: BentoSprinkles["borderRadius"];
99
fontSize: ComponentProps<typeof Body>["size"];
1010
};
11+
12+
export const defaultInputConfig: InputConfig = {
13+
radius: 4,
14+
paddingX: 16,
15+
paddingY: 16,
16+
fontSize: "large",
17+
};

Diff for: src/Field/createFormFields.tsx

+2-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { createNumberInput } from "../NumberInput/createNumberInput";
66
import { createNumberField } from "../NumberField/createNumberField";
77
import { createSelectField, DropdownConfig } from "../SelectField/createSelectField";
88
import { SelectionControlConfig } from "./SelectionControlConfig";
9-
import { InputConfig } from "./InputConfig";
9+
import { defaultInputConfig, InputConfig } from "./InputConfig";
1010

1111
type FieldsConfig = {
1212
field: FieldConfig;
@@ -25,12 +25,7 @@ export function createFormFields(
2525
},
2626
internalSpacing: 4,
2727
},
28-
input: {
29-
radius: 4,
30-
paddingX: 16,
31-
paddingY: 16,
32-
fontSize: "large",
33-
},
28+
input: defaultInputConfig,
3429
selectionControl: {
3530
paddingY: 8,
3631
controlLabelSpacing: 8,

Diff for: src/Icons/IconSearch.tsx

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { IconProps } from "./IconProps";
2+
import { svgIconProps } from "./svgIconProps";
3+
4+
export function IconSearch(props: IconProps) {
5+
return (
6+
<svg viewBox="0 0 24 24" {...svgIconProps(props)}>
7+
<path d="M9.579 0A9.586 9.586 0 0 0 .136 7.972a9.58 9.58 0 0 0 6.269 10.65 9.585 9.585 0 0 0 8.56-1.11l5.923 5.916a1.797 1.797 0 1 0 2.54-2.54l-5.918-5.92A9.58 9.58 0 0 0 9.579 0Zm-5.99 9.582a5.988 5.988 0 0 1 5.99-5.989 5.99 5.99 0 0 1 4.236 10.224A5.99 5.99 0 0 1 3.589 9.582Z" />
8+
</svg>
9+
);
10+
}

Diff for: src/Icons/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ export * from "./IconClose";
77
export * from "./IconInformative";
88
export * from "./IconNegative";
99
export * from "./IconProps";
10+
export * from "./IconSearch";
1011
export * from "./IconUser";
1112
export * from "./IconWarning";

Diff for: src/SearchBar/SearchBar.css.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { style } from "@vanilla-extract/css";
2+
3+
export const input = style({
4+
selectors: {
5+
[`&::-webkit-search-decoration,
6+
&::-webkit-search-cancel-button,
7+
&::-webkit-search-results-button,
8+
&::-webkit-search-results-decoration`]: {
9+
WebkitAppearance: "none",
10+
},
11+
},
12+
});

Diff for: src/SearchBar/createSearchBar.tsx

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { useTextField } from "@react-aria/textfield";
2+
import { FunctionComponent, useRef } from "react";
3+
import useDimensions from "react-cool-dimensions";
4+
import { IconButton, IconClose, IconSearch, LocalizedString } from "..";
5+
import { Box } from "../internal";
6+
import { inputRecipe } from "../Field/Field.css";
7+
import { bodyRecipe } from "../Typography/Body/Body.css";
8+
import { input } from "./SearchBar.css";
9+
import { FieldType } from "../Field/createField";
10+
import { defaultInputConfig, InputConfig } from "../Field/InputConfig";
11+
import { IconProps } from "../Icons/IconProps";
12+
13+
type Props = {
14+
value: string;
15+
onChange: (value: string) => unknown;
16+
onBlur?: () => unknown;
17+
placeholder: LocalizedString;
18+
disabled?: boolean;
19+
clearButtonLabel: LocalizedString;
20+
};
21+
22+
export type SearchBarConfig = {
23+
clearIcon: FunctionComponent<IconProps>;
24+
searchIcon: FunctionComponent<IconProps>;
25+
};
26+
27+
export function createSearchBar(
28+
Field: FieldType,
29+
config: InputConfig & SearchBarConfig = {
30+
...defaultInputConfig,
31+
clearIcon: IconClose,
32+
searchIcon: IconSearch,
33+
}
34+
) {
35+
return function SearchBar(props: Props) {
36+
const inputRef = useRef<HTMLInputElement>(null);
37+
38+
const { observe: leftAccessoryRef, width: leftAccessoryWidth } = useDimensions({
39+
// This is needed to include the padding in the width calculation
40+
useBorderBoxSize: true,
41+
});
42+
43+
const { observe: rightAccessoryRef, width: rightAccessoryWidth } = useDimensions({
44+
// This is needed to include the padding in the width calculation
45+
useBorderBoxSize: true,
46+
});
47+
48+
const { labelProps, inputProps, descriptionProps, errorMessageProps } = useTextField(
49+
{
50+
...props,
51+
isDisabled: props.disabled,
52+
},
53+
inputRef
54+
);
55+
56+
const rightAccessoryContent =
57+
props.value.length > 0 ? (
58+
<IconButton
59+
label={props.clearButtonLabel}
60+
onPress={() => props.onChange("")}
61+
size={16}
62+
icon={config.clearIcon}
63+
color="primary"
64+
/>
65+
) : null;
66+
67+
return (
68+
<Field
69+
{...props}
70+
labelProps={labelProps}
71+
assistiveTextProps={descriptionProps}
72+
errorMessageProps={errorMessageProps}
73+
>
74+
<Box position="relative" display="flex">
75+
<Box
76+
ref={leftAccessoryRef}
77+
position="absolute"
78+
display="flex"
79+
justifyContent="center"
80+
alignItems="center"
81+
paddingX={config.paddingX}
82+
top={0}
83+
bottom={0}
84+
left={0}
85+
>
86+
<config.searchIcon size={16} />
87+
</Box>
88+
<Box
89+
as="input"
90+
type="search"
91+
ref={inputRef}
92+
{...inputProps}
93+
// NOTE(gabro): this is to please TS, since the inputProps type is very broad
94+
color={undefined}
95+
width="full"
96+
height={undefined}
97+
className={[
98+
input,
99+
inputRecipe({ validation: "valid" }),
100+
bodyRecipe({
101+
color: props.disabled ? "disabled" : "default",
102+
weight: "regular",
103+
size: config.fontSize,
104+
}),
105+
]}
106+
display="flex"
107+
style={{
108+
flexGrow: 1,
109+
paddingLeft: leftAccessoryWidth,
110+
paddingRight: rightAccessoryWidth,
111+
}}
112+
borderRadius={config.radius}
113+
paddingX={config.paddingX}
114+
paddingY={config.paddingY}
115+
/>
116+
{rightAccessoryContent && (
117+
<Box
118+
ref={rightAccessoryRef}
119+
position="absolute"
120+
display="flex"
121+
justifyContent="center"
122+
alignItems="center"
123+
paddingX={config.paddingX}
124+
top={0}
125+
bottom={0}
126+
right={0}
127+
>
128+
{rightAccessoryContent}
129+
</Box>
130+
)}
131+
</Box>
132+
</Field>
133+
);
134+
};
135+
}

Diff for: src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export * from "./Link/createLink";
2323
export * from "./List/createList";
2424
export * from "./Modal/createModal";
2525
export * from "./Placeholder/Placeholder";
26+
export * from "./SearchBar/createSearchBar";
2627
export * from "./Tabs/createTabs";
2728
export * from "./Toast/createToast";
2829
export * from "./Toast/useToast";

Diff for: stories/Components/SearchBar.stories.tsx

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { SearchBar } from "../";
2+
import { createComponentStories, formatMessage, textArgType } from "../util";
3+
4+
const { defaultExport, createControlledStory } = createComponentStories({
5+
component: SearchBar,
6+
args: {
7+
placeholder: formatMessage("Search for anything"),
8+
},
9+
argTypes: {
10+
placeholder: textArgType,
11+
},
12+
});
13+
14+
export default defaultExport;
15+
16+
export const searchBar = createControlledStory("design systems", {});

Diff for: stories/index.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,18 @@ import {
2121
createAreaLoader,
2222
createAvatar,
2323
createFormLayoutComponents,
24+
createSearchBar,
2425
} from "../src";
2526
import { sprinkles } from "./sprinkles.css";
2627

2728
export * from "../src";
2829
export const Box = createBentoBox(sprinkles);
2930
export const { Stack, Column, Columns, Inline, Inset } = createLayoutComponents(Box);
31+
export const { Field, CheckboxField, NumberField, RadioGroupField, SelectField, TextField } =
32+
createFormFields();
3033
export const Button = createButton({});
3134
export const Actions = createActions(Button);
3235
export const { Form, FormSection, FormRow } = createFormLayoutComponents(Actions);
33-
export const { CheckboxField, NumberField, RadioGroupField, SelectField, TextField } =
34-
createFormFields();
3536
export const Banner = createBanner({});
3637
export const { Toast, ToastProvider } = createToast(Button, {});
3738
export const Card = createCard<24 | 32 | 40>({});
@@ -46,3 +47,4 @@ export const Tooltip = createTooltip();
4647
export const Tabs = createTabs();
4748
export const AreaLoader = createAreaLoader();
4849
export const Avatar = createAvatar();
50+
export const SearchBar = createSearchBar(Field);

0 commit comments

Comments
 (0)