Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/attach/src/__snapshots__/Component.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ exports[`Attach Snapshots tests should match snapshot 1`] = `
class="component size-48"
>
<button
class="component secondary size-48 hug size-48 component secondary component secondary withLeftAddons"
class="component component component component component secondary secondary secondary size48 hug size48 defaultPaddings defaultWidth minHeight component"
type="button"
>
<span
class="addons"
class="addon"
>
<svg
class="icon"
Expand Down
4 changes: 2 additions & 2 deletions packages/button/src/Component.responsive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { useIsDesktop } from '@alfalab/core-components-mq';

import { ButtonDesktop } from './desktop';
import { ButtonMobile } from './mobile';
import { type ButtonProps } from './typings';
import { type ButtonProps, type ButtonRef } from './typings';

export const Button = forwardRef<HTMLAnchorElement | HTMLButtonElement, ButtonProps>(
export const Button = forwardRef<ButtonRef, ButtonProps>(
(
{
children,
Expand Down
41 changes: 23 additions & 18 deletions packages/button/src/Component.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { MouseEvent, useState, FC, forwardRef } from 'react';
import React, { MouseEvent, useState, FC, forwardRef, useRef } from 'react';
import {
render,
fireEvent,
Expand Down Expand Up @@ -100,9 +100,9 @@ describe('Button', () => {
expect(container.firstElementChild).toHaveAttribute('disabled');
});

it('should set disabled attribute to <a>', () => {
it('should set `aria-disabled` attribute to <a>', () => {
const { container } = render(<Button href='test' disabled={true} />);
expect(container.firstElementChild).toHaveAttribute('disabled');
expect(container.firstElementChild).toHaveAttribute('aria-disabled', 'true');
});
});

Expand Down Expand Up @@ -130,7 +130,7 @@ describe('Button', () => {
const size = 56;
const { container } = render(<Button size={size} />);

expect(container.firstElementChild).toHaveClass(`size-${size}`);
expect(container.firstElementChild).toHaveClass(`size${size}`);
});

it('should set `block` class', () => {
Expand All @@ -146,10 +146,10 @@ describe('Button', () => {
expect(container.firstElementChild).toHaveClass(view);
});

it('should set `iconOnly` class', () => {
it("shouldn't set `defaultWidth` class", () => {
const { container } = render(<Button />);

expect(container.firstElementChild).toHaveClass('iconOnly');
expect(container.firstElementChild).not.toHaveClass('defaultWidth');
});

it('should set `nowrap` class', () => {
Expand Down Expand Up @@ -179,7 +179,7 @@ describe('Button', () => {
it('should set `allowBackdropBlur` class', () => {
const { container } = render(<Button allowBackdropBlur={true} />);

expect(container.firstElementChild).toHaveClass('allowBackdropBlur');
expect(container.firstElementChild).toHaveClass('blurred');
});
});

Expand Down Expand Up @@ -260,11 +260,12 @@ describe('Button', () => {
);

const button = getByTestId(dataTestId);
const getLoader = () => container.querySelector('svg');
const getLoader = () =>
container.querySelector(`[data-test-id=${getButtonTestIds(dataTestId).spinner}]`);

const start = Date.now();

await fireEvent.click(button);
fireEvent.click(button);

await waitFor(() => expect(getLoader()).toBeInTheDocument());

Expand All @@ -283,7 +284,8 @@ describe('Button', () => {
);

const button = getByTestId(dataTestId);
const getLoader = () => container.querySelector('svg');
const getLoader = () =>
container.querySelector(`[data-test-id=${getButtonTestIds(dataTestId).spinner}]`);

const start = Date.now();

Expand Down Expand Up @@ -312,18 +314,21 @@ describe('Button', () => {
expect(props['data-test-id']).toBe(dataTestId);
});

it('should pass `to` instead `href` to custom component', () => {
const cb = jest.fn();
cb.mockReturnValue(null);
it('should pass `href` to custom component', () => {
expect.assertions(1);

render(<Button Component={forwardRef(cb)} href='test' />);
const CustomComponent = forwardRef<HTMLElement, { href?: string }>((props, _) => {
const firstRenderRef = useRef(true);

expect(cb).toHaveBeenCalled();
if (firstRenderRef.current) {
firstRenderRef.current = false;
expect(props).toEqual(expect.objectContaining({ href: 'test' }));
}

const props = cb.mock.calls[0][0];
return null;
});

expect(props.href).toBeFalsy();
expect(props.to).toBe('test');
render(<Button Component={CustomComponent} href='test' />);
});
});

Expand Down
44 changes: 22 additions & 22 deletions packages/button/src/__snapshots__/Component.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ exports[`Button Snapshots tests should match snapshot 1`] = `
"baseElement": <body>
<div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
type="button"
/>
</div>
</body>,
"container": <div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
type="button"
/>
</div>,
Expand Down Expand Up @@ -77,14 +77,16 @@ exports[`Button Snapshots tests should render anchor if href pass 1`] = `
"baseElement": <body>
<div>
<a
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
aria-disabled="false"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
href="https://some-url"
/>
</div>
</body>,
"container": <div>
<a
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
aria-disabled="false"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
href="https://some-url"
/>
</div>,
Expand Down Expand Up @@ -148,14 +150,14 @@ exports[`Button Snapshots tests should render button by default 1`] = `
"baseElement": <body>
<div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
type="button"
/>
</div>
</body>,
"container": <div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
type="button"
/>
</div>,
Expand Down Expand Up @@ -219,11 +221,11 @@ exports[`Button Snapshots tests should render left addons 1`] = `
"baseElement": <body>
<div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
type="button"
>
<span
class="addons"
class="addon"
>
<div>
Left addons
Expand All @@ -234,11 +236,11 @@ exports[`Button Snapshots tests should render left addons 1`] = `
</body>,
"container": <div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
type="button"
>
<span
class="addons"
class="addon"
>
<div>
Left addons
Expand Down Expand Up @@ -306,9 +308,8 @@ exports[`Button Snapshots tests should render loader if loading & href pass 1`]
"baseElement": <body>
<div>
<a
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly loading"
disabled=""
href="https://some-url"
aria-disabled="true"
class="component loading disabled disabled disabled component component component component secondary secondary secondary size56 hug size56 minWidth minHeight loading loading loading component"
>
<svg
class="spinner component preset24 loader loader loader visible"
Expand Down Expand Up @@ -351,9 +352,8 @@ exports[`Button Snapshots tests should render loader if loading & href pass 1`]
</body>,
"container": <div>
<a
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly loading"
disabled=""
href="https://some-url"
aria-disabled="true"
class="component loading disabled disabled disabled component component component component secondary secondary secondary size56 hug size56 minWidth minHeight loading loading loading component"
>
<svg
class="spinner component preset24 loader loader loader visible"
Expand Down Expand Up @@ -453,7 +453,7 @@ exports[`Button Snapshots tests should render loader if loading pass 1`] = `
"baseElement": <body>
<div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly loading"
class="component loading component component component component secondary secondary secondary size56 hug size56 minWidth minHeight loading loading loading component"
disabled=""
type="button"
>
Expand Down Expand Up @@ -498,7 +498,7 @@ exports[`Button Snapshots tests should render loader if loading pass 1`] = `
</body>,
"container": <div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly loading"
class="component loading component component component component secondary secondary secondary size56 hug size56 minWidth minHeight loading loading loading component"
disabled=""
type="button"
>
Expand Down Expand Up @@ -600,11 +600,11 @@ exports[`Button Snapshots tests should render right addons 1`] = `
"baseElement": <body>
<div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
type="button"
>
<span
class="addons"
class="addon"
>
<div>
Right addons
Expand All @@ -615,11 +615,11 @@ exports[`Button Snapshots tests should render right addons 1`] = `
</body>,
"container": <div>
<button
class="component secondary size-56 hug size-56 component secondary component secondary iconOnly"
class="component component component component component secondary secondary secondary size56 hug size56 minWidth minHeight component"
type="button"
>
<span
class="addons"
class="addon"
>
<div>
Right addons
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { forwardRef, type MouseEventHandler, useRef } from 'react';
import mergeRefs from 'react-merge-refs';
import cn from 'classnames';

import { getDataTestId } from '@alfalab/core-components-shared';
import { Spinner } from '@alfalab/core-components-spinner';
import { useFocus } from '@alfalab/hooks';

import { ButtonComponent } from '../button-component';

import { type BaseButtonCandidateProps } from './types';

import styles from './index.module.css';

export const BaseButtonCandidate = forwardRef<HTMLElement, BaseButtonCandidateProps>(
(
{
children,
block,
className,
loaderClassName,
disabledClassName,
dataTestId,
loading,
Component = ButtonComponent,
disabled: disabledFromProps,
type,
onClick,
href,
...restProps
},
ref,
) => {
const buttonRef = useRef<HTMLElement>(null);
const [focused] = useFocus(buttonRef, 'keyboard');
const disabled = disabledFromProps || loading;
const passDisabledClassName = disabled && typeof href === 'string';

const handleClick: MouseEventHandler<HTMLElement> = (event) => {
if (disabled) {
event.preventDefault();
event.stopPropagation();
} else {
onClick?.(event);
}
};

return (
<Component
data-test-id={dataTestId}
{...restProps}
href={href}
className={cn(
styles.component,
{
[styles.focused]: focused,
[styles.block]: block,
[styles.loading]: loading,
},
passDisabledClassName && [styles.disabled, disabledClassName],
className,
)}
// @ts-expect-error anchor's type and button's type are not compatible
type={type}
disabled={disabled}
onClick={handleClick}
ref={mergeRefs([buttonRef, ref])}
>
{children}
{loading && (
<Spinner
preset={24}
visible={true}
dataTestId={getDataTestId(dataTestId, 'loader')}
className={cn(styles.loader, loaderClassName)}
/>
)}
</Component>
);
},
);
Loading