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:
regexowl 2024-11-22 13:54:08 +01:00 committed by Lucas Garfield
parent ab446c2a36
commit 6427dc5285
3 changed files with 238 additions and 2 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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>
);
};