Skip to content

Commit 13676a8

Browse files
authored
chore(miscellaneous): update examples and demos to typescript (#12056)
1 parent f783f06 commit 13676a8

File tree

14 files changed

+573
-551
lines changed

14 files changed

+573
-551
lines changed

packages/react-core/src/demos/SearchInput/SearchInput.md

Lines changed: 2 additions & 487 deletions
Large diffs are not rendered by default.
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import {
3+
ActionGroup,
4+
Button,
5+
DatePicker,
6+
Form,
7+
FormGroup,
8+
Grid,
9+
GridItem,
10+
isValidDate,
11+
Menu,
12+
MenuContent,
13+
MenuItem,
14+
MenuList,
15+
MenuToggle,
16+
Panel,
17+
PanelMain,
18+
PanelMainBody,
19+
Popper,
20+
SearchInput,
21+
TextInput,
22+
yyyyMMddFormat
23+
} from '@patternfly/react-core';
24+
25+
export const SearchInputAdvancedComposable: React.FunctionComponent = () => {
26+
const [value, setValue] = useState('');
27+
const [hasWords, setHasWords] = useState('');
28+
const [dateWithin, setDateWithin] = useState('1 day');
29+
const [date, setDate] = useState();
30+
31+
const [isAdvancedSearchOpen, setIsAdvancedSearchOpen] = useState(false);
32+
const [isDateWithinOpen, setIsDateWithinOpen] = useState(false);
33+
34+
const isInitialMount = useRef(true);
35+
const firstAttrRef = useRef(null);
36+
const searchInputRef = useRef(null);
37+
const advancedSearchPaneRef = useRef(null);
38+
const dateWithinToggleRef = useRef(undefined);
39+
const dateWithinMenuRef = useRef(undefined);
40+
41+
const onClear = () => {
42+
setValue('');
43+
setHasWords('');
44+
setDateWithin('');
45+
setDate('');
46+
};
47+
48+
const onChange = (_event, value) => {
49+
if (value.length <= hasWords.length + 1) {
50+
setValue(value);
51+
setHasWords(value);
52+
} else {
53+
setValue(hasWords);
54+
}
55+
};
56+
57+
// After initial page load, whenever the advanced search menu is opened, the browser focus should be placed on the
58+
// first advanced search form input. Whenever the advanced search menu is closed, the browser focus should
59+
// be returned to the search input.
60+
useEffect(() => {
61+
if (isInitialMount.current) {
62+
isInitialMount.current = false;
63+
} else {
64+
if (isAdvancedSearchOpen && firstAttrRef && firstAttrRef.current) {
65+
firstAttrRef.current.focus();
66+
} else if (!isAdvancedSearchOpen && searchInputRef) {
67+
searchInputRef.current.focus();
68+
}
69+
}
70+
}, [isAdvancedSearchOpen]);
71+
72+
// If a menu is open and has browser focus, then the escape key closes them and puts the browser focus onto their
73+
// respective toggle. The 'date within' menu also needs to close when the 'tab' key is hit. However, hitting tab while
74+
// focus is in the advanced search form should move the focus to the next form input, not close the advanced search
75+
// menu.
76+
const handleMenuKeys = (event) => {
77+
if (isDateWithinOpen && dateWithinMenuRef.current && dateWithinMenuRef.current.contains(event.target)) {
78+
if (event.key === 'Escape' || event.key === 'Tab') {
79+
setIsDateWithinOpen(!isDateWithinOpen);
80+
dateWithinToggleRef.current.focus();
81+
}
82+
}
83+
if (isAdvancedSearchOpen && advancedSearchPaneRef.current && advancedSearchPaneRef.current.contains(event.target)) {
84+
if (
85+
event.key === 'Escape' ||
86+
(event.key === 'Tab' &&
87+
!event.shiftKey &&
88+
advancedSearchPaneRef.current.querySelector('button[type=reset]') === event.target)
89+
) {
90+
setIsAdvancedSearchOpen(!isAdvancedSearchOpen);
91+
searchInputRef.current.focus();
92+
}
93+
}
94+
};
95+
96+
// If a menu is open and has browser focus, then clicking outside the menu should close it.
97+
const handleClickOutside = (event) => {
98+
if (
99+
isDateWithinOpen &&
100+
dateWithinMenuRef &&
101+
dateWithinMenuRef.current &&
102+
!dateWithinMenuRef.current.contains(event.target)
103+
) {
104+
setIsDateWithinOpen(false);
105+
}
106+
if (
107+
isAdvancedSearchOpen &&
108+
advancedSearchPaneRef &&
109+
advancedSearchPaneRef.current &&
110+
!advancedSearchPaneRef.current.contains(event.target)
111+
) {
112+
setIsAdvancedSearchOpen(false);
113+
}
114+
};
115+
116+
useEffect(() => {
117+
window.addEventListener('keydown', handleMenuKeys);
118+
window.addEventListener('click', handleClickOutside);
119+
return () => {
120+
window.removeEventListener('keydown', handleMenuKeys);
121+
window.removeEventListener('click', handleClickOutside);
122+
};
123+
}, [dateWithinMenuRef.current, advancedSearchPaneRef.current, isAdvancedSearchOpen, isDateWithinOpen]);
124+
125+
// This demo and its handling of 'date within' and a date picker is modeled after the gmail advanced search form.
126+
const onSubmit = (event, _value) => {
127+
event.preventDefault();
128+
129+
if (isValidDate(new Date(date)) && dateWithin) {
130+
const afterDate = new Date(date);
131+
const toDate = new Date(date);
132+
switch (dateWithin) {
133+
case '1 day':
134+
afterDate.setDate(afterDate.getDate());
135+
toDate.setDate(toDate.getDate() + 2);
136+
break;
137+
case '3 days':
138+
afterDate.setDate(afterDate.getDate() - 2);
139+
toDate.setDate(toDate.getDate() + 4);
140+
break;
141+
case '1 week':
142+
afterDate.setDate(afterDate.getDate() - 6);
143+
toDate.setDate(toDate.getDate() + 8);
144+
break;
145+
case '2 weeks':
146+
afterDate.setDate(afterDate.getDate() - 13);
147+
toDate.setDate(toDate.getDate() + 15);
148+
break;
149+
case '1 month':
150+
afterDate.setMonth(afterDate.getMonth() - 1);
151+
afterDate.setDate(afterDate.getDate() + 1);
152+
toDate.setMonth(toDate.getMonth() + 1);
153+
toDate.setDate(toDate.getDate() + 1);
154+
break;
155+
case '2 months':
156+
afterDate.setMonth(afterDate.getMonth() - 2);
157+
afterDate.setDate(afterDate.getDate() + 1);
158+
toDate.setMonth(toDate.getMonth() + 2);
159+
toDate.setDate(toDate.getDate() + 1);
160+
break;
161+
case '6 months':
162+
afterDate.setMonth(afterDate.getMonth() - 6);
163+
afterDate.setDate(afterDate.getDate() + 1);
164+
toDate.setMonth(toDate.getMonth() + 6);
165+
toDate.setDate(toDate.getDate() + 1);
166+
break;
167+
case '1 year':
168+
afterDate.setFullYear(afterDate.getFullYear() - 1);
169+
afterDate.setDate(afterDate.getDate() + 1);
170+
toDate.setFullYear(toDate.getFullYear() + 1);
171+
toDate.setDate(toDate.getDate() + 1);
172+
break;
173+
}
174+
setValue(`${hasWords && hasWords + ' '}after:${yyyyMMddFormat(afterDate)} to:${yyyyMMddFormat(toDate)}`);
175+
} else {
176+
setValue(hasWords);
177+
}
178+
179+
setIsAdvancedSearchOpen(false);
180+
};
181+
182+
const searchInput = (
183+
<SearchInput
184+
value={value}
185+
onChange={onChange}
186+
onToggleAdvancedSearch={(e, isOpen) => {
187+
e.stopPropagation();
188+
setIsAdvancedSearchOpen(isOpen);
189+
}}
190+
isAdvancedSearchOpen={isAdvancedSearchOpen}
191+
onClear={onClear}
192+
onSearch={onSubmit}
193+
ref={searchInputRef}
194+
id="custom-advanced-search"
195+
aria-label="Composable advanced search"
196+
/>
197+
);
198+
199+
// Clicking the 'date within' toggle should open its associated menu and then place the browser
200+
// focus on the first menu item.
201+
const toggleDateWithinMenu = (ev) => {
202+
ev.stopPropagation(); // Stop handleClickOutside from handling
203+
setTimeout(() => {
204+
if (dateWithinMenuRef.current) {
205+
const firstElement = dateWithinMenuRef.current.querySelector('li > button:not(:disabled)');
206+
firstElement && firstElement.focus();
207+
}
208+
}, 0);
209+
setIsDateWithinOpen(!isDateWithinOpen);
210+
};
211+
212+
// Selecting a date within option closes the menu, sets the value of date within, and puts browser focus back
213+
// on the date within toggle.
214+
const onDateWithinSelect = (e, itemId) => {
215+
e.stopPropagation();
216+
setIsDateWithinOpen(false);
217+
setDateWithin(itemId);
218+
if (dateWithinToggleRef && dateWithinToggleRef.current) {
219+
dateWithinToggleRef.current.focus();
220+
}
221+
};
222+
223+
const dateWithinOptions = (
224+
<Menu ref={dateWithinMenuRef} selected={dateWithin} onSelect={onDateWithinSelect}>
225+
<MenuContent>
226+
<MenuList>
227+
<MenuItem itemId="1 day">1 day</MenuItem>
228+
<MenuItem itemId="3 days">3 days</MenuItem>
229+
<MenuItem itemId="1 week">1 week</MenuItem>
230+
<MenuItem itemId="2 weeks">2 weeks</MenuItem>
231+
<MenuItem itemId="1 month">1 month</MenuItem>
232+
<MenuItem itemId="2 months">2 months</MenuItem>
233+
<MenuItem itemId="6 months">6 months</MenuItem>
234+
<MenuItem itemId="1 year">1 year</MenuItem>
235+
</MenuList>
236+
</MenuContent>
237+
</Menu>
238+
);
239+
240+
const dateWithinToggle = (
241+
<MenuToggle
242+
ref={dateWithinToggleRef}
243+
onClick={toggleDateWithinMenu}
244+
isExpanded={isDateWithinOpen}
245+
style={{ width: '100%' }}
246+
>
247+
{dateWithin}
248+
</MenuToggle>
249+
);
250+
251+
const advancedForm = (
252+
<div ref={advancedSearchPaneRef} role="dialog" aria-label="Advanced search form">
253+
<Panel variant="raised">
254+
<PanelMain>
255+
<PanelMainBody>
256+
<Form>
257+
<FormGroup label="Has the words" fieldId="has-words" key="has-words">
258+
<TextInput
259+
type="text"
260+
id="has-words"
261+
value={hasWords}
262+
onChange={(_event, value) => {
263+
setHasWords(value);
264+
setValue(value);
265+
}}
266+
ref={firstAttrRef}
267+
/>
268+
</FormGroup>
269+
<Grid hasGutter md={6}>
270+
<GridItem>
271+
<FormGroup label="Date within" fieldId="date-within" key="date-within">
272+
<Popper
273+
trigger={dateWithinToggle}
274+
triggerRef={dateWithinToggleRef}
275+
popper={dateWithinOptions}
276+
popperRef={dateWithinMenuRef}
277+
isVisible={isDateWithinOpen}
278+
/>
279+
</FormGroup>
280+
</GridItem>
281+
<GridItem>
282+
<FormGroup label="Of date" fieldId="date" key="date">
283+
<DatePicker
284+
id="datePicker"
285+
style={{ width: '100%' }}
286+
value={date}
287+
onChange={(_e, newValue) => setDate(newValue)}
288+
appendTo={() => document.querySelector('#datePicker')}
289+
/>
290+
</FormGroup>
291+
</GridItem>
292+
</Grid>
293+
<ActionGroup>
294+
<Button variant="primary" type="submit" onClick={(e) => onSubmit(null, e)}>
295+
Submit
296+
</Button>
297+
{!!onClear && (
298+
<Button variant="link" type="reset" onClick={onClear}>
299+
Reset
300+
</Button>
301+
)}
302+
</ActionGroup>
303+
</Form>
304+
</PanelMainBody>
305+
</PanelMain>
306+
</Panel>
307+
</div>
308+
);
309+
310+
// Popper is just one way to build a relationship between a toggle and a menu.
311+
return (
312+
<Popper
313+
trigger={searchInput}
314+
triggerRef={searchInputRef}
315+
popper={advancedForm}
316+
popperRef={advancedSearchPaneRef}
317+
isVisible={isAdvancedSearchOpen}
318+
enableFlip={false}
319+
appendTo={() => document.querySelector('#custom-advanced-search')}
320+
/>
321+
);
322+
};

0 commit comments

Comments
 (0)