From 432693e35efdb4309292286c2ae771e61f3fe323 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: Sat, 18 Jan 2025 19:58:54 +0500
Subject: [PATCH] feat: component 'ProgressBar'

---
 src/common/interfaces/componentsProp.ts       |   2 +-
 src/common/interfaces/componentsProps.ts      |  24 ++-
 src/components/Knob/helpers.ts                |   4 +-
 .../ProgressBar/ProgressBar.stories.ts        | 140 ++++++++++++++++++
 src/components/ProgressBar/ProgressBar.vue    |  84 +++++++++++
 5 files changed, 248 insertions(+), 6 deletions(-)
 create mode 100644 src/components/ProgressBar/ProgressBar.stories.ts
 create mode 100644 src/components/ProgressBar/ProgressBar.vue

diff --git a/src/common/interfaces/componentsProp.ts b/src/common/interfaces/componentsProp.ts
index 0b549ea..ae44f55 100644
--- a/src/common/interfaces/componentsProp.ts
+++ b/src/common/interfaces/componentsProp.ts
@@ -55,7 +55,7 @@ export interface IMDItemProps {
   onClick?: () => void;
 }
 
-export interface IKnobColorGap {
+export interface IColorGap {
   start: number;
   end: number;
   color: TThemeColor;
diff --git a/src/common/interfaces/componentsProps.ts b/src/common/interfaces/componentsProps.ts
index ec87cbd..07b4d81 100644
--- a/src/common/interfaces/componentsProps.ts
+++ b/src/common/interfaces/componentsProps.ts
@@ -11,14 +11,13 @@ import type {
   TThemeColorNoWhite,
 } from '@interfaces/common';
 import type {
-  IKnobColorGap,
+  IColorGap,
   IMDItemProps,
   ISBOption,
   ISelectGroup,
   ISelectOption,
   ISliderOptions,
   ITableColumn,
-  ITableItem,
   ITreeItem,
 } from '@interfaces/componentsProp';
 
@@ -74,7 +73,7 @@ export interface IKnobProps {
   step?: number;
   size?: TSize;
   theme?: TThemeColor;
-  colorGaps?: IKnobColorGap[];
+  colorGaps?: IColorGap[];
   negativeTheme?: TThemeColor;
   color?: TThemeColor;
   background?: string;
@@ -192,6 +191,25 @@ export interface IButtonProps {
   darknessTextColor?: TDarkness;
 }
 
+export interface IProgressBarProps {
+  value: number;
+  max?: number;
+  width?: string;
+  height?: string;
+  size?: TSize;
+  fontSize?: string;
+  colorGaps?: IColorGap[];
+  colorInactiveGaps?: IColorGap[];
+  theme?: TThemeColor;
+  inactiveTheme?: TThemeColor;
+  darknessTheme?: TDarkness;
+  darknessInactiveTheme?: TDarkness;
+  showLabel?: boolean;
+  labelBefore?: string;
+  labelAfter?: string;
+  noBorder?: boolean;
+}
+
 export interface IRatingProps {
   count?: number;
   size?: TSize;
diff --git a/src/components/Knob/helpers.ts b/src/components/Knob/helpers.ts
index 2a53cd0..56cb24e 100644
--- a/src/components/Knob/helpers.ts
+++ b/src/components/Knob/helpers.ts
@@ -1,6 +1,6 @@
 import { convertThemeToColor } from '@helpers/common';
 import { EThemeColor, type TDarkness, type TSize, type TThemeColor } from '@interfaces/common';
-import type { IKnobColorGap } from '@interfaces/componentsProp';
+import type { IColorGap } from '@interfaces/componentsProp';
 
 export const calcNewValue = (
   event: MouseEvent,
@@ -40,7 +40,7 @@ export const calcStart = (container: Element) => {
 };
 
 export const calcThemeColor = (
-  colorGaps: IKnobColorGap[] | undefined,
+  colorGaps: IColorGap[] | undefined,
   theme: TThemeColor,
   darknessTheme: TDarkness,
   value: number,
diff --git a/src/components/ProgressBar/ProgressBar.stories.ts b/src/components/ProgressBar/ProgressBar.stories.ts
new file mode 100644
index 0000000..27e2b24
--- /dev/null
+++ b/src/components/ProgressBar/ProgressBar.stories.ts
@@ -0,0 +1,140 @@
+import type { Meta, StoryObj } from '@storybook/vue3';
+
+import ProgressBar from './ProgressBar.vue';
+
+const meta: Meta = {
+  title: 'Components/ProgressBar',
+  component: ProgressBar,
+  tags: ['autodocs'],
+  parameters: {
+    docs: {
+      description: {
+        component: 'A component that is used as a ProgressBar.',
+      },
+    },
+  },
+  argTypes: {
+    value: { control: 'number' },
+    max: { control: 'number' },
+    width: { control: 'text' },
+    height: { control: 'text' },
+    labelBefore: { control: 'text' },
+    labelAfter: { control: 'text' },
+    showLabel: { control: 'boolean' },
+    noBorder: { control: 'boolean' },
+    size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] },
+    fontSize: { control: 'text' },
+    colorGaps: { control: 'object' },
+    colorInactiveGaps: { control: 'object' },
+    darknessTheme: { control: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] },
+    darknessInactiveTheme: {
+      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',
+      ],
+    },
+    inactiveTheme: {
+      control: 'select',
+      options: [
+        'white',
+        'blue',
+        'sky',
+        'cyan',
+        'teal',
+        'green',
+        'yellow',
+        'orange',
+        'pink',
+        'fuchsia',
+        'purple',
+        'indigo',
+        'rose',
+        'red',
+        'black',
+      ],
+    },
+  },
+  args: {},
+} satisfies Meta<typeof ProgressBar>;
+
+export default meta;
+
+type Story = StoryObj<typeof meta>;
+
+export const Simple: Story = {
+  args: {
+    value: 40,
+  },
+};
+
+export const Small: Story = {
+  args: {
+    size: 'small',
+    value: 40,
+    theme: 'red',
+    inactiveTheme: 'red',
+    labelAfter: '/100',
+    width: '500px',
+  },
+};
+
+export const Full: Story = {
+  args: {
+    colorGaps: [
+      {
+        start: 0,
+        end: 10,
+        color: 'purple',
+        darknessColor: '700',
+      },
+      {
+        start: 10,
+        end: 20,
+        color: 'red',
+      },
+      {
+        start: 20,
+        end: 30,
+        color: 'orange',
+      },
+      {
+        start: 30,
+        end: 40,
+        color: 'yellow',
+      },
+      {
+        start: 40,
+        end: 50,
+        color: 'green',
+      },
+    ],
+
+    size: 'huge',
+    value: 35,
+    max: 50,
+    labelAfter: '',
+    showLabel: true,
+    labelBefore: '$',
+    inactiveTheme: 'white',
+    darknessInactiveTheme: '400',
+    noBorder: true,
+  },
+};
diff --git a/src/components/ProgressBar/ProgressBar.vue b/src/components/ProgressBar/ProgressBar.vue
new file mode 100644
index 0000000..61cd383
--- /dev/null
+++ b/src/components/ProgressBar/ProgressBar.vue
@@ -0,0 +1,84 @@
+<script setup lang="ts">
+import type { IProgressBarProps } from '@interfaces/componentsProps';
+import { computed } from 'vue';
+import { convertThemeToColor, convertThemeToTextColor } from '@helpers/common';
+
+const props = withDefaults(defineProps<IProgressBarProps>(), {
+  value: 0,
+  max: 100,
+  size: 'normal',
+  theme: 'black',
+  inactiveTheme: 'white',
+  darknessTheme: '500',
+  darknessInactiveTheme: '300',
+  showLabel: true,
+  labelAfter: '%',
+});
+const active = computed(() => `${(props.value / props.max) * 100}%`);
+const activeColor = computed(() => {
+  if (!props.colorGaps) return convertThemeToColor(props.theme, props.darknessTheme);
+  const current = props.colorGaps.find((item) => item.start <= props.value && props.value <= item.end);
+  if (!current) return convertThemeToColor(props.theme, props.darknessTheme);
+  return convertThemeToColor(current.color, current.darknessColor);
+});
+const inactiveColor = computed(() => {
+  if (!props.colorInactiveGaps) return convertThemeToColor(props.inactiveTheme, props.darknessInactiveTheme);
+  const current = props.colorInactiveGaps.find((item) => item.start <= props.value && props.value <= item.end);
+  if (!current) return convertThemeToColor(props.inactiveTheme, props.darknessInactiveTheme);
+  return convertThemeToColor(current.color, current.darknessColor);
+});
+const textColor = computed(() => convertThemeToTextColor(props.theme, props.darknessTheme));
+const fontSize = computed(() => {
+  if (props.fontSize) return props.fontSize;
+  const size = props.size;
+  if (size === 'normal') return '16px';
+  if (size === 'large') return '20px';
+  if (size === 'huge') return '24px';
+  return '12px';
+});
+const defaultHeight = computed(() => {
+  const size = props.size;
+  if (size === 'normal') return '30px';
+  if (size === 'large') return '45px';
+  if (size === 'huge') return '60px';
+  return '15px';
+});
+</script>
+
+<template>
+  <section
+    class="container"
+    :style="`width: ${width ?? '300px'}; height: ${height ?? defaultHeight}; border: ${noBorder ? '' : '2px solid black'}`"
+  >
+    <div class="active">
+      <span v-show="showLabel" class="value">{{ labelBefore }}{{ value }}{{ labelAfter }}</span>
+    </div>
+  </section>
+</template>
+
+<style scoped>
+.container {
+  position: relative;
+  overflow: hidden;
+  border-radius: calc(v-bind(fontSize) / 2.5);
+  background-color: v-bind(inactiveColor);
+}
+.active {
+  width: v-bind(active);
+  height: 100%;
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  position: absolute;
+  top: 0;
+  left: 0;
+  transition: width 0.4s ease-in-out;
+  background-color: v-bind(activeColor);
+}
+.value {
+  font-weight: bold;
+  font-size: v-bind(fontSize);
+  color: v-bind(textColor);
+}
+</style>
-- 
GitLab