Skip to content

Commit

Permalink
Add more UI components, improve Selector, MultipleSelector and RadioB…
Browse files Browse the repository at this point in the history
…uttons and add debounced to SearchInput
  • Loading branch information
anagperal committed Jul 2, 2024
1 parent add9d75 commit 56f3bbe
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import React, { useCallback } from "react";
import { Checkbox, InputLabel, FormHelperText } from "@material-ui/core";
import { Checkbox as MUICheckbox, InputLabel, FormHelperText } from "@material-ui/core";
import styled from "styled-components";

import i18n from "../../../utils/i18n";

type NACheckboxProps = {
type CheckboxProps = {
id: string;
label?: string;
checked: boolean;
onChange: (isChecked: boolean) => void;
helperText?: string;
disabled?: boolean;
indeterminate?: boolean;
errorText?: string;
error?: boolean;
required?: boolean;
};

export const NACheckbox: React.FC<NACheckboxProps> = React.memo(
export const Checkbox: React.FC<CheckboxProps> = React.memo(
({
id,
label,
Expand All @@ -34,7 +35,7 @@ export const NACheckbox: React.FC<NACheckboxProps> = React.memo(
return (
<Container>
<CheckboxWrapper>
<Checkbox
<MUICheckbox
id={id}
checked={checked}
indeterminate={indeterminate}
Expand All @@ -44,7 +45,7 @@ export const NACheckbox: React.FC<NACheckboxProps> = React.memo(
inputProps={{ "aria-label": label || `${id}-label` }}
/>
<Label htmlFor={id} disabled={disabled}>
{label || i18n.t("N/A")}
{label}
</Label>
</CheckboxWrapper>
<StyledFormHelperText id={`${id}-helper-text`}>{helperText}</StyledFormHelperText>
Expand Down
22 changes: 22 additions & 0 deletions src/webapp/components/loader/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Backdrop, CircularProgress } from "@material-ui/core";
import React from "react";
import styled from "styled-components";

export const Loader: React.FC = () => (
<StyledBackdrop open={true}>
<StyledLoaderContainer>
<CircularProgress color="inherit" size={50} />
</StyledLoaderContainer>
</StyledBackdrop>
);

const StyledLoaderContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;

const StyledBackdrop = styled(Backdrop)`
color: ${props => props.theme.palette.common.white};
z-index: 1;
`;
115 changes: 70 additions & 45 deletions src/webapp/components/radio-buttons-group/RadioButtonsGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,87 @@
import { FormControlLabel, Radio, RadioGroup, FormHelperText } from "@material-ui/core";
import { FormControlLabel, InputLabel, Radio, RadioGroup, FormHelperText } from "@material-ui/core";
import React from "react";
import styled from "styled-components";

export type RadioOption<T extends string = string> = {
value: T;
label: string;
disabled?: boolean;
};
import { Option } from "../utils/option";

type RadioButtonsGroupProps = {
type RadioButtonsGroupProps<Value extends string = string> = {
id: string;
selected: string;
label?: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
options: RadioOption[];
options: Option<Value>[];
gap?: string;
helperText?: string;
errorText?: string;
error?: boolean;
disabled?: boolean;
required?: boolean;
};

export const RadioButtonsGroup: React.FC<RadioButtonsGroupProps> = React.memo(
({
id,
selected,
onChange,
options,
gap = "24px",
helperText = "",
errorText = "",
error = false,
}) => {
return (
<>
<StyledRadioGroup
aria-label={id}
name={id}
value={selected}
onChange={onChange}
gap={gap}
>
{options.map(option => (
<FormControlLabel
key={option.value}
value={option.value}
control={<StyledRadio />}
label={option.label}
disabled={option.disabled}
aria-label={option.label}
/>
))}
</StyledRadioGroup>
<StyledFormHelperText id={`${id}-helper-text`} error={error && !!errorText}>
{error && !!errorText ? errorText : helperText}
</StyledFormHelperText>
</>
);
export function RadioButtonsGroup<Value extends string>({
id,
selected,
label,
onChange,
options,
gap = "24px",
helperText = "",
errorText = "",
error = false,
disabled = false,
required = false,
}: RadioButtonsGroupProps<Value>): JSX.Element {
return (
<Container>
{label && (
<Label className={required ? "required" : ""} htmlFor={id}>
{label}
</Label>
)}
<StyledRadioGroup
aria-label={id}
name={id}
value={selected}
onChange={onChange}
gap={gap}
>
{options.map(option => (
<FormControlLabel
key={option.value}
value={option.value}
control={<StyledRadio />}
label={option.label}
disabled={option.disabled || disabled}
aria-label={option.label}
/>
))}
</StyledRadioGroup>
<StyledFormHelperText id={`${id}-helper-text`} error={error && !!errorText}>
{error && !!errorText ? errorText : helperText}
</StyledFormHelperText>
</Container>
);
}

const Container = styled.div`
display: flex;
flex-direction: column;
width: 100%;
`;

const Label = styled(InputLabel)`
display: inline-block;
font-weight: 700;
font-size: 0.875rem;
color: ${props => props.theme.palette.text.primary};
margin-block-end: 8px;
&.required::after {
content: "*";
color: ${props => props.theme.palette.common.red};
margin-inline-start: 4px;
}
);
`;

const StyledRadioGroup = styled(RadioGroup)<{ gap: string }>`
flex-direction: row;
Expand Down
23 changes: 17 additions & 6 deletions src/webapp/components/search-input/SearchInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,11 @@ export const SearchInput: React.FC<SearchInputProps> = React.memo(

useEffect(() => updateStateValue(value), [value]);

// TODO: needs debounce function from Collection
// eslint-disable-next-line react-hooks/exhaustive-deps
const onChangeDebounced = useCallback(
(value: string) => {
if (onChange) {
onChange(value);
}
},
debounce((value: string) => {
if (onChange) onChange(value);
}, 400),
[onChange]
);

Expand Down Expand Up @@ -61,6 +59,19 @@ export const SearchInput: React.FC<SearchInputProps> = React.memo(
}
);

function debounce<F extends (...args: any[]) => any>(func: F, delay: number) {
let timeout: ReturnType<typeof setTimeout> | null = null;

const debounced = (...args: Parameters<F>): void => {
if (timeout !== null) {
clearTimeout(timeout);
}
timeout = setTimeout(() => func(...args), delay);
};

return debounced as (...args: Parameters<F>) => ReturnType<F>;
}

const Container = styled.div`
display: flex;
flex-direction: column;
Expand Down
5 changes: 3 additions & 2 deletions src/webapp/components/selector/MultipleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import React, { useCallback } from "react";
import styled from "styled-components";
import { Select, InputLabel, MenuItem, FormHelperText, Chip } from "@material-ui/core";
import { IconChevronDown24, IconCross16 } from "@dhis2/ui";
import { SelectorOption, getLabelFromValue } from "./utils/selectorHelper";
import { getLabelFromValue } from "./utils/selectorHelper";
import { Option } from "../utils/option";

type MultipleSelectorProps<Value extends string = string> = {
id: string;
selected: Value[];
onChange: (value: Value[]) => void;
options: SelectorOption<Value>[];
options: Option<Value>[];
label?: string;
placeholder?: string;
disabled?: boolean;
Expand Down
5 changes: 3 additions & 2 deletions src/webapp/components/selector/Selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import React, { useCallback } from "react";
import styled from "styled-components";
import { Select, InputLabel, MenuItem, FormHelperText } from "@material-ui/core";
import { IconChevronDown24 } from "@dhis2/ui";
import { SelectorOption, getLabelFromValue } from "./utils/selectorHelper";
import { getLabelFromValue } from "./utils/selectorHelper";
import { Option } from "../utils/option";

type SelectorProps<Value extends string = string> = {
id: string;
selected: Value;
onChange: (value: Value) => void;
options: SelectorOption<Value>[];
options: Option<Value>[];
label?: string;
placeholder?: string;
disabled?: boolean;
Expand Down
8 changes: 2 additions & 6 deletions src/webapp/components/selector/utils/selectorHelper.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
export type SelectorOption<Value extends string = string> = {
value: Value;
label: string;
disabled?: boolean;
};
import { Option } from "../../utils/option";

export function getLabelFromValue<Value extends string = string>(
value: Value,
options: SelectorOption<Value>[]
options: Option<Value>[]
) {
return options.find(option => option.value === value)?.label;
}
5 changes: 5 additions & 0 deletions src/webapp/components/utils/option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export type Option<Value extends string = string> = {
value: Value;
label: string;
disabled?: boolean;
};

0 comments on commit 56f3bbe

Please sign in to comment.