From e3d5451d9ae8058bdd721d9163b33c0489676539 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: Wed, 18 Dec 2024 11:23:19 +0500 Subject: [PATCH] feat: component "Popup" --- README.md | 6 +- src/App.vue | 23 ++++++ src/common/interfaces/componentsProps.ts | 8 +++ src/stories/components/Button/Button.vue | 2 +- src/stories/components/Popup/Popup.stories.ts | 72 +++++++++++++++++++ src/stories/components/Popup/Popup.vue | 71 ++++++++++++++++++ 6 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 src/stories/components/Popup/Popup.stories.ts create mode 100644 src/stories/components/Popup/Popup.vue diff --git a/README.md b/README.md index 1976a8e..bf8163a 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,17 @@ ## СпиÑок компонентов: - TreeList; - MenuDial; +- Popup; - Slider; - Drawer; - Modal; - SelectButton; - Button; - ToggleSwitch; -- Divider; +- Divider. + +## Components count: 10 +## Bundle size: 248.7KB ### ÐаÑтройка Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ diff --git a/src/App.vue b/src/App.vue index 5838a25..d7bf047 100644 --- a/src/App.vue +++ b/src/App.vue @@ -111,6 +111,7 @@ import Slider from '@stories/components/Slider/Slider.vue'; import type { ISBOption } from '@interfaces/componentsProp'; import Modal from '@stories/components/Modal/Modal.vue'; import MenuDial from '@stories/components/MenuDial/MenuDial.vue'; +import Popup from '@stories/components/Popup/Popup.vue'; const gentleIcons = [ Age18Icon, @@ -282,10 +283,32 @@ const visible = ref(false); const onClose = () => console.log('close!'); const value = ref(); const active = ref(false); +const popupActive = ref(false); +const popupActive2 = ref(false); const sliderValue = ref(1); </script> <template> + <div class="hui" style="width: 500px; height: 500px; background-color: gray"></div> + <Popup v-model:active="popupActive" parentSelector=".hui" theme="sky"> + <Button + @click=" + () => { + popupActive = false; + visible = true; + } + " + label="Открыть модальное окно" + /> + </Popup> + <Popup v-model:active="popupActive2" theme="sky" + >Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet fugiat harum maiores placeat + soluta, vel velit voluptas. Accusamus aut, error et minima neque praesentium, ratione, + reprehenderit repudiandae saepe ut vero! Lorem ipsum dolor sit amet, consectetur adipisicing + elit. Amet fugiat harum maiores placeat soluta, vel velit voluptas. Accusamus aut, error et + minima neque praesentium, ratione, reprehenderit repudiandae saepe ut vero!</Popup + > + <Modal v-model:visible="visible" theme="red" @onClose="onClose" ><template #header>huuuuuuuuuuui</template>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque explicabo, facere fuga hic id impedit magnam maiores minima necessitatibus, nemo diff --git a/src/common/interfaces/componentsProps.ts b/src/common/interfaces/componentsProps.ts index 8d663ab..8a1459e 100644 --- a/src/common/interfaces/componentsProps.ts +++ b/src/common/interfaces/componentsProps.ts @@ -77,6 +77,14 @@ export interface IModalProps { headerDivider?: boolean; } +export interface IPopupProps { + parentSelector?: string; + theme?: TThemeColor; + maxWidth?: string; + maxHeight?: string; + padding?: string; +} + export interface ISBProps { options: ISBOption[]; size?: TSize; diff --git a/src/stories/components/Button/Button.vue b/src/stories/components/Button/Button.vue index cef4d55..be2e174 100644 --- a/src/stories/components/Button/Button.vue +++ b/src/stories/components/Button/Button.vue @@ -80,7 +80,7 @@ const width = computed(() => (props.width ? `${props.width}px` : 'max-content')) .button { position: relative; border-radius: 7px; - display: flex; + display: inline-flex; gap: 8px; justify-content: center; align-items: center; diff --git a/src/stories/components/Popup/Popup.stories.ts b/src/stories/components/Popup/Popup.stories.ts new file mode 100644 index 0000000..c8d3e5c --- /dev/null +++ b/src/stories/components/Popup/Popup.stories.ts @@ -0,0 +1,72 @@ +import type { Meta, StoryObj } from '@storybook/vue3'; + +import Popup from './Popup.vue'; +import Button from '@stories/components/Button/Button.vue'; + +const meta: Meta = { + title: 'Components/Popup', + component: Popup, + tags: ['autodocs'], + parameters: { + docs: { + description: { + component: 'A component that is used as a Popup. Can be used with icon.', + }, + }, + }, + argTypes: { + default: { control: 'text' }, + parentSelector: { control: 'text' }, + maxWidth: { control: 'text' }, + maxHeight: { control: 'text' }, + padding: { control: 'text' }, + theme: { + control: 'select', + options: [ + 'white', + 'slate', + 'blue', + 'sky', + 'teal', + 'green', + 'yellow', + 'orange', + 'pink', + 'fuchsia', + 'purple', + 'indigo', + 'rose', + 'red', + 'black', + ], + }, + }, + args: {}, +} satisfies Meta<typeof Popup>; + +export default meta; + +type Story = StoryObj<typeof meta>; + +export const Primary: Story = { + args: { + default: + 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet fugiat harum maiores placeat\n soluta, vel velit voluptas. Accusamus aut, error et minima neque praesentium, ratione,\n reprehenderit repudiandae saepe ut vero! Lorem ipsum dolor sit amet, consectetur adipisicing\n elit. Amet fugiat harum maiores placeat soluta, vel velit voluptas. Accusamus aut, error et\n minima neque praesentium, ratione, reprehenderit repudiandae saepe ut vero!', + maxWidth: '200px', + maxHeight: '100px', + }, +}; + +export const Full: Story = { + render: (args) => ({ + components: { Popup, Button }, + setup() { + return { args }; + }, + template: + '<Popup v-bind="args"><Button label="Создать" theme="sky" /><p style="display: inline-block; padding: 0 30px"></p><Button label="Удалить" theme="red" /></Popup>', + }), + args: { + theme: 'black', + }, +}; diff --git a/src/stories/components/Popup/Popup.vue b/src/stories/components/Popup/Popup.vue new file mode 100644 index 0000000..a9eb103 --- /dev/null +++ b/src/stories/components/Popup/Popup.vue @@ -0,0 +1,71 @@ +<script setup lang="ts"> +import type { IPopupProps } from '@interfaces/componentsProps'; +import { computed, ref } from 'vue'; +import { convert300ThemeToColor, convert500ThemeToColor } from '@helpers/colors'; + +const props = withDefaults(defineProps<IPopupProps>(), { + parentSelector: 'body', + theme: 'white', + maxWidth: '300px', + maxHeight: '100px', + padding: '5px', +}); +const active = defineModel<boolean>('active'); +const themeColor = computed(() => convert500ThemeToColor(props.theme)); +const scrollColor = computed(() => convert300ThemeToColor(props.theme)); + +const top = ref(); +const left = ref(); +const isContainer = ref(); + +const container = document.querySelector(props.parentSelector); +if (container) { + container.addEventListener('pointerdown', (e) => { + if (e.button === 2) { + console.log('e.clientY, e.clientX ', e.clientY, e.clientX); + isContainer.value = true; + if (!active.value) active.value = true; + top.value = e.clientY; + left.value = e.clientX; + e.stopPropagation(); + } + }); + container.addEventListener('contextmenu', (e) => { + if (isContainer.value) e.preventDefault(); + }); +} + +document.addEventListener('pointerdown', (e) => { + if (e.button === 0) active.value = false; +}); +</script> + +<template> + <section + oncontextmenu="return false" + id="popup" + @pointerdown.stop="" + :style="`top: ${top}px; left: ${left}px; opacity: ${active ? 1 : 0}; pointer-events: ${active ? 'auto' : 'none'}; padding: ${padding}`" + > + <div :style="`max-width: ${maxWidth}; max-height: ${maxHeight}; overflow: auto;`"> + <slot /> + <p v-if="!$slots.default" style="background-color: black; color: white; padding: 10px"> + Popup + </p> + </div> + </section> +</template> + +<style scoped> +#popup { + position: fixed; + transition: opacity 0.2s ease-in-out; + background-color: v-bind(themeColor); + border: 1px solid #403e46; + border-radius: 5px; +} +::-webkit-scrollbar-thumb { + border-radius: 5px; + background-color: v-bind(scrollColor); +} +</style> -- GitLab