안드로이드 이이콘 변경

This commit is contained in:
Macbook
2026-06-15 17:58:55 +09:00
parent ba00841834
commit 8b45362a00
5 changed files with 55 additions and 10 deletions
+2 -1
View File
@@ -609,7 +609,7 @@ const DesktopTab: React.FC<{ canBuildDesktop: boolean }> = ({ canBuildDesktop })
}; };
const AppDownloadModal: React.FC<Props> = ({ onClose, canBuildDesktop = false }) => { const AppDownloadModal: React.FC<Props> = ({ onClose, canBuildDesktop = false }) => {
const [tab, setTab] = useState<TabId>('mobile'); const [tab, setTab] = useState<TabId>('desktop');
return ( return (
<DraggableModalFrame <DraggableModalFrame
@@ -619,6 +619,7 @@ const AppDownloadModal: React.FC<Props> = ({ onClose, canBuildDesktop = false })
badge={tab === 'mobile' ? 'Mobile' : 'Desktop'} badge={tab === 'mobile' ? 'Mobile' : 'Desktop'}
width={480} width={480}
dialogClassName="sp-modal app-download-modal" dialogClassName="sp-modal app-download-modal"
recenterOnResize
> >
<div className="app-download-tabs" role="tablist" aria-label="다운로드 종류"> <div className="app-download-tabs" role="tablist" aria-label="다운로드 종류">
<button <button
+4 -1
View File
@@ -24,6 +24,8 @@ export interface AppPopupProps {
overlayClassName?: string; overlayClassName?: string;
bodyClassName?: string; bodyClassName?: string;
centered?: boolean; centered?: boolean;
/** 콘텐츠 크기 변화·창 리사이즈 시 (드래그 전까지) 중앙 재정렬 */
recenterOnResize?: boolean;
zIndex?: number; zIndex?: number;
closeOnBackdrop?: boolean; closeOnBackdrop?: boolean;
backdrop?: boolean; backdrop?: boolean;
@@ -48,6 +50,7 @@ export const AppPopup: React.FC<AppPopupProps> = ({
overlayClassName, overlayClassName,
bodyClassName = 'app-popup-body', bodyClassName = 'app-popup-body',
centered = true, centered = true,
recenterOnResize = false,
zIndex = 9999, zIndex = 9999,
closeOnBackdrop = true, closeOnBackdrop = true,
backdrop = false, backdrop = false,
@@ -68,7 +71,7 @@ export const AppPopup: React.FC<AppPopupProps> = ({
headerTouchStyle, headerTouchStyle,
panelStyle, panelStyle,
headerCursor, headerCursor,
} = useDraggablePanel({ centerOnMount: centered, initialPosition }); } = useDraggablePanel({ centerOnMount: centered, recenterOnResize, initialPosition });
const overlayCls = [ const overlayCls = [
'app-popup-overlay', 'app-popup-overlay',
@@ -16,6 +16,8 @@ export interface DraggableModalFrameProps {
zIndex?: number; zIndex?: number;
width?: number | string; width?: number | string;
closeOnBackdrop?: boolean; closeOnBackdrop?: boolean;
/** 콘텐츠 크기 변화 시 (드래그 전까지) 중앙 재정렬 */
recenterOnResize?: boolean;
} }
export const DraggableModalFrame: React.FC<DraggableModalFrameProps> = ({ export const DraggableModalFrame: React.FC<DraggableModalFrameProps> = ({
@@ -30,6 +32,7 @@ export const DraggableModalFrame: React.FC<DraggableModalFrameProps> = ({
zIndex = 1000, zIndex = 1000,
width, width,
closeOnBackdrop = true, closeOnBackdrop = true,
recenterOnResize = false,
}) => ( }) => (
<AppPopup <AppPopup
onClose={onClose} onClose={onClose}
@@ -44,6 +47,7 @@ export const DraggableModalFrame: React.FC<DraggableModalFrameProps> = ({
backdrop backdrop
closeOnBackdrop={closeOnBackdrop} closeOnBackdrop={closeOnBackdrop}
centered centered
recenterOnResize={recenterOnResize}
> >
{children} {children}
</AppPopup> </AppPopup>
@@ -36,7 +36,7 @@ export const WindowsLogo: React.FC<{ size?: number; className?: string }> = ({
</svg> </svg>
); );
/** Android 로봇 로고 — 공식 그린 (#3DDC84) */ /** Android 로봇 로고 — 머리·몸통·양팔·양다리 (공식 그린 #A4C639/#8BC34A 톤) */
export const AndroidLogo: React.FC<{ size?: number; className?: string }> = ({ export const AndroidLogo: React.FC<{ size?: number; className?: string }> = ({
size = 22, size = 22,
className, className,
@@ -45,11 +45,31 @@ export const AndroidLogo: React.FC<{ size?: number; className?: string }> = ({
width={size} width={size}
height={size} height={size}
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="#3ddc84" fill="none"
className={className} className={className}
aria-hidden aria-hidden
> >
<path d="M17.6 9.48l1.84-3.18a.38.38 0 0 0-.66-.38l-1.86 3.22a11.4 11.4 0 0 0-9.84 0L5.22 5.92a.38.38 0 1 0-.66.38L6.4 9.48A10.78 10.78 0 0 0 1 18h22a10.78 10.78 0 0 0-5.4-8.52zM7 15.25a1.25 1.25 0 1 1 1.25-1.25A1.25 1.25 0 0 1 7 15.25zm10 0A1.25 1.25 0 1 1 18.25 14 1.25 1.25 0 0 1 17 15.25z" /> {/* 안테나 */}
<g stroke="#9bcf4e" strokeWidth="0.95" strokeLinecap="round">
<line x1="8.7" y1="2.9" x2="9.9" y2="4.9" />
<line x1="15.3" y1="2.9" x2="14.1" y2="4.9" />
</g>
{/* 머리(돔) */}
<path
d="M6.4 9.2C6.4 6.05 8.9 3.7 12 3.7s5.6 2.35 5.6 5.5z"
fill="#9bcf4e"
/>
{/* 눈 */}
<circle cx="9.7" cy="6.6" r="0.72" fill="#ffffff" />
<circle cx="14.3" cy="6.6" r="0.72" fill="#ffffff" />
{/* 몸통 */}
<rect x="6.5" y="9.1" width="11" height="8.5" rx="1.3" fill="#9bcf4e" />
{/* 양팔 */}
<rect x="3.3" y="9.7" width="2.3" height="6.4" rx="1.15" fill="#9bcf4e" />
<rect x="18.4" y="9.7" width="2.3" height="6.4" rx="1.15" fill="#9bcf4e" />
{/* 양다리 */}
<rect x="8.3" y="16.7" width="2.3" height="4.4" rx="1.15" fill="#9bcf4e" />
<rect x="13.4" y="16.7" width="2.3" height="4.4" rx="1.15" fill="#9bcf4e" />
</svg> </svg>
); );
+22 -5
View File
@@ -9,6 +9,8 @@ export interface DraggablePosition {
export interface UseDraggablePanelOptions { export interface UseDraggablePanelOptions {
/** 마운트 후 패널 크기 기준 화면 중앙 배치 */ /** 마운트 후 패널 크기 기준 화면 중앙 배치 */
centerOnMount?: boolean; centerOnMount?: boolean;
/** 콘텐츠 크기 변화·창 리사이즈 시 (드래그 전까지) 중앙 재정렬 */
recenterOnResize?: boolean;
initialPosition?: DraggablePosition; initialPosition?: DraggablePosition;
panelRef?: RefObject<HTMLElement | null>; panelRef?: RefObject<HTMLElement | null>;
} }
@@ -32,6 +34,7 @@ function estimateCenterPosition(
export function useDraggablePanel(options: UseDraggablePanelOptions = {}) { export function useDraggablePanel(options: UseDraggablePanelOptions = {}) {
const { const {
centerOnMount = false, centerOnMount = false,
recenterOnResize = false,
initialPosition = DEFAULT_POS, initialPosition = DEFAULT_POS,
panelRef: externalRef, panelRef: externalRef,
} = options; } = options;
@@ -44,29 +47,42 @@ export function useDraggablePanel(options: UseDraggablePanelOptions = {}) {
const [positionReady, setPositionReady] = useState(!centerOnMount); const [positionReady, setPositionReady] = useState(!centerOnMount);
const [dragging, setDragging] = useState(false); const [dragging, setDragging] = useState(false);
const dragOrigin = useRef({ mx: 0, my: 0, px: 0, py: 0 }); const dragOrigin = useRef({ mx: 0, my: 0, px: 0, py: 0 });
const centeredOnce = useRef(false); const movedByUser = useRef(false);
const unbindDragRef = useRef<(() => void) | null>(null); const unbindDragRef = useRef<(() => void) | null>(null);
useEffect(() => { useEffect(() => {
if (!centerOnMount || centeredOnce.current) return; if (!centerOnMount) return;
const el = panelRef.current; const el = panelRef.current;
if (!el) return; if (!el) return;
const placeCenter = () => { const placeCenter = () => {
if (movedByUser.current) return;
const w = el.offsetWidth || DEFAULT_CENTER_ESTIMATE.width; const w = el.offsetWidth || DEFAULT_CENTER_ESTIMATE.width;
const h = el.offsetHeight || DEFAULT_CENTER_ESTIMATE.height; const h = el.offsetHeight || DEFAULT_CENTER_ESTIMATE.height;
setPos({ setPos({
x: Math.max(8, (window.innerWidth - w) / 2), x: Math.max(8, (window.innerWidth - w) / 2),
y: Math.max(8, (window.innerHeight - h) / 2), y: Math.max(8, (window.innerHeight - h) / 2),
}); });
centeredOnce.current = true;
setPositionReady(true); setPositionReady(true);
}; };
placeCenter(); placeCenter();
const id = requestAnimationFrame(placeCenter); const id = requestAnimationFrame(placeCenter);
return () => cancelAnimationFrame(id);
}, [centerOnMount, panelRef]); if (!recenterOnResize) {
return () => cancelAnimationFrame(id);
}
const ro = new ResizeObserver(() => placeCenter());
ro.observe(el);
const onResize = () => placeCenter();
window.addEventListener('resize', onResize);
return () => {
cancelAnimationFrame(id);
ro.disconnect();
window.removeEventListener('resize', onResize);
};
}, [centerOnMount, recenterOnResize, panelRef]);
const endDrag = useCallback(() => { const endDrag = useCallback(() => {
unbindDragRef.current?.(); unbindDragRef.current?.();
@@ -86,6 +102,7 @@ export function useDraggablePanel(options: UseDraggablePanelOptions = {}) {
/* ignore */ /* ignore */
} }
const { x, y } = clientXYFromReact(e); const { x, y } = clientXYFromReact(e);
movedByUser.current = true;
setDragging(true); setDragging(true);
dragOrigin.current = { mx: x, my: y, px: pos.x, py: pos.y }; dragOrigin.current = { mx: x, my: y, px: pos.x, py: pos.y };