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(vx-shape): add BarRounded shape #774

Merged
merged 3 commits into from
Aug 2, 2020
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
1 change: 1 addition & 0 deletions packages/vx-shape/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export { default as Area } from './shapes/Area';
export { default as AreaClosed } from './shapes/AreaClosed';
export { default as AreaStack } from './shapes/AreaStack';
export { default as Bar } from './shapes/Bar';
export { default as BarRounded } from './shapes/BarRounded';
export { default as BarGroup } from './shapes/BarGroup';
export { default as BarGroupHorizontal } from './shapes/BarGroupHorizontal';
export { default as BarStack } from './shapes/BarStack';
Expand Down
82 changes: 82 additions & 0 deletions packages/vx-shape/src/shapes/BarRounded.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React from 'react';
import cx from 'classnames';

export type BarRoundedProps = {
/** className to apply to path element. */
className?: string;
/** reference to path element. */
innerRef?: React.Ref<SVGPathElement>;
/** left position of the bar */
x: number;
/** top position of the bar */
y: number;
/** width of the bar starting from x */
width: number;
/** height of the bar starting from y */
height: number;
/** corner radius of the bar. clamped to center of the shorter side of the bar (Math.min(width,height) / 2) */
radius: number;
/** apply corner radius to top left corner, top right corner, bottom right corner, and bottom left corner */
all?: boolean;
/** apply corner radius to top left corner, and top right corner */
top?: boolean;
/** apply corner radius to bottom right corner, and bottom left corner */
bottom?: boolean;
/** apply corner radius to top left corner, and bottom left corner */
left?: boolean;
/** apply corner radius to top right corner, and bottom right corner */
right?: boolean;
/** apply corner radius to top left corner */
topLeft?: boolean;
/** apply corner radius to top right corner */
topRight?: boolean;
/** apply corner radius to bottom left corner */
bottomLeft?: boolean;
/** apply corner radius to bottom right */
bottomRight?: boolean;
};

export default function BarRounded({
className,
innerRef,
x,
y,
width,
height,
radius,
all = false,
top = false,
bottom = false,
left = false,
right = false,
topLeft = false,
topRight = false,
bottomLeft = false,
bottomRight = false,
...restProps
}: BarRoundedProps & Omit<React.SVGProps<SVGPathElement>, keyof BarRoundedProps>) {
topRight = all || top || right || topRight;
bottomRight = all || bottom || right || bottomRight;
bottomLeft = all || bottom || left || bottomLeft;
topLeft = all || top || left || topLeft;

// clamp radius to center of shortest side of the rect
radius = Math.min(radius, Math.min(width, height) / 2);

const diameter = 2 * radius;
const path = `M${x + radius},${y} h${width - diameter}
${topRight ? `a${radius},${radius} 0 0 1 ${radius},${radius}` : `h${radius}v${radius}`}
v${height - diameter}
${bottomRight ? `a${radius},${radius} 0 0 1 ${-radius},${radius}` : `v${radius}h${-radius}`}
h${diameter - width}
${bottomLeft ? `a${radius},${radius} 0 0 1 ${-radius},${-radius}` : `h${-radius}v${-radius}`}
v${diameter - height}
${topLeft ? `a${radius},${radius} 0 0 1 ${radius},${-radius}` : `v${-radius}h${radius}`}
z`
.split('\n')
.join('');

return (
<path ref={innerRef} className={cx('vx-bar-rounded', className)} d={path} {...restProps} />
);
}
98 changes: 98 additions & 0 deletions packages/vx-shape/test/BarRounded.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react';
import { shallow, mount } from 'enzyme';

import { BarRounded } from '../src';

const testProps = { x: 0, y: 0, width: 10, height: 20, radius: 2 };
const BarRoundedWrapper = (restProps = {}) => shallow(<BarRounded {...testProps} {...restProps} />);

describe('<BarRounded />', () => {
test('it should be defined', () => {
expect(BarRounded).toBeDefined();
});

test('it should have the .vx-bar class', () => {
expect(
BarRoundedWrapper({
className: 'test',
}).prop('className'),
).toBe('vx-bar-rounded test');
});

test('it should expose its ref via an innerRef prop', () => {
return new Promise(done => {
const refCallback = (ref: SVGPathElement) => {
expect(ref.tagName).toMatch('path');
done();
};
mount(
<svg>
<BarRounded innerRef={refCallback} {...testProps} />
</svg>,
);
});
});

test('it should set top left corner radius', () => {
const wrapper = BarRoundedWrapper({ topLeft: true });
expect(wrapper.prop('d')).toBe('M2,0 h6 h2v2 v16 v2h-2 h-6 h-2v-2 v-16 a2,2 0 0 1 2,-2z');
});

test('it should set top right corner radius', () => {
const wrapper = BarRoundedWrapper({ topRight: true });
expect(wrapper.prop('d')).toBe('M2,0 h6 a2,2 0 0 1 2,2 v16 v2h-2 h-6 h-2v-2 v-16 v-2h2z');
});

test('it should set bottom left corner radius', () => {
const wrapper = BarRoundedWrapper({ bottomLeft: true });
expect(wrapper.prop('d')).toBe('M2,0 h6 h2v2 v16 v2h-2 h-6 a2,2 0 0 1 -2,-2 v-16 v-2h2z');
});

test('it should set bottom right corner radius', () => {
const wrapper = BarRoundedWrapper({ bottomRight: true });
expect(wrapper.prop('d')).toBe('M2,0 h6 h2v2 v16 a2,2 0 0 1 -2,2 h-6 h-2v-2 v-16 v-2h2z');
});

test('it should set top left & top right corner radius', () => {
const wrapper = BarRoundedWrapper({ top: true });
expect(wrapper.prop('d')).toBe(
'M2,0 h6 a2,2 0 0 1 2,2 v16 v2h-2 h-6 h-2v-2 v-16 a2,2 0 0 1 2,-2z',
);
});

test('it should set bottom left & bottom right corner radius', () => {
const wrapper = BarRoundedWrapper({ bottom: true });
expect(wrapper.prop('d')).toBe(
'M2,0 h6 h2v2 v16 a2,2 0 0 1 -2,2 h-6 a2,2 0 0 1 -2,-2 v-16 v-2h2z',
);
});

test('it should set top left & bottom left corner radius', () => {
const wrapper = BarRoundedWrapper({ left: true });
expect(wrapper.prop('d')).toBe(
'M2,0 h6 h2v2 v16 v2h-2 h-6 a2,2 0 0 1 -2,-2 v-16 a2,2 0 0 1 2,-2z',
);
});

test('it should set top right & bottom right corner radius', () => {
const wrapper = BarRoundedWrapper({ right: true });
expect(wrapper.prop('d')).toBe(
'M2,0 h6 a2,2 0 0 1 2,2 v16 a2,2 0 0 1 -2,2 h-6 h-2v-2 v-16 v-2h2z',
);
});

test('it should set all corner radius', () => {
const wrapper = BarRoundedWrapper({ all: true });
expect(wrapper.prop('d')).toBe(
'M2,0 h6 a2,2 0 0 1 2,2 v16 a2,2 0 0 1 -2,2 h-6 a2,2 0 0 1 -2,-2 v-16 a2,2 0 0 1 2,-2z',
);
});

test('it should clamp radius to the center of the shortest side of the rect', () => {
const wrapper = BarRoundedWrapper({ topLeft: true, width: 4, radius: 400 });
const r = Math.min(4, testProps.height) / 2;
expect(wrapper.prop('d')).toBe(
`M2,0 h0 h2v2 v16 v2h-2 h0 h-2v-2 v-16 a${r},${r} 0 0 1 ${r},-${r}z`,
);
});
});