From 2caf0a97f4908b2eaf4d658583fd416403d405d1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=9C=D0=B0?=
 =?UTF-8?q?=D0=BB=D1=8E=D0=B3=D0=B8=D0=BD?= <d.malygin@iqdev.digital>
Date: Fri, 10 Jan 2025 17:36:57 +0500
Subject: [PATCH] feat: component "Tag"

---
 src/Playground.vue                            |   5 +
 src/common/interfaces/componentsProps.ts      |  16 +-
 .../components/Checkbox/Checkbox.stories.ts   |   2 +-
 src/stories/components/Checkbox/Checkbox.vue  |   8 +-
 src/stories/components/Tag/Tag.stories.ts     | 147 ++++++++++++++++++
 src/stories/components/Tag/Tag.vue            |  65 ++++++++
 6 files changed, 235 insertions(+), 8 deletions(-)
 create mode 100644 src/stories/components/Tag/Tag.stories.ts
 create mode 100644 src/stories/components/Tag/Tag.vue

diff --git a/src/Playground.vue b/src/Playground.vue
index 9caebc0..ce15ff7 100644
--- a/src/Playground.vue
+++ b/src/Playground.vue
@@ -14,6 +14,7 @@ import Table from '@stories/components/Table/Table.vue';
 import { ref } from 'vue';
 import type { ISBOption, ISliderOptions, ITableColumn } from '@interfaces/componentsProp';
 import Checkbox from '@stories/components/Checkbox/Checkbox.vue';
+import Tag from '@stories/components/Tag/Tag.vue';
 
 const visibleDrawer = ref(false);
 const sliderOptions: ISliderOptions[] = [
@@ -184,6 +185,10 @@ const activeCheckbox = ref();
 
 <template>
   <h2 class="title gradient-text">Playground</h2>
+  <Tag theme="sky">
+    <template #icon-right><TrashIcon color="#3333aa" size="18" /></template>
+    <template #icon-left><TrashIcon color="sky" size="18" /></template>
+  </Tag>
   {{ activeCheckbox }}
   <Checkbox v-model:active="activeCheckbox" size="small" />
   <Checkbox v-model:active="activeCheckbox" />
diff --git a/src/common/interfaces/componentsProps.ts b/src/common/interfaces/componentsProps.ts
index 443f8b2..7d32e0b 100644
--- a/src/common/interfaces/componentsProps.ts
+++ b/src/common/interfaces/componentsProps.ts
@@ -157,6 +157,20 @@ export interface ITSProps {
   disabled?: boolean;
 }
 
+export interface ITagProps {
+  value?: string;
+  size?: TSize;
+  rounded?: boolean;
+  iconLeft?: TIcons;
+  iconRight?: TIcons;
+  theme?: TThemeColor;
+  background?: TThemeColor;
+  border?: TThemeColor;
+  darknessTheme?: TDarkness;
+  darknessBackground?: TDarkness;
+  darknessBorder?: TDarkness;
+}
+
 export interface ICheckboxProps {
   label?: string;
   labelPos?: TPosition;
@@ -171,7 +185,7 @@ export interface ICheckboxProps {
   darknessTheme?: TDarkness;
   darknessActiveTheme?: TDarkness;
   darknessTextColor?: TDarkness;
-  darknessBorderColor?: TDarkness;
+  darknessBorder?: TDarkness;
 }
 
 export interface IDividerProps {
diff --git a/src/stories/components/Checkbox/Checkbox.stories.ts b/src/stories/components/Checkbox/Checkbox.stories.ts
index d0124e2..9bdc8ac 100644
--- a/src/stories/components/Checkbox/Checkbox.stories.ts
+++ b/src/stories/components/Checkbox/Checkbox.stories.ts
@@ -30,7 +30,7 @@ const meta: Meta = {
       control: 'select',
       options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
     },
-    darknessBorderColor: {
+    darknessBorder: {
       control: 'select',
       options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
     },
diff --git a/src/stories/components/Checkbox/Checkbox.vue b/src/stories/components/Checkbox/Checkbox.vue
index 80eb924..d4572c1 100644
--- a/src/stories/components/Checkbox/Checkbox.vue
+++ b/src/stories/components/Checkbox/Checkbox.vue
@@ -16,7 +16,7 @@ const props = withDefaults(defineProps<ICheckboxProps>(), {
   darknessTheme: '500',
   darknessActiveTheme: '500',
   darknessTextColor: '500',
-  darknessBorderColor: '500',
+  darknessBorder: '500',
 });
 const active = defineModel('active');
 // watch(, () => {});
@@ -27,11 +27,7 @@ const iconColor = computed(() =>
 );
 const color = computed(() => convertThemeToColor(props.textColor, props.darknessTextColor));
 const borderColor = computed(() =>
-  props.invalid
-    ? 'red'
-    : props.disabled
-      ? '#62708c'
-      : convertThemeToColor(props.borderColor, props.darknessBorderColor),
+  props.invalid ? 'red' : props.disabled ? '#62708c' : convertThemeToColor(props.borderColor, props.darknessBorder),
 );
 const elSize = computed(() => {
   const size = props.size;
diff --git a/src/stories/components/Tag/Tag.stories.ts b/src/stories/components/Tag/Tag.stories.ts
new file mode 100644
index 0000000..91c23aa
--- /dev/null
+++ b/src/stories/components/Tag/Tag.stories.ts
@@ -0,0 +1,147 @@
+import type { Meta, StoryObj } from '@storybook/vue3';
+
+import Tag from './Tag.vue';
+import { iconsSet } from '@/common/constants/icons';
+
+const meta: Meta = {
+  title: 'Components/Tag',
+  component: Tag,
+  tags: ['autodocs'],
+  parameters: {
+    docs: {
+      description: {
+        component: 'A component is used to categorize content. Can be used with icon.',
+      },
+    },
+  },
+  argTypes: {
+    value: { control: 'text' },
+    rounded: { control: 'boolean' },
+    size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] },
+    iconLeft: { control: 'select', options: Object.keys(iconsSet) },
+    iconRight: { control: 'select', options: Object.keys(iconsSet) },
+    darknessTheme: {
+      control: 'select',
+      options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
+    },
+    darknessBackground: {
+      control: 'select',
+      options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
+    },
+    darknessBorder: {
+      control: 'select',
+      options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
+    },
+    theme: {
+      control: 'select',
+      options: [
+        'white',
+        'blue',
+        'sky',
+        'cyan',
+        'teal',
+        'green',
+        'yellow',
+        'orange',
+        'pink',
+        'fuchsia',
+        'purple',
+        'indigo',
+        'rose',
+        'red',
+        'black',
+      ],
+    },
+    background: {
+      control: 'select',
+      options: [
+        'white',
+        'blue',
+        'sky',
+        'cyan',
+        'teal',
+        'green',
+        'yellow',
+        'orange',
+        'pink',
+        'fuchsia',
+        'purple',
+        'indigo',
+        'rose',
+        'red',
+        'black',
+      ],
+    },
+    border: {
+      control: 'select',
+      options: [
+        'white',
+        'blue',
+        'sky',
+        'cyan',
+        'teal',
+        'green',
+        'yellow',
+        'orange',
+        'pink',
+        'fuchsia',
+        'purple',
+        'indigo',
+        'rose',
+        'red',
+        'black',
+      ],
+    },
+  },
+  args: {},
+} satisfies Meta<typeof Tag>;
+
+export default meta;
+
+type Story = StoryObj<typeof meta>;
+
+export const Simple: Story = {
+  args: {
+    active: true,
+  },
+};
+
+export const Small: Story = {
+  args: {
+    size: 'small',
+    theme: 'red',
+    value: 'Dangerous',
+    iconRight: 'Age18Icon',
+  },
+};
+
+export const Normal_rounded: Story = {
+  args: {
+    size: 'normal',
+    theme: 'green',
+    value: 'Be calm',
+    rounded: true,
+  },
+};
+
+export const Large: Story = {
+  args: {
+    size: 'large',
+    theme: 'sky',
+    value: 'Ice cream',
+    iconLeft: 'CoinsIcon',
+  },
+};
+
+export const Huge: Story = {
+  args: {
+    size: 'huge',
+    theme: 'indigo',
+    value: 'Unique',
+    iconLeft: 'BallFootballIcon',
+    iconRight: 'ArrowForwardIcon',
+    darknessBackground: '300',
+    darknessBorder: '500',
+    border: 'indigo',
+  },
+};
diff --git a/src/stories/components/Tag/Tag.vue b/src/stories/components/Tag/Tag.vue
new file mode 100644
index 0000000..d7d0839
--- /dev/null
+++ b/src/stories/components/Tag/Tag.vue
@@ -0,0 +1,65 @@
+<script setup lang="ts">
+import type { ITagProps } from '@interfaces/componentsProps';
+import { computed } from 'vue';
+import { convertThemeToColor } from '@helpers/common';
+import { iconsSet } from '@/common/constants/icons';
+
+const props = withDefaults(defineProps<ITagProps>(), {
+  value: 'Tag',
+  size: 'normal',
+  theme: 'black',
+  darknessTheme: '700',
+  darknessBackground: '200',
+  darknessBorder: '500',
+});
+const textColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme));
+const backgroundColor = computed(() =>
+  convertThemeToColor(
+    props.background ?? (props.theme === 'white' ? 'black' : props.theme === 'black' ? 'white' : props.theme),
+    props.darknessBackground,
+  ),
+);
+const borderColor = computed(() => (props.border ? convertThemeToColor(props.border, props.darknessBorder) : ''));
+const fontSize = computed(() => {
+  const size = props.size;
+  if (size === 'normal') return '16px';
+  if (size === 'large') return '20px';
+  if (size === 'huge') return '24px';
+  return '12px';
+});
+const padding = computed(() => {
+  const size = props.size;
+  if (size === 'normal') return '5px 11px';
+  if (size === 'large') return '6px 13px';
+  if (size === 'huge') return '7px 16px';
+  return '3px 7px';
+});
+</script>
+
+<template>
+  <div
+    class="container"
+    :style="`border-radius: ${rounded ? fontSize : `calc(${fontSize} / 2)`}; gap: calc(${fontSize} / 2.5); border: ${['normal', 'small'].includes(size) ? '1px' : '2px'} solid ${borderColor}`"
+  >
+    <slot name="icon-left"></slot>
+    <component v-show="iconLeft" :is="iconsSet[iconLeft ?? 0]" :color="textColor" :size="fontSize.slice(0, -2)" />
+    <span class="text">{{ value }}</span>
+    <component v-show="iconRight" :is="iconsSet[iconRight ?? 0]" :color="textColor" :size="fontSize.slice(0, -2)" />
+    <slot name="icon-right"></slot>
+  </div>
+</template>
+
+<style scoped>
+.container {
+  display: flex;
+  width: max-content;
+  padding: v-bind(padding);
+  align-items: center;
+  background-color: v-bind(backgroundColor);
+}
+.text {
+  font-weight: bold;
+  font-size: v-bind(fontSize);
+  color: v-bind(textColor);
+}
+</style>
-- 
GitLab