Skip to content

Commit

Permalink
Merge pull request #428 from Groww-OSS/develop
Browse files Browse the repository at this point in the history
Develop to Main
  • Loading branch information
prashant-adhikari-groww authored Jun 25, 2024
2 parents ec16e3c + c921fa2 commit e4f5222
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 163 deletions.
2 changes: 1 addition & 1 deletion packages/ui-toolkit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@groww-tech/ui-toolkit",
"version": "0.5.9",
"version": "0.6.0",
"description": "A lightning nature UI",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ export const VARIANTS = {
export const BUTTON_SIZES = {
SMALL: 'Small',
BASE: 'Base',
LARGE: 'Large',
XLARGE: 'XLarge'
LARGE: 'Large'
};

export const ROLE = {
BUTTON: 'button',
LINK: 'link'
};

export const DEFAULT_TABINDEX = 0;

export const KEYBOARD_EVENTS = {
ENTER: 'enter'
};
229 changes: 145 additions & 84 deletions packages/ui-toolkit/src/components/atoms/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,32 @@ import cn from 'classnames';

import { ReactIconProps } from '@groww-tech/icon-store';

import { VARIANTS, BUTTON_SIZES } from './Button.constants';
import {
VARIANTS,
BUTTON_SIZES,
ROLE,
DEFAULT_TABINDEX,
KEYBOARD_EVENTS
} from './Button.constants';
import { Loader, LOADER_TYPE } from '../Loader';
import { ICON_POSITION } from '../../../utils/constants';

import { isEmpty } from '../../../utils/helper';
import './button.css';


const isValidHyperLink = (href: string | undefined) : boolean => {
return (
!isEmpty(href) && (!(href === '#' || href === 'javascript:void(0)'))
);
};


const Button = (props: Props) => {
const {
size,
buttonText,
variant,
onClick,
isAccent,
isCompact,
isDisabled,
Expand All @@ -24,73 +37,86 @@ const Button = (props: Props) => {
isLoading,
isFullWidth,
dataTestId,
isFixToBottom
isFixToBottom,
role,
href,
type,
rel,
target,
onClick
} = props;

const primaryButtonClasses = cn(
{
contentOnColour: !isLoading && !isDisabled,
backgroundAccent: !isDisabled || (isLoading && isDisabled),
btn96ButtonHover: !isDisabled,
btn96ButtonDisable: isDisabled && !isLoading
backgroundAccent: !isDisabled || (isLoading && isDisabled)
});

const secondaryButtonClasses = cn({
btn96SecondaryButtonWithoutAccent: !isAccent,
'mint-btn-secondary-no-accent': !isAccent,
backgroundAccentSubtle: (isAccent && !isDisabled) || (isAccent && isDisabled && isLoading),
contentDisabled: isDisabled,
borderPrimary: !isAccent,
btn96ButtonDisable: isDisabled && !isLoading
'mint-btn-secondary-no-accent-border': !isAccent
});

const tertiaryButtonClasses = cn({
btn96TertiaryButtonDisabled: isDisabled && !isLoading,
btn96TertiaryButtonWithAccent: isAccent && !isDisabled && !isLoading,
btn96TertiaryButtonWithoutAccent: !isAccent && !isDisabled
'mint-btn-tertiary-accent': isAccent && !isDisabled && !isLoading,
'mint-btn-tertiary-no-accent': !isAccent && !isDisabled,
'mint-btn-tertiary-disabled': isDisabled && !isLoading
});

const postiveButtonClasses = cn({
backgroundPositive: !isDisabled || (isLoading && isDisabled),
contentOnColour: !isLoading && !isDisabled,
btn96ButtonHover: !isDisabled,
btn96ButtonDisable: isDisabled && !isLoading
contentOnColour: !isLoading && !isDisabled
});

const negativeButtonClasses = cn({
backgroundNegative: !isDisabled || (isLoading && isDisabled),
contentOnColour: !isLoading && !isDisabled,
btn96ButtonHover: !isDisabled,
btn96ButtonDisable: isDisabled && !isLoading,
contentOnColour: !isLoading && !isDisabled
});

const fontClasses = cn({
'cur-po': !isLoading && !isDisabled,
btn96LoaderCursor: isLoading || isDisabled,
bodySmallHeavy: size === BUTTON_SIZES.SMALL,
bodyBaseHeavy: size === BUTTON_SIZES.BASE,
bodyLargeHeavy: size === BUTTON_SIZES.LARGE,
bodyXLargeHeavy: size === BUTTON_SIZES.XLARGE,
contentDisabled: isDisabled,
contentPrimary: !isAccent && !isDisabled,
contentAccent: isAccent && !isDisabled
});


const baseClasses = cn('btn96DefaultClass absolute-center', fontClasses,
const baseClasses = cn('mint-btn-default absolute-center backgroundTransparent', fontClasses,
{
'cur-po': !isLoading && !isDisabled,
btn96SmallButton: size === BUTTON_SIZES.SMALL,
btn96MediumButton: size === BUTTON_SIZES.BASE,
btn96LargeButton: size === BUTTON_SIZES.LARGE,
btn96XLargeButton: size === BUTTON_SIZES.XLARGE,
btn86FullWidth: isFullWidth,
btn96LoadingButton: isLoading,
btn96CompactButton: variant === VARIANTS.TERTIARY && isCompact,
btn96ButtonLabel: variant !== VARIANTS.TERTIARY && !isDisabled
'mint-btn-cursor-default': isLoading,
'mint-btn-small': size === BUTTON_SIZES.SMALL,
'mint-btn-medium': size === BUTTON_SIZES.BASE,
'mint-btn-large': size === BUTTON_SIZES.LARGE,
'mint-btn-full-width': isFullWidth,
'mint-btn-loader': isLoading,
'mint-btn-compact': variant === VARIANTS.TERTIARY && isCompact,
'mint-btn-disabled': isDisabled && !isLoading
});


const getButtonClasses = (variant: string) => {
const loaderClasses = cn('mint-btn-loader-item', {
'mint-btn-loader-primary': variant === VARIANTS.PRIMARY || variant === VARIANTS.POSITIVE || variant === VARIANTS.NEGATIVE,
'mint-btn-loader-no-accent': (variant === VARIANTS.SECONDARY || variant === VARIANTS.TERTIARY) && !isAccent
});

const fixedToBottomClass = cn({
'mint-btn-fixed-bottom': isFixToBottom,
borderPrimary: isFixToBottom,
backgroundPrimary: isFixToBottom
});

const borderBottomClasses = cn({
'mint-btn-tertiary-label-border': variant === VARIANTS.TERTIARY && !isAccent,
borderNeutral: variant === VARIANTS.TERTIARY && !isDisabled && !isLoading && !isAccent,
borderDisabled: variant === VARIANTS.TERTIARY && isDisabled && !isLoading && !isAccent
});


const getButtonClassesBasedOnVariant = () => {
switch (variant) {
case VARIANTS.PRIMARY:
return cn(baseClasses, primaryButtonClasses);
Expand All @@ -107,35 +133,16 @@ const Button = (props: Props) => {
case VARIANTS.NEGATIVE:
return cn(baseClasses, negativeButtonClasses);

default :
default:
return cn(baseClasses, primaryButtonClasses);
}
};


const loaderClasses = cn('btn96LoaderSize btn96LoaderMargin', {
btn96PrimaryButtonLoader: variant === VARIANTS.PRIMARY || variant === VARIANTS.POSITIVE || variant === VARIANTS.NEGATIVE,
btn96LoaderWithAccent: (variant === VARIANTS.SECONDARY || variant === VARIANTS.TERTIARY) && isAccent,
btn96LoaderWithoutAccent: (variant === VARIANTS.SECONDARY || variant === VARIANTS.TERTIARY) && !isAccent
});

const fixedToBottomClass = cn({
btn96BottomFixed: isFixToBottom,
borderPrimary: isFixToBottom,
backgroundPrimary: isFixToBottom
});

const borderBottomClasses = cn({
btn96TertiaryButtonBorder: variant === VARIANTS.TERTIARY && !isAccent,
borderNeutral: variant === VARIANTS.TERTIARY && !isDisabled && !isLoading && !isAccent,
borderDisabled: variant === VARIANTS.TERTIARY && isDisabled && !isLoading && !isAccent
});


const getIconSize = () => {
if (size === BUTTON_SIZES.SMALL) return 16;
if (size === BUTTON_SIZES.BASE) return 20;
if (size === BUTTON_SIZES.LARGE || size === BUTTON_SIZES.XLARGE) return 24;
if (size === BUTTON_SIZES.LARGE) return 24;
};


Expand All @@ -151,56 +158,103 @@ const Button = (props: Props) => {


const onButtonClick = (e: React.MouseEvent) => {
if (!isDisabled && !isLoading) {
onClick(e);

} else {
if (isDisabled || isLoading) {
e.stopPropagation();
return;
}

onClick?.(e);
};


return (
<div className={fixedToBottomClass}>
<div
className={getButtonClasses(variant)}
const getButtonElement = (Component: React.ElementType<ButtonProps | AnchorButtonProps>, props: any) => {
return (
<Component
className={getButtonClassesBasedOnVariant()}
data-test-id={dataTestId.length ? dataTestId : null}
onClick={onButtonClick}
tabIndex={DEFAULT_TABINDEX}
{...props}
>
{
isLoading &&
<div className="absolute-center btn96LoaderContainer">
<Loader
loaderType={LOADER_TYPE.CIRCULAR}
loaderClassName={loaderClasses}
/>
</div>
}
<>
{leadingIcon && getIconUI(ICON_POSITION.LEADING)}

<span className={borderBottomClasses}>
{buttonText}
</span>

{trailingIcon && getIconUI(ICON_POSITION.TRAILING)}
</>

</div>
</div>
{renderButtonContent()}
</Component>
);
};


const LinkButtonRender = () => {
const linkProps = {
role: ROLE.BUTTON, // We are adding the role of "button" to the <a> element since we are using it as a button. Additionally, the <button> element does not need a role attribute, as the semantic tag is sufficient.
...((!isDisabled && !isLoading) && { href }),
...(rel && { rel }),
...(target && { target })
};

return getButtonElement('a', linkProps);
};


const DefaultButtonRender = () => {
const buttonProps = { type: type ?? ROLE.BUTTON, disabled: isDisabled };

return getButtonElement('button', buttonProps);
};


const renderButtonContent = () => (
<>
{
isLoading && <div className="absolute-center mint-btn-loader-container">
<Loader
loaderType={LOADER_TYPE.CIRCULAR}
loaderClassName={loaderClasses}
/>
</div>
}
<>
{leadingIcon && getIconUI(ICON_POSITION.LEADING)}

<span className={cn('truncate', borderBottomClasses)}>
{buttonText}
</span>

{trailingIcon && getIconUI(ICON_POSITION.TRAILING)}
</>
</>
);


const renderButton = () => {
/* If a link doesn't have a meaningful href, it should be rendered using a <button> element.
Ref : https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/HEAD/docs/rules/anchor-is-valid.md#fail */

if (role === ROLE.LINK && isValidHyperLink(href)) {
return LinkButtonRender();
}

return DefaultButtonRender();
};

return (
isFixToBottom ? <div className={fixedToBottomClass}>{renderButton()}</div> : renderButton()
);
};


type RequiredProps = {
buttonText: string;
onClick: (e: React.MouseEvent) => void;
};


type OptionalProps = {
onClick?: (e: React.MouseEvent) => void;
}


type DefaultProps = {
size: ValueOf<typeof BUTTON_SIZES>;
variant: ValueOf<typeof VARIANTS>;
role: ValueOf<typeof ROLE>;
isLoading: boolean;
isAccent: boolean;
isCompact: boolean;
Expand All @@ -212,9 +266,16 @@ type DefaultProps = {
dataTestId: string;
};


type AnchorButtonProps = React.AnchorHTMLAttributes<HTMLAnchorElement>;


type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;

Button.defaultProps = {
size: BUTTON_SIZES.BASE,
variant: VARIANTS.PRIMARY,
role: ROLE.BUTTON,
isLoading: false,
isAccent: false,
isCompact: false,
Expand All @@ -226,6 +287,6 @@ Button.defaultProps = {
dataTestId: ''
} as DefaultProps;

export type Props = RequiredProps & DefaultProps;
export type Props = DefaultProps & RequiredProps & OptionalProps & ButtonProps & AnchorButtonProps;

export default Button;
Loading

0 comments on commit e4f5222

Please sign in to comment.