goldenChat base source add
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 시간대 선택 — 하단 바 클릭·설정 화면 공용
|
||||
*/
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { TIMEZONE_OPTIONS, formatUnixClock, getTimezoneAbbr, normalizeTimezone } from '../utils/timezone';
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
onChange: (tz: string) => void;
|
||||
/** compact: 하단 바용 버튼 스타일 */
|
||||
variant?: 'bar' | 'select';
|
||||
/** 하단 바: 마지막 봉 시각 표시 (unix 초) */
|
||||
displayUnix?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const TimezonePicker: React.FC<Props> = ({ value, onChange, variant = 'bar', displayUnix, className = '' }) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [menuPos, setMenuPos] = useState<{ bottom: number; right: number } | null>(null);
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const triggerRef = useRef<HTMLButtonElement>(null);
|
||||
const menuRef = useRef<HTMLUListElement>(null);
|
||||
const tz = normalizeTimezone(value);
|
||||
|
||||
const updateMenuPos = useCallback(() => {
|
||||
const btn = triggerRef.current;
|
||||
if (!btn) return;
|
||||
const r = btn.getBoundingClientRect();
|
||||
setMenuPos({
|
||||
bottom: window.innerHeight - r.top + 4,
|
||||
right: Math.max(8, window.innerWidth - r.right),
|
||||
});
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!open || variant !== 'bar') return;
|
||||
updateMenuPos();
|
||||
const onResize = () => updateMenuPos();
|
||||
window.addEventListener('resize', onResize);
|
||||
window.addEventListener('scroll', onResize, true);
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
window.removeEventListener('scroll', onResize, true);
|
||||
};
|
||||
}, [open, variant, updateMenuPos]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onDoc = (e: MouseEvent) => {
|
||||
const t = e.target as Node;
|
||||
if (rootRef.current?.contains(t) || menuRef.current?.contains(t)) return;
|
||||
setOpen(false);
|
||||
};
|
||||
const id = window.setTimeout(() => {
|
||||
document.addEventListener('mousedown', onDoc);
|
||||
}, 0);
|
||||
return () => {
|
||||
window.clearTimeout(id);
|
||||
document.removeEventListener('mousedown', onDoc);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
if (variant === 'select') {
|
||||
return (
|
||||
<select
|
||||
className={`stg-select ${className}`}
|
||||
value={tz}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
{TIMEZONE_OPTIONS.map(o => (
|
||||
<option key={o.id} value={o.id}>{o.label}</option>
|
||||
))}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
const menu = open && variant === 'bar' && menuPos && (
|
||||
<ul
|
||||
ref={menuRef}
|
||||
className="tz-picker-menu tz-picker-menu--portal"
|
||||
role="listbox"
|
||||
style={{ bottom: menuPos.bottom, right: menuPos.right }}
|
||||
>
|
||||
{TIMEZONE_OPTIONS.map(o => (
|
||||
<li key={o.id}>
|
||||
<button
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={o.id === tz}
|
||||
className={`tz-picker-item${o.id === tz ? ' active' : ''}`}
|
||||
onClick={() => {
|
||||
onChange(o.id);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<span className="tz-picker-item-label">{o.label}</span>
|
||||
<span className="tz-picker-item-abbr">{o.abbr}</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`tz-picker ${className}`} ref={rootRef}>
|
||||
<button
|
||||
ref={triggerRef}
|
||||
type="button"
|
||||
className="tv-time-display tz-picker-trigger"
|
||||
onClick={() => {
|
||||
if (open) {
|
||||
setOpen(false);
|
||||
return;
|
||||
}
|
||||
updateMenuPos();
|
||||
setOpen(true);
|
||||
}}
|
||||
title="시간대 변경"
|
||||
aria-expanded={open}
|
||||
aria-haspopup="listbox"
|
||||
>
|
||||
{displayUnix != null && displayUnix > 0 && (
|
||||
<span className="tz-picker-clock">{formatUnixClock(displayUnix, tz, true)}</span>
|
||||
)}
|
||||
<span className="tz-picker-abbr">{getTimezoneAbbr(tz)}</span>
|
||||
<span className="tz-picker-caret">▾</span>
|
||||
</button>
|
||||
{menu && createPortal(menu, document.body)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TimezonePicker;
|
||||
Reference in New Issue
Block a user