Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjusted use of BaseComponent and always forwarding ref to it #867

Merged
merged 6 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 27 additions & 27 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
{
"configurations": [
{
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"connect": { "host": "localhost", "port": 5678 },
"pathMappings": [
"configurations": [
{
"localRoot": "${workspaceFolder}/backend_py/primary",
"remoteRoot": "/home/appuser/backend_py/primary"
"name": "Python: Remote Attach",
"type": "python",
"request": "attach",
"connect": { "host": "localhost", "port": 5678 },
"pathMappings": [
{
"localRoot": "${workspaceFolder}/backend_py/primary",
"remoteRoot": "/home/appuser/backend_py/primary"
}
]
},
{
"name": "TS: Launch Chrome and Attach",
"request": "launch",
"type": "chrome",
"webRoot": "${workspaceFolder}/frontend",
"url": "http://localhost:8080"
},
{
"name": "Run script",
"type": "node",
"cwd": "${workspaceFolder}/frontend",
"request": "launch",
"program": "${workspaceFolder}/frontend/scripts/add-api-suffix.cjs",
"args": ["--suffix", "'__api'", "--dir", "'src/api/'"]
}
]
},
{
"name": "TS: Launch Chrome and Attach",
"request": "launch",
"type": "chrome",
"webRoot": "${workspaceFolder}/frontend",
"url": "http://localhost:8080"
},
{
"name": "Run script",
"type": "node",
"cwd": "${workspaceFolder}/frontend",
"request": "launch",
"program": "${workspaceFolder}/frontend/scripts/add-api-suffix.cjs",
"args": ["--suffix", "'__api'", "--dir", "'src/api/'"]
}
]
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ export type RealizationPickerProps = {
onChange?: (realizationPickerSelection: RealizationPickerSelection) => void;
} & BaseComponentProps;

export const RealizationPicker: React.FC<RealizationPickerProps> = (props) => {
function RealizationPickerComponent(props: RealizationPickerProps, ref: React.ForwardedRef<HTMLDivElement>) {
const [selections, setSelections] = React.useState<Selection[]>(
props.initialRangeTags
? [...props.initialRangeTags].map((rangeTag) => {
Expand Down Expand Up @@ -410,7 +410,7 @@ export const RealizationPicker: React.FC<RealizationPickerProps> = (props) => {
const numSelectedRealizations = calcUniqueSelections(selections, props.validRealizations).length;

return (
<BaseComponent disabled={props.disabled}>
<BaseComponent ref={ref} disabled={props.disabled}>
<div className="relative border border-gray-300 rounded p-2 pr-6 min-h-[3rem]">
<ul className="flex flex-wrap items-center cursor-text gap-1 h-full" onPointerDown={handlePointerDown}>
{selections.map((selection) => (
Expand Down Expand Up @@ -450,6 +450,6 @@ export const RealizationPicker: React.FC<RealizationPickerProps> = (props) => {
</div>
</BaseComponent>
);
};
}

RealizationPicker.displayName = "RealizationPicker";
export const RealizationPicker = React.forwardRef(RealizationPickerComponent);
16 changes: 11 additions & 5 deletions frontend/src/lib/components/BaseComponent/baseComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,23 @@ import { resolveClassNames } from "@lib/utils/resolveClassNames";
export type BaseComponentProps = {
disabled?: boolean;
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
};

export const BaseComponent = React.forwardRef((props: BaseComponentProps, ref: React.ForwardedRef<HTMLDivElement>) => {
return (
<div
ref={ref}
className={resolveClassNames({
"opacity-50": props.disabled,
"pointer-events-none": props.disabled,
"cursor-default": props.disabled,
})}
className={resolveClassNames(
{
"opacity-50": props.disabled,
"pointer-events-none": props.disabled,
"cursor-default": props.disabled,
},
props.className ?? ""
)}
style={props.style}
>
{props.children}
</div>
Expand Down
17 changes: 11 additions & 6 deletions frontend/src/lib/components/Button/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ export type ButtonProps = {
endIcon?: React.ReactNode;
color?: "primary" | "danger" | "success" | "secondary";
size?: "small" | "medium" | "large";
buttonRef?: React.Ref<HTMLButtonElement>;
} & ButtonUnstyledProps;

export const Button = React.forwardRef((props: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) => {
const { disabled, variant, children, startIcon, endIcon, color, ...rest } = props;
function ButtonComponent(props: ButtonProps, ref: React.ForwardedRef<HTMLDivElement>) {
const { disabled, variant, children, startIcon, endIcon, color, buttonRef, ...rest } = props;

const internalRef = React.useRef<HTMLButtonElement>(null);
React.useImperativeHandle<HTMLButtonElement | null, HTMLButtonElement | null>(buttonRef, () => internalRef.current);

const classNames = [
"inline-flex",
"items-center",
Expand Down Expand Up @@ -73,10 +78,10 @@ export const Button = React.forwardRef((props: ButtonProps, ref: React.Forwarded
);

return (
<BaseComponent disabled={disabled}>
<BaseComponent disabled={disabled} ref={ref}>
<ButtonUnstyled
{...rest}
ref={ref}
ref={buttonRef}
slotProps={{
root: {
className: resolveClassNames(...classNames),
Expand All @@ -87,6 +92,6 @@ export const Button = React.forwardRef((props: ButtonProps, ref: React.Forwarded
</ButtonUnstyled>
</BaseComponent>
);
});
}

Button.displayName = "Button";
export const Button = React.forwardRef(ButtonComponent);
58 changes: 28 additions & 30 deletions frontend/src/lib/components/Checkbox/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type CheckboxProps = {
onChange?: (event: React.ChangeEvent<HTMLInputElement>, checked: boolean) => void;
} & BaseComponentProps;

export const Checkbox: React.FC<CheckboxProps> = (props) => {
function CheckboxComponent(props: CheckboxProps, ref: React.ForwardedRef<HTMLDivElement>) {
const { onChange } = props;

const [checked, setChecked] = React.useState<boolean>(props.checked ?? false);
Expand All @@ -36,36 +36,34 @@ export const Checkbox: React.FC<CheckboxProps> = (props) => {
);

return (
<BaseComponent disabled={props.disabled}>
<div className="flex gap-2 items-center">
<input
id={props.id ?? id.current}
name={props.name}
ref={(el) => el && (el.indeterminate = props.indeterminate ?? false)}
type="checkbox"
checked={checked}
onChange={handleChange}
className={resolveClassNames(
"w-4",
"h-4",
"text-blue-600",
"border-gray-300",
"rounded",
"focus:ring-blue-500",
"cursor-pointer"
)}
/>
{props.label && (
<label
htmlFor={props.id ?? id.current}
className={resolveClassNames("block", "text-gray-900", "cursor-pointer")}
>
{props.label}
</label>
<BaseComponent ref={ref} disabled={props.disabled} className="flex gap-2 items-center">
<input
id={props.id ?? id.current}
name={props.name}
ref={(el) => el && (el.indeterminate = props.indeterminate ?? false)}
type="checkbox"
checked={checked}
onChange={handleChange}
className={resolveClassNames(
"w-4",
"h-4",
"text-blue-600",
"border-gray-300",
"rounded",
"focus:ring-blue-500",
"cursor-pointer"
)}
</div>
/>
{props.label && (
<label
htmlFor={props.id ?? id.current}
className={resolveClassNames("block", "text-gray-900", "cursor-pointer")}
>
{props.label}
</label>
)}
</BaseComponent>
);
};
}

Checkbox.displayName = "Checkbox";
export const Checkbox = React.forwardRef(CheckboxComponent);
46 changes: 22 additions & 24 deletions frontend/src/lib/components/CollapsibleGroup/collapsibleGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type CollapsibleGroupProps = {
onChange?: (expanded: boolean) => void;
} & BaseComponentProps;

export const CollapsibleGroup: React.FC<CollapsibleGroupProps> = (props) => {
function CollapsibleGroupComponent(props: CollapsibleGroupProps, ref: React.ForwardedRef<HTMLDivElement>) {
const [expanded, setExpanded] = React.useState(props.expanded ?? false);
const [prevExpanded, setPrevExpanded] = React.useState<boolean | undefined>(props.expanded);

Expand All @@ -28,30 +28,28 @@ export const CollapsibleGroup: React.FC<CollapsibleGroupProps> = (props) => {
};

return (
<BaseComponent disabled={props.disabled}>
<div className="shadow">
<div
className={resolveClassNames(
"flex flex-row justify-between items-center bg-slate-100 cursor-pointer p-2 select-none gap-2",
{ "border-b": expanded }
)}
onClick={handleClick}
title={expanded ? "Collapse" : "Expand"}
>
{props.icon && React.cloneElement(props.icon, { className: "w-4 h-4" })}
<h3 className="text-sm font-semibold flex-grow leading-none">{props.title}</h3>
{expanded ? <ExpandLess fontSize="small" /> : <ExpandMore fontSize="small" />}
</div>
<div
className={resolveClassNames("p-2", {
hidden: !expanded,
})}
>
{props.children}
</div>
<BaseComponent ref={ref} disabled={props.disabled} className="shadow">
<div
className={resolveClassNames(
"flex flex-row justify-between items-center bg-slate-100 cursor-pointer p-2 select-none gap-2",
{ "border-b": expanded }
)}
onClick={handleClick}
title={expanded ? "Collapse" : "Expand"}
>
{props.icon && React.cloneElement(props.icon, { className: "w-4 h-4" })}
<h3 className="text-sm font-semibold flex-grow leading-none">{props.title}</h3>
{expanded ? <ExpandLess fontSize="small" /> : <ExpandMore fontSize="small" />}
</div>
<div
className={resolveClassNames("p-2", {
hidden: !expanded,
})}
>
{props.children}
</div>
</BaseComponent>
);
};
}

CollapsibleGroup.displayName = "CollapsibleGroup";
export const CollapsibleGroup = React.forwardRef(CollapsibleGroupComponent);
12 changes: 7 additions & 5 deletions frontend/src/lib/components/Dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function makeOptionListItems<TValue>(options: DropdownOption<TValue>[]): OptionL
return optionsWithSeperators;
}

export function Dropdown<TValue = string>(props: DropdownProps<TValue>) {
function DropdownComponent<TValue = string>(props: DropdownProps<TValue>, ref: React.ForwardedRef<HTMLDivElement>) {
const { onChange } = props;

const valueWithDefault = props.value ?? null;
Expand All @@ -103,7 +103,7 @@ export function Dropdown<TValue = string>(props: DropdownProps<TValue>) {
const [startIndex, setStartIndex] = React.useState<number>(0);
const [keyboardFocus, setKeyboardFocus] = React.useState<boolean>(false);

const inputRef = React.useRef<HTMLInputElement>(null);
const inputRef = React.useRef<HTMLDivElement>(null);
const dropdownRef = React.useRef<HTMLDivElement>(null);
const debounceTimerRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);

Expand Down Expand Up @@ -482,7 +482,7 @@ export function Dropdown<TValue = string>(props: DropdownProps<TValue>) {
}

return (
<BaseComponent disabled={props.disabled}>
<BaseComponent ref={ref} disabled={props.disabled}>
<div style={{ width: props.width }} id={props.wrapperId} className="flex hover input-comp rounded">
<div className="flex-grow">
<Input
Expand Down Expand Up @@ -569,6 +569,10 @@ export function Dropdown<TValue = string>(props: DropdownProps<TValue>) {
);
}

export const Dropdown = React.forwardRef(DropdownComponent) as <TValue = string>(
props: DropdownProps<TValue> & { ref?: React.Ref<HTMLDivElement> }
) => React.ReactElement;

type OptionProps<TValue> = DropdownOption<TValue> & {
isSelected: boolean;
isFocused: boolean;
Expand Down Expand Up @@ -613,5 +617,3 @@ function SeparatorItem(props: { text: string }): React.ReactNode {
</div>
);
}

Dropdown.displayName = "Dropdown";
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import React from "react";

import { Button, ButtonProps } from "../Button/button";

export type HoldPressedIntervalCallbackButtonProps = ButtonProps & {
export type HoldPressedIntervalCallbackButtonProps = Omit<ButtonProps, "ref"> & {
onHoldPressedIntervalCallback: () => void;
};

export function HoldPressedIntervalCallbackButton(props: HoldPressedIntervalCallbackButtonProps): React.ReactNode {
function HoldPressedIntervalCallbackButtonComponent(
props: HoldPressedIntervalCallbackButtonProps,
ref: React.ForwardedRef<HTMLDivElement>
): React.ReactNode {
const { onHoldPressedIntervalCallback, ...other } = props;

const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
Expand Down Expand Up @@ -39,10 +42,13 @@ export function HoldPressedIntervalCallbackButton(props: HoldPressedIntervalCall
return (
<Button
{...other}
ref={ref}
onClick={handleClick}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerLeave={handlePointerUp}
/>
);
}

export const HoldPressedIntervalCallbackButton = React.forwardRef(HoldPressedIntervalCallbackButtonComponent);
Loading
Loading