Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add ToolButton #6837

Merged
merged 3 commits into from
May 14, 2024
Merged
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
112 changes: 112 additions & 0 deletions packages/vkui/src/components/ToolButton/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
```jsx { "props": { "layout": false, "iframe": false } }
const containerStyles = {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
};

const Example = () => {
const [mode, setMode] = useState('primary');
const [appearance, setAppearance] = useState('accent');
const [direction, setDirection] = useState('column');
const [sizeY, setSizeY] = useState('compact');
const [rounded, setRounded] = useState(false);
const [disabled, setDisabled] = useState(false);
const [addText, setAddText] = useState(true);
const [hasLink, setHasLink] = useState(false);
const platform = usePlatform();

React.useEffect(() => {
if (platform === 'vkcom') {
setSizeY('compact');
}
}, [platform]);

const buttonLink = hasLink ? '#' : undefined;
const buttonText = addText ? 'Button' : undefined;

return (
<div style={{ display: 'flex', flexDirection: 'row-reverse' }}>
<AdaptivityProvider sizeY={sizeY}>
<div style={containerStyles}>
<Div>
<ToolButton
IconCompact={Icon20Add}
IconRegular={Icon24Add}
mode={mode}
direction={direction}
appearance={appearance}
rounded={rounded}
disabled={disabled}
href={buttonLink}
onClick={() => {}}
>
{buttonText}
</ToolButton>
</Div>
</div>
</AdaptivityProvider>
<div style={{ minWidth: 200 }}>
<FormItem top="direction">
<Select
value={direction}
onChange={(e) => setDirection(e.target.value)}
options={[
{ label: 'row', value: 'row' },
{ label: 'column', value: 'column' },
]}
/>
</FormItem>
<FormItem top="mode">
<Select
value={mode}
onChange={(e) => setMode(e.target.value)}
options={[
{ label: 'primary', value: 'primary' },
{ label: 'secondary', value: 'secondary' },
{ label: 'tertiary', value: 'tertiary' },
{ label: 'outline', value: 'outline' },
]}
/>
</FormItem>
<FormItem top="appearance">
<Select
value={appearance}
onChange={(e) => setAppearance(e.target.value)}
options={[
{ label: 'accent', value: 'accent' },
{ label: 'neutral', value: 'neutral' },
]}
/>
</FormItem>
<FormItem top="sizeY">
<Select
value={sizeY}
onChange={(e) => setSizeY(e.target.value)}
options={[
{ label: 'compact', value: 'compact' },
{
label: 'regular',
value: 'regular',
disabled: platform === 'vkcom',
},
]}
/>
</FormItem>
<FormItem top="props">
<Checkbox onChange={(e) => setRounded(e.target.checked)}>rounded</Checkbox>
<Checkbox onChange={(e) => setDisabled(e.target.checked)}>disabled</Checkbox>
<Checkbox onChange={(e) => setAddText(e.target.checked)} checked={addText}>
add text
</Checkbox>
<Checkbox onChange={(e) => setHasLink(e.target.checked)}>add href</Checkbox>
</FormItem>
</div>
</div>
);
};

<Example />;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as React from 'react';
import { Icon20Add, Icon24Add } from '@vkontakte/icons';
import { ComponentPlayground, type ComponentPlaygroundProps } from '@vkui-e2e/playground-helpers';
import { ButtonGroup } from '../ButtonGroup/ButtonGroup';
import { ToolButton, type ToolButtonProps } from './ToolButton';

export const ToolButtonPlayground = (props: ComponentPlaygroundProps) => {
return (
<ComponentPlayground
{...props}
propSets={[
{
$adaptivity: 'y',
direction: ['column', 'row'],
children: ['Кнопка'],
},
{
$adaptivity: 'y',
rounded: [true],
},
{
appearance: ['accent', 'neutral'],
mode: ['primary', 'secondary', 'tertiary', 'outline'],
children: ['Кнопка'],
},
]}
>
{(props: ToolButtonProps) => {
const icons = {
IconCompact: Icon20Add,
IconRegular: Icon24Add,
};

return (
<ButtonGroup mode={props.direction === 'row' ? 'vertical' : 'horizontal'}>
<ToolButton {...icons} {...props} />
<ToolButton {...icons} {...props} hovered />
<ToolButton {...icons} {...props} activated />
<ToolButton {...icons} {...props} disabled />
</ButtonGroup>
);
}}
</ComponentPlayground>
);
};
12 changes: 12 additions & 0 deletions packages/vkui/src/components/ToolButton/ToolButton.e2e.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as React from 'react';
import { test } from '@vkui-e2e/test';
import { ToolButtonPlayground } from './ToolButton.e2e-playground';

test('ToolButton', async ({
mount,
expectScreenshotClippedToContent,
componentPlaygroundProps,
}) => {
await mount(<ToolButtonPlayground {...componentPlaygroundProps} />);
await expectScreenshotClippedToContent();
});
160 changes: 160 additions & 0 deletions packages/vkui/src/components/ToolButton/ToolButton.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
.ToolButton {
position: relative;
display: flex;
box-sizing: border-box;
text-decoration: none;
border: 0;
margin: 0;
padding: 10px;
border-radius: var(--vkui--size_border_radius_paper--regular);
inline-size: 100%;
flex-grow: 1;
flex-basis: 0;
align-items: center;
justify-content: center;
font-family: var(--vkui--font_caption1--font_family--regular);
font-size: var(--vkui--font_caption1--font_size--regular);
line-height: var(--vkui--font_caption1--line_height--regular);
}

.ToolButton--rounded {
border-radius: var(--vkui--size_border_radius_rounded--regular);
}

.ToolButton--sizeY-regular {
font-family: var(--vkui--font_subhead--font_family--regular);
font-size: var(--vkui--font_subhead--font_size--regular);
line-height: var(--vkui--font_subhead--line_height--regular);
}

@media (--sizeY-regular) {
.ToolButton--sizeY-none {
font-family: var(--vkui--font_subhead--font_family--regular);
font-size: var(--vkui--font_subhead--font_size--regular);
line-height: var(--vkui--font_subhead--line_height--regular);
}
}

.ToolButton[disabled] {
opacity: var(--vkui--opacity_disable);
}

/* ToolButton's directions */
.ToolButton--direction-row {
flex-direction: row;
}

.ToolButton--direction-column {
flex-direction: column;
}

/* stylelint-disable selector-max-universal -- gap для элементов */
.ToolButton--direction-row > * {
margin-inline-end: 4px;
}

.ToolButton--direction-row > *:last-child {
margin-inline-end: 0;
}

.ToolButton--direction-column > * {
margin-block-end: 4px;
}

.ToolButton--direction-column > *:last-child {
margin-block-end: 0;
}
/* stylelint-enable selector-max-universal */

/* ToolButton's backgrounds */
/* Mode = Primary */
.ToolButton--mode-primary.ToolButton--appearance-accent {
background-color: var(--vkui--color_background_accent_themed);
}

.ToolButton--mode-primary.ToolButton--appearance-accent.ToolButton--hover {
background-color: var(--vkui--color_background_accent_themed--hover);
}

.ToolButton--mode-primary.ToolButton--appearance-accent.ToolButton--active {
background-color: var(--vkui--color_background_accent_themed--active);
}

.ToolButton--mode-primary.ToolButton--appearance-neutral {
background-color: var(--vkui--color_background_secondary);
}

.ToolButton--mode-primary.ToolButton--appearance-neutral.ToolButton--hover {
background-color: var(--vkui--color_background_secondary--hover);
}

.ToolButton--mode-primary.ToolButton--appearance-neutral.ToolButton--active {
background-color: var(--vkui--color_background_secondary--active);
}

/* Mode = Secondary */
.ToolButton--mode-secondary {
background-color: var(--vkui--color_background_secondary_alpha);
}

.ToolButton--mode-secondary.ToolButton--hover {
background-color: var(--vkui--color_background_secondary_alpha--hover);
}

.ToolButton--mode-secondary.ToolButton--active {
background-color: var(--vkui--color_background_secondary_alpha--active);
}

/* Mode = Tertiary */
.ToolButton--mode-tertiary,
.ToolButton--mode-outline {
background-color: var(--vkui--color_transparent);
}

.ToolButton--mode-tertiary.ToolButton--hover,
.ToolButton--mode-outline.ToolButton--hover {
background-color: var(--vkui--color_transparent--hover);
}

.ToolButton--mode-tertiary.ToolButton--active,
.ToolButton--mode-outline.ToolButton--active {
background-color: var(--vkui--color_transparent--active);
}

/*
ToolButtons text colors
*/
.ToolButton.ToolButton--appearance-accent {
color: var(--vkui--color_text_accent_themed);
}

.ToolButton.ToolButton--appearance-neutral {
color: var(--vkui--color_text_primary);
}

.ToolButton--mode-primary.ToolButton--appearance-accent {
color: var(--vkui--color_text_contrast_themed);
}

/*
Outline buttons borders
*/
.ToolButton--mode-outline.ToolButton--appearance-accent,
.ToolButton--mode-outline.ToolButton--appearance-accent.ToolButton--hover,
.ToolButton--mode-outline.ToolButton--appearance-accent.ToolButton--active {
box-shadow: inset 0 0 0 1px var(--vkui--color_stroke_accent_themed);
}

.ToolButton--mode-outline.ToolButton--appearance-neutral {
box-shadow: inset 0 0 0 1px var(--vkui--color_field_border_alpha);
}

.ToolButton--mode-outline.ToolButton--appearance-neutral.ToolButton--hover {
background-color: var(--vkui--color_background_secondary--hover);
box-shadow: unset;
}

.ToolButton--mode-outline.ToolButton--appearance-neutral.ToolButton--active {
background-color: var(--vkui--color_background_secondary--active);
box-shadow: unset;
}
24 changes: 24 additions & 0 deletions packages/vkui/src/components/ToolButton/ToolButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { withCartesian } from '@project-tools/storybook-addon-cartesian';
import { Meta, StoryObj } from '@storybook/react';
import { Icon20Add, Icon24Add } from '@vkontakte/icons';
import { CanvasFullLayout } from '../../storybook/constants';
import { ToolButton, ToolButtonProps } from './ToolButton';

const story: Meta<ToolButtonProps> = {
title: 'Blocks/ToolButton',
component: ToolButton,
parameters: CanvasFullLayout,
decorators: [withCartesian],
};

export default story;

type Story = StoryObj<ToolButtonProps>;

export const Playground: Story = {
args: {
children: 'ToolButton',
IconCompact: Icon20Add,
IconRegular: Icon24Add,
},
};
24 changes: 24 additions & 0 deletions packages/vkui/src/components/ToolButton/ToolButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';
import { render, screen } from '@testing-library/react';
import { Icon20Add, Icon24Add } from '@vkontakte/icons';
import { baselineComponent } from '../../testing/utils';
import { ToolButton, ToolButtonProps } from './ToolButton';

const ToolButtonTest = (props: Omit<ToolButtonProps, 'IconCompact' | 'IconRegular'>) => (
<ToolButton data-testid="custom-btn" IconCompact={Icon20Add} IconRegular={Icon24Add} {...props} />
);
const button = () => screen.getByTestId('custom-btn');

describe('ToolButton', () => {
baselineComponent((props) => <ToolButtonTest {...props}>ToolButton</ToolButtonTest>);

it('Component: ToolButton is handled as a native button', () => {
render(<ToolButtonTest>Native ToolButton</ToolButtonTest>);
expect(button().tagName.toLowerCase()).toMatch('button');
});

it('Component: ToolButton with valid href is handled as a native link', () => {
render(<ToolButtonTest href="#">Native Link</ToolButtonTest>);
expect(button().tagName.toLowerCase()).toMatch('a');
});
});
Loading
Loading