Files
medusa-store/packages/design-system/ui/src/components/button/button.tsx
2024-01-15 11:43:16 +01:00

134 lines
4.2 KiB
TypeScript

import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "cva"
import * as React from "react"
import { clx } from "@/utils/clx"
import { Spinner } from "@medusajs/icons"
const buttonVariants = cva({
base: clx(
"transition-fg relative inline-flex w-fit items-center justify-center overflow-hidden rounded-md outline-none",
"disabled:bg-ui-bg-disabled disabled:border-ui-border-base disabled:text-ui-fg-disabled disabled:shadow-buttons-neutral disabled:after:hidden",
"after:transition-fg after:absolute after:inset-0 after:content-['']"
),
variants: {
variant: {
primary: clx(
"shadow-buttons-inverted text-ui-fg-on-inverted bg-ui-button-inverted after:button-inverted-gradient",
"hover:bg-ui-button-inverted-hover hover:after:button-inverted-hover-gradient",
"active:bg-ui-button-inverted-pressed active:after:button-inverted-pressed-gradient",
"focus-visible:!shadow-buttons-inverted-focus"
),
secondary: clx(
"shadow-buttons-neutral text-ui-fg-base bg-ui-button-neutral after:button-neutral-gradient",
"hover:bg-ui-button-neutral-hover hover:after:button-neutral-hover-gradient",
"active:bg-ui-button-neutral-pressed active:after:button-neutral-pressed-gradient",
"focus-visible:shadow-buttons-neutral-focus"
),
transparent: clx(
"after:hidden",
"text-ui-fg-base bg-ui-button-transparent",
"hover:bg-ui-button-transparent-hover",
"active:bg-ui-button-transparent-pressed",
"focus-visible:shadow-buttons-neutral-focus focus-visible:bg-ui-bg-base",
"disabled:!bg-transparent disabled:!shadow-none"
),
danger: clx(
"shadow-buttons-colored shadow-buttons-danger text-ui-fg-on-color bg-ui-button-danger after:button-danger-gradient",
"hover:bg-ui-button-danger-hover hover:after:button-danger-hover-gradient",
"active:bg-ui-button-danger-pressed active:after:button-danger-pressed-gradient",
"focus-visible:shadow-buttons-danger-focus"
),
},
size: {
small: "txt-compact-small-plus gap-x-1 px-2 py-1",
base: "txt-compact-small-plus gap-x-1.5 px-3 py-1.5",
large: "txt-compact-medium-plus gap-x-1.5 px-4 py-2.5",
xlarge: "txt-compact-large-plus gap-x-1.5 px-5 py-3.5",
},
},
defaultVariants: {
size: "base",
variant: "primary",
},
})
interface ButtonProps
extends React.ComponentPropsWithoutRef<"button">,
VariantProps<typeof buttonVariants> {
isLoading?: boolean
asChild?: boolean
}
/**
* This component is based on the `button` element and supports all of its props
*/
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
/**
* The button's style.
*/
variant = "primary",
/**
* The button's size.
*/
size = "base",
className,
/**
* Whether to remove the wrapper `button` element and use the
* passed child element instead.
*/
asChild = false,
children,
/**
* Whether to show a loading spinner.
*/
isLoading = false,
disabled,
...props
}: ButtonProps,
ref
) => {
const Component = asChild ? Slot : "button"
/**
* In the case of a button where asChild is true, and isLoading is true, we ensure that
* only on element is passed as a child to the Slot component. This is because the Slot
* component only accepts a single child.
*/
const renderInner = () => {
if (isLoading) {
return (
<span className="pointer-events-none">
<div
className={clx(
"bg-ui-bg-disabled absolute inset-0 flex items-center justify-center rounded-md"
)}
>
<Spinner className="animate-spin" />
</div>
{children}
</span>
)
}
return children
}
return (
<Component
ref={ref}
{...props}
className={clx(buttonVariants({ variant, size }), className)}
disabled={disabled || isLoading}
>
{renderInner()}
</Component>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }