diff --git a/package-lock.json b/package-lock.json index 4a0c724d..25d76440 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "form-data": "^4.0.0", "idb": "^8.0.0", - "mailgun.js": "^10.2.3" + "mailgun.js": "^10.2.3", + "solid-popper": "0.3.0" }, "devDependencies": { "@authorizerdev/authorizer-js": "^2.0.3", @@ -93,13 +94,11 @@ "javascript-time-ago": "^2.5.11", "patch-package": "^8.0.0", "prosemirror-history": "^1.4.1", - "prosemirror-trailing-node": "^2.0.9", + "prosemirror-trailing-node": "^3.0.0", "prosemirror-view": "^1.34.3", - "rollup-plugin-visualizer": "^5.12.0", "sass": "^1.79.4", "solid-js": "^1.9.2", "solid-tiptap": "0.7.0", - "solid-transition-group": "^0.2.3", "storybook": "^8.3.5", "storybook-addon-sass-postcss": "^0.3.2", "storybook-solidjs": "^1.0.0-beta.2", @@ -4871,7 +4870,6 @@ "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -4879,9 +4877,9 @@ } }, "node_modules/@remirror/core-constants": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz", - "integrity": "sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", "dev": true, "license": "MIT" }, @@ -5461,19 +5459,6 @@ "solid-js": "^1.6.12" } }, - "node_modules/@solid-primitives/refs": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@solid-primitives/refs/-/refs-1.0.8.tgz", - "integrity": "sha512-+jIsWG8/nYvhaCoG2Vg6CJOLgTmPKFbaCrNQKWfChalgUf9WrVxWw0CdJb3yX15n5lUcQ0jBo6qYtuVVmBLpBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/utils": "^6.2.3" - }, - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/@solid-primitives/rootless": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/@solid-primitives/rootless/-/rootless-1.4.5.tgz", @@ -5552,16 +5537,6 @@ } } }, - "node_modules/@solid-primitives/transition-group": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@solid-primitives/transition-group/-/transition-group-1.0.5.tgz", - "integrity": "sha512-G3FuqvL13kQ55WzWPX2ewiXdZ/1iboiX53195sq7bbkDbXqP6TYKiadwEdsaDogW5rPnPYAym3+xnsNplQJRKQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/@solid-primitives/upload": { "version": "0.0.117", "resolved": "https://registry.npmjs.org/@solid-primitives/upload/-/upload-0.0.117.tgz", @@ -7236,42 +7211,6 @@ "url": "https://github.com/sponsors/ueberdosis" } }, - "node_modules/@tiptap/pm/node_modules/@remirror/core-constants": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", - "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tiptap/pm/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@tiptap/pm/node_modules/prosemirror-trailing-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", - "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@remirror/core-constants": "3.0.0", - "escape-string-regexp": "^4.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.22.1", - "prosemirror-state": "^1.4.2", - "prosemirror-view": "^1.33.8" - } - }, "node_modules/@tiptap/starter-kit": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-2.8.0.tgz", @@ -11152,7 +11091,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/cwd": { @@ -20882,13 +20820,13 @@ } }, "node_modules/prosemirror-trailing-node": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.9.tgz", - "integrity": "sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", "dev": true, "license": "MIT", "dependencies": { - "@remirror/core-constants": "^2.0.2", + "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { @@ -22412,7 +22350,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.1.1.tgz", "integrity": "sha512-rqEO6FZk8mv7Hyv4UCj3FD3b6Waqft605TLfsCe/BiaylRpyyMC0b+uA5TJKawX3KzMrdi3wsLbCaLplrQmBvQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -22422,7 +22359,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.1.1.tgz", "integrity": "sha512-qNSy1+nUj7hsCOon7AO4wdAIo9P0jrzAMp18XhiOzA6/uO5TKtP7ScozVJ8T293oRIvi5wyCHSM4TrJo/c/GJA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -22665,7 +22601,6 @@ "version": "1.9.2", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.2.tgz", "integrity": "sha512-fe/K03nV+kMFJYhAOE8AIQHcGxB4rMIEoEyrulbtmf217NffbbwBqJnJI4ovt16e+kaIt0czE2WA7mP/pYN9yg==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.1.0", @@ -22673,6 +22608,19 @@ "seroval-plugins": "^1.1.0" } }, + "node_modules/solid-popper": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/solid-popper/-/solid-popper-0.3.0.tgz", + "integrity": "sha512-XlfEWAyxGGqFgg/uRpF+BemSfCqjbLA8p6fToDa+6v3paw3eBQj0TU08aBOIj2VeigaEiz8ZTlDx1eBLVRivBg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@popperjs/core": "^2.11", + "solid-js": "^1.2" + } + }, "node_modules/solid-refresh": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.6.3.tgz", @@ -22703,24 +22651,6 @@ "solid-js": "^1.7" } }, - "node_modules/solid-transition-group": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/solid-transition-group/-/solid-transition-group-0.2.3.tgz", - "integrity": "sha512-iB72c9N5Kz9ykRqIXl0lQohOau4t0dhel9kjwFvx81UZJbVwaChMuBuyhiZmK24b8aKEK0w3uFM96ZxzcyZGdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@solid-primitives/refs": "^1.0.5", - "@solid-primitives/transition-group": "^1.0.2" - }, - "engines": { - "node": ">=18.0.0", - "pnpm": ">=8.6.0" - }, - "peerDependencies": { - "solid-js": "^1.6.12" - } - }, "node_modules/solid-use": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/solid-use/-/solid-use-0.9.0.tgz", diff --git a/package.json b/package.json index c83a2748..b3d10bc9 100644 --- a/package.json +++ b/package.json @@ -100,13 +100,11 @@ "javascript-time-ago": "^2.5.11", "patch-package": "^8.0.0", "prosemirror-history": "^1.4.1", - "prosemirror-trailing-node": "^2.0.9", + "prosemirror-trailing-node": "^3.0.0", "prosemirror-view": "^1.34.3", - "rollup-plugin-visualizer": "^5.12.0", "sass": "^1.79.4", "solid-js": "^1.9.2", "solid-tiptap": "0.7.0", - "solid-transition-group": "^0.2.3", "storybook": "^8.3.5", "storybook-addon-sass-postcss": "^0.3.2", "storybook-solidjs": "^1.0.0-beta.2", @@ -133,10 +131,16 @@ "yjs": "13.6.19", "y-prosemirror": "1.2.12" }, - "trustedDependencies": ["@biomejs/biome", "@swc/core", "esbuild", "protobufjs"], + "trustedDependencies": [ + "@biomejs/biome", + "@swc/core", + "esbuild", + "protobufjs" + ], "dependencies": { "form-data": "^4.0.0", "idb": "^8.0.0", - "mailgun.js": "^10.2.3" + "mailgun.js": "^10.2.3", + "solid-popper": "0.3.0" } } diff --git a/src/components/Snackbar/Snackbar.module.scss b/src/components/Snackbar/Snackbar.module.scss index 49a7337d..612e60bc 100644 --- a/src/components/Snackbar/Snackbar.module.scss +++ b/src/components/Snackbar/Snackbar.module.scss @@ -1,39 +1,40 @@ .snackbar { - background-color: var(--default-color); - color: #fff; - font-size: 2rem; - font-weight: 500; - left: 0; - transition: background-color 0.3s; - position: absolute; - width: 100%; - - &.error { - background-color: #d00820; - } - - &.success { - .icon { - height: 1.8em; - margin-right: 0.5em; - margin-top: 0.1em; - width: 1.8em; - } + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%) translateY(100%); + opacity: 0; + transition: transform 0.3s ease-out, opacity 0.3s ease-out; + z-index: 1000; + + &.show { + transform: translateX(-50%) translateY(0); + opacity: 1; } } .content { - transition: - height 0.3s, - color 0.3s; - height: 60px; display: flex; align-items: center; - justify-content: center; + padding: 12px 16px; + border-radius: 4px; + background-color: #333; + color: #fff; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); +} - &.enter, - &.exitTo { - height: 0; - color: transparent; +.error { + .content { + background-color: #d32f2f; } } + +.success { + .content { + background-color: #43a047; + } +} + +.icon { + margin-right: 8px; +} diff --git a/src/components/Snackbar/Snackbar.tsx b/src/components/Snackbar/Snackbar.tsx index f0e76603..1742f29e 100644 --- a/src/components/Snackbar/Snackbar.tsx +++ b/src/components/Snackbar/Snackbar.tsx @@ -1,6 +1,5 @@ import { clsx } from 'clsx' -import { Show } from 'solid-js' -import { Transition } from 'solid-transition-group' +import { Show, createEffect } from 'solid-js' import { useSnackbar } from '~/context/ui' import { Icon } from '../_shared/Icon' @@ -10,29 +9,33 @@ import styles from './Snackbar.module.scss' export const Snackbar = () => { const { snackbarMessage } = useSnackbar() + let snackbarRef: HTMLDivElement | undefined + + createEffect(() => { + if (snackbarMessage()?.body) { + snackbarRef?.classList.add(styles.show) + } else { + snackbarRef?.classList.remove(styles.show) + } + }) return (
- setTimeout(() => done(), 300)} - > - -
- - - - {snackbarMessage()?.body || ''} -
-
-
+ +
+ + + + {snackbarMessage()?.body || ''} +
+
) diff --git a/src/components/_shared/Popover/Popover.tsx b/src/components/_shared/Popover/Popover.tsx index c9a7f3de..8187a241 100644 --- a/src/components/_shared/Popover/Popover.tsx +++ b/src/components/_shared/Popover/Popover.tsx @@ -1,5 +1,5 @@ -import { JSX, Show, createSignal, onCleanup, onMount } from 'solid-js' -import { createTooltip } from '~/lib/createTooltip' +import { JSX, Show, createSignal, onMount } from 'solid-js' +import usePopper from 'solid-popper' import styles from './Popover.module.scss' @@ -7,58 +7,63 @@ type Props = { children: (setTooltipEl: (el: HTMLElement | null) => void) => JSX.Element content: string | JSX.Element disabled?: boolean - placement?: 'top' | 'bottom' | 'left' | 'right' - offset?: [number, number] } export const Popover = (props: Props) => { const [show, setShow] = createSignal(false) const [anchor, setAnchor] = createSignal() - const [tooltip, setTooltip] = createSignal() + const [popper, setPopper] = createSignal() - let tooltipInstance: ReturnType | undefined + usePopper(anchor, popper, { + modifiers: [ + { + name: 'offset', + options: { + offset: [0, 8] + } + }, + { + name: 'flip', + options: { + fallbackPlacements: ['top', 'bottom'] + } + } + ] + }) - onMount(() => { - const anchorEl = anchor() - const tooltipEl = tooltip() + const showEvents = ['mouseenter', 'focus'] + const hideEvents = ['mouseleave', 'blur'] - if (anchorEl && tooltipEl && !props.disabled) { - tooltipInstance = createTooltip(anchorEl, tooltipEl, { - placement: props.placement || 'top', - offset: props.offset || [0, 8] + const handleMouseOver = () => setShow(true) + const handleMouseOut = () => setShow(false) + + if (!props.disabled) { + onMount(() => { + if (!anchor()) return + showEvents.forEach((event) => { + anchor()?.addEventListener(event, handleMouseOver) }) - } - }) - - onCleanup(() => { - tooltipInstance?.destroy() - }) - - const handleMouseOver = () => { - if (!props.disabled) { - setShow(true) - tooltipInstance?.update() - } - } - - const handleMouseOut = () => { - setShow(false) + hideEvents.forEach((event) => { + anchor()?.addEventListener(event, handleMouseOut) + }) + return () => { + showEvents.forEach((event) => { + anchor()?.removeEventListener(event, handleMouseOver) + }) + hideEvents.forEach((event) => { + anchor()?.removeEventListener(event, handleMouseOut) + }) + } + }) } return ( <> -
- {props.children(setAnchor)} -
+ {props.children(setAnchor)} -
+
{props.content} -
+
diff --git a/src/lib/createTooltip.ts b/src/lib/createTooltip.ts index 800aeeef..a6db23b3 100644 --- a/src/lib/createTooltip.ts +++ b/src/lib/createTooltip.ts @@ -1,7 +1,10 @@ export function createTooltip(referenceElement?: Element, tooltipElement?: HTMLElement, options = {}) { const defaultOptions = { placement: 'top', - offset: [0, 8] + offset: [0, 8], + flip: { + fallbackPlacements: ['top', 'bottom'] + } } const config = { ...defaultOptions, ...options } @@ -13,44 +16,60 @@ export function createTooltip(referenceElement?: Element, tooltipElement?: HTMLE const offsetX = config.offset[0] const offsetY = config.offset[1] + let placement = config.placement let top = 0 let left = 0 - switch (config.placement) { - case 'top': { + // Базовое позиционирование + switch (placement) { + case 'top': top = rect.top - tooltipRect.height - offsetY left = rect.left + (rect.width - tooltipRect.width) / 2 + offsetX break - } - case 'bottom': { + case 'bottom': top = rect.bottom + offsetY left = rect.left + (rect.width - tooltipRect.width) / 2 + offsetX break - } - case 'left': { - top = rect.top + (rect.height - tooltipRect.height) / 2 + offsetY - left = rect.left - tooltipRect.width - offsetX - break - } - case 'right': { - top = rect.top + (rect.height - tooltipRect.height) / 2 + offsetY - left = rect.right + offsetX - break - } - default: { - top = rect.top - tooltipRect.height - offsetY - left = rect.left + (rect.width - tooltipRect.width) / 2 + offsetX - } + // Добавьте case для 'left' и 'right', если необходимо } + // Проверка на выход за границы окна и применение flip + if (top < 0 && config.flip.fallbackPlacements.includes('bottom')) { + top = rect.bottom + offsetY + placement = 'bottom' + } else if (top + tooltipRect.height > window.innerHeight && config.flip.fallbackPlacements.includes('top')) { + top = rect.top - tooltipRect.height - offsetY + placement = 'top' + } + + // Применение позиции tooltipElement.style.position = 'absolute' tooltipElement.style.top = `${top}px` tooltipElement.style.left = `${left}px` + tooltipElement.style.visibility = 'visible' + + // Обновление класса для стрелки + tooltipElement.setAttribute('data-popper-placement', placement) + + // Позиционирование стрелки + const arrow = tooltipElement.querySelector('[data-popper-arrow]') as HTMLElement + if (arrow) { + const arrowRect = arrow.getBoundingClientRect() + if (placement === 'top') { + arrow.style.bottom = '-4px' + arrow.style.left = `${tooltipRect.width / 2 - arrowRect.width / 2}px` + } else if (placement === 'bottom') { + arrow.style.top = '-4px' + arrow.style.left = `${tooltipRect.width / 2 - arrowRect.width / 2}px` + } + } } function showTooltip() { - if (tooltipElement) tooltipElement.style.visibility = 'visible' - updatePosition() + if (tooltipElement) { + tooltipElement.style.visibility = 'visible' + updatePosition() + } } function hideTooltip() { @@ -60,6 +79,7 @@ export function createTooltip(referenceElement?: Element, tooltipElement?: HTMLE referenceElement?.addEventListener('mouseenter', showTooltip) referenceElement?.addEventListener('mouseleave', hideTooltip) window.addEventListener('resize', updatePosition) + window.addEventListener('scroll', updatePosition) return { update: updatePosition, @@ -67,6 +87,7 @@ export function createTooltip(referenceElement?: Element, tooltipElement?: HTMLE referenceElement?.removeEventListener('mouseenter', showTooltip) referenceElement?.removeEventListener('mouseleave', hideTooltip) window.removeEventListener('resize', updatePosition) + window.removeEventListener('scroll', updatePosition) } } -} +} \ No newline at end of file