Skip to content

Commit

Permalink
Selector components full typed and change generic T to Value
Browse files Browse the repository at this point in the history
  • Loading branch information
anagperal committed Jul 1, 2024
1 parent c9e0fb8 commit 6c325f3
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 167 deletions.
177 changes: 84 additions & 93 deletions src/webapp/components/selector/MultipleSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { Select, InputLabel, MenuItem, FormHelperText, Chip } from "@material-ui
import { IconChevronDown24, IconCross16 } from "@dhis2/ui";
import { SelectorOption, getLabelFromValue } from "./utils/selectorHelper";

type MultipleSelectorProps<T extends string = string> = {
type MultipleSelectorProps<Value extends string = string> = {
id: string;
selected: T[];
onChange: (value: SelectorOption["value"][]) => void;
options: SelectorOption<T>[];
selected: Value[];
onChange: (value: Value[]) => void;
options: SelectorOption<Value>[];
label?: string;
placeholder?: string;
disabled?: boolean;
Expand All @@ -18,97 +18,88 @@ type MultipleSelectorProps<T extends string = string> = {
required?: boolean;
};

export const MultipleSelector: React.FC<MultipleSelectorProps> = React.memo(
({
id,
label,
placeholder = "",
selected,
onChange,
options,
disabled = false,
helperText = "",
errorText = "",
error = false,
required = false,
}) => {
const handleChange = useCallback(
(
event: React.ChangeEvent<{
value: unknown;
}>,
_child: React.ReactNode
) => {
const value = event.target.value as SelectorOption["value"][];
onChange(value);
},
[onChange]
);
export function MultipleSelector<Value extends string>({
id,
label,
placeholder = "",
selected,
onChange,
options,
disabled = false,
helperText = "",
errorText = "",
error = false,
required = false,
}: MultipleSelectorProps<Value>): JSX.Element {
const handleChange = useCallback(
(
event: React.ChangeEvent<{
value: unknown;
}>,
_child: React.ReactNode
) => {
const value = event.target.value as Value[];
onChange(value);
},
[onChange]
);

const handleDelete = useCallback(
(
event: React.MouseEvent<HTMLDivElement, MouseEvent>,
value: SelectorOption["value"]
) => {
event.stopPropagation();
onChange(selected?.filter(selection => selection !== value));
},
[onChange, selected]
);
const handleDelete = useCallback(
(event: React.MouseEvent<HTMLDivElement, MouseEvent>, value: Value) => {
event.stopPropagation();
onChange(selected?.filter(selection => selection !== value));
},
[onChange, selected]
);

return (
<Container>
{label && (
<Label className={required ? "required" : ""} htmlFor={id}>
{label}
</Label>
)}
<StyledSelect
labelId={label || `${id}-label`}
id={id}
value={selected}
onChange={handleChange}
disabled={disabled}
variant="outlined"
IconComponent={IconChevronDown24}
error={error}
renderValue={(selected: unknown) =>
(selected as SelectorOption["value"][])?.length ? (
<div>
{(selected as SelectorOption["value"][]).map(value => (
<SelectedChip
key={value}
label={getLabelFromValue(value, options)}
deleteIcon={<IconCross16 />}
onDelete={event => handleDelete(event, value)}
onMouseDown={event => handleDelete(event, value)}
/>
))}
</div>
) : (
placeholder
)
}
displayEmpty
multiple
>
{options.map(option => (
<MenuItem
key={option.value}
value={option.value}
disabled={option.disabled}
>
{option.label}
</MenuItem>
))}
</StyledSelect>
<StyledFormHelperText id={`${id}-helper-text`} error={error && !!errorText}>
{error && !!errorText ? errorText : helperText}
</StyledFormHelperText>
</Container>
);
}
);
return (
<Container>
{label && (
<Label className={required ? "required" : ""} htmlFor={id}>
{label}
</Label>
)}
<StyledSelect
labelId={label || `${id}-label`}
id={id}
value={selected}
onChange={handleChange}
disabled={disabled}
variant="outlined"
IconComponent={IconChevronDown24}
error={error}
renderValue={(selected: unknown) =>
(selected as Value[])?.length ? (
<div>
{(selected as Value[]).map(value => (
<SelectedChip
key={value}
label={getLabelFromValue(value, options)}
deleteIcon={<IconCross16 />}
onDelete={event => handleDelete(event, value)}
onMouseDown={event => handleDelete(event, value)}
/>
))}
</div>
) : (
placeholder
)
}
displayEmpty
multiple
>
{options.map(option => (
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
</MenuItem>
))}
</StyledSelect>
<StyledFormHelperText id={`${id}-helper-text`} error={error && !!errorText}>
{error && !!errorText ? errorText : helperText}
</StyledFormHelperText>
</Container>
);
}

const Container = styled.div`
display: flex;
Expand Down
131 changes: 62 additions & 69 deletions src/webapp/components/selector/Selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import { Select, InputLabel, MenuItem, FormHelperText } from "@material-ui/core"
import { IconChevronDown24 } from "@dhis2/ui";
import { SelectorOption, getLabelFromValue } from "./utils/selectorHelper";

type SelectorProps<T extends string = string> = {
type SelectorProps<Value extends string = string> = {
id: string;
selected: T;
onChange: (value: SelectorOption["value"]) => void;
options: SelectorOption<T>[];
selected: Value;
onChange: (value: Value) => void;
options: SelectorOption<Value>[];
label?: string;
placeholder?: string;
disabled?: boolean;
Expand All @@ -18,72 +18,65 @@ type SelectorProps<T extends string = string> = {
required?: boolean;
};

export const Selector: React.FC<SelectorProps> = React.memo(
({
id,
label,
placeholder = "",
selected,
onChange,
options,
disabled = false,
helperText = "",
errorText = "",
error = false,
required = false,
}) => {
const handleChange = useCallback(
(
event: React.ChangeEvent<{
value: unknown;
}>,
_child: React.ReactNode
) => {
const value = event.target.value as SelectorOption["value"];
onChange(value);
},
[onChange]
);
export function Selector<Value extends string>({
id,
label,
placeholder = "",
selected,
onChange,
options,
disabled = false,
helperText = "",
errorText = "",
error = false,
required = false,
}: SelectorProps<Value>): JSX.Element {
const handleChange = useCallback(
(
event: React.ChangeEvent<{
value: unknown;
}>,
_child: React.ReactNode
) => {
const value = event.target.value as Value;
onChange(value);
},
[onChange]
);

return (
<Container>
{label && (
<Label className={required ? "required" : ""} htmlFor={id}>
{label}
</Label>
)}
<StyledSelect
labelId={label || `${id}-label`}
id={id}
value={selected}
onChange={handleChange}
disabled={disabled}
variant="outlined"
IconComponent={IconChevronDown24}
error={error}
renderValue={(selected: unknown) =>
getLabelFromValue(selected as SelectorOption["value"], options) ||
placeholder
}
displayEmpty
>
{options.map(option => (
<MenuItem
key={option.value}
value={option.value}
disabled={option.disabled}
>
{option.label}
</MenuItem>
))}
</StyledSelect>
<StyledFormHelperText id={`${id}-helper-text`} error={error && !!errorText}>
{error && !!errorText ? errorText : helperText}
</StyledFormHelperText>
</Container>
);
}
);
return (
<Container>
{label && (
<Label className={required ? "required" : ""} htmlFor={id}>
{label}
</Label>
)}
<StyledSelect
labelId={label || `${id}-label`}
id={id}
value={selected}
onChange={handleChange}
disabled={disabled}
variant="outlined"
IconComponent={IconChevronDown24}
error={error}
renderValue={(selected: unknown) =>
getLabelFromValue(selected as Value, options) || placeholder
}
displayEmpty
>
{options.map(option => (
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
</MenuItem>
))}
</StyledSelect>
<StyledFormHelperText id={`${id}-helper-text`} error={error && !!errorText}>
{error && !!errorText ? errorText : helperText}
</StyledFormHelperText>
</Container>
);
}

const Container = styled.div`
display: flex;
Expand Down
10 changes: 5 additions & 5 deletions src/webapp/components/selector/utils/selectorHelper.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
export type SelectorOption<T extends string = string> = {
value: T;
export type SelectorOption<Value extends string = string> = {
value: Value;
label: string;
disabled?: boolean;
};

export function getLabelFromValue<T extends string = string>(
value: SelectorOption["value"],
options: SelectorOption<T>[]
export function getLabelFromValue<Value extends string = string>(
value: Value,
options: SelectorOption<Value>[]
) {
return options.find(option => option.value === value)?.label;
}

0 comments on commit 6c325f3

Please sign in to comment.