Loading src/App.vue +4 −0 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -129,6 +131,8 @@ const gentleIcons = { ArrowForwardIcon, ArrowLeftIcon, ArrowRightIcon, ArrowRightShortIcon, ArrowDoubleRightShortIcon, ArrowShortDownIcon, ArrowsVerticalIcon, AtIcon, Loading src/common/interfaces/componentsProps.ts +4 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading src/components/Paginator/Paginator.stories.ts +9 −33 Original line number Diff line number Diff line Loading @@ -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: [ Loading @@ -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>; Loading @@ -82,3 +52,9 @@ type Story = StoryObj<typeof meta>; export const Simple: Story = { args: {}, }; export const Full: Story = { args: { itemsPerPageOptions: ['10', '20', '30'], }, }; src/components/Paginator/Paginator.vue +55 −12 Original line number Diff line number Diff line <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> Loading @@ -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> src/components/Paginator/PaginatorItem.vue +44 −8 Original line number Diff line number Diff line <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> Loading @@ -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> Loading
src/App.vue +4 −0 Original line number Diff line number Diff line Loading @@ -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, Loading @@ -129,6 +131,8 @@ const gentleIcons = { ArrowForwardIcon, ArrowLeftIcon, ArrowRightIcon, ArrowRightShortIcon, ArrowDoubleRightShortIcon, ArrowShortDownIcon, ArrowsVerticalIcon, AtIcon, Loading
src/common/interfaces/componentsProps.ts +4 −0 Original line number Diff line number Diff line Loading @@ -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 { Loading
src/components/Paginator/Paginator.stories.ts +9 −33 Original line number Diff line number Diff line Loading @@ -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: [ Loading @@ -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>; Loading @@ -82,3 +52,9 @@ type Story = StoryObj<typeof meta>; export const Simple: Story = { args: {}, }; export const Full: Story = { args: { itemsPerPageOptions: ['10', '20', '30'], }, };
src/components/Paginator/Paginator.vue +55 −12 Original line number Diff line number Diff line <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> Loading @@ -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>
src/components/Paginator/PaginatorItem.vue +44 −8 Original line number Diff line number Diff line <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> Loading @@ -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>