From aa4b2705084fc3d6c661dab113b3f8783c881839 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 15:29:32 +0500
Subject: [PATCH] feat: component "Checkbox"

---
 src/Playground.vue                            |   7 +
 src/common/interfaces/componentsProps.ts      |  17 ++
 .../components/Checkbox/Checkbox.stories.ts   | 171 ++++++++++++++++++
 src/stories/components/Checkbox/Checkbox.vue  | 143 +++++++++++++++
 src/stories/components/Table/Table.stories.ts |   1 -
 src/stories/components/Table/helpers.ts       |   1 -
 6 files changed, 338 insertions(+), 2 deletions(-)
 create mode 100644 src/stories/components/Checkbox/Checkbox.stories.ts
 create mode 100644 src/stories/components/Checkbox/Checkbox.vue

diff --git a/src/Playground.vue b/src/Playground.vue
index ef2d371..9caebc0 100644
--- a/src/Playground.vue
+++ b/src/Playground.vue
@@ -13,6 +13,7 @@ import Popup from '@stories/components/Popup/Popup.vue';
 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';
 
 const visibleDrawer = ref(false);
 const sliderOptions: ISliderOptions[] = [
@@ -178,10 +179,16 @@ const tableData = [
     },
   ],
 ];
+const activeCheckbox = ref();
 </script>
 
 <template>
   <h2 class="title gradient-text">Playground</h2>
+  {{ activeCheckbox }}
+  <Checkbox v-model:active="activeCheckbox" size="small" />
+  <Checkbox v-model:active="activeCheckbox" />
+  <Checkbox v-model:active="activeCheckbox" size="large" />
+  <Checkbox v-model:active="activeCheckbox" size="huge" />
   <Table
     show-all-lines
     :columns="tableColumns"
diff --git a/src/common/interfaces/componentsProps.ts b/src/common/interfaces/componentsProps.ts
index c42ddc7..443f8b2 100644
--- a/src/common/interfaces/componentsProps.ts
+++ b/src/common/interfaces/componentsProps.ts
@@ -157,6 +157,23 @@ export interface ITSProps {
   disabled?: boolean;
 }
 
+export interface ICheckboxProps {
+  label?: string;
+  labelPos?: TPosition;
+  name?: string;
+  size?: TSize;
+  disabled?: boolean;
+  invalid?: boolean;
+  theme?: TThemeColor;
+  activeTheme?: TThemeColor;
+  textColor?: TThemeColor;
+  borderColor?: TThemeColor;
+  darknessTheme?: TDarkness;
+  darknessActiveTheme?: TDarkness;
+  darknessTextColor?: TDarkness;
+  darknessBorderColor?: TDarkness;
+}
+
 export interface IDividerProps {
   height?: number;
   type?: TBorder;
diff --git a/src/stories/components/Checkbox/Checkbox.stories.ts b/src/stories/components/Checkbox/Checkbox.stories.ts
new file mode 100644
index 0000000..5d0f613
--- /dev/null
+++ b/src/stories/components/Checkbox/Checkbox.stories.ts
@@ -0,0 +1,171 @@
+import type { Meta, StoryObj } from '@storybook/vue3';
+
+import Checkbox from './Checkbox.vue';
+
+const meta: Meta = {
+  title: 'Components/Checkbox',
+  component: Checkbox,
+  tags: ['autodocs'],
+  parameters: {
+    docs: {
+      description: {
+        component: 'A component that is used as a Checkbox. Can be used with icon.',
+      },
+    },
+  },
+  argTypes: {
+    active: { control: 'boolean' },
+    invalid: { control: 'boolean' },
+    disabled: { control: 'boolean' },
+    label: { control: 'text' },
+    name: { control: 'text' },
+    size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] },
+    labelPos: { control: 'select', options: ['left', 'top', 'right', 'bottom'] },
+    darknessTheme: { control: 'select', options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'] },
+    darknessActiveTheme: {
+      control: 'select',
+      options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
+    },
+    darknessTextColor: {
+      control: 'select',
+      options: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
+    },
+    darknessBorderColor: {
+      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',
+      ],
+    },
+    activeTheme: {
+      control: 'select',
+      options: [
+        'white',
+        'blue',
+        'sky',
+        'cyan',
+        'teal',
+        'green',
+        'yellow',
+        'orange',
+        'pink',
+        'fuchsia',
+        'purple',
+        'indigo',
+        'rose',
+        'red',
+        'black',
+      ],
+    },
+    textColor: {
+      control: 'select',
+      options: [
+        'white',
+        'blue',
+        'sky',
+        'cyan',
+        'teal',
+        'green',
+        'yellow',
+        'orange',
+        'pink',
+        'fuchsia',
+        'purple',
+        'indigo',
+        'rose',
+        'red',
+        'black',
+      ],
+    },
+    borderColor: {
+      control: 'select',
+      options: [
+        'white',
+        'blue',
+        'sky',
+        'cyan',
+        'teal',
+        'green',
+        'yellow',
+        'orange',
+        'pink',
+        'fuchsia',
+        'purple',
+        'indigo',
+        'rose',
+        'red',
+        'black',
+      ],
+    },
+  },
+  args: {},
+} satisfies Meta<typeof Checkbox>;
+
+export default meta;
+
+type Story = StoryObj<typeof meta>;
+
+export const Simple: Story = {
+  args: {
+    active: true,
+  },
+};
+
+export const Small: Story = {
+  args: {
+    active: false,
+    size: 'small',
+    theme: 'yellow',
+    activeTheme: 'blue',
+    darknessTheme: '300',
+    darknessActiveTheme: '700',
+    label: 'Are you gay?',
+  },
+};
+
+export const Large: Story = {
+  args: {
+    active: true,
+    size: 'large',
+    theme: 'green',
+    activeTheme: 'sky',
+    darknessTheme: '700',
+    darknessActiveTheme: '300',
+    label: 'Checkbox',
+    labelPos: 'top',
+    invalid: true,
+  },
+};
+
+export const Huge: Story = {
+  args: {
+    active: false,
+    size: 'huge',
+    theme: 'indigo',
+    activeTheme: 'purple',
+    darknessTheme: '500',
+    darknessActiveTheme: '500',
+    label: 'Checkbox',
+    textColor: 'blue',
+    invalid: false,
+    disabled: true,
+    labelPos: 'left',
+  },
+};
diff --git a/src/stories/components/Checkbox/Checkbox.vue b/src/stories/components/Checkbox/Checkbox.vue
new file mode 100644
index 0000000..80eb924
--- /dev/null
+++ b/src/stories/components/Checkbox/Checkbox.vue
@@ -0,0 +1,143 @@
+<script setup lang="ts">
+import type { ICheckboxProps } from '@interfaces/componentsProps';
+import { computed } from 'vue';
+import { convertThemeToColor, convertThemeToTextColor } from '@helpers/common';
+import CheckMarkIcon from '@stories/icons/Mono/CheckMarkIcon.vue';
+
+const props = withDefaults(defineProps<ICheckboxProps>(), {
+  label: '',
+  name: '',
+  labelPos: 'right',
+  size: 'normal',
+  theme: 'white',
+  activeTheme: 'black',
+  textColor: 'black',
+  borderColor: 'black',
+  darknessTheme: '500',
+  darknessActiveTheme: '500',
+  darknessTextColor: '500',
+  darknessBorderColor: '500',
+});
+const active = defineModel('active');
+// watch(, () => {});
+const themeColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme));
+const activeThemeColor = computed(() => convertThemeToColor(props.activeTheme, props.darknessActiveTheme));
+const iconColor = computed(() =>
+  props.disabled ? '#62708c' : convertThemeToTextColor(props.activeTheme, props.darknessActiveTheme),
+);
+const color = computed(() => convertThemeToColor(props.textColor, props.darknessTextColor));
+const borderColor = computed(() =>
+  props.invalid
+    ? 'red'
+    : props.disabled
+      ? '#62708c'
+      : convertThemeToColor(props.borderColor, props.darknessBorderColor),
+);
+const elSize = computed(() => {
+  const size = props.size;
+  if (size === 'normal') return 20;
+  if (size === 'large') return 30;
+  if (size === 'huge') return 40;
+  return 13;
+});
+const gap = computed(() => {
+  if (!props.label) return '0px';
+  const size = props.size;
+  if (size === 'normal') return '7px';
+  if (size === 'large') return '10px';
+  if (size === 'huge') return '15px';
+  return '5px';
+});
+const borderWidth = computed(() => (props.size === 'large' || props.size === 'huge' ? 2 : 1));
+const borderRadius = computed(() => `${elSize.value / 7 - borderWidth.value}px`);
+</script>
+
+<template>
+  <section
+    :class="[
+      'container',
+      {
+        flexColumn: ['top', 'bottom'].includes(labelPos),
+      },
+    ]"
+    @click.prevent="!disabled ? (active = !active) : ''"
+  >
+    <div class="main" :style="`width: ${elSize}px; height: ${elSize}px; border: ${borderWidth}px solid ${borderColor}`">
+      <input
+        :style="`width: ${elSize}px; height: ${elSize}px; position: absolute; z-index: 100; cursor: ${disabled ? 'initial' : 'pointer'}`"
+        v-model="active"
+        type="checkbox"
+        :name="name"
+        :value="label"
+        :disabled="disabled"
+      />
+      <div
+        :class="[
+          {
+            inactive: !active,
+            active: active,
+            disabled: disabled,
+          },
+        ]"
+      >
+        <CheckMarkIcon
+          :style="`transition: all 0.3s ease-in-out; opacity: ${active ? 1 : 0}; position: absolute;`"
+          :color="iconColor"
+          :size="elSize"
+        />
+      </div>
+    </div>
+    <p
+      :class="[
+        {
+          first: ['top', 'left'].includes(labelPos),
+        },
+      ]"
+      :style="`color: ${color}; line-height: 0.9; font-size: ${elSize}px; pointer-events: none`"
+    >
+      {{ label }}
+    </p>
+  </section>
+</template>
+
+<style scoped>
+.container {
+  position: relative;
+  display: flex;
+  gap: v-bind(gap);
+  box-sizing: content-box;
+  width: max-content;
+}
+.main {
+  position: relative;
+  border-radius: 15%;
+}
+.inactive {
+  height: 100%;
+  border-radius: v-bind(borderRadius);
+
+  background-color: v-bind(themeColor);
+  transition: all 0.2s ease-in-out;
+}
+.active {
+  width: 100%;
+  height: 100%;
+  border-radius: v-bind(borderRadius);
+  background-color: v-bind(activeThemeColor);
+  transition: all 0.2s ease-in-out;
+}
+.disabled {
+  background-color: #e1e7f1 !important;
+}
+.first {
+  order: -1;
+}
+.flexColumn {
+  flex-direction: column;
+  align-items: center;
+}
+input[type='checkbox'] {
+  all: unset;
+  width: 100%;
+}
+</style>
diff --git a/src/stories/components/Table/Table.stories.ts b/src/stories/components/Table/Table.stories.ts
index 2bafd14..c716e46 100644
--- a/src/stories/components/Table/Table.stories.ts
+++ b/src/stories/components/Table/Table.stories.ts
@@ -222,7 +222,6 @@ export const Full: Story = {
         },
       ],
     ],
-
     fontSize: '24px',
     showAllLines: true,
     border: 'fuchsia',
diff --git a/src/stories/components/Table/helpers.ts b/src/stories/components/Table/helpers.ts
index fbb49f2..9b0d9eb 100644
--- a/src/stories/components/Table/helpers.ts
+++ b/src/stories/components/Table/helpers.ts
@@ -42,7 +42,6 @@ export const calcRows = (
   } else {
     const index = sortStateActive[0];
     const value = sortStateActive[1];
-    console.log('index, value, columnToFilterType:', index, value, columnToFilterType);
     if (columnToFilterType === 'number')
       return rows.sort((a, b) =>
         value === 'down' ? +a[index].value - +b[index].value : +b[index].value - +a[index].value,
-- 
GitLab