diff --git a/src/components/middle/composer/AttachmentModal.module.scss b/src/components/middle/composer/AttachmentModal.module.scss index 612aa4a7a..58dc220fd 100644 --- a/src/components/middle/composer/AttachmentModal.module.scss +++ b/src/components/middle/composer/AttachmentModal.module.scss @@ -136,56 +136,71 @@ gap: 0.5rem; } -.drop-target { +.dropTarget { display: flex; flex-direction: column; position: relative; overflow: hidden; +} - &::before, - &::after { - content: ""; - position: absolute; - left: 0; - top: 0; - right: 0; - bottom: 0; - border-radius: var(--border-radius-default); - pointer-events: none; +.dropOutlineContainer { + pointer-events: none; + overflow: visible !important; - opacity: 0; - transition: 250ms opacity; - z-index: 1; - } + position: absolute; + top: 0.5rem; + left: 0.5rem; + width: calc(100% - 1rem); + height: calc(100% - 1rem); - &::before { - background-image: var(--drag-target-border-hovered); - background-color: var(--color-background); - } + background-color: var(--color-background); - &::after { - content: attr(data-attach-description); - display: flex; - justify-content: center; - align-items: center; - color: var(--color-primary); - } + opacity: 0; + transition: opacity 0.2s; + z-index: 1; +} + +.dropOutline { + fill: none; + + stroke: var(--color-primary); + stroke-width: 2; + stroke-dasharray: 11, 8; + stroke-linecap: round; + stroke-dashoffset: 0; + + animation: outline 0.5s linear infinite; + animation-play-state: paused; } .hovered { - .drop-target::before { - opacity: 0.95; + .dropOutline { + animation-play-state: running; } - .drop-target::after { + .dropOutlineContainer { opacity: 1; } - .caption-wrapper, - .attachments, - :global(.input-scroller) { - pointer-events: none; + .dropTarget { + &::after { + content: attr(data-attach-description); + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + color: var(--color-primary); + z-index: 1; + } + + .caption-wrapper, + .attachments, + :global(.input-scroller), + :global(.custom-scroll), + :global(.custom-scroll-x) { + pointer-events: none !important; + } } } @@ -212,3 +227,12 @@ } } } + +@keyframes outline { + from { + stroke-dashoffset: 0; + } + to { + stroke-dashoffset: -19; + } +} diff --git a/src/components/middle/composer/AttachmentModal.tsx b/src/components/middle/composer/AttachmentModal.tsx index d6825df75..e95d90f95 100644 --- a/src/components/middle/composer/AttachmentModal.tsx +++ b/src/components/middle/composer/AttachmentModal.tsx @@ -33,11 +33,13 @@ import { getHtmlTextLength } from './helpers/getHtmlTextLength'; import useAppLayout from '../../../hooks/useAppLayout'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import useDerivedState from '../../../hooks/useDerivedState'; +import useEffectOnce from '../../../hooks/useEffectOnce'; import useFlag from '../../../hooks/useFlag'; import useGetSelectionRange from '../../../hooks/useGetSelectionRange'; import useLastCallback from '../../../hooks/useLastCallback'; import useOldLang from '../../../hooks/useOldLang'; import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated'; +import useResizeObserver from '../../../hooks/useResizeObserver'; import useScrolledState from '../../../hooks/useScrolledState'; import useCustomEmojiTooltip from './hooks/useCustomEmojiTooltip'; import useEmojiTooltip from './hooks/useEmojiTooltip'; @@ -139,6 +141,10 @@ const AttachmentModal: FC = ({ onRemoveSymbol, onEmojiSelect, }) => { + // eslint-disable-next-line no-null/no-null + const ref = useRef(null); + // eslint-disable-next-line no-null/no-null + const svgRef = useRef(null); const { addRecentCustomEmoji, addRecentEmoji, updateAttachmentSettings } = getActions(); const lang = useOldLang(); @@ -388,6 +394,23 @@ const AttachmentModal: FC = ({ })); }); + const handleResize = useLastCallback(() => { + const svg = svgRef.current; + if (!svg) { + return; + } + + // Get inner width, without padding + const { width, height } = svg.getBoundingClientRect(); + svg.viewBox.baseVal.width = width; + svg.viewBox.baseVal.height = height; + }); + + // Can't listen for SVG resize + useResizeObserver(ref, handleResize); + + useEffectOnce(handleResize); + useEffect(() => { const mainButton = mainButtonRef.current; const input = document.getElementById(ATTACHMENT_MODAL_INPUT_ID); @@ -575,6 +598,9 @@ const AttachmentModal: FC = ({ data-attach-description={lang('Preview.Dragging.AddItems', 10)} data-dropzone > + + +
= ({ isQuick, isGeneric, onFileSelect }) => { + // eslint-disable-next-line no-null/no-null + const ref = useRef(null); + // eslint-disable-next-line no-null/no-null + const svgRef = useRef(null); + const [isHovered, markHovered, unmarkHovered] = useFlag(); - const handleDragLeave = (e: React.DragEvent) => { + const handleDragLeave = useLastCallback((e: React.DragEvent) => { const { relatedTarget: toTarget } = e; if (toTarget) { @@ -24,7 +32,24 @@ const DropTarget: FC = ({ isQuick, isGeneric, onFileSelect }) => { } unmarkHovered(); - }; + }); + + const handleResize = useLastCallback(() => { + const svg = svgRef.current; + if (!svg) { + return; + } + + // Get inner width, without padding + const { width, height } = svg.getBoundingClientRect(); + svg.viewBox.baseVal.width = width; + svg.viewBox.baseVal.height = height; + }); + + // Can't listen for SVG resize + useResizeObserver(ref, handleResize); + + useEffectOnce(handleResize); const className = buildClassName( 'DropTarget', @@ -34,11 +59,15 @@ const DropTarget: FC = ({ isQuick, isGeneric, onFileSelect }) => { return (
+ + +
Drop files here to send them
diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss index 805f2a818..5b091f33c 100644 --- a/src/styles/_variables.scss +++ b/src/styles/_variables.scss @@ -286,9 +286,6 @@ $color-message-story-mention-to: #74bcff; --spinner-gray-data: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEwLjggMjIuNEM2IDIxLjkgMi4xIDE4IDEuNiAxMy4yLjkgNy4xIDUuNCAxLjkgMTEuMyAxLjVjLjQgMCAuNy0uMy43LS43IDAtLjQtLjQtLjgtLjgtLjhDNC44LjQtLjIgNS45IDAgMTIuNS4yIDE4LjYgNS40IDIzLjggMTEuNSAyNGM2LjYuMiAxMi00LjggMTIuNC0xMS4yIDAtLjQtLjMtLjgtLjgtLjgtLjQgMC0uNy4zLS43LjctLjMgNS45LTUuNSAxMC40LTExLjYgOS43eiIgZmlsbD0iIzcwNzU3OSIvPjwvc3ZnPg==); --spinner-yellow-data: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTEwLjggMjIuNEM2IDIxLjkgMi4xIDE4IDEuNiAxMy4yLjkgNy4xIDUuNCAxLjkgMTEuMyAxLjVjLjQgMCAuNy0uMy43LS43IDAtLjQtLjQtLjgtLjgtLjhDNC44LjQtLjIgNS45IDAgMTIuNS4yIDE4LjYgNS40IDIzLjggMTEuNSAyNGM2LjYuMiAxMi00LjggMTIuNC0xMS4yIDAtLjQtLjMtLjgtLjgtLjgtLjQgMC0uNy4zLS43LjctLjMgNS45LTUuNSAxMC40LTExLjYgOS43eiIgZmlsbD0iI0ZERDc2NCIvPjwvc3ZnPg==); - --drag-target-border: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%23DDDFE0' stroke-width='4' stroke-dasharray='9.1%2c 10.5' stroke-dashoffset='3' stroke-linecap='round'/%3e%3c/svg%3e"); - --drag-target-border-hovered: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='%2363A2E3' stroke-width='4' stroke-dasharray='9.1%2c 10.5' stroke-dashoffset='3' stroke-linecap='round'/%3e%3c/svg%3e"); - --premium-gradient: linear-gradient(84.4deg, #6C93FF -4.85%, #976FFF 51.72%, #DF69D1 110.7%); --layer-blackout-opacity: 0.1;