mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-08 22:38:05 -05:00
fix(ds): add test id support (#9904)
This commit is contained in:
@@ -5,13 +5,13 @@ import {
|
|||||||
type AccordionItemPropsPublic,
|
type AccordionItemPropsPublic,
|
||||||
} from "./components/AccordionItem";
|
} from "./components/AccordionItem";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
|
|
||||||
export type AccordionProps = HTMLProps<"div"> & {
|
export type AccordionProps = HTMLProps<"div"> & {
|
||||||
expandedKeys: string[];
|
expandedKeys: string[];
|
||||||
type?: "multi" | "single";
|
type?: "multi" | "single";
|
||||||
setExpandedKeys(keys: string[]): void;
|
setExpandedKeys(keys: string[]): void;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
type AccordionType = React.FC<PropsWithChildren<AccordionProps>> & {
|
type AccordionType = React.FC<PropsWithChildren<AccordionProps>> & {
|
||||||
Item: React.FC<PropsWithChildren<AccordionItemPropsPublic>>;
|
Item: React.FC<PropsWithChildren<AccordionItemPropsPublic>>;
|
||||||
@@ -23,6 +23,7 @@ const Accordion: AccordionType = ({
|
|||||||
setExpandedKeys,
|
setExpandedKeys,
|
||||||
children,
|
children,
|
||||||
type = "multi",
|
type = "multi",
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
@@ -54,6 +55,7 @@ const Accordion: AccordionType = ({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn("flex flex-col gap-y-2.5 items-start", className)}
|
className={cn("flex flex-col gap-y-2.5 items-start", className)}
|
||||||
|
data-testid={testId}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{items}
|
{items}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import type { HTMLProps } from "../../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||||
import { cn } from "../../../shared/utils/cn";
|
import { cn } from "../../../shared/utils/cn";
|
||||||
import { Icon, type IconProps } from "../../icon/Icon";
|
import { Icon, type IconProps } from "../../icon/Icon";
|
||||||
import { Typography } from "../../typography/Typography";
|
import { Typography } from "../../typography/Typography";
|
||||||
@@ -10,7 +10,7 @@ export type AccordionHeaderProps = Omit<
|
|||||||
> & {
|
> & {
|
||||||
icon: IconProps["icon"];
|
icon: IconProps["icon"];
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const AccordionHeader = ({
|
export const AccordionHeader = ({
|
||||||
className,
|
className,
|
||||||
@@ -43,7 +43,8 @@ export const AccordionHeader = ({
|
|||||||
// hover modifier
|
// hover modifier
|
||||||
"data-[expanded=true]:hover:bg-light-neutral-900",
|
"data-[expanded=true]:hover:bg-light-neutral-900",
|
||||||
// focus modifier
|
// focus modifier
|
||||||
"data-[expanded=false]:focus:bg-light-neutral-900"
|
"data-[expanded=false]:focus:bg-light-neutral-900",
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon icon={icon} className={cn(iconCss, "w-6 h-6")} />
|
<Icon icon={icon} className={cn(iconCss, "w-6 h-6")} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import type { HTMLProps } from "../../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||||
import { cn } from "../../../shared/utils/cn";
|
import { cn } from "../../../shared/utils/cn";
|
||||||
import { type IconProps } from "../../icon/Icon";
|
import { type IconProps } from "../../icon/Icon";
|
||||||
import { AccordionHeader } from "./AccordionHeader";
|
import { AccordionHeader } from "./AccordionHeader";
|
||||||
@@ -11,10 +11,10 @@ export type AccordionItemProps = HTMLProps<"div"> & {
|
|||||||
value: string;
|
value: string;
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
onExpandedChange(value: boolean): void;
|
onExpandedChange(value: boolean): void;
|
||||||
};
|
} & BaseProps;
|
||||||
export type AccordionItemPropsPublic = Omit<
|
export type AccordionItemPropsPublic = Omit<
|
||||||
AccordionItemProps,
|
AccordionItemProps,
|
||||||
"expanded" | "onExpandedChange"
|
"expanded" | "onExpandedChange" | "className" | "style" | "testId"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const AccordionItem = ({
|
export const AccordionItem = ({
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import type { HTMLProps } from "../../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../../shared/types";
|
||||||
import { cn } from "../../../shared/utils/cn";
|
import { cn } from "../../../shared/utils/cn";
|
||||||
|
|
||||||
export type AccordionPanelProps = Omit<HTMLProps<"div">, "aria-expanded"> & {
|
export type AccordionPanelProps = Omit<HTMLProps<"div">, "aria-expanded"> & {
|
||||||
expanded: boolean;
|
expanded: boolean;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const AccordionPanel = ({
|
export const AccordionPanel = ({
|
||||||
className,
|
className,
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import {
|
|||||||
type PropsWithChildren,
|
type PropsWithChildren,
|
||||||
type ReactElement,
|
type ReactElement,
|
||||||
} from "react";
|
} from "react";
|
||||||
import type { ComponentVariant, HTMLProps } from "../../shared/types";
|
import type {
|
||||||
|
BaseProps,
|
||||||
|
ComponentVariant,
|
||||||
|
HTMLProps,
|
||||||
|
} from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import { buttonStyles, useAndApplyBoldTextWidth } from "./utils";
|
import { buttonStyles, useAndApplyBoldTextWidth } from "./utils";
|
||||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||||
@@ -15,7 +19,7 @@ export type ButtonProps = Omit<HTMLProps<"button">, "aria-disabled"> & {
|
|||||||
variant?: ComponentVariant;
|
variant?: ComponentVariant;
|
||||||
start?: ReactElement<HTMLProps<"svg">>;
|
start?: ReactElement<HTMLProps<"svg">>;
|
||||||
end?: ReactElement<HTMLProps<"svg">>;
|
end?: ReactElement<HTMLProps<"svg">>;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const Button = ({
|
export const Button = ({
|
||||||
size = "small",
|
size = "small",
|
||||||
@@ -24,6 +28,7 @@ export const Button = ({
|
|||||||
children,
|
children,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<ButtonProps>) => {
|
}: PropsWithChildren<ButtonProps>) => {
|
||||||
const buttonClassNames = buttonStyles[variant];
|
const buttonClassNames = buttonStyles[variant];
|
||||||
@@ -35,6 +40,7 @@ export const Button = ({
|
|||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
aria-disabled={props.disabled ? "true" : "false"}
|
aria-disabled={props.disabled ? "true" : "false"}
|
||||||
|
data-testid={testId}
|
||||||
className={cn(
|
className={cn(
|
||||||
size === "small" ? "px-2 py-3 min-w-32" : "px-3 py-4 min-w-64",
|
size === "small" ? "px-2 py-3 min-w-32" : "px-3 py-4 min-w-64",
|
||||||
"flex flex-row items-center gap-x-8",
|
"flex flex-row items-center gap-x-8",
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export const Checkbox = ({
|
|||||||
disabled && "cursor-not-allowed",
|
disabled && "cursor-not-allowed",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
data-testid={testId}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
id={id}
|
id={id}
|
||||||
@@ -42,7 +43,6 @@ export const Checkbox = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="sr-only peer"
|
className="sr-only peer"
|
||||||
data-testid={testId}
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type PropsWithChildren } from "react";
|
import { type PropsWithChildren } from "react";
|
||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import { Typography } from "../typography/Typography";
|
import { Typography } from "../typography/Typography";
|
||||||
import { chipStyles, type ChipColor, type ChipVariant } from "./utils";
|
import { chipStyles, type ChipColor, type ChipVariant } from "./utils";
|
||||||
@@ -7,18 +7,20 @@ import { chipStyles, type ChipColor, type ChipVariant } from "./utils";
|
|||||||
export type ChipProps = Omit<HTMLProps<"div">, "label"> & {
|
export type ChipProps = Omit<HTMLProps<"div">, "label"> & {
|
||||||
color?: ChipColor;
|
color?: ChipColor;
|
||||||
variant?: ChipVariant;
|
variant?: ChipVariant;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const Chip = ({
|
export const Chip = ({
|
||||||
className,
|
className,
|
||||||
color = "gray",
|
color = "gray",
|
||||||
variant = "pill",
|
variant = "pill",
|
||||||
children,
|
children,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<ChipProps>) => {
|
}: PropsWithChildren<ChipProps>) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
{...props}
|
{...props}
|
||||||
|
data-testid={testId}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-row items-center px-1.5 py-1",
|
"flex flex-row items-center px-1.5 py-1",
|
||||||
variant === "pill" ? "rounded-full" : "rounded-lg",
|
variant === "pill" ? "rounded-full" : "rounded-lg",
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
import {
|
import { useId, type PropsWithChildren } from "react";
|
||||||
useEffect,
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
useId,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
type PropsWithChildren,
|
|
||||||
} from "react";
|
|
||||||
import type { HTMLProps } from "../../shared/types";
|
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import { Icon } from "../icon/Icon";
|
import { Icon } from "../icon/Icon";
|
||||||
import { createPortal } from "react-dom";
|
|
||||||
import {
|
import {
|
||||||
FloatingOverlay,
|
FloatingOverlay,
|
||||||
FloatingPortal,
|
FloatingPortal,
|
||||||
@@ -24,13 +17,14 @@ import { FocusTrap } from "focus-trap-react";
|
|||||||
export type DialogProps = HTMLProps<"div"> & {
|
export type DialogProps = HTMLProps<"div"> & {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange(value: boolean): void;
|
onOpenChange(value: boolean): void;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const Dialog = ({
|
export const Dialog = ({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
|
testId,
|
||||||
}: PropsWithChildren<DialogProps>) => {
|
}: PropsWithChildren<DialogProps>) => {
|
||||||
const id = useId();
|
const id = useId();
|
||||||
|
|
||||||
@@ -80,6 +74,7 @@ export const Dialog = ({
|
|||||||
aria-describedby={`${id}-description`}
|
aria-describedby={`${id}-description`}
|
||||||
{...getFloatingProps()}
|
{...getFloatingProps()}
|
||||||
style={styles}
|
style={styles}
|
||||||
|
data-testid={testId}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-4xl border-1 border-light-neutral-500 outline-none",
|
"rounded-4xl border-1 border-light-neutral-500 outline-none",
|
||||||
"transition-all will-change-transform",
|
"transition-all will-change-transform",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
|
|
||||||
export type DividerProps = Omit<
|
export type DividerProps = Omit<
|
||||||
@@ -6,11 +6,12 @@ export type DividerProps = Omit<
|
|||||||
"role" | "aria-orientation"
|
"role" | "aria-orientation"
|
||||||
> & {
|
> & {
|
||||||
type?: "horizontal" | "vertical";
|
type?: "horizontal" | "vertical";
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const Divider = ({
|
export const Divider = ({
|
||||||
type = "horizontal",
|
type = "horizontal",
|
||||||
className,
|
className,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: DividerProps) => {
|
}: DividerProps) => {
|
||||||
return (
|
return (
|
||||||
@@ -23,6 +24,7 @@ export const Divider = ({
|
|||||||
)}
|
)}
|
||||||
role="separator"
|
role="separator"
|
||||||
aria-orientation={type}
|
aria-orientation={type}
|
||||||
|
data-testid={testId}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
type ReactElement,
|
type ReactElement,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import { Typography } from "../typography/Typography";
|
import { Typography } from "../typography/Typography";
|
||||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||||
@@ -19,7 +19,7 @@ export type InputProps = Omit<
|
|||||||
end?: ReactElement<HTMLProps<"svg">>;
|
end?: ReactElement<HTMLProps<"svg">>;
|
||||||
error?: string;
|
error?: string;
|
||||||
hint?: string;
|
hint?: string;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const Input = ({
|
export const Input = ({
|
||||||
className,
|
className,
|
||||||
@@ -34,6 +34,7 @@ export const Input = ({
|
|||||||
type,
|
type,
|
||||||
hint,
|
hint,
|
||||||
readOnly,
|
readOnly,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: InputProps) => {
|
}: InputProps) => {
|
||||||
const generatedId = useId();
|
const generatedId = useId();
|
||||||
@@ -45,65 +46,64 @@ export const Input = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<label
|
||||||
<label
|
htmlFor={id}
|
||||||
htmlFor={id}
|
data-testid={testId}
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col gap-y-2",
|
||||||
|
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
||||||
|
{label}
|
||||||
|
</Typography.Text>
|
||||||
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col gap-y-2",
|
"flex flex-row items-center gap-x-2.5",
|
||||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
"py-4.25 px-4",
|
||||||
|
"border-light-neutral-500 border-1 rounded-2xl",
|
||||||
|
// base
|
||||||
|
"bg-light-neutral-950",
|
||||||
|
// hover modifier
|
||||||
|
"hover:bg-light-neutral-900",
|
||||||
|
// focus modifier
|
||||||
|
"focus-within:bg-light-neutral-900",
|
||||||
|
// error state
|
||||||
|
error && " border-red-400 bg-light-neutral-970",
|
||||||
|
readOnly &&
|
||||||
|
"bg-light-neutral-985 border-none hover:bg-light-neutral-985 cursor-auto",
|
||||||
|
// disabled modifier
|
||||||
|
disabled && "hover:bg-light-neutral-950"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
{cloneIcon(start, {
|
||||||
{label}
|
className: iconCss,
|
||||||
</Typography.Text>
|
})}
|
||||||
<div
|
<input
|
||||||
|
id={id}
|
||||||
|
type={type}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
disabled={disabled}
|
||||||
|
aria-invalid={error ? "true" : "false"}
|
||||||
|
readOnly={readOnly}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-row items-center gap-x-2.5",
|
"flex-1 outline-none caret-primary-500 text-white",
|
||||||
"py-4.25 px-4",
|
"placeholder:text-light-neutral-300",
|
||||||
"border-light-neutral-500 border-1 rounded-2xl",
|
error && "text-red-400"
|
||||||
// base
|
|
||||||
"bg-light-neutral-950",
|
|
||||||
// hover modifier
|
|
||||||
"hover:bg-light-neutral-900",
|
|
||||||
// focus modifier
|
|
||||||
"focus-within:bg-light-neutral-900",
|
|
||||||
// error state
|
|
||||||
error && " border-red-400 bg-light-neutral-970",
|
|
||||||
readOnly &&
|
|
||||||
"bg-light-neutral-985 border-none hover:bg-light-neutral-985 cursor-auto",
|
|
||||||
// disabled modifier
|
|
||||||
disabled && "hover:bg-light-neutral-950"
|
|
||||||
)}
|
)}
|
||||||
>
|
{...props}
|
||||||
{cloneIcon(start, {
|
/>
|
||||||
className: iconCss,
|
{cloneIcon(end, {
|
||||||
})}
|
className: iconCss,
|
||||||
<input
|
})}
|
||||||
id={id}
|
</div>
|
||||||
type={type}
|
<Typography.Text
|
||||||
value={value}
|
fontSize="xs"
|
||||||
onChange={onChange}
|
className={cn("text-light-neutral-600 ml-4", error && "text-red-400")}
|
||||||
disabled={disabled}
|
>
|
||||||
aria-invalid={error ? "true" : "false"}
|
{error ?? hint}
|
||||||
readOnly={readOnly}
|
</Typography.Text>
|
||||||
className={cn(
|
</label>
|
||||||
"flex-1 outline-none caret-primary-500 text-white",
|
|
||||||
"placeholder:text-light-neutral-300",
|
|
||||||
error && "text-red-400"
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
{cloneIcon(end, {
|
|
||||||
className: iconCss,
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
<Typography.Text
|
|
||||||
fontSize="xs"
|
|
||||||
className={cn("text-light-neutral-600 ml-4", error && "text-red-400")}
|
|
||||||
>
|
|
||||||
{error ?? hint}
|
|
||||||
</Typography.Text>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { type PropsWithChildren, type ReactElement } from "react";
|
import { type PropsWithChildren, type ReactElement } from "react";
|
||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import { cloneIcon } from "../../shared/utils/clone-icon";
|
import { cloneIcon } from "../../shared/utils/clone-icon";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
@@ -16,7 +16,7 @@ export type InteractiveChipProps = Omit<
|
|||||||
chipType?: InteractiveChipType;
|
chipType?: InteractiveChipType;
|
||||||
start?: ReactElement<HTMLProps<"svg">>;
|
start?: ReactElement<HTMLProps<"svg">>;
|
||||||
end?: ReactElement<HTMLProps<"svg">>;
|
end?: ReactElement<HTMLProps<"svg">>;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const InteractiveChip = ({
|
export const InteractiveChip = ({
|
||||||
chipType = "elevated",
|
chipType = "elevated",
|
||||||
@@ -24,6 +24,7 @@ export const InteractiveChip = ({
|
|||||||
children,
|
children,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<InteractiveChipProps>) => {
|
}: PropsWithChildren<InteractiveChipProps>) => {
|
||||||
const buttonClassNames = buttonStyles[chipType];
|
const buttonClassNames = buttonStyles[chipType];
|
||||||
@@ -34,6 +35,7 @@ export const InteractiveChip = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
{...props}
|
{...props}
|
||||||
|
data-testid={testId}
|
||||||
aria-disabled={props.disabled ? "true" : "false"}
|
aria-disabled={props.disabled ? "true" : "false"}
|
||||||
className={cn(
|
className={cn(
|
||||||
"px-1.5 py-1 min-w-32",
|
"px-1.5 py-1 min-w-32",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useId } from "react";
|
import { useId } from "react";
|
||||||
import type { HTMLProps, IOption } from "../../shared/types";
|
import type { BaseProps, HTMLProps, IOption } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import { RadioOption } from "./RadioOption";
|
import { RadioOption } from "./RadioOption";
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ export type RadioGroupProps<T extends string> = Omit<
|
|||||||
value: T;
|
value: T;
|
||||||
onChange: (option: IOption<T>) => void;
|
onChange: (option: IOption<T>) => void;
|
||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const RadioGroup = <T extends string>({
|
export const RadioGroup = <T extends string>({
|
||||||
value,
|
value,
|
||||||
@@ -21,13 +21,17 @@ export const RadioGroup = <T extends string>({
|
|||||||
labelClassName,
|
labelClassName,
|
||||||
disabled,
|
disabled,
|
||||||
id: propId,
|
id: propId,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: RadioGroupProps<T>) => {
|
}: RadioGroupProps<T>) => {
|
||||||
const generatedId = useId();
|
const generatedId = useId();
|
||||||
const id = propId ?? generatedId;
|
const id = propId ?? generatedId;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex flex-col gap-y-1", className)}>
|
<div
|
||||||
|
data-testid={testId}
|
||||||
|
className={cn("flex flex-col gap-y-1", className)}
|
||||||
|
>
|
||||||
{options.map((o) => (
|
{options.map((o) => (
|
||||||
<RadioOption
|
<RadioOption
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useId } from "react";
|
import { useId } from "react";
|
||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
import { Typography } from "../typography/Typography";
|
import { Typography } from "../typography/Typography";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ type RadioOptionProps = Omit<HTMLProps<"input">, "id" | "checked"> & {
|
|||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
labelClassName?: string;
|
labelClassName?: string;
|
||||||
id: string;
|
id: string;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const RadioOption = ({
|
export const RadioOption = ({
|
||||||
className,
|
className,
|
||||||
@@ -17,6 +17,7 @@ export const RadioOption = ({
|
|||||||
id: propId,
|
id: propId,
|
||||||
disabled,
|
disabled,
|
||||||
onChange,
|
onChange,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: RadioOptionProps) => {
|
}: RadioOptionProps) => {
|
||||||
const generatedId = useId();
|
const generatedId = useId();
|
||||||
@@ -25,6 +26,7 @@ export const RadioOption = ({
|
|||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
|
data-testid={testId}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-x-4",
|
"flex items-center gap-x-4",
|
||||||
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
|
|
||||||
export type ScrollableMode = "auto" | "scroll";
|
export type ScrollableMode = "auto" | "scroll";
|
||||||
@@ -8,7 +8,7 @@ export type ScrollableType = "horizontal" | "vertical";
|
|||||||
export type ScrollableProps = HTMLProps<"div"> & {
|
export type ScrollableProps = HTMLProps<"div"> & {
|
||||||
mode?: ScrollableMode;
|
mode?: ScrollableMode;
|
||||||
type?: ScrollableType;
|
type?: ScrollableType;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
const scrollableStyles: Record<
|
const scrollableStyles: Record<
|
||||||
ScrollableType,
|
ScrollableType,
|
||||||
@@ -30,11 +30,13 @@ export const Scrollable = ({
|
|||||||
tabIndex,
|
tabIndex,
|
||||||
mode = "auto",
|
mode = "auto",
|
||||||
type = "vertical",
|
type = "vertical",
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<ScrollableProps>) => {
|
}: PropsWithChildren<ScrollableProps>) => {
|
||||||
const style = scrollableStyles[type][mode];
|
const style = scrollableStyles[type][mode];
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
data-testid={testId}
|
||||||
tabIndex={tabIndex ?? 0}
|
tabIndex={tabIndex ?? 0}
|
||||||
{...props}
|
{...props}
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useId, useMemo, useState } from "react";
|
import { useId, useMemo, useState } from "react";
|
||||||
import type { HTMLProps, IOption } from "../../shared/types";
|
import type { BaseProps, HTMLProps, IOption } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import ReactSelect, { createFilter } from "react-select";
|
import ReactSelect, { createFilter } from "react-select";
|
||||||
import { Typography } from "../typography/Typography";
|
import { Typography } from "../typography/Typography";
|
||||||
@@ -16,7 +16,7 @@ export type SelectProps<T> = Omit<HTMLProps<"input">, "value" | "onChange"> & {
|
|||||||
options: IOption<T>[];
|
options: IOption<T>[];
|
||||||
noOptionsText?: string;
|
noOptionsText?: string;
|
||||||
onChange(value: IOption<T> | null): void;
|
onChange(value: IOption<T> | null): void;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const Select = <T extends string>(props: SelectProps<T>) => {
|
export const Select = <T extends string>(props: SelectProps<T>) => {
|
||||||
const {
|
const {
|
||||||
@@ -32,6 +32,8 @@ export const Select = <T extends string>(props: SelectProps<T>) => {
|
|||||||
onChange,
|
onChange,
|
||||||
readOnly,
|
readOnly,
|
||||||
noOptionsText,
|
noOptionsText,
|
||||||
|
className,
|
||||||
|
testId,
|
||||||
} = props;
|
} = props;
|
||||||
const [inputValue, setInputValue] = useState("");
|
const [inputValue, setInputValue] = useState("");
|
||||||
const generatedId = useId();
|
const generatedId = useId();
|
||||||
@@ -50,10 +52,12 @@ export const Select = <T extends string>(props: SelectProps<T>) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
|
data-testid={testId}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex flex-col gap-y-2",
|
"flex flex-col gap-y-2",
|
||||||
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"
|
disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer",
|
||||||
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
<Typography.Text fontSize="s" className="text-light-neutral-200">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
@@ -15,7 +15,11 @@ export type IndeterminateSpinnerProps = BaseSpinnerProps & {
|
|||||||
value?: never;
|
value?: never;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SpinnerProps = DeterminateSpinnerProps | IndeterminateSpinnerProps;
|
export type SpinnerProps = (
|
||||||
|
| DeterminateSpinnerProps
|
||||||
|
| IndeterminateSpinnerProps
|
||||||
|
) &
|
||||||
|
BaseProps;
|
||||||
|
|
||||||
const SIZE = 48;
|
const SIZE = 48;
|
||||||
const STROKE_WIDTH = 6;
|
const STROKE_WIDTH = 6;
|
||||||
@@ -26,6 +30,7 @@ export const Spinner = ({
|
|||||||
value = 10,
|
value = 10,
|
||||||
determinate = false,
|
determinate = false,
|
||||||
className,
|
className,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: SpinnerProps) => {
|
}: SpinnerProps) => {
|
||||||
const offset = useMemo(
|
const offset = useMemo(
|
||||||
@@ -34,7 +39,13 @@ export const Spinner = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg width={SIZE} height={SIZE} className={className} {...props}>
|
<svg
|
||||||
|
data-testid={testId}
|
||||||
|
width={SIZE}
|
||||||
|
height={SIZE}
|
||||||
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
<circle
|
<circle
|
||||||
cx={SIZE / 2}
|
cx={SIZE / 2}
|
||||||
cy={SIZE / 2}
|
cy={SIZE / 2}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
type PropsWithChildren,
|
type PropsWithChildren,
|
||||||
type ReactElement,
|
type ReactElement,
|
||||||
} from "react";
|
} from "react";
|
||||||
import type { HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
@@ -16,13 +16,13 @@ import { useElementOverflow } from "./hooks/use-element-overflow";
|
|||||||
import { useElementScroll } from "./hooks/use-element-scroll";
|
import { useElementScroll } from "./hooks/use-element-scroll";
|
||||||
import { TabScroller } from "./components/TabScroller";
|
import { TabScroller } from "./components/TabScroller";
|
||||||
|
|
||||||
export type TabsProps = HTMLProps<"div">;
|
export type TabsProps = HTMLProps<"div"> & BaseProps;
|
||||||
|
|
||||||
type TabsType = React.FC<PropsWithChildren<TabsProps>> & {
|
type TabsType = React.FC<PropsWithChildren<TabsProps>> & {
|
||||||
Item: React.FC<PropsWithChildren<TabItemPropsPublic>>;
|
Item: React.FC<PropsWithChildren<TabItemPropsPublic>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Tabs: TabsType = ({ children, ...props }) => {
|
const Tabs: TabsType = ({ children, className, testId, ...props }) => {
|
||||||
const [activeIndex, setActiveIndex] = useState(0);
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const tabListRef = useRef<HTMLDivElement>(null);
|
const tabListRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -55,7 +55,7 @@ const Tabs: TabsType = ({ children, ...props }) => {
|
|||||||
}) ?? [];
|
}) ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn("w-full")}>
|
<div data-testid={testId} className={cn("w-full", className)}>
|
||||||
<div className={cn("flex flex-row items-stretch")} ref={containerRef}>
|
<div className={cn("flex flex-row items-stretch")} ref={containerRef}>
|
||||||
{canScrollLeft && isOverflowing && (
|
{canScrollLeft && isOverflowing && (
|
||||||
<TabScroller onScroll={scrollLeft} position="left" />
|
<TabScroller onScroll={scrollLeft} position="left" />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Typography } from "../typography/Typography";
|
|||||||
import { toastStyles } from "./utils";
|
import { toastStyles } from "./utils";
|
||||||
import type { JSX } from "react";
|
import type { JSX } from "react";
|
||||||
import { invariant } from "../../shared/utils/invariant";
|
import { invariant } from "../../shared/utils/invariant";
|
||||||
|
import type { BaseProps } from "../../shared/types";
|
||||||
|
|
||||||
type RenderContentProps = {
|
type RenderContentProps = {
|
||||||
onDismiss: () => void;
|
onDismiss: () => void;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
} from "@floating-ui/react";
|
} from "@floating-ui/react";
|
||||||
import { useRef, useState, type PropsWithChildren } from "react";
|
import { useRef, useState, type PropsWithChildren } from "react";
|
||||||
import { Typography } from "../typography/Typography";
|
import { Typography } from "../typography/Typography";
|
||||||
|
import type { BaseProps } from "../../shared/types";
|
||||||
|
|
||||||
type ControlledTooltipProps = {
|
type ControlledTooltipProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -33,10 +34,9 @@ type TooltipTriggerType = "click" | "hover";
|
|||||||
type BaseTooltipProps = {
|
type BaseTooltipProps = {
|
||||||
text: string;
|
text: string;
|
||||||
withArrow?: boolean;
|
withArrow?: boolean;
|
||||||
className?: string;
|
|
||||||
placement?: UseFloatingOptions["placement"];
|
placement?: UseFloatingOptions["placement"];
|
||||||
trigger?: TooltipTriggerType;
|
trigger?: TooltipTriggerType;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export type TooltipProps = BaseTooltipProps &
|
export type TooltipProps = BaseTooltipProps &
|
||||||
(ControlledTooltipProps | UncontrolledTooltipProps);
|
(ControlledTooltipProps | UncontrolledTooltipProps);
|
||||||
@@ -50,6 +50,7 @@ export const Tooltip = ({
|
|||||||
open,
|
open,
|
||||||
setOpen: setOpenProp,
|
setOpen: setOpenProp,
|
||||||
trigger = "hover",
|
trigger = "hover",
|
||||||
|
testId,
|
||||||
}: PropsWithChildren<TooltipProps>) => {
|
}: PropsWithChildren<TooltipProps>) => {
|
||||||
const [localOpen, setLocalOpen] = useState(false);
|
const [localOpen, setLocalOpen] = useState(false);
|
||||||
const arrowRef = useRef(null);
|
const arrowRef = useRef(null);
|
||||||
@@ -95,6 +96,7 @@ export const Tooltip = ({
|
|||||||
ref={refs.setReference}
|
ref={refs.setReference}
|
||||||
{...getReferenceProps()}
|
{...getReferenceProps()}
|
||||||
className={className}
|
className={className}
|
||||||
|
data-testid={testId}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
type FontWeight,
|
type FontWeight,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import { cn } from "../../shared/utils/cn";
|
import { cn } from "../../shared/utils/cn";
|
||||||
|
import type { BaseProps } from "../../shared/types";
|
||||||
|
|
||||||
type SupportedReactNodes = "h6" | "h5" | "h4" | "h3" | "h2" | "h1" | "span";
|
type SupportedReactNodes = "h6" | "h5" | "h4" | "h3" | "h2" | "h1" | "span";
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ export type BaseTypographyProps = React.HTMLAttributes<HTMLElement> & {
|
|||||||
fontSize?: FontSize;
|
fontSize?: FontSize;
|
||||||
fontWeight?: FontWeight;
|
fontWeight?: FontWeight;
|
||||||
as: SupportedReactNodes;
|
as: SupportedReactNodes;
|
||||||
};
|
} & BaseProps;
|
||||||
|
|
||||||
export const BaseTypography = ({
|
export const BaseTypography = ({
|
||||||
fontSize,
|
fontSize,
|
||||||
@@ -21,6 +22,7 @@ export const BaseTypography = ({
|
|||||||
className,
|
className,
|
||||||
children,
|
children,
|
||||||
as,
|
as,
|
||||||
|
testId,
|
||||||
...props
|
...props
|
||||||
}: PropsWithChildren<BaseTypographyProps>) => {
|
}: PropsWithChildren<BaseTypographyProps>) => {
|
||||||
const Component = as;
|
const Component = as;
|
||||||
@@ -28,6 +30,7 @@ export const BaseTypography = ({
|
|||||||
return (
|
return (
|
||||||
<Component
|
<Component
|
||||||
{...props}
|
{...props}
|
||||||
|
data-testid={testId}
|
||||||
className={cn(
|
className={cn(
|
||||||
"tg-family-outfit text-white leading-[100%]",
|
"tg-family-outfit text-white leading-[100%]",
|
||||||
fontSize ? fontSizes[fontSize] : undefined,
|
fontSize ? fontSizes[fontSize] : undefined,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"email": "stephan@all-hands.dev"
|
"email": "stephan@all-hands.dev"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"version": "1.0.0-beta.7",
|
"version": "1.0.0-beta.8",
|
||||||
"description": "OpenHands UI Components",
|
"description": "OpenHands UI Components",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"openhands",
|
"openhands",
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
export type BaseProps = {
|
export type BaseProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: React.CSSProperties;
|
|
||||||
testId?: string;
|
testId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HTMLProps<T extends React.ElementType> = Omit<
|
export type HTMLProps<T extends React.ElementType> = Omit<
|
||||||
React.ComponentPropsWithoutRef<T>,
|
React.ComponentPropsWithoutRef<T>,
|
||||||
"children"
|
"children" | "style" | "className"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type ComponentVariant = "primary" | "secondary" | "tertiary";
|
export type ComponentVariant = "primary" | "secondary" | "tertiary";
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { cloneElement, isValidElement, type ReactElement } from "react";
|
import { cloneElement, isValidElement, type ReactElement } from "react";
|
||||||
import type { ComponentVariant, HTMLProps } from "../../shared/types";
|
import type { BaseProps, HTMLProps } from "../../shared/types";
|
||||||
|
|
||||||
export const cloneIcon = (
|
export const cloneIcon = (
|
||||||
icon?: ReactElement<HTMLProps<"svg">>,
|
icon?: ReactElement<HTMLProps<"svg"> & BaseProps>,
|
||||||
props?: HTMLProps<"svg">
|
props?: HTMLProps<"svg"> & BaseProps
|
||||||
) => {
|
) => {
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user