Files
egbim_qa_platform/packages/ufb-react/src/components/button.tsx
T
root 33453ecc55
Deploy staging / deploy (push) Failing after 6s
Initial deployment setup
2026-08-31 16:45:24 +09:00

157 lines
4.0 KiB
TypeScript

/**
* Copyright 2025 LY Corporation
*
* LY Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
'use client';
import * as React from 'react';
import { Slot, Slottable } from '@radix-ui/react-slot';
import type { VariantProps } from 'class-variance-authority';
import { cva } from 'class-variance-authority';
import type { ButtonVariant, Radius, Size } from '../lib/types';
import { cn, composeRefs } from '../lib/utils';
import { Spinner } from './spinner';
import useTheme from './use-theme';
const defaultVariants: {
variant: ButtonVariant;
size?: Size;
radius?: Radius;
loading?: boolean;
} = {
variant: 'primary',
size: undefined,
radius: undefined,
loading: false,
};
const buttonVariants = cva('button', {
variants: {
variant: {
primary: 'button-primary',
secondary: 'button-secondary',
destructive: 'button-destructive',
ghost: 'button-ghost',
outline: 'button-outline',
},
size: {
small: 'button-small',
medium: 'button-medium',
large: 'button-large',
},
radius: {
small: 'button-radius-small',
medium: 'button-radius-medium',
large: 'button-radius-large',
},
loading: {
true: '!text-transparent [&>*:not(.button-loading)]:!invisible',
false: '',
},
},
defaultVariants,
});
const buttonLoadingVariants = cva('button-loading', {
variants: {
variant: {
primary: 'button-loading-primary',
secondary: 'button-loading-secondary',
destructive: 'button-loading-destructive',
ghost: 'button-loading-ghost',
outline: 'button-loading-outline',
},
},
defaultVariants,
});
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
variant?: ButtonVariant;
size?: Size;
radius?: Radius;
loading?: boolean;
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
(
{
className,
type = 'button',
variant = 'primary',
size,
radius,
disabled = false,
loading = false,
asChild = false,
children,
...props
},
ref,
) => {
const Comp = asChild ? Slot : 'button';
const { themeSize, themeRadius } = useTheme();
const buttonRef = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
if (!buttonRef.current) return;
const childNodes = buttonRef.current.childNodes;
const isSvgOnly = Array.from(childNodes).every(
(node) =>
((node as HTMLElement).nodeType === Node.ELEMENT_NODE &&
(node as HTMLElement).tagName.toLowerCase() === 'svg') ||
((node as HTMLElement).nodeType === Node.TEXT_NODE &&
!((node as HTMLElement).textContent || '').trim()),
);
if (isSvgOnly) {
buttonRef.current.classList.add('svg-only');
}
}, []);
return (
<Comp
className={cn(
buttonVariants({
variant,
size: size ?? themeSize,
radius: radius ?? themeRadius,
loading,
className,
}),
)}
type={type}
disabled={disabled || loading}
ref={composeRefs(buttonRef, ref)}
{...props}
>
<Slottable>{children}</Slottable>
{loading && (
<span className={cn(buttonLoadingVariants({ variant }))}>
<Spinner size={size ?? themeSize} />
</span>
)}
</Comp>
);
},
);
Button.displayName = 'Button';
export { Button, type ButtonProps, buttonVariants };