From 2daef95a6cdb784217ebaff0ce61b7f6dbc337a5 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 16:59:32 +0500 Subject: [PATCH] feat: component 'Rating' --- src/App.vue | 2 + src/Playground.vue | 11 ++- src/common/constants/icons.ts | 2 + .../components/Rating/Rating.stories.ts | 47 ++++------ src/stories/components/Rating/Rating.vue | 87 ++++++++++++++----- src/stories/icons/Mono/StarFilledIcon.vue | 24 +++++ src/stories/icons/Mono/StarIcon.vue | 12 ++- 7 files changed, 130 insertions(+), 55 deletions(-) create mode 100644 src/stories/icons/Mono/StarFilledIcon.vue diff --git a/src/App.vue b/src/App.vue index 6b6fa05..d843398 100644 --- a/src/App.vue +++ b/src/App.vue @@ -110,6 +110,7 @@ import TriangleIcon from '@stories/icons/Mono/TriangleIcon.vue'; import Playground from '@/Playground.vue'; import ArrowShortDownIcon from '@stories/icons/Mono/ArrowShortDownIcon.vue'; import StarIcon from '@stories/icons/Mono/StarIcon.vue'; +import StarFilledIcon from '@stories/icons/Mono/StarFilledIcon.vue'; const gentleIcons = { Age18Icon, @@ -214,6 +215,7 @@ const gentleIcons = { SearchIcon, SettingsIcon, StarIcon, + StarFilledIcon, SortDownIcon, SortHorizontalIcon, SortUpIcon, diff --git a/src/Playground.vue b/src/Playground.vue index 64a17e4..27fe1cd 100644 --- a/src/Playground.vue +++ b/src/Playground.vue @@ -18,6 +18,8 @@ import Tag from '@stories/components/Tag/Tag.vue'; import Select from '@stories/components/Select/Select.vue'; import AtIcon from '@stories/icons/Mono/AtIcon.vue'; import Knob from '@stories/components/Knob/Knob.vue'; +import Rating from '@stories/components/Rating/Rating.vue'; +import HomeIcon from '@stories/icons/Mono/HomeIcon.vue'; const visibleDrawer = ref(false); const sliderOptions: ISliderOptions[] = [ @@ -197,7 +199,14 @@ const knob = ref(0); <template> <h2 class="title gradient-text">Playground</h2> - {{ knob }} + <Rating theme="red"> + <template #offIcon> + <CrossIcon color="red" /> + </template> + <template #onIcon> + <HomeIcon color="blue" /> + </template> + </Rating> <Knob v-model="knob" /> <Select :options="selectOptions" theme="sky"> <template #icon-left-First> diff --git a/src/common/constants/icons.ts b/src/common/constants/icons.ts index 1b44784..3691b61 100644 --- a/src/common/constants/icons.ts +++ b/src/common/constants/icons.ts @@ -110,6 +110,7 @@ import SortVerticalIcon from '@stories/icons/Mono/SortVerticalIcon.vue'; import ArrowShortDownIcon from '@stories/icons/Mono/ArrowShortDownIcon.vue'; import SearchIcon from '@stories/icons/Mono/SearchIcon.vue'; import StarIcon from '@stories/icons/Mono/StarIcon.vue'; +import StarFilledIcon from '@stories/icons/Mono/StarFilledIcon.vue'; export const iconsSet: Record<string, Component> = { Age18: Age18Icon, @@ -214,6 +215,7 @@ export const iconsSet: Record<string, Component> = { Search: SearchIcon, Settings: SettingsIcon, Star: StarIcon, + StarFilled: StarFilledIcon, SortDown: SortDownIcon, SortHorizontal: SortHorizontalIcon, SortUp: SortUpIcon, diff --git a/src/stories/components/Rating/Rating.stories.ts b/src/stories/components/Rating/Rating.stories.ts index 2efc991..320ab67 100644 --- a/src/stories/components/Rating/Rating.stories.ts +++ b/src/stories/components/Rating/Rating.stories.ts @@ -14,17 +14,10 @@ const meta: Meta = { }, }, argTypes: { - label: { control: 'text' }, - padding: { control: 'text' }, + count: { control: 'number' }, + gap: { control: 'text' }, size: { control: 'select', options: ['small', 'normal', 'large', 'huge'] }, - textStyle: { control: 'select', options: ['bold', 'italic'] }, - iconPos: { control: 'select', options: ['left', 'top', 'right', 'bottom'] }, - width: { control: 'text' }, darknessTheme: { 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'], - }, theme: { control: 'select', options: [ @@ -45,26 +38,6 @@ const meta: Meta = { 'black', ], }, - textColor: { - control: 'select', - options: [ - 'white', - 'blue', - 'sky', - 'cyan', - 'teal', - 'green', - 'yellow', - 'orange', - 'pink', - 'fuchsia', - 'purple', - 'indigo', - 'rose', - 'red', - 'black', - ], - }, }, args: {}, } satisfies Meta<typeof Rating>; @@ -76,3 +49,19 @@ type Story = StoryObj<typeof meta>; export const Simple: Story = { args: {}, }; + +export const Small: Story = { + args: { + size: 'small', + }, +}; + +export const Full: Story = { + args: { + theme: 'sky', + darknessTheme: '400', + count: 7, + gap: '2px', + size: 'huge', + }, +}; diff --git a/src/stories/components/Rating/Rating.vue b/src/stories/components/Rating/Rating.vue index a97b830..6f32d32 100644 --- a/src/stories/components/Rating/Rating.vue +++ b/src/stories/components/Rating/Rating.vue @@ -1,39 +1,79 @@ <script setup lang="ts"> import type { IRatingProps } from '@interfaces/componentsProps'; -import { ref } from 'vue'; +import { computed, type Ref, ref } from 'vue'; import { iconsSet } from '@/common/constants/icons'; +import StarFilledIcon from '@stories/icons/Mono/StarFilledIcon.vue'; +import { convertThemeToColor } from '@helpers/common'; const props = withDefaults(defineProps<IRatingProps>(), { count: 5, + gap: '5px', + size: 'normal', + theme: 'black', + darknessTheme: '500', }); -const value = defineModel(); +const value = defineModel({ + default: 0, +}) as Ref<number>; const onHoverIndex = ref(); -// const textColor = computed(() => {}); +const themeColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme)); +const themeColorOnHover = computed(() => convertThemeToColor(props.theme, '200')); +const iconSize = computed(() => { + const size = props.size; + if (size === 'normal') return '20px'; + if (size === 'large') return '30px'; + if (size === 'huge') return '40px'; + return '10px'; +}); +const onActiveClick = (index: number) => { + if (value.value > index) { + value.value = index; + return; + } + value.value = 0; +}; </script> <template> - {{ value }} - <ul class="list"> - <li v-for="index of Array(count).keys()" :key="index" class="item"> - <component - class="icon" - :is="iconsSet[icon ?? 'Star']" - color="black" - @pointerenter="onHoverIndex = index" - @pointerleave="onHoverIndex = null" - /> - <Transition> + <ul class="list" :style="`gap: ${gap}`"> + <li v-for="index of Array(count).keys()" :key="index" class="item iconSize"> + <div v-show="value < index + 1 && !$slots.offIcon" class="iconSize iconContainer"> <component - class="hoverIcon" - v-show="onHoverIndex === index" - :is="iconsSet[icon ?? 'Star']" - color="gray" + class="icon absoluteIcon" + :is="iconsSet['Star']" + :color="themeColor" @pointerenter="onHoverIndex = index" @pointerleave="onHoverIndex = null" - @click="value = index + 1" - /></Transition> + :size="iconSize" + /> + <Transition> + <component + class="absoluteIcon" + v-show="onHoverIndex === index" + :is="iconsSet['Star']" + :color="themeColorOnHover" + @pointerenter="onHoverIndex = index" + @pointerleave="onHoverIndex = null" + @click="value = index + 1" + :size="iconSize" + /> + </Transition> + </div> + <div v-show="value < index + 1" @click="value = index + 1"> + <slot name="offIcon" :size="iconSize"></slot> + </div> + <StarFilledIcon + v-show="value >= index + 1 && !$slots.onIcon" + :color="themeColor" + :size="iconSize" + class="absoluteIcon" + @click="onActiveClick(index + 1)" + /> + <div class="iconSize" v-show="value >= index + 1" @click="onActiveClick(index + 1)"> + <slot name="onIcon" :size="iconSize"></slot> + </div> </li> </ul> </template> @@ -45,7 +85,6 @@ const onHoverIndex = ref(); .item { position: relative; cursor: pointer; - transition: all 1s ease-in-out; } .icon { opacity: 1; @@ -54,11 +93,15 @@ const onHoverIndex = ref(); opacity: 0; } } -.hoverIcon { +.absoluteIcon { position: absolute; top: 0; left: 0; } +.iconSize { + width: v-bind(iconSize); + height: v-bind(iconSize); +} .v-enter-active, .v-leave-active { transition: opacity 0.15s ease; diff --git a/src/stories/icons/Mono/StarFilledIcon.vue b/src/stories/icons/Mono/StarFilledIcon.vue new file mode 100644 index 0000000..85b83ee --- /dev/null +++ b/src/stories/icons/Mono/StarFilledIcon.vue @@ -0,0 +1,24 @@ +<script setup lang="ts"> +interface Props { + color?: string; + size?: string | number; +} +defineProps<Props>(); +</script> + +<template> + <svg + :width="size ?? '40px'" + :height="size ?? '40px'" + viewBox="0 0 16 16" + xmlns="http://www.w3.org/2000/svg" + fill="none" + > + <path + :fill="color ?? '#000000'" + d="M8.67.912a.75.75 0 00-1.34 0L5.266 5.006l-4.622.662a.75.75 0 00-.412 1.285l3.335 3.18-.786 4.488a.75.75 0 001.082.796L8 13.287l4.137 2.13a.75.75 0 001.082-.796l-.786-4.489 3.335-3.18a.75.75 0 00-.412-1.284l-4.622-.662L8.67.912z" + /> + </svg> +</template> + +<style scoped></style> diff --git a/src/stories/icons/Mono/StarIcon.vue b/src/stories/icons/Mono/StarIcon.vue index 8d6a078..56fe686 100644 --- a/src/stories/icons/Mono/StarIcon.vue +++ b/src/stories/icons/Mono/StarIcon.vue @@ -7,12 +7,18 @@ defineProps<Props>(); </script> <template> - <svg :width="size ?? '40px'" :height="size ?? '40px'" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> + <svg + :width="size ?? '40px'" + :height="size ?? '40px'" + viewBox="0 0 16 16" + xmlns="http://www.w3.org/2000/svg" + fill="none" + > <path + :fill="color ?? '#000000'" fill-rule="evenodd" + d="M8 .5a.75.75 0 01.67.412l2.064 4.094 4.622.662a.75.75 0 01.412 1.285l-3.335 3.18.786 4.488a.75.75 0 01-1.082.796L8 13.287l-4.137 2.13a.75.75 0 01-1.082-.796l.786-4.489-3.335-3.18a.75.75 0 01.412-1.284l4.622-.662L7.33.912A.75.75 0 018 .5zm0 2.416L6.43 6.03a.75.75 0 01-.564.405l-3.48.498 2.507 2.39a.75.75 0 01.22.672l-.594 3.396 3.138-1.616a.75.75 0 01.686 0l3.138 1.616-.595-3.396a.75.75 0 01.221-.672l2.507-2.39-3.48-.498a.75.75 0 01-.563-.405L8 2.916z" clip-rule="evenodd" - d="M10.1507 2.3649C10.8306 0.713558 13.1694 0.713567 13.8494 2.3649L16.1856 8.0386L21.4255 8.34683C23.2632 8.45493 23.9912 10.7786 22.5437 11.916L18.1816 15.3433L19.9202 20.2694C20.5648 22.0955 18.497 23.6802 16.9012 22.5831L12 19.2135L7.09881 22.5831C5.50303 23.6802 3.43525 22.0955 4.07977 20.2694L5.81838 15.3433L1.45635 11.916C0.0087955 10.7787 0.736801 8.45493 2.57454 8.34683L7.81442 8.0386L10.1507 2.3649ZM12 3.1264L9.18559 9.9614L2.69199 10.3434L8.18164 14.6567L5.96575 20.935L12 16.7865L18.0343 20.935L15.8184 14.6567L21.308 10.3434L14.8144 9.9614L12 3.1264Z" - :fill="color ?? '#000000'" /> </svg> </template> -- GitLab