Skip to content

Commit d3b099a

Browse files
Add SearchWithin component (#1342) (#2062)
* Add SearchWithin component (#1342) Co-authored-by: Robert Snow <[email protected]>
1 parent 18e094c commit d3b099a

File tree

16 files changed

+800
-28
lines changed

16 files changed

+800
-28
lines changed

packages/@adobe/spectrum-css-temp/components/searchwithin/index.css

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,55 @@ governing permissions and limitations under the License.
1111
*/
1212

1313
@import '../commons/index.css';
14-
15-
:root {
16-
--spectrum-searchwithin-width: 250px;
14+
.spectrum-SearchWithin {
15+
--spectrum-searchwithin-width: var(--spectrum-global-dimension-size-3000);
16+
--spectrum-searchwithin-searchfield-width: var(--spectrum-global-dimension-size-1600);
1717

1818
/* Force override */
1919
--spectrum-searchwithin-border-radius: 0;
20-
--spectrum-searchwithin-dropdown-min-width: 0;
20+
--spectrum-searchwithin-picker-min-width: 0;
2121
}
2222

2323
.spectrum-SearchWithin {
24-
width: var(--spectrum-searchwithin-width);
24+
inline-size: var(--spectrum-searchwithin-width);
2525
display: inline-flex;
2626
position: relative;
27+
}
2728

28-
.spectrum-Dropdown {
29-
width: auto;
30-
min-width: 0;
31-
}
29+
.spectrum-SearchWithin-picker {
30+
inline-size: auto;
31+
min-inline-size: var(--spectrum-global-dimension-size-900);
32+
flex-shrink: 0;
3233

33-
.spectrum-Dropdown-trigger {
34-
border-top-right-radius: var(--spectrum-searchwithin-border-radius);
35-
border-bottom-right-radius: var(--spectrum-searchwithin-border-radius);
34+
> button {
35+
contain: unset;
36+
border-end-start-radius: var(--spectrum-searchwithin-border-radius);
37+
border-start-start-radius: var(--spectrum-searchwithin-border-radius);
3638
}
39+
}
40+
41+
.spectrum-SearchWithin-searchfield {
42+
min-inline-size: var(--spectrum-searchwithin-searchfield-width);
43+
flex-grow: 1;
44+
order: -1;
45+
margin-inline-end: calc(
46+
-1 * var(--spectrum-textfield-border-size)
47+
); /* hides right border */
3748

38-
.spectrum-Dropdown-label {
39-
/* Override dropdown's min-width and be tiny */
40-
min-width: var(--spectrum-searchwithin-dropdown-min-width);
49+
&:focus-within {
50+
z-index: 1; /* shows entire focus ring */
4151
}
4252

43-
.spectrum-Textfield {
53+
> input {
54+
border-end-end-radius: var(--spectrum-searchwithin-border-radius);
55+
border-start-end-radius: var(--spectrum-searchwithin-border-radius);
4456
flex: 1;
45-
margin-left: calc(var(--spectrum-textfield-border-size) * -1); /* hides left border */
46-
border-top-left-radius: var(--spectrum-searchwithin-border-radius);
47-
border-bottom-left-radius: var(--spectrum-searchwithin-border-radius);
57+
box-sizing: border-box;
58+
min-inline-size: inherit;
4859

49-
&:hover, &:focus {
50-
position: relative; /* shows left border */
60+
&:hover,
61+
&:focus {
62+
position: relative; /* shows right border */
5163
}
5264
}
53-
54-
.spectrum-ClearButton {
55-
position: absolute;
56-
top: 0;
57-
right: 0;
58-
}
5965
}

packages/@react-spectrum/form/stories/Form.stories.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {Item, Picker} from '@react-spectrum/picker';
2121
import {NumberField} from '@react-spectrum/numberfield';
2222
import {Radio, RadioGroup} from '@react-spectrum/radio';
2323
import React, {Key, useState} from 'react';
24+
import {SearchField} from '@react-spectrum/searchfield';
25+
import {SearchWithin} from '@react-spectrum/searchwithin';
2426
import {storiesOf} from '@storybook/react';
2527
import {TextArea, TextField} from '@react-spectrum/textfield';
2628

@@ -162,6 +164,12 @@ function render(props: any = {}) {
162164
<Item>Purple</Item>
163165
</Picker>
164166
<TextArea label="Comments" placeholder="How do you feel?" />
167+
<SearchWithin label="Search">
168+
<SearchField placeholder="Search" />
169+
<Picker label="State" placeholder="Select a state" items={states}>
170+
{item => <Item key={item.abbr}>{item.name}</Item>}
171+
</Picker>
172+
</SearchWithin>
165173
</Form>
166174
);
167175
}
@@ -178,6 +186,7 @@ function FormWithControls(props: any = {}) {
178186
let [favoriteColor2, setFavoriteColor2] = useState('green' as Key);
179187
let [howIFeel2, setHowIFeel2] = useState('I feel good, o I feel so good!');
180188
let [preventDefault, setPreventDefault] = useState(true);
189+
let [favoriteColor3, setFavoriteColor3] = useState('green' as Key);
181190

182191
return (
183192
<Flex>
@@ -218,6 +227,17 @@ function FormWithControls(props: any = {}) {
218227
</Picker>
219228
<TextArea name="comments-controlled" label="Comments" placeholder="How do you feel? controlled" value={howIFeel} onChange={setHowIFeel} />
220229
<TextArea name="comments-uncontrolled" label="Comments" placeholder="How do you feel? default" defaultValue="hello" />
230+
<SearchWithin label="Search">
231+
<SearchField placeholder="Search" />
232+
<Picker name="favorite-color3" label="Favorite color searchwithin" selectedKey={favoriteColor3} onSelectionChange={setFavoriteColor3}>
233+
<Item key="red">Red</Item>
234+
<Item key="orange">Orange</Item>
235+
<Item key="yellow">Yellow</Item>
236+
<Item key="green">Green</Item>
237+
<Item key="blue">Blue</Item>
238+
<Item key="purple">Purple</Item>
239+
</Picker>
240+
</SearchWithin>
221241
<ButtonGroup>
222242
<Button variant="primary" type="submit">Submit</Button>
223243
</ButtonGroup>
@@ -306,6 +326,18 @@ function FormWithControls(props: any = {}) {
306326
Comments default
307327
<textarea placeholder="How do you feel?" defaultValue="hello" />
308328
</label>
329+
<label>
330+
Favorite Color searchwithin
331+
<input type="text" placeholder="Search" />
332+
<select onChange={e => setFavoriteColor3(e.target.value)}>
333+
<option value="red" selected={favoriteColor3 === 'red'}>Red</option>
334+
<option value="orange" selected={favoriteColor3 === 'orange'}>Orange</option>
335+
<option value="yellow" selected={favoriteColor3 === 'yellow'}>Yellow</option>
336+
<option value="green" selected={favoriteColor3 === 'green'}>Green</option>
337+
<option value="blue" selected={favoriteColor3 === 'blue'}>Blue</option>
338+
<option value="purple" selected={favoriteColor3 === 'purple'}>Purple</option>
339+
</select>
340+
</label>
309341
<ButtonGroup>
310342
<Button variant="secondary" type="reset">Reset</Button>
311343
<Button variant="primary" type="submit">Submit</Button>

packages/@react-spectrum/picker/src/Picker.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
SlotProvider,
1919
useDOMRef,
2020
useIsMobileDevice,
21+
useSlotProps,
2122
useStyleProps,
2223
useUnwrapDOMRef
2324
} from '@react-spectrum/utils';
@@ -46,6 +47,7 @@ import {useProvider, useProviderProps} from '@react-spectrum/provider';
4647
import {useSelectState} from '@react-stately/select';
4748

4849
function Picker<T extends object>(props: SpectrumPickerProps<T>, ref: DOMRef<HTMLDivElement>) {
50+
props = useSlotProps(props, 'picker');
4951
props = useProviderProps(props);
5052
props = useFormProps(props);
5153
let formatMessage = useMessageFormatter(intlMessages);

packages/@react-spectrum/searchfield/src/SearchField.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* governing permissions and limitations under the License.
1111
*/
1212

13-
import {classNames} from '@react-spectrum/utils';
13+
import {classNames, useSlotProps} from '@react-spectrum/utils';
1414
import {ClearButton} from '@react-spectrum/button';
1515
import Magnifier from '@spectrum-icons/ui/Magnifier';
1616
import React, {forwardRef, RefObject, useRef} from 'react';
@@ -23,6 +23,7 @@ import {useSearchField} from '@react-aria/searchfield';
2323
import {useSearchFieldState} from '@react-stately/searchfield';
2424

2525
function SearchField(props: SpectrumSearchFieldProps, ref: RefObject<TextFieldRef>) {
26+
props = useSlotProps(props, 'searchfield');
2627
props = useProviderProps(props);
2728
let defaultIcon = (
2829
<Magnifier data-testid="searchicon" />
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @react-spectrum/searchwithin
2+
3+
This package is part of [react-spectrum](https://github.com/adobe/react-spectrum). See the repo for more details.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2020 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import {generatePowerset} from '../../story-utils';
14+
import {Grid, repeat} from '../../layout';
15+
import {Item, Picker} from '@react-spectrum/picker';
16+
import {Meta, Story} from '@storybook/react';
17+
import React from 'react';
18+
import {SearchField} from '@react-spectrum/searchfield';
19+
import {SearchWithin} from '../';
20+
import {SpectrumSearchWithinProps} from '@react-types/searchwithin';
21+
22+
let states = [
23+
{isRequired: true},
24+
{isDisabled: true},
25+
{necessityIndicator: 'label'}
26+
];
27+
28+
let items = [
29+
{name: 'All', id: 'all'},
30+
{name: 'Campaigns', id: 'campaigns'},
31+
{name: 'Tags', id: 'tags'},
32+
{name: 'Audiences', id: 'audiences'}
33+
];
34+
35+
let combinations = generatePowerset(states);
36+
37+
function shortName(key) {
38+
let returnVal = '';
39+
switch (key) {
40+
case 'isRequired':
41+
returnVal = 'req';
42+
break;
43+
case 'isDisabled':
44+
returnVal = 'disable';
45+
break;
46+
case 'necessityIndicator':
47+
returnVal = 'necInd=label';
48+
break;
49+
}
50+
return returnVal;
51+
}
52+
53+
const meta: Meta<SpectrumSearchWithinProps> = {
54+
title: 'SearchWithin',
55+
parameters: {
56+
chromaticProvider: {colorSchemes: ['light', 'dark', 'lightest', 'darkest'], locales: ['en-US', 'ar-AE'], scales: ['medium', 'large']}
57+
}
58+
};
59+
60+
export default meta;
61+
62+
const Template: Story<SpectrumSearchWithinProps> = (args) => (
63+
<Grid columns={repeat(4, '1fr')} autoFlow="row" gap="size-200">
64+
{combinations.map(c => {
65+
let key = Object.keys(c).map(k => shortName(k)).join(' ');
66+
if (!key) {
67+
key = 'empty';
68+
}
69+
70+
return (
71+
<SearchWithin key={key} {...args} {...c} label={args['aria-label'] ? undefined : key}>
72+
<SearchField placeholder="Search" />
73+
<Picker defaultSelectedKey="all">
74+
{items.map((item) => <Item key={item.id}>{item.name}</Item>)}
75+
</Picker>
76+
</SearchWithin>
77+
);
78+
})}
79+
</Grid>
80+
);
81+
82+
// Chromatic can't handle the size of the side label story so removed some extraneous props that don't matter for side label case.
83+
const TemplateSideLabel: Story<SpectrumSearchWithinProps> = (args) => (
84+
<Grid columns={repeat(2, '1fr')} autoFlow="row" gap="size-200" width={900}>
85+
{combinations.map(c => {
86+
let key = Object.keys(c).map(k => shortName(k)).join(' ');
87+
if (!key) {
88+
key = 'empty';
89+
}
90+
91+
return (
92+
<SearchWithin key={key} {...args} {...c} label={args['aria-label'] ? undefined : key}>
93+
<SearchField placeholder="Search" />
94+
<Picker defaultSelectedKey="all">
95+
{items.map((item) => <Item key={item.id}>{item.name}</Item>)}
96+
</Picker>
97+
</SearchWithin>
98+
);
99+
})}
100+
</Grid>
101+
);
102+
103+
export const PropDefaults = Template.bind({});
104+
PropDefaults.storyName = 'default';
105+
PropDefaults.args = {};
106+
107+
export const PropLabelSide = TemplateSideLabel.bind({});
108+
PropLabelSide.storyName = 'label side';
109+
PropLabelSide.args = {...PropDefaults.args, labelPosition: 'side'};
110+
111+
export const PropNoLabel = TemplateSideLabel.bind({});
112+
PropNoLabel.storyName = 'no label';
113+
PropNoLabel.args = {...PropDefaults.args, label: undefined, 'aria-label': 'Aria Label'};
114+
115+
export const PropCustomWidth = Template.bind({});
116+
PropCustomWidth.storyName = 'custom width';
117+
PropCustomWidth.args = {...PropDefaults.args, width: 300};
118+

0 commit comments

Comments
 (0)