Files
unicorn-utterances/src/components/button/button.tsx
2023-10-20 17:37:46 -04:00

148 lines
3.1 KiB
TypeScript

import { JSXNode, PropsWithChildren } from "../types";
import { JSX } from "preact";
import { ForwardedRef, forwardRef } from "preact/compat";
type AllowedTags = "a" | "button" | "span" | "div";
type AllowedElements<Tag extends AllowedTags> = (
Tag extends "a"
? HTMLAnchorElement
: Tag extends "div"
? HTMLDivElement
: Tag extends "span"
? HTMLSpanElement
: HTMLButtonElement
);
type ButtonProps<Tag extends AllowedTags> = PropsWithChildren<
{
tag?: Tag;
class?: string;
leftIcon?: JSXNode;
rightIcon?: JSXNode;
// For when the user is _actually_ focused on another element, like react-aria radio buttons
isFocusVisible?: boolean;
variant?:
| "primary-emphasized"
| "secondary-emphasized"
| "primary"
| "secondary";
} & JSX.HTMLAttributes<AllowedElements<Tag>>
>;
const ButtonWrapper = forwardRef<AllowedElements<AllowedTags> | null, ButtonProps<AllowedTags>>(
(
{
tag = "a" as never,
class: className,
children,
variant = "primary",
leftIcon,
rightIcon,
isFocusVisible,
...props
},
ref,
) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const Wrapper: any = tag;
return (
<Wrapper
{...props}
aria-label={props["aria-label"]}
class={[
"button",
isFocusVisible ? "focusVisible" : "",
className,
variant,
]
.filter((c) => !!c)
.join(" ")}
ref={ref}
>
{leftIcon && (
<div aria-hidden="true" class="buttonIcon">
{leftIcon}
</div>
)}
<span className="innerText">{children}</span>
{rightIcon && (
<div aria-hidden="true" class="buttonIcon">
{rightIcon}
</div>
)}
</Wrapper>
);
},
);
export const Button = forwardRef<AllowedElements<AllowedTags> | null, ButtonProps<AllowedTags>>(
(
{ class: className = "", ...props },
ref,
) => {
return (
<ButtonWrapper
{...props}
class={`text-style-button-regular regular ${className}`}
ref={ref}
/>
);
},
);
export const LargeButton = forwardRef<AllowedElements<AllowedTags> | null, ButtonProps<AllowedTags>>(
(
{ class: className = "", ...props },
ref,
) => {
return (
<ButtonWrapper
{...props}
class={`text-style-button-large large ${className}`}
ref={ref}
/>
);
},
);
type IconOnlyButtonProps<T extends AllowedTags = "a"> = Omit<
ButtonProps<T>,
"leftIcon" | "rightIcon"
>;
export const IconOnlyButton = forwardRef<AllowedElements<AllowedTags> | null, IconOnlyButtonProps<AllowedTags>>(
(
{ class: className = "", children, ...props },
ref,
) => {
return (
<ButtonWrapper
{...props}
class={`iconOnly regular ${className}`}
ref={ref}
>
<div class="iconOnlyButtonIcon" aria-hidden="true">
{children}
</div>
</ButtonWrapper>
);
},
);
export const LargeIconOnlyButton = forwardRef<AllowedElements<AllowedTags> | null, IconOnlyButtonProps<AllowedTags>>(
(
{ class: className = "", children, ...props },
ref,
) => {
return (
<ButtonWrapper {...props} class={`iconOnly large ${className}`} ref={ref}>
<div class="iconOnlyButtonIcon" aria-hidden="true">
{children}
</div>
</ButtonWrapper>
);
},
);