Skip to content

Commit

Permalink
Merge pull request #64 from Gkuzin13/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Gkuzin13 authored Oct 30, 2023
2 parents 5b43196 + 30cbc83 commit 177e9d7
Show file tree
Hide file tree
Showing 14 changed files with 85 additions and 52 deletions.
4 changes: 2 additions & 2 deletions apps/client/src/components/Elements/ColorsGrid/ColorsGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
import * as PanelStyled from '@/components/Panels/StylePanel/StylePanel.styled';
import * as Styled from './ColorsGrid.styled';
import { getStyleTitle } from '@/utils/string';
import { createTitle } from '@/utils/string';
import type { NodeColor } from 'shared';
import ColorCircle from '../ColorCircle/ColorCircle';

Expand Down Expand Up @@ -45,7 +45,7 @@ const ColorsGrid = ({ withLabel = false, value, onSelect }: Props) => {
checked={color.value === value}
value={color.value}
aria-label={`${color.name} color`}
title={getStyleTitle('Color', color.name)}
title={createTitle('Color', color.name)}
color={
color.value === value ? 'secondary-dark' : 'secondary-light'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { memo } from 'react';
import { CONTROL } from '@/constants/panels/control';
import { getKeyTitle } from '@/utils/string';
import { createKeyTitle } from '@/utils/string';
import * as PanelStyled from '../Panels.styled';
import Icon from '@/components/Elements/Icon/Icon';

Expand Down Expand Up @@ -35,8 +35,8 @@ const ControlPanel = ({ enabledControls, onControl }: Props) => {
return (
<PanelStyled.Button
key={control.name}
title={getKeyTitle(control.name, [
...control.modifierKeys.map((key) => key.replace(/key/i, '')),
title={createKeyTitle(control.name, [
...control.modifierKeys,
control.key,
])}
disabled={getDisabledByControlValue(control.value)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { memo } from 'react';
import type { NodeStyle } from 'shared';
import { ANIMATED } from '@/constants/panels/style';
import * as Styled from './StylePanel.styled';
import { getStyleTitle } from '@/utils/string';
import { createTitle } from '@/utils/string';

type Props = {
value: NodeStyle['animated'];
Expand All @@ -18,7 +18,7 @@ const AnimatedSection = ({ value, isDisabled, onAnimatedChange }: Props) => {
<Styled.Label>{ANIMATED.name}</Styled.Label>
<Styled.Toggle
aria-label="Toggle Animated"
title={getStyleTitle(ANIMATED.name, valueTitle)}
title={createTitle(ANIMATED.name, valueTitle)}
pressed={value}
color={value ? 'primary' : 'secondary-light'}
disabled={isDisabled}
Expand Down
4 changes: 2 additions & 2 deletions apps/client/src/components/Panels/StylePanel/FillSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { memo } from 'react';
import type { NodeFill } from 'shared';
import { FILL } from '@/constants/panels/style';
import * as Styled from './StylePanel.styled';
import { getStyleTitle } from '@/utils/string';
import { createTitle } from '@/utils/string';
import Icon from '@/components/Elements/Icon/Icon';

type FillValue = Exclude<NodeFill, undefined>;
Expand All @@ -28,7 +28,7 @@ const FillSection = ({ value, onFillChange }: Props) => {
<Styled.Item
key={fill.value}
value={fill.value ?? 'none'}
title={getStyleTitle('Fill', fill.name)}
title={createTitle('Fill', fill.name)}
checked={fill.value === value}
color={
fill.value === value ? 'secondary-dark' : 'secondary-light'
Expand Down
4 changes: 2 additions & 2 deletions apps/client/src/components/Panels/StylePanel/LineSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { memo } from 'react';
import type { NodeLine } from 'shared';
import { LINE } from '@/constants/panels/style';
import * as Styled from './StylePanel.styled';
import { getStyleTitle } from '@/utils/string';
import { createTitle } from '@/utils/string';
import Icon from '@/components/Elements/Icon/Icon';

type Props = {
Expand All @@ -26,7 +26,7 @@ const LineSection = ({ value, onLineChange }: Props) => {
<Styled.Item
key={line.value}
value={line.value}
title={getStyleTitle('Line', line.name)}
title={createTitle('Line', line.name)}
checked={line.value === value}
color={
line.value === value ? 'secondary-dark' : 'secondary-light'
Expand Down
4 changes: 2 additions & 2 deletions apps/client/src/components/Panels/StylePanel/SizeSection.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { memo } from 'react';
import type { NodeSize } from 'shared';
import { SIZE } from '@/constants/panels/style';
import { getStyleTitle } from '@/utils/string';
import { createTitle } from '@/utils/string';
import Icon from '@/components/Elements/Icon/Icon';
import { getSizeValue } from '@/utils/shape';
import * as Styled from './StylePanel.styled';
Expand All @@ -26,7 +26,7 @@ const SizeSection = ({ value, onSizeChange }: Props) => {
return (
<Styled.Item
key={size.name}
title={getStyleTitle('Size', size.name)}
title={createTitle('Size', size.name)}
value={size.value}
checked={size.value === value}
color={
Expand Down
4 changes: 2 additions & 2 deletions apps/client/src/components/Panels/ToolsPanel/ToolsPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Fragment, memo } from 'react';
import Divider from '@/components/Elements/Divider/Divider';
import { TOOLS, type ToolType } from '@/constants/panels/tools';
import { getKeyTitle } from '@/utils/string';
import { createKeyTitle } from '@/utils/string';
import * as Styled from '../Panels.styled';
import Icon from '@/components/Elements/Icon/Icon';

Expand All @@ -19,7 +19,7 @@ const ToolsPanel = ({ activeTool, onToolSelect }: Props) => {
<Styled.Button
size="sm"
color={activeTool === tool.value ? 'primary' : 'secondary'}
title={getKeyTitle(tool.name, [tool.key])}
title={createKeyTitle(tool.name, [tool.key])}
data-testid={`tool-button-${tool.value}`}
onClick={() => onToolSelect(tool.value)}
>
Expand Down
29 changes: 22 additions & 7 deletions apps/client/src/components/Panels/ZoomPanel/ZoomPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { ZOOM } from '@/constants/panels/zoom';
import ZoomPanel from './ZoomPanel';

describe('ZoomPanel', () => {
it('should call handleZoomChange when clicked', () => {
it('should call handleZoomChange when zoom in clicked', () => {
const handleZoomChange = vi.fn();

render(<ZoomPanel value={100} onZoomChange={handleZoomChange} />);

const zoomActions = Object.values(ZOOM);
fireEvent.click(screen.getByTestId('zoom-in-button'));

zoomActions.forEach((action) => {
fireEvent.click(screen.getByTitle(new RegExp(action.name)));
});
expect(handleZoomChange).toHaveBeenCalledTimes(1);
});

it('should call handleZoomChange when zoom out clicked', () => {
const handleZoomChange = vi.fn();

render(<ZoomPanel value={100} onZoomChange={handleZoomChange} />);

fireEvent.click(screen.getByTestId('zoom-out-button'));

expect(handleZoomChange).toHaveBeenCalledTimes(1);
});

it('should call handleZoomChange when zoom reset clicked', () => {
const handleZoomChange = vi.fn();

render(<ZoomPanel value={120} onZoomChange={handleZoomChange} />);

fireEvent.click(screen.getByTestId('zoom-reset-button'));

expect(handleZoomChange).toHaveBeenCalledTimes(zoomActions.length);
expect(handleZoomChange).toHaveBeenCalledTimes(1);
});
});
14 changes: 12 additions & 2 deletions apps/client/src/components/Panels/ZoomPanel/ZoomPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ZOOM, type ZoomAction } from '@/constants/panels/zoom';
import * as PanelStyled from '../Panels.styled';
import * as Styled from './ZoomPanel.styled';
import Icon from '@/components/Elements/Icon/Icon';
import { createKeyTitle } from '@/utils/string';

type Props = {
value: number;
Expand Down Expand Up @@ -40,21 +41,30 @@ const ZoomPanel = ({ value, onZoomChange }: Props) => {
<PanelStyled.Button
disabled={value === DEFAULT_ZOOM_VALUE}
title={ZOOM.reset.name}
data-testId="zoom-reset-button"
css={{ fontSize: '$1', width: 'calc($5 * 1.5)' }}
onClick={() => handleZoomAction(ZOOM.reset.value)}
>
{stageScalePercent}
</PanelStyled.Button>
<PanelStyled.Button
disabled={value === ZOOM_RANGE.MAX}
title={ZOOM.in.name}
title={createKeyTitle(ZOOM.in.name, [
ZOOM.in.key,
...ZOOM.in.modifierKeys,
])}
data-testId="zoom-in-button"
onClick={() => handleZoomAction(ZOOM.in.value)}
>
<Icon name={ZOOM.in.icon} size="sm" />
</PanelStyled.Button>
<PanelStyled.Button
disabled={value === ZOOM_RANGE.MIN}
title={ZOOM.out.name}
title={createKeyTitle(ZOOM.out.name, [
ZOOM.out.key,
...ZOOM.out.modifierKeys,
])}
data-testId="zoom-out-button"
onClick={() => handleZoomAction(ZOOM.out.value)}
>
<Icon name={ZOOM.out.icon} size="sm" />
Expand Down
5 changes: 5 additions & 0 deletions apps/client/src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ export type Entity<Value = string | number> = {
value: Value;
icon: IconName;
};

export type ShortcutKeyCombo<Value = string | number> = Entity<Value> & {
key: string;
modifierKeys: readonly string[];
};
12 changes: 4 additions & 8 deletions apps/client/src/constants/panels/control.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
import type { HistoryActionKey } from '@/stores/reducers/history';
import { type canvasActions } from '@/stores/slices/canvas';
import { KEYS, type Key } from '../keys';
import type { Entity } from '@/constants/index';
import type { ShortcutKeyCombo } from '@/constants/index';

type Control = Entity<HistoryActionKey | keyof typeof canvasActions> & {
key: string;
modifierKeys: readonly Key[];
};
type Control = ShortcutKeyCombo<HistoryActionKey | keyof typeof canvasActions>;

export const CONTROL: readonly Control[] = [
{
name: 'Undo',
value: 'undo',
icon: 'arrowBackUp',
key: 'Z',
modifierKeys: [KEYS.CTRL],
modifierKeys: ['Ctrl'],
},
{
name: 'Redo',
value: 'redo',
icon: 'arrowForwardUp',
key: 'Z',
modifierKeys: [KEYS.CTRL, KEYS.SHIFT],
modifierKeys: ['Ctrl', 'Shift'],
},
{
name: 'Delete',
Expand Down
10 changes: 7 additions & 3 deletions apps/client/src/constants/panels/zoom.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { Entity } from '../index';
import type { Entity, ShortcutKeyCombo } from '../index';

export type ZoomAction = (typeof ZOOM)[keyof typeof ZOOM]['value'];

type Zoom = {
in: Entity;
out: Entity;
in: ShortcutKeyCombo;
out: ShortcutKeyCombo;
reset: Omit<Entity, 'icon'>;
};

Expand All @@ -13,11 +13,15 @@ export const ZOOM: Zoom = {
name: 'Zoom In',
value: 'increase',
icon: 'plus',
key: 'Ctrl',
modifierKeys: ['Mouse Wheel++'],
},
out: {
name: 'Zoom Out',
value: 'decrease',
icon: 'minus',
key: 'Ctrl',
modifierKeys: ['Mouse Wheel--'],
},
reset: {
name: 'Reset Zoom',
Expand Down
18 changes: 9 additions & 9 deletions apps/client/src/utils/__tests__/string.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
capitalizeFirstLetter,
getKeyTitle,
getStyleTitle,
createKeyTitle,
createTitle,
hexToRGBa,
} from '../string';

Expand All @@ -25,27 +25,27 @@ describe('capitalizeFirstLetter', () => {
});
});

describe('getKeyTitle', () => {
describe('createKeyTitle', () => {
it('should return a formatted string for a single key', () => {
expect(getKeyTitle('foo', ['bar'])).toBe('Foo — Bar');
expect(createKeyTitle('foo', ['bar'])).toBe('Foo — Bar');
});

it('should return a formatted string for multiple keys', () => {
expect(getKeyTitle('foo', ['bar', 'foo'])).toBe('Foo — Bar + Foo');
expect(createKeyTitle('foo', ['bar', 'foo'])).toBe('Foo — Bar + Foo');
});

it('should return empty string if name is an empty string', () => {
expect(getKeyTitle('', ['foo', 'bar'])).toBe(' — Foo + Bar');
expect(createKeyTitle('', ['foo', 'bar'])).toBe(' — Foo + Bar');
});

it('should return keys as strings when provided with non-string values', () => {
expect(getKeyTitle('foo', [<any>12, null])).toBe('Foo — 12 + Null');
expect(createKeyTitle('foo', [<any>12, null])).toBe('Foo — 12 + Null');
});
});

describe('getStyleTitle', () => {
describe('createTitle', () => {
it('returns a formatted title', () => {
expect(getStyleTitle('foo', 'bar')).toBe('foo — bar');
expect(createTitle('foo', 'bar')).toBe('foo — bar');
});
});

Expand Down
19 changes: 11 additions & 8 deletions apps/client/src/utils/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,27 @@ export function capitalizeFirstLetter(string: string) {
return string[0].toUpperCase() + string.slice(1).toLowerCase();
}

export const getKeyTitle = (name: string, keys: string[]) => {
export const createTitle = (name: string, value: string) =>
`${name}${value}`;

export const createKeyTitle = (name: string, keys: string[]) => {
if (typeof name !== 'string') {
throw new Error('The provided name must be a string');
}

const capitalizedName = name.length ? capitalizeFirstLetter(name) : '';
const capitalizedName = capitalizeFirstLetter(name);

if (keys.length === 1) {
return `${capitalizedName}${capitalizeFirstLetter(`${keys[0]}`)}`;
const capitalizedKey = capitalizeFirstLetter(keys[0]);

return createTitle(capitalizedName, capitalizedKey);
}

return `${capitalizedName}${keys
const joinedKeys = keys
.map((key) => capitalizeFirstLetter(`${key}`))
.join(' + ')}`;
};
.join(' + ');

export const getStyleTitle = (name: string, value: string) => {
return `${name}${value}`;
return createTitle(capitalizedName, joinedKeys);
};

export const hexToRGBa = (hex: string, alpha = 1) => {
Expand Down

0 comments on commit 177e9d7

Please sign in to comment.