jbpenrath
2025-01-07 23:28:47 +01:00
committed by Jean-Baptiste PENRATH
parent 0f6a8dfa72
commit 56d9ed88f0
27 changed files with 1497 additions and 1546 deletions

View File

@@ -0,0 +1,5 @@
---
"@openfun/cunningham-react": major
---
Upgrade to React 19

View File

@@ -2,14 +2,16 @@ import React, {
AnchorHTMLAttributes,
ButtonHTMLAttributes,
createElement,
forwardRef,
ReactNode,
RefAttributes,
} from "react";
type DomProps = ButtonHTMLAttributes<HTMLButtonElement> &
AnchorHTMLAttributes<HTMLAnchorElement>;
export type ButtonProps = Omit<DomProps, "color"> & {
export type ButtonElement = HTMLButtonElement & HTMLAnchorElement;
export type ButtonProps = Omit<DomProps, "color"> &
RefAttributes<ButtonElement> & {
size?: "medium" | "small" | "nano";
color?:
| "primary"
@@ -24,11 +26,7 @@ export type ButtonProps = Omit<DomProps, "color"> & {
fullWidth?: boolean;
};
export type ButtonElement = HTMLButtonElement & HTMLAnchorElement;
export const Button = forwardRef<ButtonElement, ButtonProps>(
(
{
export const Button = ({
children,
color = "primary",
size = "medium",
@@ -37,10 +35,9 @@ export const Button = forwardRef<ButtonElement, ButtonProps>(
active,
className,
fullWidth,
...props
},
ref,
) => {
...props
}: ButtonProps) => {
const classes = [
"c__button",
"c__button--" + color,
@@ -77,5 +74,4 @@ export const Button = forwardRef<ButtonElement, ButtonProps>(
{!!icon && iconPosition === "right" && iconElement}
</>,
);
},
);
};

View File

@@ -1,11 +1,11 @@
import React, {
InputHTMLAttributes,
PropsWithChildren,
forwardRef,
useEffect,
useRef,
useState,
ReactNode,
RefAttributes,
} from "react";
import classNames from "classnames";
import { Field, FieldProps } from ":/components/Forms/Field";
@@ -16,15 +16,19 @@ export type CheckboxOnlyProps = {
};
export type CheckboxProps = InputHTMLAttributes<HTMLInputElement> &
RefAttributes<HTMLInputElement> &
FieldProps &
CheckboxOnlyProps;
export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
(
{ indeterminate, className = "", checked, label, ...props }: CheckboxProps,
export const Checkbox = ({
indeterminate,
className = "",
checked,
label,
ref,
) => {
const inputRef = useRef<HTMLInputElement>();
...props
}: CheckboxProps) => {
const inputRef = useRef<HTMLInputElement>(null);
const [value, setValue] = useState<boolean>(!!checked);
useEffect(() => {
@@ -69,7 +73,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
if (typeof ref === "function") {
ref(checkboxRef);
}
inputRef.current = checkboxRef || undefined;
inputRef.current = checkboxRef || null;
}}
/>
<Indeterminate />
@@ -80,8 +84,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(
</Field>
</label>
);
},
);
};
export const CheckboxGroup = ({
children,

View File

@@ -1,4 +1,4 @@
import React, { forwardRef, Ref, useMemo, useRef, useState } from "react";
import React, { RefAttributes, useMemo, useRef, useState } from "react";
import {
CalendarDate,
createCalendar,
@@ -83,24 +83,22 @@ const DropdownValues = ({ options, downShift }: DropdownValuesProps) => (
</div>
);
interface CalendarAuxProps extends CalendarAria {
type CalendarAuxProps = CalendarAria &
RefAttributes<HTMLDivElement> & {
minYear?: number;
maxYear?: number;
state: RangeCalendarState | CalendarState;
}
};
const CalendarAux = forwardRef(
(
{
const CalendarAux = ({
state,
minYear = 1900, // in gregorian calendar.
maxYear = 2050, // in gregorian calendar.
prevButtonProps,
nextButtonProps,
calendarProps,
}: CalendarAuxProps,
ref: Ref<HTMLDivElement>,
) => {
ref,
}: CalendarAuxProps) => {
const { t } = useCunningham();
const useTimeZoneFormatter = (formatOptions: DateFormatterOptions) => {
@@ -310,8 +308,7 @@ const CalendarAux = forwardRef(
onClick={() => state.focusPreviousSection(true)}
disabled={
!!state.minValue &&
state.minValue.year >
state.focusedDate.add({ years: -1 }).year
state.minValue.year > state.focusedDate.add({ years: -1 }).year
}
aria-label={t(
"components.forms.date_picker.previous_year_button_aria_label",
@@ -355,8 +352,7 @@ const CalendarAux = forwardRef(
<DropdownValues options={yearItems} downShift={downshiftYear} />
</div>
);
},
);
};
export const Calendar = (props: CalendarProps<DateValue>) => {
const { locale } = useLocale();

View File

@@ -1,9 +1,8 @@
import React, {
forwardRef,
PropsWithChildren,
Ref,
useMemo,
useRef,
RefAttributes,
} from "react";
import {
DateRangePickerState,
@@ -36,7 +35,8 @@ export type DatePickerAuxSubProps = FieldProps & {
};
export type DatePickerAuxProps = PropsWithChildren &
DatePickerAuxSubProps & {
DatePickerAuxSubProps &
RefAttributes<HTMLDivElement> & {
pickerState: DateRangePickerState | DatePickerState;
pickerProps: Pick<
DateRangePickerAria | DatePickerAria,
@@ -54,9 +54,7 @@ export type DatePickerAuxProps = PropsWithChildren &
* This component is used by date and date range picker components.
* It contains the common logic between the two.
*/
const DatePickerAux = forwardRef(
(
{
const DatePickerAux = ({
className,
pickerState,
pickerProps,
@@ -70,16 +68,14 @@ const DatePickerAux = forwardRef(
disabled = false,
optionalClassName,
isRange,
ref,
...props
}: DatePickerAuxProps,
ref: Ref<HTMLDivElement>,
) => {
}: DatePickerAuxProps) => {
const { t, currentLocale } = useCunningham();
const pickerRef = useRef<HTMLDivElement>(null);
const isDateInvalid = useMemo(
() =>
pickerState.validationState === "invalid" || props.state === "error",
() => pickerState.validationState === "invalid" || props.state === "error",
[pickerState.validationState, props.state],
);
@@ -98,9 +94,7 @@ const DatePickerAux = forwardRef(
"c__date-picker--invalid": isDateInvalid,
"c__date-picker--success": props.state === "success",
"c__date-picker--focused":
!isDateInvalid &&
!disabled &&
(pickerState.isOpen || isFocused),
!isDateInvalid && !disabled && (pickerState.isOpen || isFocused),
})}
>
<div
@@ -201,7 +195,6 @@ const DatePickerAux = forwardRef(
</Field>
</I18nProvider>
);
},
);
};
export default DatePickerAux;

View File

@@ -1,6 +1,6 @@
import React, {
forwardRef,
PropsWithChildren,
RefAttributes,
useEffect,
useImperativeHandle,
useRef,
@@ -15,13 +15,13 @@ import {
FileUploaderRefType,
} from ":/components/Forms/FileUploader/index";
interface DropZoneProps extends FileUploaderProps, PropsWithChildren {
type DropZoneProps = FileUploaderProps &
RefAttributes<FileUploaderRefType> &
PropsWithChildren<{
files: File[];
}
}>;
export const DropZone = forwardRef<FileUploaderRefType, DropZoneProps>(
(
{
export const DropZone = ({
multiple,
name,
state,
@@ -34,10 +34,9 @@ export const DropZone = forwardRef<FileUploaderRefType, DropZoneProps>(
files,
onFilesChange,
children,
...props
}: DropZoneProps,
ref,
) => {
...props
}: DropZoneProps) => {
const [dragActive, setDragActive] = useState(false);
const container = useRef<HTMLLabelElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
@@ -92,14 +91,10 @@ export const DropZone = forwardRef<FileUploaderRefType, DropZoneProps>(
return (
<label
className={classNames(
"c__file-uploader",
"c__file-uploader--" + state,
{
className={classNames("c__file-uploader", "c__file-uploader--" + state, {
"c__file-uploader--active": dragActive,
"c__file-uploader--animate-icon": animateIcon,
},
)}
})}
onDragEnter={() => {
setDragActive(true);
}}
@@ -157,5 +152,4 @@ export const DropZone = forwardRef<FileUploaderRefType, DropZoneProps>(
</div>
</label>
);
},
);
};

View File

@@ -1,16 +1,14 @@
import React, { forwardRef, useEffect, useMemo, useState } from "react";
import React, { useEffect, useMemo, useState } from "react";
import { useCunningham } from ":/components/Provider";
import { Button } from ":/components/Button";
import {
FileUploaderProps,
FileUploaderRefType,
} from ":/components/Forms/FileUploader/index";
import { FileUploaderProps } from ":/components/Forms/FileUploader/index";
import { DropZone } from ":/components/Forms/FileUploader/DropZone";
export const FileUploaderMono = forwardRef<
FileUploaderRefType,
FileUploaderProps
>(({ fakeDefaultFiles, ...props }, ref) => {
export const FileUploaderMono = ({
fakeDefaultFiles,
ref,
...props
}: FileUploaderProps) => {
const { t } = useCunningham();
const [file, setFile] = useState<File | undefined>(
fakeDefaultFiles && fakeDefaultFiles.length > 0
@@ -91,4 +89,4 @@ export const FileUploaderMono = forwardRef<
)}
</>
);
});
};

View File

@@ -1,17 +1,16 @@
import React, { forwardRef, useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { useCunningham } from ":/components/Provider";
import { formatBytes } from ":/components/Forms/FileUploader/utils";
import { Button } from ":/components/Button";
import {
FileUploaderProps,
FileUploaderRefType,
} from ":/components/Forms/FileUploader/index";
import { FileUploaderProps } from ":/components/Forms/FileUploader/index";
import { DropZone } from ":/components/Forms/FileUploader/DropZone";
export const FileUploaderMulti = forwardRef<
FileUploaderRefType,
FileUploaderProps
>(({ fullWidth, fakeDefaultFiles, ...props }, ref) => {
export const FileUploaderMulti = ({
fullWidth,
fakeDefaultFiles,
ref,
...props
}: FileUploaderProps) => {
const { t } = useCunningham();
const [files, setFiles] = useState<File[]>(fakeDefaultFiles || []);
@@ -59,4 +58,4 @@ export const FileUploaderMulti = forwardRef<
)}
</>
);
});
};

View File

@@ -1,11 +1,12 @@
import React, { forwardRef, InputHTMLAttributes, ReactElement } from "react";
import React, { InputHTMLAttributes, ReactElement, RefAttributes } from "react";
import { Field, FieldProps, FieldState } from ":/components/Forms/Field";
import { FileUploaderMulti } from ":/components/Forms/FileUploader/FileUploaderMulti";
import { FileUploaderMono } from ":/components/Forms/FileUploader/FileUploaderMono";
export interface FileUploaderProps
extends Omit<FieldProps, "state">,
InputHTMLAttributes<HTMLInputElement> {
InputHTMLAttributes<HTMLInputElement>,
RefAttributes<FileUploaderRefType> {
state?: FieldState | "uploading" | undefined;
multiple?: boolean;
icon?: ReactElement;
@@ -25,8 +26,11 @@ export interface FileUploaderRefType {
reset: () => void;
}
export const FileUploader = forwardRef<FileUploaderRefType, FileUploaderProps>(
({ fullWidth, ...props }, ref) => {
export const FileUploader = ({
fullWidth,
ref,
...props
}: FileUploaderProps) => {
return (
<Field fullWidth={fullWidth} className={props.className}>
{props.multiple ? (
@@ -36,5 +40,4 @@ export const FileUploader = forwardRef<FileUploaderRefType, FileUploaderProps>(
)}
</Field>
);
},
);
};

View File

@@ -1,12 +1,9 @@
import React, { forwardRef } from "react";
import React from "react";
import { Input, InputProps } from ":/components/Forms/Input/index";
import { Button } from ":/components/Button";
import { useCunningham } from ":/components/Provider";
export const InputPassword = forwardRef<
HTMLInputElement,
Omit<InputProps, "rightIcon">
>((props: InputProps, ref) => {
export const InputPassword = (props: Omit<InputProps, "rightIcon">) => {
const [showPassword, setShowPassword] = React.useState(false);
const { className, ...otherProps } = props;
const customClassName = "c__input--password";
@@ -14,7 +11,6 @@ export const InputPassword = forwardRef<
return (
<Input
{...otherProps}
ref={ref}
className={className + " " + customClassName}
type={showPassword ? "text" : "password"}
rightIcon={
@@ -38,4 +34,4 @@ export const InputPassword = forwardRef<
}
/>
);
});
};

View File

@@ -1,7 +1,7 @@
import React, {
forwardRef,
InputHTMLAttributes,
ReactNode,
RefAttributes,
useEffect,
useRef,
useState,
@@ -20,12 +20,11 @@ export type InputOnlyProps = {
};
export type InputProps = InputHTMLAttributes<HTMLInputElement> &
RefAttributes<HTMLInputElement> &
FieldProps &
InputOnlyProps;
export const Input = forwardRef<HTMLInputElement, InputProps>(
(
{
export const Input = ({
className,
defaultValue,
label,
@@ -34,10 +33,9 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
rightIcon,
charCounter,
charCounterMax,
...props
}: InputProps,
ref,
) => {
...props
}: InputProps) => {
const classes = ["c__input"];
const inputRef = useRef<HTMLInputElement | null>(null);
const [inputFocus, setInputFocus] = useState(false);
@@ -132,11 +130,8 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
}}
/>
</LabelledBox>
{!!rightIcon && (
<div className="c__input__icon-right">{rightIcon}</div>
)}
{!!rightIcon && <div className="c__input__icon-right">{rightIcon}</div>}
</div>
</Field>
);
},
);
};

View File

@@ -1,8 +1,8 @@
import React, {
InputHTMLAttributes,
PropsWithChildren,
forwardRef,
ReactNode,
RefAttributes,
} from "react";
import classNames from "classnames";
import { Field, FieldProps } from ":/components/Forms/Field";
@@ -12,11 +12,11 @@ export type RadioOnlyProps = {
};
export type RadioProps = InputHTMLAttributes<HTMLInputElement> &
RefAttributes<HTMLInputElement> &
FieldProps &
RadioOnlyProps;
export const Radio = forwardRef<HTMLInputElement, RadioProps>(
({ className, label, ...props }: RadioProps, ref) => {
export const Radio = ({ className, label, ref, ...props }: RadioProps) => {
const {
compact,
fullWidth,
@@ -42,8 +42,7 @@ export const Radio = forwardRef<HTMLInputElement, RadioProps>(
</Field>
</label>
);
},
);
};
export const RadioGroup = ({
className,

View File

@@ -1,4 +1,4 @@
import React, { forwardRef, PropsWithChildren, ReactNode } from "react";
import React, { PropsWithChildren, ReactNode, RefAttributes } from "react";
import { SelectMulti } from ":/components/Forms/Select/multi";
import { SelectMono } from ":/components/Forms/Select/mono";
import { FieldProps } from ":/components/Forms/Field";
@@ -28,6 +28,7 @@ export interface SelectHandle {
}
export type SelectProps = PropsWithChildren &
RefAttributes<SelectHandle> &
FieldProps & {
label: string;
hideLabel?: boolean;
@@ -53,16 +54,12 @@ export type SelectProps = PropsWithChildren &
target: { value: string | undefined };
}) => void;
};
export const Select = forwardRef<SelectHandle, SelectProps>((props, ref) => {
export const Select = (props: SelectProps) => {
if (props.defaultValue && props.value) {
throw new Error(
"You cannot use both defaultValue and value props on Select component",
);
}
return props.multi ? (
<SelectMulti {...props} ref={ref} />
) : (
<SelectMono {...props} ref={ref} />
);
});
return props.multi ? <SelectMulti {...props} /> : <SelectMono {...props} />;
};

View File

@@ -1,10 +1,4 @@
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import React, { useEffect, useImperativeHandle, useRef, useState } from "react";
import { useCombobox } from "downshift";
import classNames from "classnames";
import { useCunningham } from ":/components/Provider";
@@ -15,11 +9,13 @@ import {
SelectMonoAux,
SubProps,
} from ":/components/Forms/Select/mono-common";
import { SelectHandle } from ":/components/Forms/Select";
import { isOptionWithRender } from ":/components/Forms/Select/utils";
export const SelectMonoSearchable = forwardRef<SelectHandle, SubProps>(
({ showLabelWhenSelected = true, ...props }, ref) => {
export const SelectMonoSearchable = ({
showLabelWhenSelected = true,
ref,
...props
}: SubProps) => {
const { t } = useCunningham();
const [optionsToDisplay, setOptionsToDisplay] = useState(props.options);
const [hasInputFocused, setHasInputFocused] = useState(false);
@@ -152,5 +148,4 @@ export const SelectMonoSearchable = forwardRef<SelectHandle, SubProps>(
downshiftReturn.selectedItem.render()}
</SelectMonoAux>
);
},
);
};

View File

@@ -1,17 +1,12 @@
import { useSelect, UseSelectReturnValue } from "downshift";
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
} from "react";
import React, { useEffect, useImperativeHandle, useRef } from "react";
import {
optionToString,
optionToValue,
SelectMonoAux,
SubProps,
} from ":/components/Forms/Select/mono-common";
import { Option, SelectHandle, SelectProps } from ":/components/Forms/Select";
import { Option, SelectProps } from ":/components/Forms/Select";
import { SelectedOption } from ":/components/Forms/Select/utils";
/**
@@ -32,8 +27,7 @@ const useKeepSelectedItemInSyncWithOptions = (
}, [props.value, props.options]);
};
export const SelectMonoSimple = forwardRef<SelectHandle, SubProps>(
(props, ref) => {
export const SelectMonoSimple = ({ ref, ...props }: SubProps) => {
const downshiftReturn = useSelect({
...props.downshiftProps,
items: props.options,
@@ -67,5 +61,4 @@ export const SelectMonoSimple = forwardRef<SelectHandle, SubProps>(
<SelectedOption option={downshiftReturn.selectedItem} {...props} />
</SelectMonoAux>
);
},
);
};

View File

@@ -1,12 +1,11 @@
import React, { forwardRef, useEffect, useState } from "react";
import React, { useEffect, useState } from "react";
import { UseSelectStateChange } from "downshift";
import { optionToValue, SubProps } from ":/components/Forms/Select/mono-common";
import { SelectMonoSearchable } from ":/components/Forms/Select/mono-searchable";
import { SelectMonoSimple } from ":/components/Forms/Select/mono-simple";
import { Option, SelectHandle, SelectProps } from ":/components/Forms/Select";
import { Option, SelectProps } from ":/components/Forms/Select";
export const SelectMono = forwardRef<SelectHandle, SelectProps>(
(props, ref) => {
export const SelectMono = (props: SelectProps) => {
const defaultSelectedItem = props.defaultValue
? props.options.find(
(option) => optionToValue(option) === props.defaultValue,
@@ -53,15 +52,12 @@ export const SelectMono = forwardRef<SelectHandle, SelectProps>(
{...props}
downshiftProps={commonDownshiftProps}
value={value}
ref={ref}
/>
) : (
<SelectMonoSimple
{...props}
downshiftProps={commonDownshiftProps}
value={value}
ref={ref}
/>
);
},
);
};

View File

@@ -12,7 +12,7 @@ import { SelectMenu } from ":/components/Forms/Select/select-menu";
export const SelectMultiMenu = (
props: SelectMultiAuxProps & {
selectRef: React.RefObject<HTMLDivElement>;
selectRef: React.RefObject<HTMLDivElement | null>;
},
) => {
const { t } = useCunningham();

View File

@@ -1,10 +1,4 @@
import React, {
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import React, { useEffect, useImperativeHandle, useRef, useState } from "react";
import { useCombobox, useMultipleSelection } from "downshift";
import { optionToString } from ":/components/Forms/Select/mono-common";
import {
@@ -12,10 +6,8 @@ import {
SelectMultiAux,
SubProps,
} from ":/components/Forms/Select/multi-common";
import { SelectHandle } from ":/components/Forms/Select/index";
export const SelectMultiSearchable = forwardRef<SelectHandle, SubProps>(
(props, ref) => {
export const SelectMultiSearchable = ({ ref, ...props }: SubProps) => {
const [inputValue, setInputValue] = React.useState<string>();
const inputRef = useRef<HTMLInputElement>(null);
const options = React.useMemo(
@@ -30,8 +22,7 @@ export const SelectMultiSearchable = forwardRef<SelectHandle, SubProps>(
selectedItems: props.selectedItems,
onStateChange({ selectedItems: newSelectedItems, type }) {
switch (type) {
case useMultipleSelection.stateChangeTypes
.SelectedItemKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownDelete:
case useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem:
@@ -151,10 +142,7 @@ export const SelectMultiSearchable = forwardRef<SelectHandle, SubProps>(
}}
useMultipleSelectionReturn={useMultipleSelectionReturn}
>
<span
className="c__select__inner__value__input"
data-value={inputValue}
>
<span className="c__select__inner__value__input" data-value={inputValue}>
<input
{...inputProps}
onFocus={() => {
@@ -169,5 +157,4 @@ export const SelectMultiSearchable = forwardRef<SelectHandle, SubProps>(
</span>
</SelectMultiAux>
);
},
);
};

View File

@@ -1,4 +1,4 @@
import React, { forwardRef, useImperativeHandle, useRef } from "react";
import React, { useImperativeHandle, useRef } from "react";
import { useMultipleSelection, useSelect } from "downshift";
import {
getMultiOptionsFilter,
@@ -9,10 +9,9 @@ import {
optionsEqual,
optionToString,
} from ":/components/Forms/Select/mono-common";
import { Option, SelectHandle } from ":/components/Forms/Select/index";
import { Option } from ":/components/Forms/Select/index";
export const SelectMultiSimple = forwardRef<SelectHandle, SubProps>(
(props, ref) => {
export const SelectMultiSimple = ({ ref, ...props }: SubProps) => {
const isSelected = (option: Option) =>
!!props.selectedItems.find((selectedItem) =>
optionsEqual(selectedItem, option),
@@ -25,17 +24,14 @@ export const SelectMultiSimple = forwardRef<SelectHandle, SubProps>(
highlighted: isSelected(option),
}));
}
return props.options.filter(
getMultiOptionsFilter(props.selectedItems, ""),
);
return props.options.filter(getMultiOptionsFilter(props.selectedItems, ""));
}, [props.selectedItems]);
const useMultipleSelectionReturn = useMultipleSelection({
selectedItems: props.selectedItems,
onStateChange({ selectedItems: newSelectedItems, type }) {
switch (type) {
case useMultipleSelection.stateChangeTypes
.SelectedItemKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.SelectedItemKeyDownDelete:
case useMultipleSelection.stateChangeTypes.DropdownKeyDownBackspace:
case useMultipleSelection.stateChangeTypes.FunctionRemoveSelectedItem:
@@ -78,8 +74,7 @@ export const SelectMultiSimple = forwardRef<SelectHandle, SubProps>(
// Remove the item if it is already selected.
props.onSelectedItemsChange(
props.selectedItems.filter(
(selectedItem) =>
!optionsEqual(selectedItem, newSelectedItem),
(selectedItem) => !optionsEqual(selectedItem, newSelectedItem),
),
);
} else {
@@ -131,5 +126,4 @@ export const SelectMultiSimple = forwardRef<SelectHandle, SubProps>(
useMultipleSelectionReturn={useMultipleSelectionReturn}
/>
);
},
);
};

View File

@@ -1,20 +1,15 @@
import React, { forwardRef, useEffect } from "react";
import React, { useEffect } from "react";
import { optionToValue } from ":/components/Forms/Select/mono-common";
import { SelectMultiSearchable } from ":/components/Forms/Select/multi-searchable";
import { SelectMultiSimple } from ":/components/Forms/Select/multi-simple";
import { SubProps } from ":/components/Forms/Select/multi-common";
import {
Option,
SelectHandle,
SelectProps,
} from ":/components/Forms/Select/index";
import { Option, SelectProps } from ":/components/Forms/Select/index";
export type SelectMultiProps = Omit<SelectProps, "onChange"> & {
onChange?: (event: { target: { value: string[] } }) => void;
};
export const SelectMulti = forwardRef<SelectHandle, SelectMultiProps>(
(props, ref) => {
export const SelectMulti = (props: SelectMultiProps) => {
const getSelectedItemsFromProps = () => {
const valueToUse = props.defaultValue ?? props.value ?? [];
return props.options.filter((option) =>
@@ -58,7 +53,6 @@ export const SelectMulti = forwardRef<SelectHandle, SelectMultiProps>(
{...props}
selectedItems={selectedItems}
onSelectedItemsChange={onSelectedItemsChange}
ref={ref}
/>
) : (
<SelectMultiSimple
@@ -66,8 +60,6 @@ export const SelectMulti = forwardRef<SelectHandle, SelectMultiProps>(
{...props}
selectedItems={selectedItems}
onSelectedItemsChange={onSelectedItemsChange}
ref={ref}
/>
);
},
);
};

View File

@@ -7,7 +7,7 @@ import { SelectMultiAuxProps } from ":/components/Forms/Select/multi-common";
export interface SelectDropdownProps extends PropsWithChildren {
isOpen: boolean;
selectRef: React.RefObject<HTMLDivElement>;
selectRef: React.RefObject<HTMLDivElement | null>;
menuOptionsStyle?: SelectProps["menuOptionsStyle"];
downshiftReturn:
| SelectAuxProps["downshiftReturn"]

View File

@@ -1,4 +1,4 @@
import React, { InputHTMLAttributes, forwardRef } from "react";
import React, { InputHTMLAttributes, RefAttributes } from "react";
import classNames from "classnames";
import { Field, FieldProps } from ":/components/Forms/Field";
@@ -8,11 +8,16 @@ export type SwitchOnlyProps = {
};
export type SwitchProps = InputHTMLAttributes<HTMLInputElement> &
RefAttributes<HTMLInputElement> &
FieldProps &
SwitchOnlyProps;
export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
({ label, labelSide = "left", ...props }: SwitchProps, ref) => {
export const Switch = ({
label,
labelSide = "left",
ref,
...props
}: SwitchProps) => {
const {
compact,
className,
@@ -50,5 +55,4 @@ export const Switch = forwardRef<HTMLInputElement, SwitchProps>(
</Field>
</label>
);
},
);
};

View File

@@ -1,5 +1,5 @@
import React, {
forwardRef,
RefAttributes,
TextareaHTMLAttributes,
useEffect,
useRef,
@@ -11,14 +11,22 @@ import { LabelledBox } from ":/components/Forms/LabelledBox";
import { randomString } from ":/utils";
export type TextAreaProps = TextareaHTMLAttributes<HTMLTextAreaElement> &
RefAttributes<HTMLTextAreaElement> &
FieldProps & {
label?: string;
charCounter?: boolean;
charCounterMax?: number;
};
export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
({ label, id, defaultValue, charCounter, charCounterMax, ...props }, ref) => {
export const TextArea = ({
label,
id,
defaultValue,
charCounter,
charCounterMax,
ref,
...props
}: TextAreaProps) => {
const areaRef = useRef<HTMLTextAreaElement | null>(null);
const [inputFocus, setInputFocus] = useState(false);
const [value, setValue] = useState(defaultValue || props.value || "");
@@ -100,5 +108,4 @@ export const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
</div>
</Field>
);
},
);
};

View File

@@ -9,7 +9,7 @@ import classNames from "classnames";
import { useHandleClickOutside } from ":/hooks/useHandleClickOutside";
export type PopoverProps = PropsWithChildren & {
parentRef: RefObject<HTMLDivElement>;
parentRef: RefObject<HTMLDivElement | null>;
onClickOutside: () => void;
borderless?: boolean;
};
@@ -22,7 +22,7 @@ export const Popover = ({
}: PopoverProps) => {
const popoverRef = useRef<HTMLDivElement>(null);
useHandleClickOutside(parentRef, onClickOutside);
const timeout = useRef<ReturnType<typeof setTimeout>>();
const timeout = useRef<ReturnType<typeof setTimeout>>(null);
const [topPosition, setTopPosition] = useState<number | undefined>();
useLayoutEffect(() => {

View File

@@ -25,7 +25,7 @@ export interface ToastProps extends PropsWithChildren {
export const Toast = (props: ToastProps) => {
const [animateDisappear, setAnimateDisappear] = React.useState(false);
const container = useRef<HTMLDivElement>(null);
const disappearTimeout = useRef<NodeJS.Timeout>();
const disappearTimeout = useRef<NodeJS.Timeout>(null);
// Register a timeout to remove the toast after the duration.
useEffect(() => {
@@ -34,7 +34,7 @@ export const Toast = (props: ToastProps) => {
}
disappearTimeout.current = setTimeout(async () => {
setAnimateDisappear(true);
disappearTimeout.current = undefined;
disappearTimeout.current = null;
}, props.duration);
return () => {
if (disappearTimeout.current) {

View File

@@ -1,4 +1,9 @@
import React, { PropsWithChildren, ReactElement, ReactNode } from "react";
import React, {
PropsWithChildren,
ReactElement,
ReactNode,
RefObject,
} from "react";
import { OverlayArrow } from "react-aria-components";
import {
mergeProps,
@@ -72,7 +77,11 @@ export const Tooltip = ({
return (
<>
{React.cloneElement(
React.Children.toArray(props.children)[0] as ReactElement,
React.Children.toArray(props.children)[0] as ReactElement<
typeof useTooltipTriggerRes.triggerProps & {
ref: RefObject<ReactElement | null>;
}
>,
{
ref,
...useTooltipTriggerRes.triggerProps,

View File

@@ -1,7 +1,7 @@
import { RefObject, useEffect } from "react";
export const useHandleClickOutside = (
ref: RefObject<HTMLDivElement>,
ref: RefObject<HTMLDivElement | null>,
onClickOutside: any,
) => {
useEffect(() => {