diff --git a/src/App.vue b/src/App.vue index 6b6fa05bbb7ef90665dbbf0c194d59d910e29828..d84339827ab6924918d7cf21c54b9aa3db06a0b3 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 64a17e42e71b1c1757ef028128f1079829a6cb2b..27fe1cd63e688ffa7af88e7504a3e104918f8c0f 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 1b4478448997e6b715fb4bcf395fa61c9a3ec694..3691b61a0f7da33f9b2c22811b31e44391aa881b 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 2efc991b7faaafa536d83d445509e21e2f4d91cc..320ab6793056b81b98501b4fc89ddb02f6f9814c 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 a97b83053cde778e2bcc6fe95dd23de13d826936..6f32d32e8f5f3be7e0850530dc4d9ce9ab7afcec 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 0000000000000000000000000000000000000000..85b83eecc3f64ca8ad843f1c8c0a5a143deead32 --- /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 8d6a078c4642626363281cd0b0fae8c26a9c56bb..56fe686c825e2b2c416bde8c7a3053c1b23ca838 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>