From 69fb3dc113334f0c90d27a8e903079863698b7f5 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 22:01:50 +0500
Subject: [PATCH] feat: start component 'Paginator'

---
 src/App.vue                                   |  6 +-
 src/common/constants/icons.ts                 |  2 +-
 src/common/interfaces/componentsProps.ts      |  7 ++
 src/components/Paginator/Paginator.stories.ts | 84 +++++++++++++++++++
 src/components/Paginator/Paginator.vue        | 55 ++++++++++++
 src/components/Paginator/PaginatorItem.vue    | 38 +++++++++
 .../ProgressBar/ProgressBar.stories.ts        |  2 +-
 src/icons/Mono/ArrowDoubleLeftShortIcon.vue   | 64 ++++++++++++++
 ...ortDownIcon.vue => ArrowDownShortIcon.vue} |  0
 src/icons/Mono/ArrowLeftShortIcon.vue         | 24 ++++++
 10 files changed, 279 insertions(+), 3 deletions(-)
 create mode 100644 src/components/Paginator/Paginator.stories.ts
 create mode 100644 src/components/Paginator/Paginator.vue
 create mode 100644 src/components/Paginator/PaginatorItem.vue
 create mode 100644 src/icons/Mono/ArrowDoubleLeftShortIcon.vue
 rename src/icons/Mono/{ArrowShortDownIcon.vue => ArrowDownShortIcon.vue} (100%)
 create mode 100644 src/icons/Mono/ArrowLeftShortIcon.vue

diff --git a/src/App.vue b/src/App.vue
index fc3bf13..8ee3a49 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -108,9 +108,11 @@ import UserIcon from '@icons/Mono/UserIcon.vue';
 import ArrowsVerticalIcon from '@icons/Mono/ArrowsVerticalIcon.vue';
 import TriangleIcon from '@icons/Mono/TriangleIcon.vue';
 import Playground from '@/Playground.vue';
-import ArrowShortDownIcon from '@icons/Mono/ArrowShortDownIcon.vue';
+import ArrowShortDownIcon from '@icons/Mono/ArrowDownShortIcon.vue';
 import StarIcon from '@icons/Mono/StarIcon.vue';
 import StarFilledIcon from '@icons/Mono/StarFilledIcon.vue';
+import ArrowLeftShortIcon from '@icons/Mono/ArrowLeftShortIcon.vue';
+import ArrowDoubleLeftShortIcon from '@icons/Mono/ArrowDoubleLeftShortIcon.vue';
 
 const gentleIcons = {
   Age18Icon,
@@ -122,6 +124,8 @@ const gentleIcons = {
   AnchorIcon,
   AnchorLinkIcon,
   ArchiveIcon,
+  ArrowLeftShortIcon,
+  ArrowDoubleLeftShortIcon,
   ArrowForwardIcon,
   ArrowLeftIcon,
   ArrowRightIcon,
diff --git a/src/common/constants/icons.ts b/src/common/constants/icons.ts
index fe781d0..9ae57d6 100644
--- a/src/common/constants/icons.ts
+++ b/src/common/constants/icons.ts
@@ -107,7 +107,7 @@ import SortHorizontalIcon from '@icons/Mono/SortHorizontalIcon.vue';
 import SortDownIcon from '@icons/Mono/SortDownIcon.vue';
 import SortUpIcon from '@icons/Mono/SortUpIcon.vue';
 import SortVerticalIcon from '@icons/Mono/SortVerticalIcon.vue';
-import ArrowShortDownIcon from '@icons/Mono/ArrowShortDownIcon.vue';
+import ArrowShortDownIcon from '@icons/Mono/ArrowDownShortIcon.vue';
 import SearchIcon from '@icons/Mono/SearchIcon.vue';
 import StarIcon from '@icons/Mono/StarIcon.vue';
 import StarFilledIcon from '@icons/Mono/StarFilledIcon.vue';
diff --git a/src/common/interfaces/componentsProps.ts b/src/common/interfaces/componentsProps.ts
index 07b4d81..81547c6 100644
--- a/src/common/interfaces/componentsProps.ts
+++ b/src/common/interfaces/componentsProps.ts
@@ -56,6 +56,13 @@ export interface ITIProps {
   themeColor: string;
 }
 
+export interface IPaginatorProps {
+  value?: number;
+  total?: number;
+  itemsPerPage?: number;
+  size?: TSize;
+}
+
 export interface IMDProps {
   items: IMDItemProps[];
   size?: TSize;
diff --git a/src/components/Paginator/Paginator.stories.ts b/src/components/Paginator/Paginator.stories.ts
new file mode 100644
index 0000000..c3f7619
--- /dev/null
+++ b/src/components/Paginator/Paginator.stories.ts
@@ -0,0 +1,84 @@
+import type { Meta, StoryObj } from '@storybook/vue3';
+
+import Paginator from './Paginator.vue';
+
+const meta: Meta = {
+  title: 'Components/Paginator',
+  component: Paginator,
+  tags: ['autodocs'],
+  parameters: {
+    docs: {
+      description: {
+        component: 'A component to display data in paged format and provide navigation between pages.',
+      },
+    },
+  },
+  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 Paginator>;
+
+export default meta;
+
+type Story = StoryObj<typeof meta>;
+
+export const Simple: Story = {
+  args: {},
+};
diff --git a/src/components/Paginator/Paginator.vue b/src/components/Paginator/Paginator.vue
new file mode 100644
index 0000000..f4f9093
--- /dev/null
+++ b/src/components/Paginator/Paginator.vue
@@ -0,0 +1,55 @@
+<script setup lang="ts">
+import ArrowDoubleLeftShortIcon from '@icons/Mono/ArrowDoubleLeftShortIcon.vue';
+import ArrowLeftShortIcon from '@icons/Mono/ArrowLeftShortIcon.vue';
+import type { IPaginatorProps } from '@interfaces/componentsProps';
+import PaginatorItem from '@components/Paginator/PaginatorItem.vue';
+import { computed, ref } from 'vue';
+
+const props = withDefaults(defineProps<IPaginatorProps>(), {
+  total: 10,
+  size: 'normal',
+  theme: 'black',
+  darknessTheme: '500',
+});
+
+const current = ref<number | undefined>(props.value);
+
+const items = computed(() => {
+  if (!current.value) return [1, 2, 3, 4, 5];
+  const start = Math.floor(current.value / 5);
+  return [start + 1, start + 2, start + 3, start + 4, start + 5];
+});
+const iconSize = computed(() => {
+  const size = props.size;
+  if (size === 'normal') return '20';
+  if (size === 'large') return '25';
+  if (size === 'huge') return '30';
+  return '15';
+});
+const itemSize = computed(() => `${+iconSize.value * 1.5}px`);
+</script>
+
+<template>
+  <section class="container">
+    <PaginatorItem class="paginatorItem">
+      <ArrowDoubleLeftShortIcon :size="iconSize" />
+    </PaginatorItem>
+    <PaginatorItem class="paginatorItem">
+      <ArrowLeftShortIcon :size="iconSize" />
+    </PaginatorItem>
+    <PaginatorItem v-for="item of items" :key="item" class="paginatorItem">
+      {{ item }}
+    </PaginatorItem>
+  </section>
+</template>
+
+<style scoped>
+.container {
+  display: flex;
+  gap: 5px;
+}
+.paginatorItem {
+  width: v-bind(itemSize);
+  height: v-bind(itemSize);
+}
+</style>
diff --git a/src/components/Paginator/PaginatorItem.vue b/src/components/Paginator/PaginatorItem.vue
new file mode 100644
index 0000000..048735e
--- /dev/null
+++ b/src/components/Paginator/PaginatorItem.vue
@@ -0,0 +1,38 @@
+<script setup lang="ts"></script>
+
+<template>
+  <div class="item">
+    <div class="bg"></div>
+    <slot />
+  </div>
+</template>
+
+<style>
+.item {
+  position: relative;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  cursor: pointer;
+}
+.item:hover > .bg {
+  opacity: 1;
+}
+.item:active > .bg {
+  opacity: 1;
+  background-color: rgba(0, 0, 0, 0.2) !important;
+}
+.bg {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  z-index: 2;
+  border-radius: 50%;
+  background-color: rgba(0, 0, 0, 0.1);
+  opacity: 0;
+  transition: all 0.15s ease;
+}
+</style>
diff --git a/src/components/ProgressBar/ProgressBar.stories.ts b/src/components/ProgressBar/ProgressBar.stories.ts
index 27e2b24..285c1c3 100644
--- a/src/components/ProgressBar/ProgressBar.stories.ts
+++ b/src/components/ProgressBar/ProgressBar.stories.ts
@@ -9,7 +9,7 @@ const meta: Meta = {
   parameters: {
     docs: {
       description: {
-        component: 'A component that is used as a ProgressBar.',
+        component: 'A process status indicator.',
       },
     },
   },
diff --git a/src/icons/Mono/ArrowDoubleLeftShortIcon.vue b/src/icons/Mono/ArrowDoubleLeftShortIcon.vue
new file mode 100644
index 0000000..a4dc9ed
--- /dev/null
+++ b/src/icons/Mono/ArrowDoubleLeftShortIcon.vue
@@ -0,0 +1,64 @@
+<script setup lang="ts">
+interface Props {
+  color?: string;
+  size?: string | number;
+}
+defineProps<Props>();
+</script>
+
+<template>
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    :width="`${size ?? 40}px`"
+    :height="`${size ?? 40}px`"
+    viewBox="0 0 532.153 532.153"
+    xml:space="preserve"
+  >
+    <line
+      x1="155"
+      y1="-100"
+      x2="-50"
+      y2="100"
+      transform="matrix(1.066014 0 0 1.167066 83.13335 150)"
+      fill="none"
+      :stroke="color ?? '#000000'"
+      stroke-width="50"
+      stroke-linecap="round"
+    />
+    <line
+      x1="-50"
+      y1="100"
+      x2="155"
+      y2="300"
+      transform="matrix(1.066014 0 0 1.167066 83.13335 150)"
+      fill="none"
+      :stroke="color ?? '#000000'"
+      stroke-width="50"
+      stroke-linecap="round"
+    />
+    <line
+      x1="335"
+      y1="-100"
+      x2="110"
+      y2="100"
+      transform="matrix(1.066014 0 0 1.167066 83.13335 150)"
+      fill="none"
+      :stroke="color ?? '#000000'"
+      stroke-width="50"
+      stroke-linecap="round"
+    />
+    <line
+      x1="110"
+      y1="100"
+      x2="335"
+      y2="300"
+      transform="matrix(1.066014 0 0 1.167066 83.13335 150)"
+      fill="none"
+      :stroke="color ?? '#000000'"
+      stroke-width="50"
+      stroke-linecap="round"
+    />
+  </svg>
+</template>
+
+<style scoped></style>
diff --git a/src/icons/Mono/ArrowShortDownIcon.vue b/src/icons/Mono/ArrowDownShortIcon.vue
similarity index 100%
rename from src/icons/Mono/ArrowShortDownIcon.vue
rename to src/icons/Mono/ArrowDownShortIcon.vue
diff --git a/src/icons/Mono/ArrowLeftShortIcon.vue b/src/icons/Mono/ArrowLeftShortIcon.vue
new file mode 100644
index 0000000..8c2f7b9
--- /dev/null
+++ b/src/icons/Mono/ArrowLeftShortIcon.vue
@@ -0,0 +1,24 @@
+<script setup lang="ts">
+interface Props {
+  color?: string;
+  size?: string | number;
+}
+defineProps<Props>();
+</script>
+
+<template>
+  <svg :width="`${size ?? 40}px`" :height="`${size ?? 40}px`" viewBox="-4.5 0 20 20" xmlns="http://www.w3.org/2000/svg">
+    <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+      <g transform="translate(-345.000000, -6679.000000)" :fill="color ?? '#000000'">
+        <g id="icons" transform="translate(56.000000, 160.000000)">
+          <path
+            d="M299.633777,6519.29231 L299.633777,6519.29231 C299.228878,6518.90256 298.573377,6518.90256 298.169513,6519.29231 L289.606572,6527.55587 C288.797809,6528.33636 288.797809,6529.60253 289.606572,6530.38301 L298.231646,6538.70754 C298.632403,6539.09329 299.27962,6539.09828 299.685554,6538.71753 L299.685554,6538.71753 C300.100809,6538.32879 300.104951,6537.68821 299.696945,6537.29347 L291.802968,6529.67648 C291.398069,6529.28574 291.398069,6528.65315 291.802968,6528.26241 L299.633777,6520.70538 C300.038676,6520.31563 300.038676,6519.68305 299.633777,6519.29231"
+            id="arrow_left-[#335]"
+          ></path>
+        </g>
+      </g>
+    </g>
+  </svg>
+</template>
+
+<style scoped></style>
-- 
GitLab