import React, { useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { styled } from 'styled-components';
import { useSnapshot } from 'valtio';
import { feedbackStore, invokeSectionFeedback, resetSectionFeedback } from '../../model/feedback';
import { SectionFeedbackCommentForm } from './SectionFeedbackCommentForm';
const StyledWrapper = styled.div`
background-color: #fff;
border-radius: 12px;
box-shadow: 0 0.5rem 1.2rem #f0f1f2;
transition: height 0.3s ease-in-out;
width: 262px;
position: fixed;
z-index: 1001;
padding: 10px 18px;
border-radius: 8px;
transition: top 0.3s ease-in-out, right 0.3s ease-in-out;
`;
const SectionFeedback: React.FC = () => {
const feedbackState = useSnapshot(feedbackStore);
const location = useLocation();
useEffect(() => {
const observer = new MutationObserver(() => {
const buttons = document.querySelectorAll('.comment-link');
if (!buttons || buttons.length === 0) return;
const focusCommentInput = (e) => {
const button = e.target.closest('.comment-link');
invokeSectionFeedback(button.getAttribute('data-feedback-hash'));
};
buttons.forEach((button) => {
button.addEventListener('click', focusCommentInput);
});
return () => {
buttons.forEach((button) => {
button.removeEventListener('click', focusCommentInput);
});
};
});
observer.observe(document.body, { childList: true, subtree: true });
return () => {
observer.disconnect();
};
}, []);
const [position, setPosition] = useState({ top: 0, right: 12 });
useEffect(() => {
const handleClick = (e: MouseEvent) => {
const target = e.target as HTMLElement;
const closestCommentLink = target.closest('.comment-link');
if (!feedbackState.section || !closestCommentLink) return;
const escapedSection = CSS.escape(feedbackState.section);
const elements = document.querySelectorAll(`.comment-link[data-feedback-hash=${escapedSection}]`);
const closestElement = findClosetElement(e, elements);
if (closestElement) {
const rect = closestElement.getBoundingClientRect();
const top = Math.min(Math.max(rect.top, 0), window.innerHeight - 380);
setPosition({ top, right: 12 });
}
};
window.addEventListener('click', handleClick);
return () => {
window.removeEventListener('click', handleClick);
};
}, [feedbackState.section]);
useEffect(() => {
resetSectionFeedback();
}, [location.pathname]);
if (!feedbackState.section) return null;
return (
);
};
const findClosetElement = (e: MouseEvent, elements: NodeListOf): Element | null => {
let closestElement: Element | null = null;
let closestDistance = Infinity;
elements.forEach((element) => {
const rect = element.getBoundingClientRect();
const distance = Math.sqrt(
Math.pow(rect.left + rect.width / 2 - e.clientX, 2) + Math.pow(rect.top + rect.height / 2 - e.clientY, 2),
);
if (distance < closestDistance) {
closestDistance = distance;
closestElement = element;
}
});
return closestElement;
};
export default SectionFeedback;