Skip to content

Commit

Permalink
more refactor to carousel, alerts, button (#277)
Browse files Browse the repository at this point in the history
updates to unit tests
refactor the component
  • Loading branch information
prabhuignoto authored Jul 25, 2023
1 parent b598c7b commit b83d0d2
Show file tree
Hide file tree
Showing 39 changed files with 551 additions and 390 deletions.
4 changes: 2 additions & 2 deletions packages/documentation/components/card/widget-variants.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ export const Default = (
export const CustomImage = (
<Card alignHeader="left" height={250} shadow={false}>
<Image
src="https://mmc.tirto.id/image/otf/500x0/2016/07/26/TIRTO-20140522_batman_warner-bros_ratio-16x9.jpg"
src="https://images.unsplash.com/photo-1690184432588-81068877d852"
loaderSize="md"
/>
</Card>
);

export const CustomImageCode = `
<Card alignHeader="left" height={250} shadow={false}>
<Image src="https://mmc.tirto.id/image/otf/500x0/2016/07/26/TIRTO-20140522_batman_warner-bros_ratio-16x9.jpg" loaderSize='md' />
<Image src="https://images.unsplash.com/photo-1690184432588-81068877d852" loaderSize='md' />
</Card>
`;
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,10 @@ exports[`Accordion > should render snapshot 1`] = `
class="_accordion_f93c7f _no-border_f93c7f"
>
<div
<<<<<<< HEAD
aria-controls="accordion-body-S109p7681yopYHLaTmrir"
aria-controls="accordion-body-FXJAkc8UzzObBYg3smxBR"
aria-expanded="false"
class="_header_c2ceb5 _focusable_c2ceb5 _size_c2ceb5"
id="accordion-eq20UJtE5wulfN4V-NPXn"
=======
aria-controls="accordion-body-ylmb_HCLwEG6TjWllk6pl"
aria-expanded="false"
class="_header_c2ceb5 _focusable_c2ceb5 _size_c2ceb5"
id="accordion-m2khAmLLYptJsjjdjJeGx"
>>>>>>> 19fbdfd83e5f639d925ea98336455960528e1eaf
id="accordion-z_CzG1qgrj87JTu4UhU0h"
role="heading"
style="--rc-accordion-header-height: 40px; outline: none; position: relative;"
tabindex="0"
Expand Down Expand Up @@ -47,15 +40,9 @@ exports[`Accordion > should render snapshot 1`] = `
/>
</div>
<div
<<<<<<< HEAD
aria-labelledby="accordion-eq20UJtE5wulfN4V-NPXn"
class="_body_f93c7f _animate_f93c7f _close_f93c7f"
id="accordion-body-S109p7681yopYHLaTmrir"
=======
aria-labelledby="accordion-m2khAmLLYptJsjjdjJeGx"
aria-labelledby="accordion-z_CzG1qgrj87JTu4UhU0h"
class="_body_f93c7f _animate_f93c7f _close_f93c7f"
id="accordion-body-ylmb_HCLwEG6TjWllk6pl"
>>>>>>> 19fbdfd83e5f639d925ea98336455960528e1eaf
id="accordion-body-FXJAkc8UzzObBYg3smxBR"
role="region"
style="--title-color: #000; --transition: cubic-bezier(0.19, 1, 0.22, 1); --max-height: 0px;"
>
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/components/accordion/accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import cls from 'classnames';
import { nanoid } from 'nanoid';
import * as React from 'react';
import React from 'react';
import {
CSSProperties,
useCallback,
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/components/alert/alert.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Import necessary modules and icons
import { CheckIcon, CloseIcon, ErrorIcon, InfoIcon, WarningIcon } from '@icons';
import classNames from 'classnames';
import * as React from 'react';
import React from 'react';
import { CSSProperties, useCallback, useMemo } from 'react';
import useFocus from '../common/effects/useFocusNew';
import { AlertProps } from './alert-model';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

exports[`Button > should render button snapshot 1`] = `
<div
aria-busy="false"
aria-disabled="false"
aria-label="My Button"
class="_rounded_155e98 _sm_155e98 _default_155e98 _btn_155e98"
role="button"
Expand Down
30 changes: 30 additions & 0 deletions packages/lib/components/button/__tests__/button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,34 @@ describe('Button', () => {

expect(getByRole('button')).toHaveAttribute('tabIndex', '0');
});

it('should render button in busy state', () => {
const { getByRole } = render(<Button label="My Button" isBusy />);
expect(getByRole('button')).toHaveClass(styles.disabled);
expect(getByRole('button')).toHaveAttribute('aria-busy', 'true');
});

it('should render button without border', () => {
const { getByRole } = render(<Button label="My Button" border={false} />);
expect(getByRole('button')).toHaveClass(styles.no_border);
});

it('should render button with correct accent', () => {
const { getByRole } = render(<Button label="My Button" accent="flat" />);
expect(getByRole('button')).toHaveClass(styles.flat);
});

it('should render children correctly', () => {
const { getByText } = render(
<Button>
<div>Child Node</div>
</Button>
);
expect(getByText('Child Node')).toBeInTheDocument();
});

it('should render progress button correctly', () => {
const { getByRole } = render(<Button type="progress" />);
expect(getByRole('button')).toHaveClass(styles.progress);
});
});
108 changes: 68 additions & 40 deletions packages/lib/components/button/button.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
/* eslint-disable react/prop-types */
import classNames from 'classnames';
import * as React from 'react';
import { useImperativeHandle, useMemo, useRef } from 'react';
import React from 'react';
import { useImperativeHandle, useRef, useCallback } from 'react';
import useFocusNew from '../common/effects/useFocusNew';
import { isDark } from '../common/utils';
import { Spinner } from '../spinner/spinner';
import { ButtonProps } from './button-model';
import styles from './button.module.scss';

/**
* Button Component - A customizable button component.
*
* @component
*
* @param {object} props - The component's props
* @param {boolean} props.border - Determines whether the button has a border. Default is true.
* @param {ReactNode} props.children - The content inside the button. Can be text or an element.
* @param {boolean} props.disabled - Determines whether the button is disabled. Default is false.
* @param {boolean} props.focusable - Determines whether the button can be focused. Default is true.
* @param {'default'|'progress'} props.type - The type of the button. Default is 'default'.
* @param {string} props.label - The label of the button. Default is ''.
* @param {function} props.onClick - The function to call when the button is clicked.
* @param {'sm'|'md'|'lg'} props.size - The size of the button. Default is 'sm'.
* @param {object} props.style - The style to apply to the button. Default is {}.
* @param {'rounded'|'otherAccent'} props.accent - The accent of the button. Default is 'rounded'.
* @param {boolean} props.isBusy - Determines whether the button is in a "busy" state (e.g., waiting for a response). Default is false.
*
* @returns {ReactNode} React component
*/

const Button = React.forwardRef<HTMLDivElement, ButtonProps>((props, ref) => {
const {
border = true,
Expand All @@ -23,21 +44,19 @@ const Button = React.forwardRef<HTMLDivElement, ButtonProps>((props, ref) => {
isBusy = false,
} = props;

const isDarkMode = useMemo(() => isDark(), []);
// check if the theme is dark mode
const isDarkMode = isDark();

const buttonClass = useMemo(
() =>
classNames(
{
[styles[`default`]]: type === 'progress',
[styles.no_border]: !border,
[styles.disabled]: disabled || isBusy,
[styles.dark]: isDarkMode,
[styles[accent]]: true,
},
[styles[size], styles[type], styles.btn]
),
[disabled, isDarkMode, isBusy]
// setup classnames for the button
const buttonClass = classNames(
{
[styles['default']]: type === 'progress',
[styles.no_border]: !border,
[styles.disabled]: disabled || isBusy,
[styles.dark]: isDarkMode,
[styles[accent]]: true,
},
[styles[size], styles[type], styles.btn]
);

// setup for focus
Expand All @@ -55,18 +74,36 @@ const Button = React.forwardRef<HTMLDivElement, ButtonProps>((props, ref) => {

useFocusNew(focusable ? buttonRef : null);

const focusableProps = useMemo(
() => ({
tabIndex: focusable ? 0 : -1,
}),
[]
);
// determine if the button can be focused
const tabIndex = focusable ? 0 : -1;

// handler for button click
const handleClick = () => !disabled && onClick?.();
const handleClick = useCallback(
() => !disabled && onClick?.(),
[disabled, onClick]
);

// handle keyboard 'Enter' event
const handleKeyUp = useCallback(
(ev: React.KeyboardEvent) => ev.key === 'Enter' && handleClick(),
[handleClick]
);

// render the spinner
const renderSpinner = () => (
<span className={styles.progress_wrapper}>
{/* <CircularProgress size={'xs'} /> */}
<Spinner />
</span>
);

// render the children
const renderChildren = () => (
<span className={styles.icon_container}>{children}</span>
);

const handleKeyUp = (ev: React.KeyboardEvent) =>
ev.key === 'Enter' && handleClick();
// render the label
const renderLabel = () => <span className={styles.label}>{label}</span>;

return (
<div
Expand All @@ -76,23 +113,14 @@ const Button = React.forwardRef<HTMLDivElement, ButtonProps>((props, ref) => {
ref={buttonRef}
role="button"
style={style}
{...focusableProps}
tabIndex={tabIndex}
aria-label={label}
aria-disabled={disabled}
aria-busy={isBusy}
>
{type === 'progress' && !disabled && (
<span className={styles.progress_wrapper} role="img">
{/* <CircularProgress size={'xs'} /> */}
<Spinner />
</span>
)}
{children && (
<span className={styles.icon_container} role="img">
{children}
</span>
)}
{label && type !== 'icon' && (
<span className={styles.label}>{label}</span>
)}
{type === 'progress' && !disabled && renderSpinner()}
{children && renderChildren()}
{label && type !== 'icon' && renderLabel()}
</div>
);
});
Expand Down
47 changes: 47 additions & 0 deletions packages/lib/components/card/__tests__/card.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { render } from '@testing-library/react';
import { Card } from '../card';
import styles from '../card.module.scss';

describe('Card', () => {
it('should render a basic card', async () => {
Expand All @@ -18,4 +19,50 @@ describe('Card', () => {
expect(getByText('header')).toBeInTheDocument();
expect(getByText('footer')).toBeInTheDocument();
});

it('should render with custom height', () => {
const { container } = render(<Card height={300} />);
const card = container.firstChild;
expect(card).toHaveStyle('--height: 300px');
});

it('should render without border', () => {
const { container } = render(<Card border={false} />);
const card = container.firstChild;
expect(card).toHaveClass(styles.border_less);
});

it('should render with shadow', () => {
const { container } = render(<Card shadow />);
const card = container.firstChild;
expect(card).toHaveClass(styles.shadow);
});

it('should align header and footer correctly', () => {
const { container } = render(
<Card
alignHeader="right"
alignFooter="center"
header={<span>header</span>}
footer={<span>footer</span>}
/>
);
const card = container.firstChild as HTMLElement;
if (card) {
const header = card.querySelector(`.${styles.header}`);
const footer = card.querySelector(`.${styles.footer}`);

expect(header).toHaveClass(styles.align_right);
expect(footer).toHaveClass(styles.align_center);
}
});

it('should render children correctly', () => {
const { getByText } = render(
<Card>
<div>Child Node</div>
</Card>
);
expect(getByText('Child Node')).toBeInTheDocument();
});
});
Loading

1 comment on commit b83d0d2

@vercel
Copy link

@vercel vercel bot commented on b83d0d2 Jul 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.