Skip to content

Commit

Permalink
chore: remove forwardRef and context.provider
Browse files Browse the repository at this point in the history
React 19 makes the use of forwardRef and context.provider obsolete.

Signed-off-by: Maxim Stykow <[email protected]>
  • Loading branch information
mstykow committed Dec 15, 2024
1 parent 9025c27 commit 4f4df2b
Show file tree
Hide file tree
Showing 13 changed files with 273 additions and 308 deletions.
1 change: 0 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export default tseslint.config(
'jest.config.ts',
'notices.template.html',
'tools',
'typings',
'vite.config.mts',
],
extends: [
Expand Down
256 changes: 122 additions & 134 deletions src/Frontend/Components/Autocomplete/Listbox/Listbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from '@mui/material/useAutocomplete';
import { SxProps } from '@mui/system';
import { groupBy as _groupBy } from 'lodash';
import { forwardRef, useMemo, useState } from 'react';
import { useMemo, useState } from 'react';
import { GroupedVirtuoso, Virtuoso, VirtuosoHandle } from 'react-virtuoso';

import { GroupContainer, styles } from './Listbox.style';
Expand All @@ -21,6 +21,7 @@ export type ListboxProps<
Value,
FreeSolo extends boolean | undefined,
> = React.HTMLAttributes<HTMLElement> & {
ref?: React.RefObject<HTMLDivElement>;
virtuosoRef: React.RefObject<VirtuosoHandle | null>;
options: Array<Value>;
groupProps?: {
Expand Down Expand Up @@ -61,147 +62,134 @@ interface Groups<Value> {
firstSelectedIndex: number;
}

export const Listbox = forwardRef(
<Value, FreeSolo extends boolean | undefined>(
{
virtuosoRef,
closePopper,
getOptionKey,
getOptionProps,
groupBy,
groupProps,
optionText,
options,
renderOptionEndIcon,
renderOptionStartIcon,
...listboxProps
}: ListboxProps<Value, FreeSolo>,
forwardedRef: React.ForwardedRef<HTMLDivElement>,
) => {
const [height, setHeight] = useState<number>(Number.MAX_SAFE_INTEGER); // will result in max-height
export const Listbox = <Value, FreeSolo extends boolean | undefined>({
virtuosoRef,
closePopper,
getOptionKey,
getOptionProps,
groupBy,
groupProps,
optionText,
options,
renderOptionEndIcon,
renderOptionStartIcon,
ref,
...listboxProps
}: ListboxProps<Value, FreeSolo>) => {
const [height, setHeight] = useState<number>(Number.MAX_SAFE_INTEGER); // will result in max-height

const groups = useMemo((): Groups<Value> | undefined => {
if (!groupBy) {
return undefined;
}
const groups = useMemo((): Groups<Value> | undefined => {
if (!groupBy) {
return undefined;
}

const grouped = _groupBy(options, groupBy);
const flattened = Object.values(grouped).flat();
const grouped = _groupBy(options, groupBy);
const flattened = Object.values(grouped).flat();

return {
options: flattened,
groupNames: Object.keys(grouped),
groupCounts: Object.values(grouped).map((group) => group.length),
firstSelectedIndex: flattened.findIndex(
(option, index) =>
!!getOptionProps({ option, index })['aria-selected'],
),
};
}, [getOptionProps, groupBy, options]);
return {
options: flattened,
groupNames: Object.keys(grouped),
groupCounts: Object.values(grouped).map((group) => group.length),
firstSelectedIndex: flattened.findIndex(
(option, index) => !!getOptionProps({ option, index })['aria-selected'],
),
};
}, [getOptionProps, groupBy, options]);

return (
<MuiPaper
{...listboxProps}
ref={forwardedRef}
elevation={4}
square
role={'listbox'}
>
{groups ? renderGroupedList(groups) : renderList(options)}
</MuiPaper>
);
return (
<MuiPaper {...listboxProps} ref={ref} elevation={4} square role={'listbox'}>
{groups ? renderGroupedList(groups) : renderList(options)}
</MuiPaper>
);

function renderGroupedList({
groupCounts,
groupNames,
options,
firstSelectedIndex,
}: Groups<Value>) {
return (
<GroupedVirtuoso
ref={virtuosoRef}
style={{ ...styles.virtuoso, height }}
increaseViewportBy={20}
initialTopMostItemIndex={
~firstSelectedIndex && {
index: firstSelectedIndex,
align: 'center',
}
function renderGroupedList({
groupCounts,
groupNames,
options,
firstSelectedIndex,
}: Groups<Value>) {
return (
<GroupedVirtuoso
ref={virtuosoRef}
style={{ ...styles.virtuoso, height }}
increaseViewportBy={20}
initialTopMostItemIndex={
~firstSelectedIndex && {
index: firstSelectedIndex,
align: 'center',
}
totalListHeightChanged={setHeight}
groupCounts={groupCounts}
groupContent={(index) => {
const IconComp = groupProps?.icon || (() => null);
const ActionComp = groupProps?.action || (() => null);
}
totalListHeightChanged={setHeight}
groupCounts={groupCounts}
groupContent={(index) => {
const IconComp = groupProps?.icon || (() => null);
const ActionComp = groupProps?.action || (() => null);

return (
<GroupContainer role={'group'}>
<IconComp name={groupNames[index]} />
<MuiTypography
sx={{ ...styles.overflowEllipsis, paddingTop: '2px' }}
>
{groupNames[index]}
</MuiTypography>
<ActionComp name={groupNames[index]} />
</GroupContainer>
);
}}
itemContent={(index) =>
renderOption({ index, option: options[index] })
}
/>
);
}
return (
<GroupContainer role={'group'}>
<IconComp name={groupNames[index]} />
<MuiTypography
sx={{ ...styles.overflowEllipsis, paddingTop: '2px' }}
>
{groupNames[index]}
</MuiTypography>
<ActionComp name={groupNames[index]} />
</GroupContainer>
);
}}
itemContent={(index) => renderOption({ index, option: options[index] })}
/>
);
}

function renderList(options: Array<Value>) {
return (
<Virtuoso
ref={virtuosoRef}
style={{
...styles.virtuoso,
height,
}}
data={options}
itemContent={(index, option) => renderOption({ option, index })}
increaseViewportBy={20}
totalListHeightChanged={setHeight}
/>
);
}
function renderList(options: Array<Value>) {
return (
<Virtuoso
ref={virtuosoRef}
style={{
...styles.virtuoso,
height,
}}
data={options}
itemContent={(index, option) => renderOption({ option, index })}
increaseViewportBy={20}
totalListHeightChanged={setHeight}
/>
);
}

function renderOption({
index,
function renderOption({
index,
option,
}: UseAutocompleteRenderedOption<Value>) {
const { key, ...optionProps } = getOptionProps({
option,
}: UseAutocompleteRenderedOption<Value>) {
const { key, ...optionProps } = getOptionProps({
option,
index,
});
index,
});

return (
<MuiListItemButton
{...optionProps}
selected={optionProps['aria-selected'] as boolean}
disabled={optionProps['aria-disabled'] as boolean}
key={getOptionKey?.(option) ?? key}
sx={{ gap: '12px', ...optionText.sx }}
dense
>
{renderOptionStartIcon?.(option, { closePopper })}
<MuiListItemText
primary={optionText.primary(option)}
primaryTypographyProps={{
sx: { ...styles.overflowEllipsis },
}}
secondary={optionText.secondary?.(option)}
secondaryTypographyProps={{
variant: 'caption',
sx: styles.overflowEllipsis,
}}
/>
{renderOptionEndIcon?.(option, { closePopper })}
</MuiListItemButton>
);
}
},
);
return (
<MuiListItemButton
{...optionProps}
selected={optionProps['aria-selected'] as boolean}
disabled={optionProps['aria-disabled'] as boolean}
key={getOptionKey?.(option) ?? key}
sx={{ gap: '12px', ...optionText.sx }}
dense
>
{renderOptionStartIcon?.(option, { closePopper })}
<MuiListItemText
primary={optionText.primary(option)}
primaryTypographyProps={{
sx: { ...styles.overflowEllipsis },
}}
secondary={optionText.secondary?.(option)}
secondaryTypographyProps={{
variant: 'caption',
sx: styles.overflowEllipsis,
}}
/>
{renderOptionEndIcon?.(option, { closePopper })}
</MuiListItemButton>
);
}
};
88 changes: 42 additions & 46 deletions src/Frontend/Components/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
SxProps,
} from '@mui/material';
import MuiCheckbox from '@mui/material/Checkbox';
import { forwardRef } from 'react';

interface CheckboxProps extends Pick<FormControlLabelProps, 'labelPlacement'> {
checked: boolean;
Expand All @@ -17,51 +16,48 @@ interface CheckboxProps extends Pick<FormControlLabelProps, 'labelPlacement'> {
indeterminate?: boolean;
label?: string;
onChange(event: React.ChangeEvent<HTMLInputElement>): void;
ref?: React.RefObject<HTMLButtonElement>;
sx?: SxProps;
}

export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
(
{
checked,
disableRipple,
disabled,
indeterminate,
label,
labelPlacement,
onChange,
sx,
...props
},
ref,
) => {
return (
<FormControlLabel
sx={{
...sx,
marginLeft: 'unset',
marginRight: !label ? 'unset' : undefined,
}}
label={label}
disabled={disabled}
labelPlacement={labelPlacement}
control={
<MuiCheckbox
{...props}
ref={ref}
disabled={disabled}
indeterminate={indeterminate}
checked={checked}
onChange={onChange}
inputProps={{
style: { zIndex: 'unset' },
}}
disableRipple={disableRipple}
size={'small'}
sx={{ padding: '7px' }}
/>
}
/>
);
},
);
export const Checkbox: React.FC<CheckboxProps> = ({
checked,
disableRipple,
disabled,
indeterminate,
label,
labelPlacement,
onChange,
ref,
sx,
...props
}) => {
return (
<FormControlLabel
sx={{
...sx,
marginLeft: 'unset',
marginRight: !label ? 'unset' : undefined,
}}
label={label}
disabled={disabled}
labelPlacement={labelPlacement}
control={
<MuiCheckbox
{...props}
ref={ref}
disabled={disabled}
indeterminate={indeterminate}
checked={checked}
onChange={onChange}
inputProps={{
style: { zIndex: 'unset' },
}}
disableRipple={disableRipple}
size={'small'}
sx={{ padding: '7px' }}
/>
}
/>
);
};
Loading

0 comments on commit 4f4df2b

Please sign in to comment.