refactor(cad): CAD 앱 겉 자체 변종을 부품 상자 부품으로 — 상태막대 켜기 칩 · 패널 탭 · 떠 있는 판 · 메뉴 · 아이콘 단추 · 확인창 (PLAN 553-6)
- 부품 상자 CSS · 단추 기본 꼴을 CAD iframe 에 실음(integration/ui-kit.ts) · 자리 · 폭은 cad-* 그대로 - 상태막대 = ui-toggle--chip 22 · 특성/레이어 = ui-tabs 36 · ↶↷ · 리본 접기 · 탐색 막대 = ui-icon-btn 20 · 32 - 빠른 특성 · 블록 라이브러리 · 자리표 = ui-float-panel · 리본 넘침 · 스냅 · 명령 자동완성 = ui-ctx-menu - window.confirm · window.prompt → askConfirm · askText · 빈 안내 = ui-empty 작은 꼴 - 높이 단: 리본 칸 19→18 · 명령 칸 26→28 · 레이어 줄 40→42 · 문서 탭 25→24 · 도각 편집 단추 24 - 마우스 바탕 = --color-hover · 켜짐 = --color-accent-soft · 도면 그림 색(--draw-*) 안 건드림 - 안 쓰는 DropdownButton 지움(쓰는 곳 0 · grep) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
/** 블록 라이브러리 팔레트 — 저장한 블록을 고르면 삽입 도구가 켜진다 (INSERT/BLOCK) */
|
||||
/** 블록 라이브러리 팔레트 — 저장한 블록을 고르면 삽입 도구가 켜진다 (INSERT/BLOCK) · 떠 있는 판 부품 */
|
||||
import type { FC } from 'react';
|
||||
import {
|
||||
deleteBlock,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
importBlocksFromFile,
|
||||
} from '../blocks/block-library';
|
||||
import { pickFile } from '../helpers/pick-file';
|
||||
import { askConfirm } from '../integration/ui-kit';
|
||||
import { startBlockInsert } from '../tools/insert-block-tool';
|
||||
import { isBlockLibraryVisible, setBlockLibraryVisible } from './ui-state';
|
||||
|
||||
@@ -16,57 +17,83 @@ export const BlockLibraryPanel: FC = () => {
|
||||
const blocks = getBlocks();
|
||||
|
||||
return (
|
||||
<div className="cad-block-library controls">
|
||||
<div className="cad-quick-properties__header">
|
||||
<span>블록 라이브러리</span>
|
||||
<button type="button" onClick={() => setBlockLibraryVisible(false)} title="닫기">
|
||||
<div className="ui-float-panel cad-block-library controls">
|
||||
<div className="ui-float-panel__head">
|
||||
<span className="ui-float-panel__title">블록 라이브러리</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="24"
|
||||
onClick={() => setBlockLibraryVisible(false)}
|
||||
title="닫기"
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="ui-float-panel__body">
|
||||
{blocks.length === 0 ? (
|
||||
<div className="ui-empty ui-empty--small">
|
||||
<p className="ui-empty__text">객체를 선택하고 BLOCK 명령으로 저장하십시오.</p>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="cad-block-library__list">
|
||||
{blocks.map((block) => (
|
||||
<li key={block.name}>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-sidebar-row cad-block-library__name"
|
||||
title={`객체 ${block.entities.length}개 — 클릭하면 삽입`}
|
||||
onClick={() => {
|
||||
setBlockLibraryVisible(false);
|
||||
void startBlockInsert(block);
|
||||
}}
|
||||
>
|
||||
{block.name}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="20"
|
||||
title="삭제"
|
||||
aria-label="삭제"
|
||||
onClick={async () => {
|
||||
if (
|
||||
await askConfirm({
|
||||
message: `블록 '${block.name}'을 삭제하시겠습니까?`,
|
||||
okLabel: '삭제',
|
||||
danger: true,
|
||||
})
|
||||
) {
|
||||
deleteBlock(block.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{blocks.length === 0 ? (
|
||||
<p className="cad-block-library__empty">객체를 선택하고 BLOCK 명령으로 저장하십시오.</p>
|
||||
) : (
|
||||
<ul className="cad-block-library__list">
|
||||
{blocks.map((block) => (
|
||||
<li key={block.name}>
|
||||
<button
|
||||
type="button"
|
||||
className="cad-block-library__name"
|
||||
title={`객체 ${block.entities.length}개 — 클릭하면 삽입`}
|
||||
onClick={() => {
|
||||
setBlockLibraryVisible(false);
|
||||
void startBlockInsert(block);
|
||||
}}
|
||||
>
|
||||
{block.name}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
title="삭제"
|
||||
onClick={() => {
|
||||
if (window.confirm(`블록 '${block.name}'을 삭제하시겠습니까?`)) {
|
||||
deleteBlock(block.name);
|
||||
}
|
||||
}}
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
<div className="cad-block-library__actions">
|
||||
<button type="button" onClick={() => exportBlocksToFile()}>
|
||||
파일로 내보내기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => pickFile('application/json', (file) => importBlocksFromFile(file))}
|
||||
>
|
||||
파일에서 가져오기
|
||||
</button>
|
||||
<div className="cad-block-library__actions">
|
||||
<button
|
||||
type="button"
|
||||
className="ui-btn ui-btn--ghost"
|
||||
data-size="24"
|
||||
onClick={() => exportBlocksToFile()}
|
||||
>
|
||||
파일로 내보내기
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-btn ui-btn--ghost"
|
||||
data-size="24"
|
||||
onClick={() => pickFile('application/json', (file) => importBlocksFromFile(file))}
|
||||
>
|
||||
파일에서 가져오기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -37,9 +37,9 @@ export const Button: FC<ButtonProps> = ({
|
||||
left = null,
|
||||
right = null,
|
||||
}) => {
|
||||
// 색은 App.css의 테마 토큰이 data-type/data-active로 준다 — 여기서는 배치만.
|
||||
// 색은 App.css의 테마 토큰이 data-type/data-active로 준다 — 여기서는 배치만 · 높이 = 크기 단 42(글 14 · 둥글기 8)
|
||||
const classParts = [
|
||||
'font-semibold py-4 h-10 flex flex-row justify-start w-full items-center',
|
||||
'font-semibold h-[42px] text-sm rounded-lg flex flex-row justify-start w-full items-center',
|
||||
size === 'regular' ? 'pl-2 pr-2 gap-2' : '',
|
||||
size === 'small' ? 'pl-2 pr-2 gap-0' : '',
|
||||
className || '',
|
||||
|
||||
@@ -16,7 +16,10 @@ export const CommandLine: FC = () => {
|
||||
const history = getCommandHistory();
|
||||
const instruction = currentPrompt().line || '명령을 입력하거나 도구를 선택하십시오.';
|
||||
|
||||
const suggestions = useMemo(() => matchCommandPrefixes(pick?.base ?? text).slice(0, 8), [text, pick]);
|
||||
const suggestions = useMemo(
|
||||
() => matchCommandPrefixes(pick?.base ?? text).slice(0, 8),
|
||||
[text, pick]
|
||||
);
|
||||
|
||||
const submit = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
@@ -102,10 +105,11 @@ export const CommandLine: FC = () => {
|
||||
<span className="cad-command-prompt">{instruction}</span>
|
||||
</form>
|
||||
{text.trim() && suggestions.length > 0 && (
|
||||
<div className="cad-command-suggestions">
|
||||
<div className="ui-ctx-menu cad-command-suggestions">
|
||||
{suggestions.map((command) => (
|
||||
<button
|
||||
type="button"
|
||||
className="ui-ctx-menu__item"
|
||||
key={command.id}
|
||||
onClick={() => {
|
||||
runCommandInput(command.id);
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import type { CSSProperties, FC, ReactNode } from 'react';
|
||||
import useLocalStorageState from 'use-local-storage-state';
|
||||
import { LOCAL_STORAGE_KEY } from '../App.types.ts';
|
||||
import { keyboardHandler } from '../helpers/keyboard-handler.ts';
|
||||
import { Button } from './Button.tsx';
|
||||
import { Icon, IconName } from './Icon/Icon.tsx';
|
||||
|
||||
interface DropdownButtonProps {
|
||||
label?: string;
|
||||
title?: string;
|
||||
iconName?: IconName;
|
||||
iconComponent?: ReactNode;
|
||||
active?: boolean;
|
||||
onClick?: () => void;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
buttonStyle?: CSSProperties;
|
||||
dataId: string;
|
||||
children?: ReactNode;
|
||||
defaultOpen?: boolean;
|
||||
}
|
||||
|
||||
export const DropdownButton: FC<DropdownButtonProps> = ({
|
||||
label,
|
||||
title,
|
||||
iconName,
|
||||
iconComponent,
|
||||
className,
|
||||
style,
|
||||
buttonStyle,
|
||||
dataId,
|
||||
children,
|
||||
defaultOpen = false,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useLocalStorageState<boolean>(
|
||||
`${LOCAL_STORAGE_KEY.DROPDOWN}___${dataId}`,
|
||||
{ defaultValue: defaultOpen }
|
||||
);
|
||||
|
||||
const classParts = [
|
||||
'flex flex-col gap-2 relative',
|
||||
className || '',
|
||||
isOpen ? ' bg-slate-900' : '',
|
||||
];
|
||||
return (
|
||||
<div className={classParts.join(' ')} style={style} data-id={dataId}>
|
||||
<Button
|
||||
iconName={iconName}
|
||||
iconComponent={iconComponent}
|
||||
label={label}
|
||||
title={title}
|
||||
active={isOpen}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
style={buttonStyle}
|
||||
className={'w-full data-[active=true]:bg-blue-950 data-[active=true]:text-white'}
|
||||
/>
|
||||
<Icon name={IconName.SolidDownSmall} className={'absolute top-2.5 right-1 text-blue-700'} />
|
||||
{isOpen && (
|
||||
<div
|
||||
className="flex flex-row flex-wrap max-w-72 gap-1 pl-1 pb-6"
|
||||
onClick={() => setIsOpen(false)}
|
||||
onKeyUp={keyboardHandler(() => setIsOpen(false))}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -139,15 +139,15 @@ export const FramePlaceholderPanel: FC = () => {
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<section className="cad-frame-tokens controls">
|
||||
<header className="cad-frame-tokens__header">
|
||||
<span>
|
||||
{folded ? '자리표 놓기' : '자리표 놓기 — 누르면 화면 가운데에 서고, 끌어서 자리를 잡습니다'}
|
||||
</span>
|
||||
<section className="ui-float-panel cad-frame-tokens controls">
|
||||
<header className="ui-float-panel__head cad-frame-tokens__header">
|
||||
<span className="ui-float-panel__title">자리표 놓기</span>
|
||||
<button
|
||||
type="button"
|
||||
className="cad-frame-tokens__fold"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="24"
|
||||
aria-expanded={!folded}
|
||||
aria-label={folded ? '자리표 패널 펴기' : '자리표 패널 접기'}
|
||||
title={folded ? '자리표 패널 펴기' : '자리표 패널 접기'}
|
||||
onClick={() => setFolded(!folded)}
|
||||
>
|
||||
@@ -155,58 +155,71 @@ export const FramePlaceholderPanel: FC = () => {
|
||||
</button>
|
||||
</header>
|
||||
{!folded && (
|
||||
<>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="cad-frame-tokens__title">글자</span>
|
||||
{TEXT_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="cad-frame-tokens__button"
|
||||
onClick={() => addTextPlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="cad-frame-tokens__title">그림 (칸에 비율 그대로 들어감)</span>
|
||||
{IMAGE_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="cad-frame-tokens__button"
|
||||
onClick={() => void addImagePlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{picked && (
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="cad-frame-tokens__title">고른 자리표 칸 크기 (mm)</span>
|
||||
<label className="cad-frame-tokens__size">
|
||||
가로
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={size.width}
|
||||
onChange={(event) => applySize(Number(event.target.value), size.height)}
|
||||
/>
|
||||
</label>
|
||||
<label className="cad-frame-tokens__size">
|
||||
세로
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={size.height}
|
||||
onChange={(event) => applySize(size.width, Number(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
<div className="ui-float-panel__body cad-frame-tokens__body">
|
||||
<p className="ui-text ui-text--muted">
|
||||
누르면 화면 가운데에 서고, 끌어서 자리를 잡습니다
|
||||
</p>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="ui-text ui-text--muted cad-frame-tokens__title">글자</span>
|
||||
{TEXT_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="ui-btn ui-btn--ghost"
|
||||
data-size="22"
|
||||
onClick={() => addTextPlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="ui-text ui-text--muted cad-frame-tokens__title">
|
||||
그림 (칸에 비율 그대로 들어감)
|
||||
</span>
|
||||
{IMAGE_TOKENS.map((token) => (
|
||||
<button
|
||||
key={token}
|
||||
type="button"
|
||||
className="ui-btn ui-btn--ghost"
|
||||
data-size="22"
|
||||
onClick={() => void addImagePlaceholder(token)}
|
||||
>
|
||||
{token}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
{picked && (
|
||||
<div className="cad-frame-tokens__group">
|
||||
<span className="ui-text ui-text--muted cad-frame-tokens__title">
|
||||
고른 자리표 칸 크기 (mm)
|
||||
</span>
|
||||
<label className="ui-text ui-text--muted cad-frame-tokens__size">
|
||||
가로
|
||||
<input
|
||||
type="number"
|
||||
className="ui-input"
|
||||
data-size="24"
|
||||
min={1}
|
||||
value={size.width}
|
||||
onChange={(event) => applySize(Number(event.target.value), size.height)}
|
||||
/>
|
||||
</label>
|
||||
<label className="ui-text ui-text--muted cad-frame-tokens__size">
|
||||
세로
|
||||
<input
|
||||
type="number"
|
||||
className="ui-input"
|
||||
data-size="24"
|
||||
min={1}
|
||||
value={size.height}
|
||||
onChange={(event) => applySize(size.width, Number(event.target.value))}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,17 +27,24 @@ export const InspectorPanel: FC<InspectorPanelProps> = ({ collapsed, onToggleCol
|
||||
</button>
|
||||
{!collapsed && (
|
||||
<>
|
||||
<div className="cad-inspector-tabs">
|
||||
{/* 탭 부품(밑줄 탭 · 단 36) — 칸 둘이 폭을 나눠 가짐 */}
|
||||
<div className="ui-tabs cad-inspector-tabs" role="tablist">
|
||||
<button
|
||||
type="button"
|
||||
data-active={tab === 'properties'}
|
||||
role="tab"
|
||||
className={`ui-tabs__tab${tab === 'properties' ? ' is-active' : ''}`}
|
||||
data-size="36"
|
||||
aria-selected={tab === 'properties'}
|
||||
onClick={() => openInspector('properties')}
|
||||
>
|
||||
특성
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={tab === 'layers'}
|
||||
role="tab"
|
||||
className={`ui-tabs__tab${tab === 'layers' ? ' is-active' : ''}`}
|
||||
data-size="36"
|
||||
aria-selected={tab === 'layers'}
|
||||
onClick={() => openInspector('layers')}
|
||||
>
|
||||
레이어
|
||||
|
||||
@@ -18,7 +18,8 @@ interface PropertiesEditorProps {
|
||||
}
|
||||
|
||||
/** 고른 것을 사본으로 바꿈 — 되돌리기 한 칸(488) · 선 특성은 글 뺌 */
|
||||
const applyToSelection = (mutate: (entity: Entity) => void) => restyle(getSelectedEntities(), mutate);
|
||||
const applyToSelection = (mutate: (entity: Entity) => void) =>
|
||||
restyle(getSelectedEntities(), mutate);
|
||||
const applyToLines = (mutate: (entity: Entity) => void) =>
|
||||
restyle(styleTargets(getSelectedEntities()), mutate);
|
||||
|
||||
@@ -30,7 +31,11 @@ export const PropertiesEditor: FC<PropertiesEditorProps> = ({ compact = false })
|
||||
const view = selectionLineStyle();
|
||||
|
||||
if (!first) {
|
||||
return <p className="cad-properties__empty">선택된 객체가 없습니다.</p>;
|
||||
return (
|
||||
<div className="ui-empty ui-empty--small">
|
||||
<p className="ui-empty__text">선택된 객체가 없습니다.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const points = sampleEntityPoints(first);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 빠른 실행 단추(↶ ↷) — 리본 탭 줄 오른쪽(접기 단추 앞)에 그린다 · 숨김 목록은 DOM 만 둔다 */
|
||||
/** 빠른 실행 단추(↶ ↷) — 리본 탭 줄 오른쪽(접기 단추 앞)에 그린다 · 아이콘 단추 부품(단 20) · 숨김 목록은 DOM 만 둔다 */
|
||||
import type { FC } from 'react';
|
||||
import { getCommandById } from '../commands/registry';
|
||||
import { runCommand } from '../commands/run-command';
|
||||
@@ -16,6 +16,9 @@ export const QuickAccessBar: FC = () => {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="20"
|
||||
aria-label={command.label}
|
||||
key={command.id}
|
||||
hidden={QUICK_ACCESS_HIDDEN.includes(id)}
|
||||
title={`${command.label} (${command.id})`}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 빠른 특성 — 선택이 있을 때만 화면 오른쪽 위에 뜨는 간이 특성 상자 (QUICKPROPERTIES) */
|
||||
/** 빠른 특성 — 선택이 있을 때만 화면 오른쪽 위에 뜨는 간이 특성 상자 (QUICKPROPERTIES) · 떠 있는 판 부품 */
|
||||
import type { FC } from 'react';
|
||||
import { PropertiesEditor } from './PropertiesEditor';
|
||||
import { getSelectedEntities } from '../state';
|
||||
@@ -8,14 +8,23 @@ export const QuickProperties: FC = () => {
|
||||
if (!isQuickPropertiesVisible() || getSelectedEntities().length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="cad-quick-properties controls">
|
||||
<div className="cad-quick-properties__header">
|
||||
<span>빠른 특성</span>
|
||||
<button type="button" onClick={() => setQuickPropertiesVisible(false)} title="닫기">
|
||||
<div className="ui-float-panel cad-quick-properties controls">
|
||||
<div className="ui-float-panel__head">
|
||||
<span className="ui-float-panel__title">빠른 특성</span>
|
||||
<button
|
||||
type="button"
|
||||
className="ui-icon-btn ui-icon-btn--ghost"
|
||||
data-size="24"
|
||||
onClick={() => setQuickPropertiesVisible(false)}
|
||||
title="닫기"
|
||||
aria-label="닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<PropertiesEditor compact />
|
||||
<div className="ui-float-panel__body">
|
||||
<PropertiesEditor compact />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/** 상태막대 — AutoCAD 하단 제도 보조 토글 */
|
||||
/** 상태막대 — AutoCAD 하단 제도 보조 토글 · 켜기 칩 부품(`ui-toggle--chip` · 단 22) */
|
||||
import type { FC } from 'react';
|
||||
import {
|
||||
getAngleStep,
|
||||
@@ -27,7 +27,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
<footer className="cad-statusbar controls">
|
||||
<button
|
||||
type="button"
|
||||
data-active={snap}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={snap}
|
||||
title="객체 스냅 (OSNAP)"
|
||||
onClick={() => setSnapEnabled(!snap)}
|
||||
>
|
||||
@@ -35,7 +37,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={ortho}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={ortho}
|
||||
title="직교 모드 (F8) — 90° 간격 각도 가이드"
|
||||
onClick={() => setAngleStep(ortho ? 0 : 90)}
|
||||
>
|
||||
@@ -43,7 +47,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={polar}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={polar}
|
||||
title="극좌표 추적 (F10) — 45° 간격 각도 가이드"
|
||||
onClick={() => setAngleStep(polar ? 0 : 45)}
|
||||
>
|
||||
@@ -51,7 +57,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={snapTracking}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={snapTracking}
|
||||
title="객체 스냅 추적 — 스냅점에 잠시 머물면 그 점에서 정렬선이 뻗는다"
|
||||
onClick={() => setSnapTrackingEnabled(!snapTracking)}
|
||||
>
|
||||
@@ -59,7 +67,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={grid}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={grid}
|
||||
title="그리드 표시"
|
||||
onClick={() => setGridEnabled(!grid)}
|
||||
>
|
||||
@@ -67,7 +77,9 @@ export const StatusBar: FC<StatusBarProps> = ({ commandLineVisible, onToggleComm
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-active={commandLineVisible}
|
||||
className="ui-toggle ui-toggle--chip"
|
||||
data-size="22"
|
||||
aria-pressed={commandLineVisible}
|
||||
title="명령행 표시/숨기기"
|
||||
onClick={onToggleCommandLine}
|
||||
>
|
||||
|
||||
@@ -3,12 +3,10 @@ import type { FC } from 'react';
|
||||
import { runCommandInput } from '../commands/run-command';
|
||||
import { redoCommand, undoCommand } from '../inputController/prompt-input';
|
||||
import { zoomExtents } from '../inputController/zoom-prompt';
|
||||
import {
|
||||
getScreenCanvasDrawController,
|
||||
isDrawingReadOnly,
|
||||
isHostSave,
|
||||
isViewOnly,
|
||||
} from '../state';
|
||||
import { getScreenCanvasDrawController, isDrawingReadOnly, isHostSave, isViewOnly } from '../state';
|
||||
|
||||
/** 아이콘 단추 부품(단 32) */
|
||||
const ICON_BUTTON = { className: 'ui-icon-btn ui-icon-btn--ghost', 'data-size': '32' } as const;
|
||||
|
||||
export const ViewControls: FC = () => {
|
||||
const zoom = getScreenCanvasDrawController()?.getScreenScale() ?? 1;
|
||||
@@ -19,27 +17,52 @@ export const ViewControls: FC = () => {
|
||||
<div className="cad-view-controls controls">
|
||||
{showUndo && (
|
||||
<>
|
||||
<button type="button" onClick={undoCommand} title="실행 취소 (Ctrl+Z)">
|
||||
<button
|
||||
type="button"
|
||||
{...ICON_BUTTON}
|
||||
onClick={undoCommand}
|
||||
title="실행 취소 (Ctrl+Z)"
|
||||
aria-label="실행 취소"
|
||||
>
|
||||
↶
|
||||
</button>
|
||||
<button type="button" onClick={redoCommand} title="다시 실행 (Ctrl+Y)">
|
||||
<button
|
||||
type="button"
|
||||
{...ICON_BUTTON}
|
||||
onClick={redoCommand}
|
||||
title="다시 실행 (Ctrl+Y)"
|
||||
aria-label="다시 실행"
|
||||
>
|
||||
↷
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="cad-view-controls__fit"
|
||||
{...ICON_BUTTON}
|
||||
onClick={() => zoomExtents()}
|
||||
title="범위 줌 (Z) — 도면 전체를 화면에 맞춘다"
|
||||
aria-label="범위 줌"
|
||||
>
|
||||
⛶
|
||||
</button>
|
||||
<button type="button" onClick={() => runCommandInput('ZOOMIN')} title="확대">
|
||||
<button
|
||||
type="button"
|
||||
{...ICON_BUTTON}
|
||||
onClick={() => runCommandInput('ZOOMIN')}
|
||||
title="확대"
|
||||
aria-label="확대"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<span>{Math.round(zoom * 100)}%</span>
|
||||
<button type="button" onClick={() => runCommandInput('ZOOMOUT')} title="축소">
|
||||
<button
|
||||
type="button"
|
||||
{...ICON_BUTTON}
|
||||
onClick={() => runCommandInput('ZOOMOUT')}
|
||||
title="축소"
|
||||
aria-label="축소"
|
||||
>
|
||||
-
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user