improvement: some improvement done for appointment and treatment shared components.

This commit is contained in:
2026-07-17 11:21:08 +03:30
parent d2ad1fd7b6
commit 1b63bfff00
12 changed files with 204 additions and 38 deletions

View File

@@ -0,0 +1,96 @@
'use client';
import { useLayoutEffect, useRef, useState, type CSSProperties, type RefObject } from 'react';
const VIEWPORT_PADDING = 12;
const DEFAULT_GAP = 8;
export type AnchoredPanelAlign = 'start' | 'stretch';
type UseAnchoredPanelPositionArgs = {
open: boolean;
/** Element the panel should attach to (field / trigger shell). */
anchorRef: RefObject<HTMLElement | null>;
/** Prefer aligning the panel's inline-start with the anchor's inline-start. */
align?: AnchoredPanelAlign;
gap?: number;
};
/**
* Positions a panel with `position: fixed`, clamped to the viewport.
* Flips above the anchor when there is not enough room below.
* Handles LTR/RTL via `document.documentElement.dir`.
*/
export function useAnchoredPanelPosition({
open,
anchorRef,
align = 'start',
gap = DEFAULT_GAP,
}: UseAnchoredPanelPositionArgs) {
const panelRef = useRef<HTMLDivElement>(null);
const [style, setStyle] = useState<CSSProperties | undefined>(undefined);
useLayoutEffect(() => {
if (!open) {
setStyle(undefined);
return;
}
function update() {
const anchor = anchorRef.current;
const panel = panelRef.current;
if (!anchor || !panel) return;
const anchorRect = anchor.getBoundingClientRect();
const panelRect = panel.getBoundingClientRect();
const vw = window.innerWidth;
const vh = window.innerHeight;
const isRtl = document.documentElement.dir === 'rtl';
let width =
align === 'stretch'
? Math.min(anchorRect.width, vw - VIEWPORT_PADDING * 2)
: Math.min(panelRect.width || panel.offsetWidth, vw - VIEWPORT_PADDING * 2);
if (width < 1) width = Math.min(280, vw - VIEWPORT_PADDING * 2);
let left: number;
if (align === 'stretch') {
left = anchorRect.left;
} else if (isRtl) {
left = anchorRect.right - width;
} else {
left = anchorRect.left;
}
left = Math.max(VIEWPORT_PADDING, Math.min(left, vw - width - VIEWPORT_PADDING));
const height = panelRect.height || panel.offsetHeight;
let top = anchorRect.bottom + gap;
if (top + height > vh - VIEWPORT_PADDING) {
const above = anchorRect.top - height - gap;
top = above >= VIEWPORT_PADDING ? above : Math.max(VIEWPORT_PADDING, vh - height - VIEWPORT_PADDING);
}
setStyle({
position: 'fixed',
top,
left,
width,
zIndex: 50,
});
}
update();
// Second pass after fonts/layout settle panel size.
const raf = window.requestAnimationFrame(update);
window.addEventListener('resize', update);
window.addEventListener('scroll', update, true);
return () => {
window.cancelAnimationFrame(raf);
window.removeEventListener('resize', update);
window.removeEventListener('scroll', update, true);
};
}, [align, anchorRef, gap, open]);
return { panelRef, style };
}