Wizard: Add Timezone dropdown and NTP servers input
This adds a Timezone select, the select is typeahead and allows to filter through all defaultTimezones. It's also scrollable so it doesn't take up all of the space. The NTP servers are currently in a text input, the server is added by confirming with a space, comma or an Enter, making the server a chip and adding it to the array of NTP servers.
This commit is contained in:
parent
ab446c2a36
commit
6427dc5285
3 changed files with 238 additions and 2 deletions
|
|
@ -0,0 +1,89 @@
|
|||
import React, { useState } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Chip,
|
||||
ChipGroup,
|
||||
FormGroup,
|
||||
HelperText,
|
||||
HelperTextItem,
|
||||
TextInputGroup,
|
||||
TextInputGroupMain,
|
||||
TextInputGroupUtilities,
|
||||
} from '@patternfly/react-core';
|
||||
import { TimesIcon } from '@patternfly/react-icons';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
addNtpServer,
|
||||
clearNtpServers,
|
||||
removeNtpServer,
|
||||
selectNtpServers,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
|
||||
const NtpServersInput = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const ntpServers = useAppSelector(selectNtpServers);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
|
||||
const onTextInputChange = (
|
||||
_event: React.FormEvent<HTMLInputElement>,
|
||||
value: string
|
||||
) => {
|
||||
setInputValue(value);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent, value: string) => {
|
||||
if (e.key === ' ' || e.key === 'Enter' || e.key === ',') {
|
||||
e.preventDefault();
|
||||
if (ntpServers?.includes(value)) {
|
||||
// TO DO error
|
||||
} else {
|
||||
dispatch(addNtpServer(value));
|
||||
setInputValue('');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormGroup isRequired={false} label="NTP servers">
|
||||
<TextInputGroup>
|
||||
<TextInputGroupMain
|
||||
placeholder="Add NTP servers"
|
||||
onChange={onTextInputChange}
|
||||
value={inputValue}
|
||||
onKeyDown={(e) => handleKeyDown(e, inputValue)}
|
||||
>
|
||||
<ChipGroup>
|
||||
{ntpServers?.map((server) => (
|
||||
<Chip
|
||||
key={server}
|
||||
onClick={() => dispatch(removeNtpServer(server))}
|
||||
>
|
||||
{server}
|
||||
</Chip>
|
||||
))}
|
||||
</ChipGroup>
|
||||
</TextInputGroupMain>
|
||||
{ntpServers && ntpServers.length > 0 && (
|
||||
<TextInputGroupUtilities>
|
||||
<Button
|
||||
variant="plain"
|
||||
onClick={() => dispatch(clearNtpServers())}
|
||||
aria-label="Remove all NTP servers"
|
||||
>
|
||||
<TimesIcon />
|
||||
</Button>
|
||||
</TextInputGroupUtilities>
|
||||
)}
|
||||
</TextInputGroup>
|
||||
<HelperText>
|
||||
<HelperTextItem variant="indeterminate">
|
||||
Confirm the NTP server by pressing space, comma or enter.
|
||||
</HelperTextItem>
|
||||
</HelperText>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default NtpServersInput;
|
||||
|
|
@ -1,7 +1,152 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectList,
|
||||
MenuToggle,
|
||||
MenuToggleElement,
|
||||
TextInputGroup,
|
||||
TextInputGroupMain,
|
||||
TextInputGroupUtilities,
|
||||
Button,
|
||||
FormGroup,
|
||||
} from '@patternfly/react-core';
|
||||
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
|
||||
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
changeTimezone,
|
||||
selectTimezone,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
|
||||
const defaultTimezones = Intl.supportedValuesOf('timeZone');
|
||||
|
||||
const TimezoneDropDown = () => {
|
||||
return <></>;
|
||||
const timezone = useAppSelector(selectTimezone);
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [filterValue, setFilterValue] = useState<string>('');
|
||||
const [selectOptions, setSelectOptions] =
|
||||
useState<string[]>(defaultTimezones);
|
||||
|
||||
useEffect(() => {
|
||||
let filteredTimezones = defaultTimezones;
|
||||
|
||||
if (filterValue) {
|
||||
filteredTimezones = defaultTimezones.filter((timezone: string) =>
|
||||
String(timezone).toLowerCase().includes(filterValue.toLowerCase())
|
||||
);
|
||||
if (!filteredTimezones.length) {
|
||||
filteredTimezones = [`No results found for "${filterValue}"`];
|
||||
}
|
||||
if (!isOpen) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
}
|
||||
setSelectOptions(filteredTimezones);
|
||||
|
||||
// This useEffect hook should run *only* on when the filter value changes.
|
||||
// eslint's exhaustive-deps rule does not support this use.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filterValue]);
|
||||
|
||||
const onToggle = (isOpen: boolean) => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const onInputClick = () => {
|
||||
if (!isOpen) {
|
||||
setIsOpen(true);
|
||||
} else if (!inputValue) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onSelect = (_event: React.MouseEvent, value: string) => {
|
||||
if (value && !value.includes('No results')) {
|
||||
setInputValue(value);
|
||||
setFilterValue('');
|
||||
dispatch(changeTimezone(value));
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onTextInputChange = (_event: React.FormEvent, value: string) => {
|
||||
setInputValue(value);
|
||||
setFilterValue(value);
|
||||
|
||||
if (value !== timezone) {
|
||||
dispatch(changeTimezone(''));
|
||||
}
|
||||
};
|
||||
|
||||
const onToggleClick = () => {
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
|
||||
const onClearButtonClick = () => {
|
||||
setInputValue('');
|
||||
setFilterValue('');
|
||||
dispatch(changeTimezone(''));
|
||||
};
|
||||
|
||||
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
|
||||
<MenuToggle
|
||||
ref={toggleRef}
|
||||
variant="typeahead"
|
||||
onClick={onToggleClick}
|
||||
isExpanded={isOpen}
|
||||
isFullWidth
|
||||
>
|
||||
<TextInputGroup isPlain>
|
||||
<TextInputGroupMain
|
||||
value={timezone ? timezone : inputValue}
|
||||
onClick={onInputClick}
|
||||
onChange={onTextInputChange}
|
||||
autoComplete="off"
|
||||
placeholder="Select a timezone"
|
||||
isExpanded={isOpen}
|
||||
/>
|
||||
|
||||
{timezone && (
|
||||
<TextInputGroupUtilities>
|
||||
<Button
|
||||
variant="plain"
|
||||
onClick={onClearButtonClick}
|
||||
aria-label="Clear input"
|
||||
>
|
||||
<TimesIcon />
|
||||
</Button>
|
||||
</TextInputGroupUtilities>
|
||||
)}
|
||||
</TextInputGroup>
|
||||
</MenuToggle>
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup isRequired={false} label="Timezone">
|
||||
<Select
|
||||
isScrollable
|
||||
isOpen={isOpen}
|
||||
selected={timezone}
|
||||
onSelect={onSelect}
|
||||
onOpenChange={onToggle}
|
||||
toggle={toggle}
|
||||
shouldFocusFirstItemOnOpen={false}
|
||||
>
|
||||
<SelectList>
|
||||
{selectOptions.map((option) => (
|
||||
<SelectOption key={option} value={option}>
|
||||
{option}
|
||||
</SelectOption>
|
||||
))}
|
||||
</SelectList>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimezoneDropDown;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
|
||||
import { Text, Form, Title } from '@patternfly/react-core';
|
||||
|
||||
import NtpServersInput from './components/NtpServersInput';
|
||||
import TimezoneDropDown from './components/TimezoneDropDown';
|
||||
|
||||
const TimezoneStep = () => {
|
||||
|
|
@ -12,6 +13,7 @@ const TimezoneStep = () => {
|
|||
</Title>
|
||||
<Text>Select a timezone for your image.</Text>
|
||||
<TimezoneDropDown />
|
||||
<NtpServersInput />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue