diff --git a/src/App.vue b/src/App.vue index e2f6e6327f7fef6790dc4f4329e1e748ff2203b3..c2f190b867b230c7bfd9b3482670922398ffb279 100644 --- a/src/App.vue +++ b/src/App.vue @@ -108,6 +108,8 @@ import ToggleSwitch from '@stories/components/ToggleSwitch/ToggleSwitch.vue'; import TriangleIcon from '@stories/icons/Mono/TriangleIcon.vue'; import Button from '@stories/components/Button/Button.vue'; import Slider from '@stories/components/Slider/Slider.vue'; +import type { ISBOption } from '@interfaces/componentsProp'; +import Modal from '@stories/components/Modal/Modal.vue'; const gentleIcons = [ Age18Icon, @@ -215,16 +217,6 @@ const gentleIcons = [ UserIcon, ]; const visibleDrawer = ref(true); -const options = [ - { - label: 'First', - textStyle: 'bold', - iconPosition: 'top', - }, - { - label: 'Second', - }, -]; const sliderOptions = [ { label: 0, @@ -273,15 +265,27 @@ const sliderOptions = [ }, { label: 18, - value: 18, - color: 'purple', }, ]; +const options: ISBOption[] = [ + { + label: 'First', + textStyle: 'bold', + iconPosition: 'top', + }, + { + label: 'Second', + }, +]; +const visible = ref(false); +const onClose = () => console.log('close!'); </script> <template> + <Modal v-model:visible="visible" @onClose="onClose"></Modal> <Slider :options="sliderOptions" + orientation="vertical" width="400" min="0" max="18" @@ -290,7 +294,7 @@ const sliderOptions = [ theme="blue" isSmooth /> - <Button theme="sky" label="I'm a button"></Button> + <Button @click="visible = true" theme="sky" label="I'm a button"></Button> <SelectButton :options="options" size="large"> <template #1Icon> <TrashIcon /> diff --git a/src/common/interfaces/componentsProps.ts b/src/common/interfaces/componentsProps.ts index a7021a9c93a12ed3506b24a8b6b66542eb4d801f..723344262c2ccca1c07f5fafe6c1e6910292eb0a 100644 --- a/src/common/interfaces/componentsProps.ts +++ b/src/common/interfaces/componentsProps.ts @@ -50,6 +50,13 @@ export interface IDrawerProps { footerDivider?: boolean; } +export interface IModalProps { + theme?: TThemeColor; + width?: number | string; + closeIcon?: TIcons; + headerDivider?: boolean; +} + export interface ISBProps { options: ISBOption[]; size?: TSize; diff --git a/src/stories/components/Modal/Modal.stories.ts b/src/stories/components/Modal/Modal.stories.ts new file mode 100644 index 0000000000000000000000000000000000000000..6eff6d1baa5a140c45a3a391229f6cff56dee450 --- /dev/null +++ b/src/stories/components/Modal/Modal.stories.ts @@ -0,0 +1,54 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; + +import Modal from './Modal.vue'; + +const meta: Meta = { + title: 'Components/Modal', + component: Modal, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: 'A component that is used to select a boolean value.', + }, + }, + }, + argTypes: { + visible: { control: 'boolean' }, + width: { control: 'text' }, + header: { control: 'text' }, + default: { control: 'text' }, + theme: { + control: 'select', + options: [ + 'white', + 'slate', + 'blue', + 'sky', + 'teal', + 'green', + 'yellow', + 'orange', + 'pink', + 'fuchsia', + 'purple', + 'indigo', + 'rose', + 'red', + 'black', + ], + }, + }, + args: { + // primary: false, + // Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args + }, +} satisfies Meta<typeof Modal>; + +export default meta; + +type Story = StoryObj<typeof meta>; + +export const Primary: Story = { + args: {}, +}; diff --git a/src/stories/components/Modal/Modal.vue b/src/stories/components/Modal/Modal.vue index c24538a532e27e9c59d68d84c3447a97a041ab90..962143be1c9ca505caf672e12a0ae62c74544cb0 100644 --- a/src/stories/components/Modal/Modal.vue +++ b/src/stories/components/Modal/Modal.vue @@ -1,36 +1,28 @@ <script setup lang="ts"> -import { useVModel } from '@vueuse/core'; import { computed } from 'vue'; -import { convertThemeToColorWhiteDefault } from '@/app/helpers'; -import type { TThemeColor } from './interfaces/index'; +import { convert500ThemeToColor } from '@helpers/colors'; +import type { IModalProps } from '@interfaces/componentsProps'; -interface Props { - isVisible: boolean; - theme?: TThemeColor; - width?: number | string; - onClose?: () => never; -} -const props = defineProps<Props>(); -const themeColor = computed(() => convertThemeToColorWhiteDefault(props.theme)); +const props = withDefaults(defineProps<IModalProps>(), { + theme: 'white', +}); +const emit = defineEmits(['onClose']); +const visible = defineModel('visible', { + set(value) { + emit('onClose'); + return value; + }, +}); +const themeColor = computed(() => convert500ThemeToColor(props.theme)); const textColor = computed(() => { if (!props.theme) return '#000000'; if (props.theme === 'white') return '#000000'; return '#ffffff'; }); -const emit = defineEmits(['update:isVisible']); -const isVisible = useVModel(props, 'isVisible', emit); -const onKeydown = (event) => { - if (event.key === 'Escape' && isVisible.value) isVisible.value = false; +const onKeydown = (event: KeyboardEvent) => { + if (event.key === 'Escape' && visible.value) visible.value = false; }; document.addEventListener('keydown', onKeydown); -const unwatch = watch(isVisible, () => { - if (!isVisible.value) { - props.onClose(); - } -}); -if (!props.onClose) { - unwatch(); -} </script> <template> @@ -39,7 +31,7 @@ if (!props.onClose) { :class="[ 'modalBackground', { - openedModalBackground: isVisible, + openedModalBackground: visible, }, ]" ></section> @@ -48,13 +40,13 @@ if (!props.onClose) { :class="[ 'modal', { - openedModal: isVisible, + openedModal: visible, }, ]" > <header class="modalHeader"> <slot name="header" /> - <div class="buttonClose" @click.prevent="isVisible = false"> + <div class="buttonClose" @click.prevent="visible = false"> <svg width="40px" height="40px" diff --git a/src/stories/components/Slider/Slider.stories.ts b/src/stories/components/Slider/Slider.stories.ts index 21452cc09b0dbba94677a308be30192cf5ab5196..78f3ed093127b2696f7a69453a7373ac41ac6f91 100644 --- a/src/stories/components/Slider/Slider.stories.ts +++ b/src/stories/components/Slider/Slider.stories.ts @@ -1,7 +1,6 @@ import type { Meta, StoryObj } from '@storybook/vue3'; import Slider from './Slider.vue'; -import type { TThemeColor } from '@interfaces/common'; const meta: Meta = { title: 'Components/Slider', @@ -15,7 +14,7 @@ const meta: Meta = { }, }, argTypes: { - options: { control: 'array' }, + options: { control: 'object' }, width: { control: 'text' }, min: { control: 'text' }, max: { control: 'text' }, @@ -81,12 +80,13 @@ export const Primary: Story = { export const Full: Story = { args: { min: '0', - max: '10', + max: '20', step: '2', - size: 'small', + size: 'large', backgroundColor: 'black', theme: 'blue', isSmooth: true, + options: [ { label: 0, @@ -138,6 +138,24 @@ export const Full: Story = { value: 18, color: 'purple', }, + { + label: 20, + value: 20, + color: 'purple', + }, ], + + width: '300', + }, +}; + +export const Smooth: Story = { + args: { + max: '1000', + isSmooth: true, + width: '300', + size: 'medium', + backgroundColor: 'blue', + theme: 'black', }, }; diff --git a/src/stories/components/Slider/Slider.vue b/src/stories/components/Slider/Slider.vue index a689cbc765f882833618b57f9ded3b10625871dd..47b6fcb0f58697288f2c9fe7b1c9735831101f41 100644 --- a/src/stories/components/Slider/Slider.vue +++ b/src/stories/components/Slider/Slider.vue @@ -1,32 +1,14 @@ <script setup lang="ts"> import { computed, ref, watch } from 'vue'; -import type { TThemeColor } from '@interfaces/common'; import { convert500ThemeToColor } from '@helpers/colors'; +import type { ISliderProps } from '@interfaces/componentsProps'; -const props = withDefaults( - defineProps<{ - width?: string | number; - min?: string | number; - max?: string | number; - step?: string | number; - size?: 'small' | 'medium' | 'large' | 'huge'; - theme?: TThemeColor; - backgroundColor?: TThemeColor; - orientation?: 'horizontal' | 'vertical'; - isSmooth?: boolean; - options?: { - label: string; - value: number; - color?: string; - }[]; - }>(), - { - width: '200', - size: 'medium', - theme: 'white', - backgroundColor: 'black', - }, -); +const props = withDefaults(defineProps<ISliderProps>(), { + width: '100', + size: 'medium', + theme: 'sky', + backgroundColor: 'black', +}); const value = defineModel('value'); const optionValue = ref( typeof value.value === 'string' @@ -35,7 +17,9 @@ const optionValue = ref( ); watch([optionValue], () => { if (props.options) { - value.value = props.options!.find((option) => option.value == optionValue.value)!.label; + value.value = props.options!.find( + (option) => (option.value ?? option.label) == optionValue.value, + )!.label; } else value.value = optionValue.value; }); watch([value], () => { @@ -49,25 +33,41 @@ watch([value], () => { const sliderButtonSize = computed(() => { switch (props.size) { case 'small': - return '25px'; + return '10px'; case 'large': - return '70px'; + return '30px'; case 'huge': - return '100px'; + return '40px'; } - return '40px'; + return '20px'; }); -const sliderHeight = computed(() => `${Math.floor(sliderButtonSize.value.slice(0, -2) / 3)}px`); +const optionsFontSize = computed(() => { + if (!props.options?.length) return; + switch (props.size) { + case 'small': + return '10px'; + case 'large': + return '14px'; + case 'huge': + return '16px'; + } + return '12px'; +}); +const widthHalf = computed(() => `${Math.floor(+props.width / 2)}px`); +const sliderHeight = computed(() => `${Math.floor(+sliderButtonSize.value.slice(0, -2) / 2.5)}px`); const sliderBorderRadius = computed(() => (props.isSmooth ? sliderHeight.value : '0%')); const sliderButtonBorderRadius = computed(() => (props.isSmooth ? '50%' : '0%')); const themeColor = computed(() => convert500ThemeToColor(props.theme)); const themeBackground = computed(() => convert500ThemeToColor(props.backgroundColor)); +const marksListPadding = computed( + () => `${Math.floor(+sliderButtonSize.value.slice(0, -2) / 2)}px`, +); </script> <template> <div :class="[ - 'slideContainer', + 'sliderContainer', { verticalSlider: orientation === 'vertical', }, @@ -82,35 +82,23 @@ const themeBackground = computed(() => convert500ThemeToColor(props.backgroundCo :max="max ?? 100" :step="step ?? 1" /> - <input type="range" list="values" class="opacity-0 size-0" /> - <div v-if="options?.length"> <ul class="marksList" :style="`width: ${width ?? 200}px`"> - <li v-for="option of options" :key="option.label">|</li> + <li + v-for="option of options" + :key="option.label" + class="mark" + :style="`color: ${convert500ThemeToColor(option?.color) ?? 'white'}; font-size: ${optionsFontSize}`" + > + {{ option.label }} + </li> </ul> - <datalist - id="values" - :class="[ - 'values', - { - datalistVertical: orientation === 'vertical', - }, - ]" - > - <template v-for="option of options" :key="option.value"> - <option - :value="option.value" - :label="option.label" - :style="`color: ${option.color ?? 'white'}`" - ></option> - </template> - </datalist> </div> </div> </template> <style scoped> -.slideContainer { +.sliderContainer { width: v-bind(width); } .slider { @@ -149,6 +137,7 @@ const themeBackground = computed(() => convert500ThemeToColor(props.backgroundCo cursor: pointer; } .verticalSlider { + margin-top: v-bind(widthHalf); transform: rotate(270deg); } datalist { @@ -156,10 +145,6 @@ datalist { justify-content: space-between; width: v-bind(width); } -.datalistVertical { - flex-direction: column; - writing-mode: vertical-lr; -} .values { padding: 0 -15px; } @@ -171,6 +156,14 @@ option { justify-content: space-between; margin-bottom: 5px; font-size: 10px; - padding: 0 10px; + padding: 0 v-bind(marksListPadding); +} +.mark { + display: flex; + justify-content: center; + line-height: 40px; + background-color: black; + width: 1px; + height: 10px; } </style>