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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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 size-48 hug size-48 withLeftAddons component"
type="button"
>
<span
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
25 changes: 14 additions & 11 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 @@ -312,18 +312,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 = (props: { href?: string }) => {
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
36 changes: 18 additions & 18 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 size-56 hug size-56 iconOnly 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 size-56 hug size-56 iconOnly 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 size-56 hug size-56 iconOnly 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 size-56 hug size-56 iconOnly 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 size-56 hug size-56 iconOnly 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 size-56 hug size-56 iconOnly component"
type="button"
/>
</div>,
Expand Down Expand Up @@ -219,7 +221,7 @@ 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 size-56 hug size-56 iconOnly component"
type="button"
>
<span
Expand All @@ -234,7 +236,7 @@ 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 size-56 hug size-56 iconOnly component"
type="button"
>
<span
Expand Down Expand Up @@ -306,8 +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=""
aria-disabled="false"
class="component loading component component component component secondary secondary secondary size-56 hug size-56 iconOnly loading component"
href="https://some-url"
>
<svg
Expand Down Expand Up @@ -351,8 +353,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=""
aria-disabled="false"
class="component loading component component component component secondary secondary secondary size-56 hug size-56 iconOnly loading component"
href="https://some-url"
>
<svg
Expand Down Expand Up @@ -453,8 +455,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"
disabled=""
class="component loading component component component component secondary secondary secondary size-56 hug size-56 iconOnly loading component"
type="button"
>
<svg
Expand Down Expand Up @@ -498,8 +499,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"
disabled=""
class="component loading component component component component secondary secondary secondary size-56 hug size-56 iconOnly loading component"
type="button"
>
<svg
Expand Down Expand Up @@ -600,7 +600,7 @@ 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 size-56 hug size-56 iconOnly component"
type="button"
>
<span
Expand All @@ -615,7 +615,7 @@ 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 size-56 hug size-56 iconOnly component"
type="button"
>
<span
Expand Down
143 changes: 143 additions & 0 deletions packages/button/src/components/base-button-candidate/Component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* eslint-disable complexity */
import React, {
type AnchorHTMLAttributes,
type ButtonHTMLAttributes,
type ComponentType,
forwardRef,
type ForwardRefExoticComponent,
type MouseEventHandler,
type RefAttributes,
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 styles from './index.module.css';

interface ComponentProps {
/**
* Растягивает компонент на ширину контейнера
* @default false
*/
block?: boolean;

/**
* Дополнительный класс
*/
className?: string;

/**
* Дополнительный класс для лоадера
*/
loaderClassName?: string;

/**
* Дополнительный класc при отключении кнопки
*/
disabledClassName?: string;

/**
* Позволяет использовать кастомный компонент для кнопки (например Link из роутера)
*/
Component?:
| ComponentType<AnchorHTMLAttributes<HTMLAnchorElement>>
| ComponentType<ButtonHTMLAttributes<HTMLButtonElement>>
| ForwardRefExoticComponent<
AnchorHTMLAttributes<HTMLAnchorElement> & RefAttributes<HTMLAnchorElement>
>
| ForwardRefExoticComponent<
ButtonHTMLAttributes<HTMLButtonElement> & RefAttributes<HTMLButtonElement>
>;

/**
* Идентификатор для систем автоматизированного тестирования.
* Для лоадер используется модификатор -loader
*/
dataTestId?: string;

/**
* Показать лоадер
* @default false
*/
loading?: boolean;
}

export interface BaseButtonCandidateProps
extends ComponentProps,
Omit<ButtonHTMLAttributes<HTMLElement>, keyof ComponentProps>,
Omit<
AnchorHTMLAttributes<HTMLElement>,
keyof ButtonHTMLAttributes<HTMLElement> | keyof ComponentProps
> {}

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

const handleClick: MouseEventHandler<HTMLElement> = (event) => {
if (disabled || loading) {
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,
)}
type={type}
disabled={disabled}
onClick={handleClick}
ref={mergeRefs([buttonRef, ref])}
>
{loading && (
<Spinner
preset={24}
visible={true}
dataTestId={getDataTestId(dataTestId, 'loader')}
className={cn(styles.loader, loaderClassName)}
/>
)}
{children}
</Component>
);
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
@import '@alfalab/core-components-vars/src/no-typography-index.css';

.component {
@mixin reset-tap-highlight-color;

display: inline-flex;
vertical-align: middle;
flex-direction: row;
flex-wrap: nowrap;
align-content: center;
align-items: center;
position: relative;

transition:
background 0.2s ease,
border 0.2s ease,
color 0.2s ease;

/* Block */

&.block {
display: flex;
width: 100%;
}

/* Focus */

&.focused {
@mixin focus-outline;
}

/* Loading */

.loader {
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
}

&.loading > *:not(.loader) {
opacity: 0;
}

/* Disabled */

@mixin disabled {
cursor: var(--disabled-cursor);

& * {
pointer-events: none;
}
}
}
Loading