Skip to content

Commit

Permalink
feat(Tag): migrate to Tailwind
Browse files Browse the repository at this point in the history
DSil committed Jan 26, 2024
1 parent ee01563 commit 436d54f
Showing 6 changed files with 92 additions and 290 deletions.
2 changes: 1 addition & 1 deletion docs/src/data/tailwind-migration-status.yaml
Original file line number Diff line number Diff line change
@@ -96,7 +96,7 @@ TableRow: true
Tabs: true
TabPanels: true
TabList: true
Tag: false
Tag: true
Tile: true
TileGroup: true
OrbitText: true
10 changes: 5 additions & 5 deletions packages/orbit-components/src/Tag/README.md
Original file line number Diff line number Diff line change
@@ -21,14 +21,14 @@ Table below contains all types of the props available in the Tag component.
| **children** | `React.Node` | | The content of the Tag. |
| dataTest | `string` | | Optional prop for testing purposes. |
| iconLeft | `React.Node` | | The displayed icon on the left. |
| id | `string` | | Set `id` for `Tag` |
| dateTag | `string` | | Optional prop, if it's true, selected color has ink background |
| id | `string` | | Set `id` for `Tag`. |
| dateTag | `string` | | Optional prop, if it's true, selected color has a different background. |
| type | [`enum`](#enum) | `neutral` | The color type of the Tag. |
| onClick | `() => void \| Promise` | | Function for handling the onClick event. |
| onRemove | `() => void \| Promise` | | Function for handling the onClick event of the close icon. [See Functional specs](#functional-specs) |
| selected | `boolean` | `false` | If `true`, the Tag will have selected styles. [See Functional specs](#functional-specs) |
| selected | `boolean` | `false` | If `true`, the Tag will have selected styles. |
| size | [`enum`](#enum) | `small` | Size of the Tag. |
| ref | `func` | | Prop for forwarded ref of the Tag |
| ref | `func` | | Prop for forwarded ref of the Tag. |

### enum

@@ -39,4 +39,4 @@ Table below contains all types of the props available in the Tag component.

## Functional specs

- By passing the `onRemove` the close icon will appear on the left side of the Tag.
- By passing the `onRemove` the close icon will appear on the right side of the Tag.
6 changes: 0 additions & 6 deletions packages/orbit-components/src/Tag/consts.ts
Original file line number Diff line number Diff line change
@@ -3,12 +3,6 @@ export enum SIZES {
NORMAL = "normal",
}

export enum STATES {
DEFAULT = "default",
HOVER = "hover",
ACTIVE = "active",
}

export enum TYPES {
COLORED = "colored",
NEUTRAL = "neutral",
12 changes: 0 additions & 12 deletions packages/orbit-components/src/Tag/helpers/resolveCircleColor.ts

This file was deleted.

28 changes: 0 additions & 28 deletions packages/orbit-components/src/Tag/helpers/resolveColor.ts

This file was deleted.

324 changes: 86 additions & 238 deletions packages/orbit-components/src/Tag/index.tsx
Original file line number Diff line number Diff line change
@@ -1,221 +1,25 @@
"use client";

import * as React from "react";
import styled, { css } from "styled-components";
import cx from "clsx";

import type * as Common from "../common/types";
import type { Theme } from "../defaultTheme";
import defaultTheme from "../defaultTheme";
import { left } from "../utils/rtl";
import CloseCircle from "../icons/CloseCircle";
import { SIZES, STATES, TYPES } from "./consts";
import KEY_CODE_MAP from "../common/keyMaps";
import resolveColor from "./helpers/resolveColor";
import resolveCircleColor from "./helpers/resolveCircleColor";
import mq from "../utils/mediaQuery";
import type { Props, Type } from "./types";

const getFontSize = ({ theme, size }: { theme: Theme; size: Props["size"] }): string | null => {
const tokens = {
[SIZES.SMALL]: theme.orbit.fontSizeTextSmall,
[SIZES.NORMAL]: theme.orbit.fontSizeTextNormal,
};

if (!size) return null;

return tokens[size];
};

const getBackgroundColor =
(state: string) =>
({ type, dateTag }: { type: Type; size: Props["size"]; dateTag?: boolean }): string => {
const states = {
[TYPES.COLORED]: {
[STATES.DEFAULT]: resolveColor({
selected: dateTag ? "paletteInkLightHover" : "paletteBlueNormal",
removable: "paletteBlueLight",
normal: "paletteBlueLight",
}),
[STATES.HOVER]: resolveColor({
selected: dateTag ? "paletteInkLightActive" : "paletteBlueNormalHover",
removable: "paletteBlueLightHover",
normal: "paletteBlueLightHover",
}),
[STATES.ACTIVE]: resolveColor({
selected: dateTag ? "paletteInkLightHover" : "paletteBlueNormalActive",
removable: "paletteBlueLightActive",
normal: "paletteBlueLightActive",
}),
},
[TYPES.NEUTRAL]: {
[STATES.DEFAULT]: resolveColor({
selected: dateTag ? "paletteInkLightHover" : "paletteBlueNormal",
removable: "paletteCloudNormal",
normal: "paletteCloudNormal",
}),
[STATES.HOVER]: resolveColor({
selected: dateTag ? "paletteInkLightActive" : "paletteBlueNormalHover",
removable: "paletteCloudNormalHover",
normal: "paletteCloudNormalHover",
}),
[STATES.ACTIVE]: resolveColor({
selected: dateTag ? "paletteInkLightHover" : "paletteBlueNormalActive",
removable: "paletteCloudNormalActive",
normal: "paletteCloudNormalActive",
}),
},
};
return states[type][state];
};

const getLineHeight = ({ theme, size }: { theme: Theme; size: Props["size"] }): string | null => {
const tokens = {
[SIZES.SMALL]: theme.orbit.lineHeightTextSmall,
[SIZES.NORMAL]: theme.orbit.lineHeightTextNormal,
};

if (!size) return null;

return tokens[size];
};

const CloseContainer = styled.div<{
actionable?: boolean;
type: Type;
selected?: boolean;
removable?: boolean;
}>`
${({ theme, actionable, type, selected }) => css`
display: flex;
margin-${left}: ${theme.orbit.spaceXSmall};
opacity: ${selected ? "1" : "0.5"};
color: ${resolveColor({
selected: "paletteWhite",
removable: type === TYPES.NEUTRAL ? "paletteInkNormal" : "paletteBlueDarker",
normal: "paletteInkLink",
})};
cursor: ${actionable && `pointer`};
transition: all ${theme.orbit.durationFast} ease-in-out;
&:active {
color: ${resolveCircleColor};
}
`};
`;

CloseContainer.defaultProps = {
theme: defaultTheme,
};

export const StyledTag = styled.div<{
selected?: boolean;
actionable?: boolean;
type: Type;
size: Props["size"];
dateTag?: boolean;
removable?: boolean;
}>`
${({ theme, actionable, type }) => css`
font-family: ${theme.orbit.fontFamily};
color: ${resolveColor({
selected: "paletteWhite",
removable: type === TYPES.NEUTRAL ? "paletteInkDark" : "paletteBlueDarker",
normal: type === TYPES.NEUTRAL ? "paletteInkDark" : "paletteBlueDarker",
})};
background: ${getBackgroundColor(STATES.DEFAULT)};
display: inline-flex;
box-sizing: border-box;
justify-content: center;
align-items: center;
line-height: ${getLineHeight};
font-size: ${getFontSize};
font-weight: ${theme.orbit.fontWeightMedium};
border-radius: ${theme.orbit.borderRadiusLarge};
padding: ${theme.orbit.spaceXSmall};
transition: color ${theme.orbit.durationFast} ease-in-out,
box-shadow ${theme.orbit.durationFast} ease-in-out,
background ${theme.orbit.durationFast} ease-in-out;
${mq.tablet(css`
border-radius: ${theme.orbit.borderRadiusNormal};
`)}
${actionable &&
css`
cursor: pointer;
&:hover,
&:focus-visible {
background: ${getBackgroundColor(STATES.HOVER)};
box-shadow: none;
}
&:focus {
background: ${getBackgroundColor(STATES.HOVER)};
}
&:active {
${CloseContainer} {
opacity: 1;
}
background: ${getBackgroundColor(STATES.ACTIVE)};
}
&:focus:not(:focus-visible):not(:active) {
background: ${getBackgroundColor(STATES.HOVER)};
}
`};
`}
`;

StyledTag.defaultProps = {
theme: defaultTheme,
};

const StyledClose = styled.div`
display: flex;
border-radius: 100%;
&:focus {
${CloseContainer} {
opacity: 1;
}
import { SIZES, TYPES } from "./consts";
import type { Props } from "./types";

const buttonClickEmulation = (
ev?: React.KeyboardEvent<HTMLDivElement>,
callback?: Common.Callback,
) => {
if (ev && ev.code === "Space") {
ev.preventDefault();
if (callback) callback();
} else if (ev && ev.code === "Enter") {
if (callback) callback();
}
`;

StyledClose.defaultProps = {
theme: defaultTheme,
};

const StyledIconContainer = styled.div`
${({ theme }) => css`
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding-right: ${theme.orbit.spaceXSmall};
svg {
width: ${theme.orbit.widthIconSmall};
height: ${theme.orbit.heightIconSmall};
}
`}
`;

StyledIconContainer.defaultProps = {
theme: defaultTheme,
};

const buttonClickEmulation =
(callback?: Common.Callback) => (ev?: React.KeyboardEvent<HTMLDivElement>) => {
if (ev && ev.keyCode === KEY_CODE_MAP.SPACE) {
ev.preventDefault();
if (callback) callback();
} else if (ev && ev.keyCode === KEY_CODE_MAP.ENTER) {
if (callback) callback();
}
};

const Tag = React.forwardRef<HTMLDivElement, Props>(
(
{
@@ -233,48 +37,92 @@ const Tag = React.forwardRef<HTMLDivElement, Props>(
ref,
) => {
return (
<StyledTag
className="orbit-tag"
actionable={!!(onClick || onRemove)}
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
<div
className={cx(
"orbit-tag",
"font-base rounded-large p-xs box-border inline-flex items-center justify-center font-medium",
"duration-fast transition-[color,_background-color,_box-shadow] ease-in-out",
"tb:rounded-normal",
size === SIZES.SMALL && "text-small leading-small",
size === SIZES.NORMAL && "text-normal leading-normal",
!!(onClick || onRemove) &&
"cursor-pointer [&_.orbit-tag-close-container]:active:opacity-100",
selected && [
"text-white-normal",
dateTag
? [
"bg-ink-light-hover",
!!(onClick || onRemove) &&
"hover:bg-ink-light-active focus:bg-ink-light-active active:bg-ink-light-hover",
]
: [
"bg-blue-normal",
!!(onClick || onRemove) &&
"hover:bg-blue-normal-hover focus:bg-blue-normal-hover active:bg-blue-normal-active",
],
],
type === TYPES.NEUTRAL && [
"text-ink-dark",
!selected && [
"bg-cloud-normal",
!!(onClick || onRemove) &&
"hover:bg-cloud-normal-hover focus:bg-cloud-normal-hover active:bg-cloud-normal-active",
],
],
type === TYPES.COLORED && [
"text-blue-darker",
!selected && [
"bg-blue-light",
!!(onClick || onRemove) &&
"hover:bg-blue-light-hover focus:bg-blue-light-hover active:bg-blue-light-active",
],
],
)}
data-test={dataTest}
id={id}
dateTag={dateTag}
size={size}
ref={ref}
type={type}
onClick={onClick}
removable={!!onRemove}
selected={selected}
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={(onClick || onRemove) && 0}
role="button"
onKeyDown={buttonClickEmulation(onClick)}
role={onClick || onRemove ? "button" : undefined}
onKeyDown={ev => buttonClickEmulation(ev, onClick)}
>
{iconLeft && <StyledIconContainer>{iconLeft}</StyledIconContainer>}
{iconLeft && (
<div className="pr-xs [&_svg]:size-icon-small flex flex-row items-center justify-center">
{iconLeft}
</div>
)}
{children}
{onRemove && (
<CloseContainer
selected={selected}
removable={!!onRemove}
type={type}
<div
className={cx(
"orbit-tag-close-container",
"ms-xs rounded-circle flex",
"duration-fast transition-[color,_opacity] ease-in-out",
"focus:opacity-100",
!selected &&
(type === TYPES.NEUTRAL
? "text-ink-normal active:text-ink-dark"
: "text-blue-darker"),
selected ? "text-white-normal opacity-100" : "opacity-50",
)}
tabIndex={0}
aria-label="close"
role="button"
onKeyDown={ev => {
ev.stopPropagation();
buttonClickEmulation(ev, onRemove);
}}
onClick={ev => {
ev.stopPropagation();
if (onRemove) onRemove();
onRemove();
}}
>
<StyledClose
tabIndex={0}
aria-label="close"
role="button"
onKeyDown={ev => {
ev.stopPropagation();
buttonClickEmulation(onRemove);
}}
>
<CloseCircle size="small" />
</StyledClose>
</CloseContainer>
<CloseCircle size="small" />
</div>
)}
</StyledTag>
</div>
);
},
);

0 comments on commit 436d54f

Please sign in to comment.