Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/early-colts-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': minor
---

Add DirectionIcon component.
5 changes: 5 additions & 0 deletions .changeset/healthy-ducks-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@cube-dev/ui-kit': patch
---

Add a visual gap between the field input and the message below.
3 changes: 2 additions & 1 deletion src/components/form/FieldWrapper/FieldWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const FieldElement = tasty({
InputArea: {
display: 'block',
flow: 'column',
gap: '.5x',
gridColumn: {
'': 'initial',
'has-sider': 2,
Expand All @@ -56,7 +57,7 @@ if (process.env.NODE_ENV === 'development') {
const MessageElement = tasty({
qa: 'Field_Message',
styles: {
preset: 't3',
preset: 't4',
color: {
'': '#dark-02',
invalid: '#danger-text',
Expand Down
87 changes: 87 additions & 0 deletions src/icons/DirectionIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { memo, useEffect, useRef, useState } from 'react';

import { tasty } from '../tasty/index';

import { CubeIconProps } from './Icon';
import { UpIcon } from './UpIcon';

const StyledUpIcon = tasty(UpIcon, {
styles: {
transformOrigin: 'center',
transition: 'rotate linear 120ms, scale linear 120ms',
},
});

export type DirectionIconProps = {
/**
* @default 'bottom'
*/
to?: Direction;
} & CubeIconProps;

const DIRECTIONS = ['left', 'right', 'top', 'bottom'];

type Direction = (typeof DIRECTIONS)[number];

const rotationByDirection: Record<Direction, number> = {
top: 0,
right: 90,
bottom: 180,
left: 270,
};

export const DirectionIcon = memo(function DirectionIcon(
props: DirectionIconProps,
) {
const { to: direction = 'bottom', ...iconProps } = props;
const lastDirectionRef = useRef<Direction>(direction);
const [rotate, setRotate] = useState(rotationByDirection[direction]);
const [flipScale, setFlipScale] = useState(1); // Tracks flipping: 1 (normal) or -1 (flipped)
const lastDirection = lastDirectionRef.current;

useEffect(() => {
if (lastDirection === direction || !DIRECTIONS.includes(direction)) {
return;
}

let appliedRotate = 0;
let nextFlipScale = flipScale;

const lastRotate = rotationByDirection[lastDirection];
const nextRotate = rotationByDirection[direction];
const diffRotate = nextRotate - lastRotate;

if (Math.abs(diffRotate) % 360 !== 180) {
if (nextRotate < lastRotate) {
appliedRotate = 90;
} else {
appliedRotate = -90;
}

if (Math.abs(diffRotate) !== 90) {
appliedRotate = -appliedRotate;
}

if (flipScale) {
appliedRotate = -appliedRotate;
}
} else {
nextFlipScale = -flipScale;
}

setRotate(rotate + appliedRotate);
setFlipScale(nextFlipScale);

lastDirectionRef.current = direction;
}, [direction]);

return (
<StyledUpIcon
{...iconProps}
style={{
rotate: `${rotate}deg`,
scale: `1 ${flipScale}`,
}}
/>
);
});
25 changes: 22 additions & 3 deletions src/icons/Icons.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Flow } from '../components/layout/Flow';

import { CubeIconProps, Icon } from './Icon';

import { SparklesIcon } from './index';
import { SparklesIcon, DirectionIcon } from './index';
import * as Icons from './index';

export default {
Expand All @@ -27,7 +27,12 @@ const Template: StoryFn<CubeIconProps> = (name) => {
<Title>16px</Title>
<Grid columns="repeat(auto-fit, 200px)" flow="row" gap="16px">
{Object.keys(Icons).map((iconName) => {
if (iconName === 'Icon' || iconName === 'wrapIcon') return null;
if (
iconName === 'Icon' ||
iconName === 'wrapIcon' ||
iconName === 'DirectionIcon'
)
return null;

const Icon = Icons[iconName];

Expand All @@ -42,7 +47,12 @@ const Template: StoryFn<CubeIconProps> = (name) => {
<Title>32px</Title>
<Grid columns="repeat(auto-fit, 200px)" flow="row" gap="16px">
{Object.keys(Icons).map((iconName) => {
if (iconName === 'Icon' || iconName === 'wrapIcon') return null;
if (
iconName === 'Icon' ||
iconName === 'wrapIcon' ||
iconName === 'DirectionIcon'
)
return null;

const Icon = Icons[iconName];

Expand All @@ -62,10 +72,19 @@ const TemplateWithSize: StoryFn<CubeIconProps> = ({ size }) => {
return <SparklesIcon size={size} />;
};

const TemplateDirectionIcon: StoryFn<CubeIconProps> = (args) => {
return <DirectionIcon {...args} />;
};

export const Default = Template.bind({});
Default.args = {};

export const WithSize = TemplateWithSize.bind({});
WithSize.args = {
size: '8x',
};

export const Direction = TemplateDirectionIcon.bind({});
Direction.args = {
to: 'bottom',
};
2 changes: 2 additions & 0 deletions src/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { CountIcon } from './CountIcon';
export { CubeIcon } from './CubeIcon';
export { DangerIcon } from './DangerIcon';
export { DashboardIcon } from './DashboardIcon';
export { DirectionIcon } from './DirectionIcon';
export { DonutIcon } from './DonutIcon';
export { DownIcon } from './DownIcon';
export { EditIcon } from './EditIcon';
Expand Down Expand Up @@ -89,3 +90,4 @@ export { wrapIcon } from './wrap-icon';

export { Icon } from './Icon';
export type { CubeIconProps } from './Icon';
export type { DirectionIconProps } from './DirectionIcon';
Loading