diff --git a/packages/assets/src/scss/_tags.scss b/packages/assets/src/scss/_tags.scss new file mode 100644 index 00000000..1f35c8e2 --- /dev/null +++ b/packages/assets/src/scss/_tags.scss @@ -0,0 +1,226 @@ +@use 'sass:map'; +@use 'functions' as *; +@use 'variables' as *; +@use 'mixins/tags' as mixins; + +$tags-types: ( + 'primary': ( + 'default': ( + 'text': $color-primary-80, + 'bg': $color-primary-10, + 'border': $color-primary-60, + ), + 'dark': ( + 'text': $color-primary-60, + 'bg': transparent, + 'border': $color-primary-80, + ), + ), + 'primary-alt': ( + 'default': ( + 'text': $color-primary_alt-90, + 'bg': $color-primary_alt-20, + 'border': $color-primary_alt-60, + ), + 'dark': ( + 'text': $color-primary_alt-60, + 'bg': transparent, + 'border': $color-primary_alt-90, + ), + ), + 'success': ( + 'default': ( + 'text': $color-success-90, + 'bg': $color-success-10, + 'border': $color-success-60, + ), + 'dark': ( + 'text': $color-success-70, + 'bg': transparent, + 'border': $color-success-80, + ), + ), + 'info': ( + 'default': ( + 'text': $color-info-100, + 'bg': $color-info-10, + 'border': $color-info-60, + ), + 'dark': ( + 'text': $color-info-70, + 'bg': transparent, + 'border': $color-info-80, + ), + ), + 'warning': ( + 'default': ( + 'text': $color-warning-100, + 'bg': $color-warning-10, + 'border': $color-warning-70, + ), + 'dark': ( + 'text': $color-warning-70, + 'bg': transparent, + 'border': $color-warning-90, + ), + ), + 'neutral': ( + 'default': ( + 'text': $color-neutral-190, + 'bg': $color-neutral-20, + 'border': $color-neutral-170, + ), + 'dark': ( + 'text': $color-neutral-110, + 'bg': transparent, + 'border': $color-neutral-140, + ), + ), + 'error': ( + 'default': ( + 'text': $color-error-90, + 'bg': $color-error-10, + 'border': $color-error-90, + ), + 'dark': ( + 'text': $color-error-70, + 'bg': transparent, + 'border': $color-error-80, + ), + ), + 'icon-tag': ( + 'default': ( + 'text': $color-neutral-210, + 'bg': $color-neutral-20, + 'border': $color-neutral-110, + ), + 'dark': ( + 'text': $color-neutral-90, + 'bg': transparent, + 'border': $color-neutral-180, + ), + ), + 'success-ghost': ( + 'default': ( + 'text': $color-neutral-240, + 'bg': transparent, + 'border': transparent, + 'dot': $color-success-90, + ), + 'dark': ( + 'text': $color-neutral-10, + 'bg': transparent, + 'border': transparent, + 'dot': $color-success-70, + ), + ), + 'neutral-ghost': ( + 'default': ( + 'text': $color-neutral-240, + 'bg': transparent, + 'border': transparent, + 'dot': $color-neutral-120, + ), + 'dark': ( + 'text': $color-neutral-10, + 'bg': transparent, + 'border': transparent, + 'dot': $color-neutral-120, + ), + ), + 'error-ghost': ( + 'default': ( + 'text': $color-neutral-240, + 'bg': transparent, + 'border': transparent, + 'dot': $color-error-90, + ), + 'dark': ( + 'text': $color-neutral-10, + 'bg': transparent, + 'border': transparent, + 'dot': $color-error-70, + ), + ), +); + +$tags-sizes: ( + 'medium': ( + 'text': $text-font-size-s, + 'padding-x': calculateRem(8px), + 'padding-y': calculateRem(3px), + 'dot': calculateRem(6px), + ), + 'small': ( + 'text': $text-font-size-xs, + 'padding-x': calculateRem(4px), + 'padding-y': 0, + 'dot': calculateRem(4px), + ), +); + +.ids-tag { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + white-space: nowrap; + border: calculateRem(1px) solid var(--ids-default-border-color); + border-radius: calculateRem(16px); + padding: calculateRem(3px) calculateRem(8px); + font-size: $text-font-size-s; + color: var(--ids-default-text-color); + fill: var(--ids-default-text-color); + background: var(--ids-default-bg-color); + transition: all 1s $transition-timing-function; + + @each $name, $theme in $tags-types { + &--#{$name} { + @include mixins.tag-variant(map.get($theme, 'default')); + } + + //TODO: decide way of implementing variant on dark background + &--#{$name}.ids-tag--dark { + @include mixins.tag-variant(map.get($theme, 'dark')); + + &.ids-tag::before { + content: ''; + position: absolute; + top: calculateRem(-30px); + left: calculateRem(-60px); + right: calculateRem(-60px); + bottom: calculateRem(-30px); + background: $color-neutral-240; + z-index: -1; + } + } + } + + @each $name, $values in $tags-sizes { + &--#{$name} { + font-size: map.get($values, 'text'); + padding: map.get($values, 'padding-y') map.get($values, 'padding-x'); + } + } + + &__content { + line-height: $base-line-height; + } + + &__ghost-dot { + margin: 0 calculateRem(6px) calculateRem(2px) 0; + border-radius: 50%; + background: var(--ids-default-dot-color); + @each $size, $values in $tags-sizes { + .ids-tag--#{$size} & { + width: map.get($values, 'dot'); + height: map.get($values, 'dot'); + } + } + } + + &__icon { + line-height: 0; + margin-right: calculateRem(4px); + } +} diff --git a/packages/assets/src/scss/mixins/_buttons.scss b/packages/assets/src/scss/mixins/_buttons.scss index 573c5564..8c528d35 100644 --- a/packages/assets/src/scss/mixins/_buttons.scss +++ b/packages/assets/src/scss/mixins/_buttons.scss @@ -2,16 +2,8 @@ @use '../functions' as *; @mixin button-state($variables, $prefix) { - @if map.has-key($variables, 'text') { - --ids-#{$prefix}-text-color: #{map.get($variables, 'text')}; - } - - @if map.has-key($variables, 'bg') { - --ids-#{$prefix}-bg-color: #{map.get($variables, 'bg')}; - } - - @if map.has-key($variables, 'border') { - --ids-#{$prefix}-border-color: #{map.get($variables, 'border')}; + @each $key, $value in $variables { + --ids-#{$prefix}-#{$key}-color: #{$value}; } @if map.has-key($variables, 'box-shadow') { diff --git a/packages/assets/src/scss/mixins/_tags.scss b/packages/assets/src/scss/mixins/_tags.scss new file mode 100644 index 00000000..bae2a703 --- /dev/null +++ b/packages/assets/src/scss/mixins/_tags.scss @@ -0,0 +1,16 @@ +@use 'sass:map'; +@use '../functions' as *; + +@mixin tag-state($variables, $prefix) { + @each $key, $value in $variables { + --ids-#{$prefix}-#{$key}-color: #{$value}; + } +} + +@mixin tag-variant($default, $dark: false) { + @include tag-state($default, 'default'); + + @if $dark { + @include tag-state($dark, 'dark'); + } +} diff --git a/packages/assets/src/scss/styles.scss b/packages/assets/src/scss/styles.scss index c1ddc353..f6a0e61d 100644 --- a/packages/assets/src/scss/styles.scss +++ b/packages/assets/src/scss/styles.scss @@ -18,6 +18,7 @@ @use 'inputs-list'; @use 'label'; @use 'links'; +@use 'tags'; @use 'inputs/alt-radio'; @use 'inputs/checkbox'; diff --git a/packages/components/src/components/Tag/Tag.stories.tsx b/packages/components/src/components/Tag/Tag.stories.tsx new file mode 100644 index 00000000..769db2fc --- /dev/null +++ b/packages/components/src/components/Tag/Tag.stories.tsx @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Tag, TagGhostType, TagSize, TagType } from './'; + +const meta: Meta = { + component: Tag, + parameters: { + layout: 'centered', + }, + tags: ['autodocs', 'foundation'], + args: { + children: 'Label', + size: TagSize.Medium, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + name: 'Primary / Default', + args: { + type: TagType.Primary, + }, +}; + +export const PrimaryAlt: Story = { + name: 'Primary Alt', + args: { + type: TagType.PrimaryAlt, + }, +}; + +export const PrimaryAltSmall: Story = { + name: 'Primary Alt Small', + args: { + type: TagType.PrimaryAlt, + size: TagSize.Small, + }, +}; + +export const Success: Story = { + name: 'Success', + args: { + type: TagType.Success, + }, +}; + +export const Info: Story = { + name: 'Info', + args: { + type: TagType.Info, + }, +}; + +export const Warning: Story = { + name: 'Warning', + args: { + type: TagType.Warning, + }, +}; + +export const Neutral: Story = { + name: 'Neutral', + args: { + type: TagType.Neutral, + }, +}; + +export const Error: Story = { + name: 'Error', + args: { + type: TagType.Error, + }, +}; + +export const IconTag: Story = { + name: 'Icon tag', + args: { + icon: 'translation-language', + type: TagType.IconTag, + }, +}; + +export const SuccessGhost: Story = { + name: 'Success Ghost', + args: { + type: TagGhostType.SuccessGhost, + }, +}; + +export const NeutralGhost: Story = { + name: 'Neutral Ghost', + args: { + type: TagGhostType.NeutralGhost, + }, +}; + +export const ErrorGhost: Story = { + name: 'Error Ghost', + args: { + type: TagGhostType.ErrorGhost, + }, +}; diff --git a/packages/components/src/components/Tag/Tag.tsx b/packages/components/src/components/Tag/Tag.tsx new file mode 100644 index 00000000..11f7a015 --- /dev/null +++ b/packages/components/src/components/Tag/Tag.tsx @@ -0,0 +1,49 @@ +import React from 'react'; + +import { Icon, IconSize } from '@ids-components/Icon'; +import { createCssClassNames } from '@ids-core/helpers/cssClassNames'; + +import { TagGhostType, TagProps, TagSize, TagType } from './Tag.types'; + +export const Tag = ({ children, className = '', isDark = false, icon, size = TagSize.Medium, type }: TagProps) => { + const isGhostType = (tagType: TagType | TagGhostType): tagType is TagGhostType => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + return Object.values(TagGhostType).includes(tagType as TagGhostType); + }; + const isGhost = isGhostType(type); + const componentClassName = createCssClassNames({ + 'ids-tag': true, + [`ids-tag--${type}`]: true, + [`ids-tag--${size}`]: true, + [`ids-tag--dark`]: isDark, + [className]: !!className, + }); + + const renderDot = () => { + if (isGhost) { + return
; + } + + return null; + }; + + const renderIcon = () => { + if (icon) { + return ( +
+ +
+ ); + } + + return null; + }; + + return ( +
+ {renderDot()} + {renderIcon()} +
{children}
+
+ ); +}; diff --git a/packages/components/src/components/Tag/Tag.types.ts b/packages/components/src/components/Tag/Tag.types.ts new file mode 100644 index 00000000..c2113b21 --- /dev/null +++ b/packages/components/src/components/Tag/Tag.types.ts @@ -0,0 +1,31 @@ +import { BaseComponentAttributes } from '@ids-types/general'; + +export enum TagSize { + Medium = 'medium', + Small = 'small', +} + +export enum TagGhostType { + SuccessGhost = 'success-ghost', + ErrorGhost = 'error-ghost', + NeutralGhost = 'neutral-ghost', +} + +export enum TagType { + Primary = 'primary', + PrimaryAlt = 'primary-alt', + Success = 'success', + Info = 'info', + Warning = 'warning', + Error = 'error', + Neutral = 'neutral', + IconTag = 'icon-tag', +} + +export interface TagProps extends BaseComponentAttributes { + children: React.ReactNode; + type: TagType | TagGhostType; + icon?: string; + size?: TagSize; + isDark?: boolean; //TODO: decide way of implementing variant on dark background +} diff --git a/packages/components/src/components/Tag/index.ts b/packages/components/src/components/Tag/index.ts new file mode 100644 index 00000000..dc5d505d --- /dev/null +++ b/packages/components/src/components/Tag/index.ts @@ -0,0 +1,2 @@ +export * from './Tag'; +export * from './Tag.types';