From 93199354c63b5149a162c86bbb37b883f455d10f 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: Mon, 20 Jan 2025 19:50:18 +0500 Subject: [PATCH] feat: 'Paginator' in process --- src/App.vue | 4 ++ src/common/interfaces/componentsProps.ts | 4 ++ src/components/Paginator/Paginator.stories.ts | 42 +++--------- src/components/Paginator/Paginator.vue | 67 +++++++++++++++---- src/components/Paginator/PaginatorItem.vue | 52 +++++++++++--- src/icons/Mono/ArrowDoubleLeftShortIcon.vue | 4 +- src/icons/Mono/ArrowDoubleRightShortIcon.vue | 64 ++++++++++++++++++ src/icons/Mono/ArrowRightShortIcon.vue | 41 ++++++++++++ 8 files changed, 223 insertions(+), 55 deletions(-) create mode 100644 src/icons/Mono/ArrowDoubleRightShortIcon.vue create mode 100644 src/icons/Mono/ArrowRightShortIcon.vue diff --git a/src/App.vue b/src/App.vue index 8ee3a49..ebb55d4 100644 --- a/src/App.vue +++ b/src/App.vue @@ -113,6 +113,8 @@ 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'; +import ArrowRightShortIcon from '@icons/Mono/ArrowRightShortIcon.vue'; +import ArrowDoubleRightShortIcon from '@icons/Mono/ArrowDoubleRightShortIcon.vue'; const gentleIcons = { Age18Icon, @@ -129,6 +131,8 @@ const gentleIcons = { ArrowForwardIcon, ArrowLeftIcon, ArrowRightIcon, + ArrowRightShortIcon, + ArrowDoubleRightShortIcon, ArrowShortDownIcon, ArrowsVerticalIcon, AtIcon, diff --git a/src/common/interfaces/componentsProps.ts b/src/common/interfaces/componentsProps.ts index 8355d0b..843197d 100644 --- a/src/common/interfaces/componentsProps.ts +++ b/src/common/interfaces/componentsProps.ts @@ -60,7 +60,11 @@ export interface IPaginatorProps { value?: number; total?: number; itemsPerPage?: number; + itemsPerPageOptions?: number[]; size?: TSize; + fontSize?: string; + theme?: TThemeColor; + darknessTheme?: TDarkness; } export interface IMDProps { diff --git a/src/components/Paginator/Paginator.stories.ts b/src/components/Paginator/Paginator.stories.ts index c3f7619..e4ff882 100644 --- a/src/components/Paginator/Paginator.stories.ts +++ b/src/components/Paginator/Paginator.stories.ts @@ -15,22 +15,12 @@ const meta: Meta = { }, 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' }, + total: { control: 'number' }, + itemsPerPage: { control: 'number' }, + itemsPerPageOptions: { control: 'object' }, 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: [ @@ -51,26 +41,6 @@ const meta: Meta = { '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>; @@ -82,3 +52,9 @@ type Story = StoryObj<typeof meta>; export const Simple: Story = { args: {}, }; + +export const Full: Story = { + args: { + itemsPerPageOptions: ['10', '20', '30'], + }, +}; diff --git a/src/components/Paginator/Paginator.vue b/src/components/Paginator/Paginator.vue index f4f9093..68ad21b 100644 --- a/src/components/Paginator/Paginator.vue +++ b/src/components/Paginator/Paginator.vue @@ -1,45 +1,83 @@ <script setup lang="ts"> import ArrowDoubleLeftShortIcon from '@icons/Mono/ArrowDoubleLeftShortIcon.vue'; +import ArrowDoubleRightShortIcon from '@icons/Mono/ArrowDoubleRightShortIcon.vue'; import ArrowLeftShortIcon from '@icons/Mono/ArrowLeftShortIcon.vue'; +import ArrowRightShortIcon from '@icons/Mono/ArrowRightShortIcon.vue'; import type { IPaginatorProps } from '@interfaces/componentsProps'; import PaginatorItem from '@components/Paginator/PaginatorItem.vue'; -import { computed, ref } from 'vue'; +import { computed, ref, type Ref } from 'vue'; +import Select from '@components/Select/Select.vue'; +import type { ISelectOption } from '@interfaces/componentsProp'; const props = withDefaults(defineProps<IPaginatorProps>(), { total: 10, size: 'normal', theme: 'black', darknessTheme: '500', + itemsPerPage: 1, }); -const current = ref<number | undefined>(props.value); +const current = defineModel({ + default: 1, +}) as Ref<number>; +const perPage = ref(props.itemsPerPage); + +const itemsLength = computed(() => Math.floor(props.total / perPage.value)); +const selectOptions = computed(() => + !props.itemsPerPageOptions ? [{ value: '1' }] : props.itemsPerPageOptions.map((item) => ({ value: String(item) })), +) as unknown as ISelectOption[]; +const isStartDisabled = computed(() => current.value === 1); +const isEndDisabled = computed(() => current.value === itemsLength.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 center = current.value; + const length = itemsLength.value; + if (center - 2 < 2) return [1, 2, 3, 4, 5]; + if (center + 2 > length) return [length - 5, length - 4, length - 3, length - 1, length]; + return [center - 2, center - 1, center, center + 1, center + 2]; }); const iconSize = computed(() => { const size = props.size; - if (size === 'normal') return '20'; + if (size === 'normal') return '10'; if (size === 'large') return '25'; if (size === 'huge') return '30'; return '15'; }); -const itemSize = computed(() => `${+iconSize.value * 1.5}px`); +const fontSize = computed(() => { + const size = props.size; + if (size === 'normal') return '16px'; + if (size === 'large') return '26px'; + if (size === 'huge') return '32px'; + return '36px'; +}); +const itemSize = computed(() => `${+iconSize.value * 2.5}px`); </script> <template> <section class="container"> - <PaginatorItem class="paginatorItem"> - <ArrowDoubleLeftShortIcon :size="iconSize" /> + <PaginatorItem @click="current = 1" :disable="isStartDisabled" class="paginatorItem"> + <ArrowDoubleLeftShortIcon :color="isStartDisabled ? '#aaa' : 'black'" :size="iconSize" /> </PaginatorItem> - <PaginatorItem class="paginatorItem"> - <ArrowLeftShortIcon :size="iconSize" /> + <PaginatorItem @click="current--" :disable="isStartDisabled" class="paginatorItem"> + <ArrowLeftShortIcon :color="isStartDisabled ? '#aaa' : 'black'" :size="iconSize" /> </PaginatorItem> - <PaginatorItem v-for="item of items" :key="item" class="paginatorItem"> - {{ item }} + <PaginatorItem + v-for="item of items" + :key="item" + @click="current = item" + :active="current === item" + class="paginatorItem" + > + <span class="digital">{{ item }}</span> </PaginatorItem> + <PaginatorItem @click="isEndDisabled ? '' : current++" :disable="isEndDisabled" class="paginatorItem"> + <ArrowRightShortIcon :color="isEndDisabled ? '#aaa' : 'black'" :size="iconSize" /> + </PaginatorItem> + <PaginatorItem @click="isEndDisabled ? '' : (current = total)" :disable="isEndDisabled" class="paginatorItem"> + <ArrowDoubleRightShortIcon :color="isEndDisabled ? '#aaa' : 'black'" :size="iconSize" /> + </PaginatorItem> + <Select v-if="itemsPerPageOptions" v-model="perPage" :options="selectOptions"></Select> </section> </template> @@ -51,5 +89,10 @@ const itemSize = computed(() => `${+iconSize.value * 1.5}px`); .paginatorItem { width: v-bind(itemSize); height: v-bind(itemSize); + user-select: none; +} +.digital { + font-size: v-bind(fontSize); + font-weight: bold; } </style> diff --git a/src/components/Paginator/PaginatorItem.vue b/src/components/Paginator/PaginatorItem.vue index 048735e..4dd8f26 100644 --- a/src/components/Paginator/PaginatorItem.vue +++ b/src/components/Paginator/PaginatorItem.vue @@ -1,8 +1,28 @@ -<script setup lang="ts"></script> +<script setup lang="ts"> +defineProps<{ + active?: boolean; + disable?: boolean; +}>(); +</script> <template> - <div class="item"> - <div class="bg"></div> + <div + :class="[ + 'item', + { + disable, + }, + ]" + > + <div + :class="[ + 'bg', + { + active, + disableBg: disable, + }, + ]" + ></div> <slot /> </div> </template> @@ -14,25 +34,41 @@ justify-content: center; align-items: center; cursor: pointer; + line-height: 1.2; } .item:hover > .bg { opacity: 1; } .item:active > .bg { opacity: 1; - background-color: rgba(0, 0, 0, 0.2) !important; + background-color: rgba(0, 0, 0, 0.2); } .bg { - width: 100%; - height: 100%; + width: 120%; + height: 120%; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); - z-index: 2; + padding: 10px; + z-index: -1; border-radius: 50%; background-color: rgba(0, 0, 0, 0.1); opacity: 0; - transition: all 0.15s ease; + transition: all 0.2s ease; +} +.item > .active { + background-color: black !important; + opacity: 1; +} +.active + * { + color: white; +} +.disable { + cursor: auto; + pointer-events: none; +} +.disableBg { + background-color: white !important; } </style> diff --git a/src/icons/Mono/ArrowDoubleLeftShortIcon.vue b/src/icons/Mono/ArrowDoubleLeftShortIcon.vue index a4dc9ed..c9465e5 100644 --- a/src/icons/Mono/ArrowDoubleLeftShortIcon.vue +++ b/src/icons/Mono/ArrowDoubleLeftShortIcon.vue @@ -39,7 +39,7 @@ defineProps<Props>(); <line x1="335" y1="-100" - x2="110" + x2="130" y2="100" transform="matrix(1.066014 0 0 1.167066 83.13335 150)" fill="none" @@ -48,7 +48,7 @@ defineProps<Props>(); stroke-linecap="round" /> <line - x1="110" + x1="130" y1="100" x2="335" y2="300" diff --git a/src/icons/Mono/ArrowDoubleRightShortIcon.vue b/src/icons/Mono/ArrowDoubleRightShortIcon.vue new file mode 100644 index 0000000..7a563bb --- /dev/null +++ b/src/icons/Mono/ArrowDoubleRightShortIcon.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="180" + y1="-100" + x2="385" + 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="385" + y1="100" + x2="180" + 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="0" + y1="-100" + x2="205" + 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="205" + y1="100" + x2="0" + 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/ArrowRightShortIcon.vue b/src/icons/Mono/ArrowRightShortIcon.vue new file mode 100644 index 0000000..7ac6fcd --- /dev/null +++ b/src/icons/Mono/ArrowRightShortIcon.vue @@ -0,0 +1,41 @@ +<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="0 0 532.153 532.153" + xmlns="http://www.w3.org/2000/svg" + > + <line + x1="180" + y1="-100" + x2="385" + 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="385" + y1="100" + x2="180" + 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> -- GitLab