fix(ui): namespace review-target css selectors (#84)

This commit is contained in:
asyouplz 2026-03-19 12:31:42 +09:00 committed by GitHub
parent 77b63f54b3
commit 1b0c80b0ad
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1394 additions and 1394 deletions

View file

@ -130,7 +130,7 @@ describe('NotificationManager', () => {
// DOM에서 확인 버튼 찾기
setTimeout(() => {
const confirmBtn = document.querySelector('.modal__action--primary') as HTMLElement;
const confirmBtn = document.querySelector('.sn-modal__action--primary') as HTMLElement;
confirmBtn?.click();
}, 10);
@ -143,7 +143,7 @@ describe('NotificationManager', () => {
// DOM에서 확인 버튼 찾기
setTimeout(() => {
const okBtn = document.querySelector('.modal__action--primary') as HTMLElement;
const okBtn = document.querySelector('.sn-modal__action--primary') as HTMLElement;
okBtn?.click();
}, 10);
@ -171,7 +171,7 @@ describe('NotificationManager', () => {
progress.update(50, 'Halfway there');
// DOM 확인
const toastEl = document.querySelector('.toast');
const toastEl = document.querySelector('.sn-toast');
expect(toastEl).toBeDefined();
});
@ -181,7 +181,7 @@ describe('NotificationManager', () => {
progress.complete('Done!');
// DOM 확인
const toastEl = document.querySelector('.toast--success');
const toastEl = document.querySelector('.sn-toast--success');
expect(toastEl).toBeDefined();
});
@ -191,7 +191,7 @@ describe('NotificationManager', () => {
progress.error('Failed to load');
// DOM 확인
const toastEl = document.querySelector('.toast--error');
const toastEl = document.querySelector('.sn-toast--error');
expect(toastEl).toBeDefined();
});
});
@ -293,17 +293,17 @@ describe('NotificationManager', () => {
it('Toast 컨테이너를 생성해야 함', () => {
manager.info('Test');
const container = document.querySelector('.toast-container');
const container = document.querySelector('.sn-toast-container');
expect(container).toBeDefined();
});
it('Toast 요소를 생성해야 함', () => {
manager.info('Test message');
const toast = document.querySelector('.toast');
const toast = document.querySelector('.sn-toast');
expect(toast).toBeDefined();
const message = toast?.querySelector('.toast__message');
const message = toast?.querySelector('.sn-toast__message');
expect(message?.textContent).toBe('Test message');
});
@ -316,7 +316,7 @@ describe('NotificationManager', () => {
// urgent priority는 Modal을 사용
setTimeout(() => {
const overlay = document.querySelector('.modal-overlay');
const overlay = document.querySelector('.sn-modal-overlay');
expect(overlay).toBeDefined();
}, 10);
});
@ -329,7 +329,7 @@ describe('NotificationManager', () => {
});
// low priority는 StatusBar를 사용
const statusBar = document.querySelector('.status-bar');
const statusBar = document.querySelector('.sn-status-bar');
expect(statusBar).toBeDefined();
});
});

View file

@ -190,10 +190,10 @@ export class DragDropZone {
if (this.dropZone) {
if (isDragging) {
this.dropZone.addClass('is-dragging');
this.dropZone.addClass('sn-is-dragging');
this.showDragOverlay();
} else {
this.dropZone.removeClass('is-dragging');
this.dropZone.removeClass('sn-is-dragging');
this.hideDragOverlay();
}
}
@ -216,7 +216,7 @@ export class DragDropZone {
icon.appendChild(this.createDropIcon());
content.createDiv('sn-drag-overlay-text').setText('Drop files to add them');
}
overlay.addClass('is-active');
overlay.addClass('sn-is-active');
}
/**
@ -225,7 +225,7 @@ export class DragDropZone {
private hideDragOverlay() {
const overlay = this.dropZone?.querySelector('.sn-drag-overlay');
if (overlay instanceof HTMLElement) {
overlay.removeClass('is-active');
overlay.removeClass('sn-is-active');
}
}
@ -324,7 +324,7 @@ export class DragDropZone {
private showMessage(message: string, type: 'success' | 'error') {
if (!this.dropZone) return;
const messageEl = this.dropZone.createDiv(`sn-drop-zone-message ${type}`);
const messageEl = this.dropZone.createDiv(`sn-drop-zone-message sn-drop-zone-message--${type}`);
messageEl.setText(message);
setTimeout(() => {

View file

@ -176,10 +176,10 @@ export class FileBrowser {
isExpanded = !isExpanded;
if (isExpanded) {
fileList.show();
folderHeader.removeClass('collapsed');
folderHeader.removeClass('sn-is-collapsed');
} else {
fileList.hide();
folderHeader.addClass('is-collapsed');
folderHeader.addClass('sn-is-collapsed');
}
});
@ -225,12 +225,12 @@ export class FileBrowser {
// 클릭 이벤트
fileItem.addEventListener('click', () => {
this.selectFile(file);
fileItem.addClass('is-selected');
fileItem.addClass('sn-is-selected');
// 다른 선택 해제
container.querySelectorAll('.sn-file-item').forEach((item) => {
if (item !== fileItem) {
item.removeClass('is-selected');
item.removeClass('sn-is-selected');
}
});
});

View file

@ -113,7 +113,7 @@ export class ProgressIndicator {
}
if (spinner instanceof HTMLElement) {
spinner.addClass('sn-hidden');
spinner.removeClass('is-spinning');
spinner.removeClass('sn-is-spinning');
}
this.update(0);
} else {
@ -172,13 +172,13 @@ export class ProgressIndicator {
// 진행률에 따른 색상 변경
if (this.currentProgress < 30) {
progressFill.removeClass('is-warning', 'is-success');
progressFill.removeClass('sn-is-warning', 'sn-is-success');
} else if (this.currentProgress < 70) {
progressFill.addClass('is-warning');
progressFill.removeClass('is-success');
progressFill.addClass('sn-is-warning');
progressFill.removeClass('sn-is-success');
} else {
progressFill.removeClass('is-warning');
progressFill.addClass('is-success');
progressFill.removeClass('sn-is-warning');
progressFill.addClass('sn-is-success');
}
}
@ -241,7 +241,7 @@ export class ProgressIndicator {
const content = this.progressElement.querySelector('.sn-progress-content');
if (content instanceof HTMLElement) {
content.addClass('is-error');
content.addClass('sn-is-error');
// 스피너를 에러 아이콘으로 변경
const spinner = content.querySelector('.sn-progress-spinner');
@ -264,7 +264,7 @@ export class ProgressIndicator {
const content = this.progressElement.querySelector('.sn-progress-content');
if (content instanceof HTMLElement) {
content.addClass('is-success');
content.addClass('sn-is-success');
// 스피너를 성공 아이콘으로 변경
const spinner = content.querySelector('.sn-progress-spinner');
@ -290,7 +290,7 @@ export class ProgressIndicator {
private startSpinnerAnimation() {
const spinner = this.progressElement?.querySelector('.sn-progress-spinner');
if (spinner instanceof HTMLElement) {
spinner.addClass('is-spinning');
spinner.addClass('sn-is-spinning');
}
}
@ -300,7 +300,7 @@ export class ProgressIndicator {
private stopSpinnerAnimation() {
const spinner = this.progressElement?.querySelector('.sn-progress-spinner');
if (spinner instanceof HTMLElement) {
spinner.removeClass('is-spinning');
spinner.removeClass('sn-is-spinning');
}
}

View file

@ -50,12 +50,12 @@ export class RecentFiles {
this.container.addClass('sn-recent-files');
// 헤더
const header = this.container.createDiv('recent-files-header');
const header = this.container.createDiv('sn-recent-files-header');
header.createEl('h3', { text: 'Recent files' });
// 초기화 버튼
const clearBtn = header.createEl('button', {
cls: 'clear-recent-btn',
cls: 'sn-clear-recent-btn',
text: 'Clear',
title: 'Clear recent files',
});
@ -86,7 +86,7 @@ export class RecentFiles {
if (validFiles.length === 0) {
listContainer.createDiv({
cls: 'empty-state',
cls: 'sn-empty-state',
text: 'No recent files',
});
return;
@ -156,7 +156,7 @@ export class RecentFiles {
// 선택 버튼
const selectBtn = actions.createEl('button', {
cls: 'select-btn',
cls: 'sn-select-btn',
title: 'Select file',
});
setIcon(selectBtn, 'check');
@ -169,7 +169,7 @@ export class RecentFiles {
// 제거 버튼
const removeBtn = actions.createEl('button', {
cls: 'remove-btn',
cls: 'sn-remove-btn',
title: 'Remove from list',
});
setIcon(removeBtn, 'x');
@ -187,7 +187,7 @@ export class RecentFiles {
// 파일이 존재하지 않는 경우 표시
if (!this.app.vault.getAbstractFileByPath(entry.path)) {
fileItem.addClass('file-not-found');
fileItem.addClass('sn-is-file-not-found');
fileName.setText(`${file.basename} (Not found)`);
}
}

View file

@ -457,7 +457,7 @@ export class StatisticsDashboard {
};
Object.entries(updates).forEach(([id, value]) => {
const card = this.element?.querySelector(`[data-stat-id="${id}"] .stats-card__value`);
const card = this.element?.querySelector(`[data-stat-id="${id}"] .sn-stats-card__value`);
if (card) {
card.textContent = value;
}
@ -506,7 +506,7 @@ export class StatisticsDashboard {
});
bar.appendChild(valueLabel);
const barLabel = createEl('span', { cls: 'bar-chart__label', text: `${hour}:00` });
const barLabel = createEl('span', { cls: 'sn-bar-chart__label', text: `${hour}:00` });
bar.appendChild(barLabel);
chartContainer.appendChild(bar);
@ -624,7 +624,7 @@ export class StatisticsDashboard {
row.appendChild(wordCountCell);
const actionCell = createEl('td');
const actionBtn = createEl('button', { cls: 'action-btn', text: 'View' });
const actionBtn = createEl('button', { cls: 'sn-action-btn', text: 'View' });
actionBtn.dataset.recordId = record.id;
actionBtn.dataset.action = 'view';
actionBtn.addEventListener('click', () => {

View file

@ -120,7 +120,7 @@ export class FilePickerModal extends Modal {
}
// 모달 클래스 추가
this.modalEl?.classList?.add('file-picker-modal');
this.modalEl?.classList?.add('sn-file-picker-modal');
this.modalEl?.classList?.add('speech-to-text-modal');
}
@ -149,17 +149,17 @@ export class FilePickerModal extends Modal {
}
// 탭 컨테이너
const tabContainer = contentEl.createDiv('file-picker-tabs');
const tabContainer = contentEl.createDiv('sn-file-picker-tabs');
// 탭 헤더
const tabHeader = tabContainer.createDiv('tab-header');
const tabHeader = tabContainer.createDiv('sn-tab-header');
const browseTab = this.createTab(tabHeader, 'Browse', true);
const recentTab = this.options.showRecentFiles
? this.createTab(tabHeader, 'Recent', false)
: null;
// 탭 콘텐츠
const tabContent = tabContainer.createDiv('tab-content');
const tabContent = tabContainer.createDiv('sn-tab-content');
const browseContent = this.createBrowseContent(tabContent);
const recentContent = this.options.showRecentFiles
? this.createRecentContent(tabContent)
@ -182,10 +182,10 @@ export class FilePickerModal extends Modal {
}
private createHeader(container: HTMLElement) {
const header = container.createDiv('file-picker-header');
const header = container.createDiv('sn-file-picker-header');
header.createEl('h2', { text: this.options.title });
const subtitle = header.createEl('p', { cls: 'file-picker-subtitle' });
const subtitle = header.createEl('p', { cls: 'sn-file-picker-subtitle' });
if (this.options.accept.length > 0) {
subtitle.setText(
`Supported formats: ${this.options.accept.map((ext) => `.${ext}`).join(', ')}`
@ -201,7 +201,7 @@ export class FilePickerModal extends Modal {
private createDragDropSection(container: HTMLElement) {
if (!this.dragDropZone) return;
const dropSection = container.createDiv('drag-drop-section');
const dropSection = container.createDiv('sn-drag-drop-section');
this.dragDropZone.mount(dropSection);
this.dragDropZone.onFilesDropped((files) => {
@ -210,7 +210,7 @@ export class FilePickerModal extends Modal {
}
private createBrowseContent(container: HTMLElement): HTMLElement {
const browseContent = container.createDiv('browse-content active');
const browseContent = container.createDiv('sn-browse-content sn-is-active');
// 파일 브라우저 마운트
this.fileBrowser.mount(browseContent);
@ -224,7 +224,7 @@ export class FilePickerModal extends Modal {
}
private createRecentContent(container: HTMLElement): HTMLElement {
const recentContent = container.createDiv('recent-content');
const recentContent = container.createDiv('sn-recent-content');
if (this.recentFiles) {
this.recentFiles.mount(recentContent);
@ -239,10 +239,10 @@ export class FilePickerModal extends Modal {
}
private createSelectedFilesSection(container: HTMLElement) {
const section = container.createDiv('selected-files-section');
const section = container.createDiv('sn-selected-files-section');
section.createEl('h3', { text: 'Selected files' });
const fileList = section.createDiv('selected-files-list');
const fileList = section.createDiv('sn-selected-files-list');
this.updateSelectedFilesList(fileList);
}
@ -252,31 +252,31 @@ export class FilePickerModal extends Modal {
if (this.selectedFiles.length === 0) {
container.createEl('p', {
text: 'No files selected',
cls: 'no-files-message',
cls: 'sn-no-files-message',
});
return;
}
this.selectedFiles.forEach((file) => {
const fileItem = container.createDiv('selected-file-item');
const fileItem = container.createDiv('sn-selected-file-item');
// 파일 정보
const fileInfo = fileItem.createDiv('file-info');
fileInfo.createEl('span', { text: file.name, cls: 'file-name' });
const fileInfo = fileItem.createDiv('sn-file-info');
fileInfo.createEl('span', { text: file.name, cls: 'sn-file-name' });
fileInfo.createEl('span', {
text: this.formatFileSize(file.stat.size),
cls: 'file-size',
cls: 'sn-file-size',
});
// 검증 상태
const validation = this.validationResults.get(file.path);
if (validation) {
const statusIcon = fileItem.createDiv('validation-status');
const statusIcon = fileItem.createDiv('sn-validation-status');
if (validation.valid) {
statusIcon.addClass('valid');
statusIcon.addClass('sn-is-valid');
statusIcon.setText('✓');
} else {
statusIcon.addClass('invalid');
statusIcon.addClass('sn-is-invalid');
statusIcon.setText('✗');
statusIcon.title =
validation.errors?.map((error) => error.message).join('\n') || '';
@ -286,7 +286,7 @@ export class FilePickerModal extends Modal {
// 제거 버튼
const removeBtn = fileItem.createEl('button', {
text: 'Remove',
cls: 'remove-file-btn',
cls: 'sn-remove-file-btn',
});
removeBtn.onclick = () => {
this.removeFile(file);
@ -296,7 +296,7 @@ export class FilePickerModal extends Modal {
}
private createFooter(container: HTMLElement) {
const footer = container.createDiv('file-picker-footer');
const footer = container.createDiv('sn-file-picker-footer');
new Setting(footer)
.addButton((btn) =>
@ -438,7 +438,7 @@ export class FilePickerModal extends Modal {
private createTab(container: HTMLElement, label: string, active: boolean): HTMLElement {
const tab = container.createDiv({
cls: `tab-button ${active ? 'active' : ''}`,
cls: `sn-tab-button ${active ? 'sn-is-active' : ''}`,
text: label,
});
return tab;
@ -451,21 +451,21 @@ export class FilePickerModal extends Modal {
recentContent: HTMLElement | null
) {
browseTab.onclick = () => {
browseTab.addClass('active');
browseContent.addClass('active');
browseTab.addClass('sn-is-active');
browseContent.addClass('sn-is-active');
if (recentTab && recentContent) {
recentTab.removeClass('active');
recentContent.removeClass('active');
recentTab.removeClass('sn-is-active');
recentContent.removeClass('sn-is-active');
}
};
if (recentTab && recentContent) {
recentTab.onclick = () => {
recentTab.addClass('active');
recentContent.addClass('active');
browseTab.removeClass('active');
browseContent.removeClass('active');
recentTab.addClass('sn-is-active');
recentContent.addClass('sn-is-active');
browseTab.removeClass('sn-is-active');
browseContent.removeClass('sn-is-active');
};
}
}
@ -484,7 +484,7 @@ export class FilePickerModal extends Modal {
}
private refreshUI() {
const selectedSection = this.modalEl.querySelector('.selected-files-list');
const selectedSection = this.modalEl.querySelector('.sn-selected-files-list');
if (selectedSection instanceof HTMLElement) {
this.updateSelectedFilesList(selectedSection);
}

View file

@ -144,7 +144,7 @@ export class FilePickerModalRefactored extends Modal {
* -
*/
private setupModal(): void {
this.modalEl.addClass('file-picker-modal', 'speech-to-text-modal');
this.modalEl.addClass('sn-file-picker-modal', 'speech-to-text-modal');
}
onOpen() {
@ -219,16 +219,16 @@ export class FilePickerModalRefactored extends Modal {
// Remove all active classes
[browseTab, browseContent, recentTab, recentContent].forEach((el) => {
el?.removeClass('active');
el?.removeClass('sn-is-active');
});
// Add active class to selected tab
if (tab === 'browse') {
browseTab.addClass('active');
browseContent.addClass('active');
browseTab.addClass('sn-is-active');
browseContent.addClass('sn-is-active');
} else if (recentTab && recentContent) {
recentTab.addClass('active');
recentContent.addClass('active');
recentTab.addClass('sn-is-active');
recentContent.addClass('sn-is-active');
}
this.state.activeTab = tab;
@ -509,7 +509,7 @@ export class FilePickerModalRefactored extends Modal {
private refreshUI(): void {
requestAnimationFrame(() => {
// Update selected files list
const listContainer = this.modalEl.querySelector('.selected-files-list');
const listContainer = this.modalEl.querySelector('.sn-selected-files-list');
if (listContainer instanceof HTMLElement) {
this.updateSelectedFilesList(listContainer);
}
@ -621,10 +621,10 @@ class FilePickerUIBuilder {
) {}
buildHeader(): void {
const header = this.container.createDiv('file-picker-header');
const header = this.container.createDiv('sn-file-picker-header');
header.createEl('h2', { text: this.options.title });
const subtitle = header.createEl('p', { cls: 'file-picker-subtitle' });
const subtitle = header.createEl('p', { cls: 'sn-file-picker-subtitle' });
this.buildSubtitle(subtitle);
}
@ -647,20 +647,20 @@ class FilePickerUIBuilder {
buildDragDropSection(): void {
if (!this.options.enableDragDrop || !this.components.dragDropZone) return;
const dropSection = this.container.createDiv('drag-drop-section');
const dropSection = this.container.createDiv('sn-drag-drop-section');
this.components.dragDropZone.mount(dropSection);
}
buildTabContainer(): TabContainer {
const tabContainer = this.container.createDiv('file-picker-tabs');
const tabHeader = tabContainer.createDiv('tab-header');
const tabContainer = this.container.createDiv('sn-file-picker-tabs');
const tabHeader = tabContainer.createDiv('sn-tab-header');
const browseTab = this.createTab(tabHeader, 'Browse', true);
const recentTab = this.options.showRecentFiles
? this.createTab(tabHeader, 'Recent', false)
: null;
const tabContent = tabContainer.createDiv('tab-content');
const tabContent = tabContainer.createDiv('sn-tab-content');
const browseContent = this.createBrowseContent(tabContent);
const recentContent = this.options.showRecentFiles
? this.createRecentContent(tabContent)
@ -671,19 +671,19 @@ class FilePickerUIBuilder {
private createTab(container: HTMLElement, label: string, active: boolean): HTMLElement {
return container.createDiv({
cls: `tab-button ${active ? 'active' : ''}`,
cls: `sn-tab-button ${active ? 'sn-is-active' : ''}`,
text: label,
});
}
private createBrowseContent(container: HTMLElement): HTMLElement {
const content = container.createDiv('browse-content active');
const content = container.createDiv('sn-browse-content sn-is-active');
this.components.fileBrowser.mount(content);
return content;
}
private createRecentContent(container: HTMLElement): HTMLElement {
const content = container.createDiv('recent-content');
const content = container.createDiv('sn-recent-content');
if (this.components.recentFiles) {
this.components.recentFiles.mount(content);
}
@ -691,10 +691,10 @@ class FilePickerUIBuilder {
}
buildSelectedFilesSection(updateCallback: (container: HTMLElement) => void): void {
const section = this.container.createDiv('selected-files-section');
const section = this.container.createDiv('sn-selected-files-section');
section.createEl('h3', { text: 'Selected files' });
const fileList = section.createDiv('selected-files-list');
const fileList = section.createDiv('sn-selected-files-list');
updateCallback(fileList);
}
@ -703,7 +703,7 @@ class FilePickerUIBuilder {
}
buildFooter(onCancel: () => void, onSubmit: () => void, fileCount: number): void {
const footer = this.container.createDiv('file-picker-footer');
const footer = this.container.createDiv('sn-file-picker-footer');
new Setting(footer)
.addButton((btn) => btn.setButtonText('Cancel').onClick(onCancel))
@ -739,7 +739,7 @@ class SelectedFilesListRenderer {
}
private renderFileItem(file: TFile): void {
const fileItem = this.container.createDiv('selected-file-item');
const fileItem = this.container.createDiv('sn-selected-file-item');
this.renderFileInfo(fileItem, file);
this.renderValidationStatus(fileItem, file);
@ -747,11 +747,11 @@ class SelectedFilesListRenderer {
}
private renderFileInfo(container: HTMLElement, file: TFile): void {
const fileInfo = container.createDiv('file-info');
fileInfo.createEl('span', { text: file.name, cls: 'file-name' });
const fileInfo = container.createDiv('sn-file-info');
fileInfo.createEl('span', { text: file.name, cls: 'sn-file-name' });
fileInfo.createEl('span', {
text: this.formatFileSize(file.stat.size),
cls: 'file-size',
cls: 'sn-file-size',
});
}
@ -759,13 +759,13 @@ class SelectedFilesListRenderer {
const validation = this.validationResults.get(file.path);
if (!validation) return;
const statusIcon = container.createDiv('validation-status');
const statusIcon = container.createDiv('sn-validation-status');
if (validation.valid) {
statusIcon.addClass('valid');
statusIcon.addClass('sn-is-valid');
statusIcon.setText('✓');
} else {
statusIcon.addClass('invalid');
statusIcon.addClass('sn-is-invalid');
statusIcon.setText('✗');
statusIcon.title = validation.errors?.map((error) => error.message).join('\n') || '';
}
@ -774,7 +774,7 @@ class SelectedFilesListRenderer {
private renderRemoveButton(container: HTMLElement, file: TFile): void {
const removeBtn = container.createEl('button', {
text: 'Remove',
cls: 'remove-file-btn',
cls: 'sn-remove-file-btn',
});
removeBtn.onclick = () => this.onRemove(file);

View file

@ -69,7 +69,7 @@ export class ToastNotification {
private static initContainer(position: NotificationPosition = 'top-right') {
if (!this.container) {
this.container = createEl('div', {
cls: `toast-container toast-container--${position}`,
cls: `sn-toast-container sn-toast-container--${position}`,
attr: {
role: 'region',
'aria-label': 'Notification area',
@ -80,7 +80,7 @@ export class ToastNotification {
document.body.appendChild(this.container);
} else {
// 위치 변경
this.container.className = `toast-container toast-container--${position}`;
this.container.className = `sn-toast-container sn-toast-container--${position}`;
}
}
@ -95,7 +95,7 @@ export class ToastNotification {
// Toast 요소 생성
const toast = createEl('div', {
cls: `toast toast--${options.type}`,
cls: `sn-toast sn-toast--${options.type}`,
attr: {
role: 'alert',
'aria-live': options.type === 'error' ? 'assertive' : 'polite',
@ -105,28 +105,28 @@ export class ToastNotification {
// 아이콘
if (options.icon !== false) {
const iconContainer = createEl('div', { cls: 'toast__icon' });
const iconContainer = createEl('div', { cls: 'sn-toast__icon' });
const statusIcon = new StatusIcon(options.type, undefined);
iconContainer.appendChild(statusIcon.create());
toast.appendChild(iconContainer);
}
// 콘텐츠
const content = createEl('div', { cls: 'toast__content' });
const content = createEl('div', { cls: 'sn-toast__content' });
if (options.title) {
const title = createEl('div', { cls: 'toast__title', text: options.title });
const title = createEl('div', { cls: 'sn-toast__title', text: options.title });
content.appendChild(title);
}
const message = createEl('div', { cls: 'toast__message', text: options.message });
const message = createEl('div', { cls: 'sn-toast__message', text: options.message });
content.appendChild(message);
// 진행률 바
if (options.progress !== undefined) {
const progressBar = createEl('div', { cls: 'toast__progress' });
const progressBar = createEl('div', { cls: 'sn-toast__progress' });
const progressFill = createEl('div', {
cls: 'toast__progress-fill',
cls: 'sn-toast__progress-fill',
attr: { style: `--sn-progress-width:${options.progress}%` },
});
progressBar.appendChild(progressFill);
@ -135,11 +135,11 @@ export class ToastNotification {
// 액션 버튼
if (options.actions && options.actions.length > 0) {
const actions = createEl('div', { cls: 'toast__actions' });
const actions = createEl('div', { cls: 'sn-toast__actions' });
options.actions.forEach((action) => {
const button = createEl('button', {
cls: `toast__action toast__action--${action.style || 'link'}`,
cls: `sn-toast__action sn-toast__action--${action.style || 'link'}`,
text: action.label,
});
button.addEventListener('click', () => {
@ -157,7 +157,7 @@ export class ToastNotification {
// 닫기 버튼
if (options.closable !== false) {
const closeBtn = createEl('button', {
cls: 'toast__close',
cls: 'sn-toast__close',
text: '×',
attr: { 'aria-label': 'Dismiss notification' },
});
@ -171,7 +171,7 @@ export class ToastNotification {
// 애니메이션
requestAnimationFrame(() => {
toast.classList.add('toast--show');
toast.classList.add('sn-toast--show');
});
// 사운드 재생
@ -200,8 +200,8 @@ export class ToastNotification {
const toast = this.notifications.get(id);
if (!toast) return;
toast.classList.remove('toast--show');
toast.classList.add('toast--hide');
toast.classList.remove('sn-toast--show');
toast.classList.add('sn-toast--hide');
setTimeout(() => {
toast.remove();
@ -232,7 +232,7 @@ export class ToastNotification {
const toast = this.notifications.get(id);
if (!toast) return;
const progressFill = toast.querySelector('.toast__progress-fill');
const progressFill = toast.querySelector('.sn-toast__progress-fill');
if (progressFill instanceof HTMLElement) {
progressFill.setAttribute('style', `--sn-progress-width:${progress}%`);
}
@ -354,7 +354,7 @@ export class ModalNotification {
}
// 오버레이 생성
this.overlay = createEl('div', { cls: 'modal-overlay' });
this.overlay = createEl('div', { cls: 'sn-modal-overlay' });
this.overlay.addEventListener('click', () => {
if (options.closable !== false) {
this.dismiss();
@ -364,7 +364,7 @@ export class ModalNotification {
// 모달 생성
this.activeModal = createEl('div', {
cls: `modal-notification modal-notification--${options.type}`,
cls: `sn-modal-notification sn-modal-notification--${options.type}`,
attr: {
role: 'alertdialog',
'aria-modal': 'true',
@ -374,7 +374,7 @@ export class ModalNotification {
});
// 헤더
const header = createEl('div', { cls: 'modal-notification__header' });
const header = createEl('div', { cls: 'sn-modal-notification__header' });
if (options.icon !== false) {
const statusIcon = new StatusIcon(options.type, undefined);
@ -383,7 +383,7 @@ export class ModalNotification {
if (options.title) {
const title = createEl('h2', {
cls: 'modal-notification__title',
cls: 'sn-modal-notification__title',
text: options.title,
attr: { id: 'modal-title' },
});
@ -392,7 +392,7 @@ export class ModalNotification {
if (options.closable !== false) {
const closeBtn = createEl('button', {
cls: 'modal-notification__close',
cls: 'sn-modal-notification__close',
text: '×',
attr: { 'aria-label': 'Close' },
});
@ -407,7 +407,7 @@ export class ModalNotification {
// 본문
const body = createEl('div', {
cls: 'modal-notification__body',
cls: 'sn-modal-notification__body',
text: options.message,
attr: { id: 'modal-message' },
});
@ -415,11 +415,11 @@ export class ModalNotification {
// 액션 버튼
if (options.actions && options.actions.length > 0) {
const footer = createEl('div', { cls: 'modal-notification__footer' });
const footer = createEl('div', { cls: 'sn-modal-notification__footer' });
options.actions.forEach((action) => {
const button = createEl('button', {
cls: `modal-notification__action modal-notification__action--${
cls: `sn-modal-notification__action sn-modal-notification__action--${
action.style || 'secondary'
}`,
text: action.label,
@ -447,8 +447,8 @@ export class ModalNotification {
// 애니메이션
requestAnimationFrame(() => {
this.overlay?.classList.add('modal-overlay--show');
this.activeModal?.classList.add('modal-notification--show');
this.overlay?.classList.add('sn-modal-overlay--show');
this.activeModal?.classList.add('sn-modal-notification--show');
});
// 사운드 재생
@ -474,8 +474,8 @@ export class ModalNotification {
static dismiss() {
if (!this.activeModal || !this.overlay) return;
this.activeModal.classList.remove('modal-notification--show');
this.overlay.classList.remove('modal-overlay--show');
this.activeModal.classList.remove('sn-modal-notification--show');
this.overlay.classList.remove('sn-modal-overlay--show');
setTimeout(() => {
this.activeModal?.remove();
@ -500,7 +500,7 @@ export class StatusBarNotification {
private static initContainer() {
if (!this.container) {
this.container = createEl('div', {
cls: 'statusbar-notification',
cls: 'sn-statusbar-notification',
attr: {
role: 'status',
'aria-live': 'polite',
@ -528,12 +528,12 @@ export class StatusBarNotification {
// 알림 생성
this.currentNotification = createEl('div', {
cls: `statusbar-notification__content statusbar-notification__content--${options.type}`,
cls: `sn-statusbar-notification__content sn-statusbar-notification__content--${options.type}`,
});
// 아이콘
if (options.icon !== false) {
const iconContainer = createEl('span', { cls: 'statusbar-notification__icon' });
const iconContainer = createEl('span', { cls: 'sn-statusbar-notification__icon' });
const statusIcon = new StatusIcon(options.type, undefined);
iconContainer.appendChild(statusIcon.create());
this.currentNotification.appendChild(iconContainer);
@ -541,7 +541,7 @@ export class StatusBarNotification {
// 메시지
const message = createEl('span', {
cls: 'statusbar-notification__message',
cls: 'sn-statusbar-notification__message',
text: options.message,
});
this.currentNotification.appendChild(message);
@ -550,7 +550,7 @@ export class StatusBarNotification {
if (options.actions && options.actions.length > 0) {
const action = options.actions[0]; // 상태바는 하나의 액션만 지원
const button = createEl('button', {
cls: 'statusbar-notification__action',
cls: 'sn-statusbar-notification__action',
text: action.label,
});
button.addEventListener('click', () => {
@ -563,7 +563,7 @@ export class StatusBarNotification {
// 닫기 버튼
if (options.closable !== false) {
const closeBtn = createEl('button', {
cls: 'statusbar-notification__close',
cls: 'sn-statusbar-notification__close',
text: '×',
attr: { 'aria-label': 'Close' },
});
@ -576,7 +576,7 @@ export class StatusBarNotification {
// 애니메이션
requestAnimationFrame(() => {
this.container?.classList.add('statusbar-notification--show');
this.container?.classList.add('sn-statusbar-notification--show');
});
// 자동 숨기기
@ -592,7 +592,7 @@ export class StatusBarNotification {
static hide() {
if (!this.container) return;
this.container.classList.remove('statusbar-notification--show');
this.container.classList.remove('sn-statusbar-notification--show');
setTimeout(() => {
this.currentNotification?.remove();

View file

@ -44,7 +44,7 @@ export class CircularProgress {
}
create(container: HTMLElement): HTMLElement {
this.element = createEl('div', { cls: 'circular-progress' });
this.element = createEl('div', { cls: 'sn-circular-progress' });
this.element.style.setProperty('--cp-size', `${this.options.size}px`);
this.element.style.setProperty(
'--cp-animation-duration',
@ -96,7 +96,7 @@ export class CircularProgress {
this.progressCircle.setAttribute('stroke-dashoffset', String(offset));
// 애니메이션 설정
this.progressCircle.classList.add('circular-progress__circle');
this.progressCircle.classList.add('sn-circular-progress__circle');
this.svg.appendChild(this.backgroundCircle);
this.svg.appendChild(this.progressCircle);
@ -263,7 +263,7 @@ export class CircularProgress {
*/
export class SemiCircularProgress extends CircularProgress {
create(container: HTMLElement): HTMLElement {
this.element = createEl('div', { cls: 'semi-circular-progress' });
this.element = createEl('div', { cls: 'sn-semi-circular-progress' });
this.element.style.setProperty('--cp-size', `${this.options.size}px`);
this.element.style.setProperty(
'--cp-animation-duration',
@ -296,7 +296,7 @@ export class SemiCircularProgress extends CircularProgress {
this.progressCircle.setAttribute('stroke-width', String(this.options.strokeWidth));
this.progressCircle.setAttribute('stroke-linecap', 'round');
this.progressCircle.setAttribute('stroke-dasharray', String(circumference));
this.progressCircle.classList.add('semi-circular-progress__circle');
this.progressCircle.classList.add('sn-semi-circular-progress__circle');
// 초기 진행률 설정
const offset = circumference - (this.currentProgress / 100) * circumference;

View file

@ -65,7 +65,7 @@ export class ProgressBar {
create(container: HTMLElement): HTMLElement {
this.element = createEl('div', {
cls: `progress-bar progress-bar--${this.options.size} progress-bar--${this.options.color}`,
cls: `sn-progress-bar sn-progress-bar--${this.options.size} sn-progress-bar--${this.options.color}`,
});
this.element.setAttribute('role', 'progressbar');
this.element.setAttribute('aria-valuemin', String(this.options.min));
@ -79,24 +79,24 @@ export class ProgressBar {
// 라벨
if (this.options.label) {
this.labelElement = createEl('div', {
cls: 'progress-bar__label',
cls: 'sn-progress-bar__label',
text: this.options.label,
});
this.element.appendChild(this.labelElement);
}
// 진행률 바 컨테이너
const barContainer = createEl('div', { cls: 'progress-bar__container' });
const barContainer = createEl('div', { cls: 'sn-progress-bar__container' });
// 진행률 채우기
this.progressFill = createEl('div', { cls: 'progress-bar__fill' });
this.progressFill = createEl('div', { cls: 'sn-progress-bar__fill' });
if (this.options.striped) {
this.progressFill.classList.add('progress-bar__fill--striped');
this.progressFill.classList.add('sn-progress-bar__fill--striped');
}
if (this.options.indeterminate) {
this.progressFill.classList.add('progress-bar__fill--indeterminate');
this.progressFill.classList.add('sn-progress-bar__fill--indeterminate');
} else {
this.updateProgress(this.currentValue);
}
@ -105,12 +105,12 @@ export class ProgressBar {
this.element.appendChild(barContainer);
// 정보 표시 영역
const infoContainer = createEl('div', { cls: 'progress-bar__info' });
const infoContainer = createEl('div', { cls: 'sn-progress-bar__info' });
// 퍼센트 표시
if (this.options.showPercentage && !this.options.indeterminate) {
this.percentageElement = createEl('span', {
cls: 'progress-bar__percentage',
cls: 'sn-progress-bar__percentage',
text: '0%',
});
infoContainer.appendChild(this.percentageElement);
@ -119,7 +119,7 @@ export class ProgressBar {
// 예상 시간 표시
if (this.options.showTimeRemaining && !this.options.indeterminate) {
this.timeRemainingElement = createEl('span', {
cls: 'progress-bar__time-remaining',
cls: 'sn-progress-bar__time-remaining',
text: 'Calculating...',
});
infoContainer.appendChild(this.timeRemainingElement);
@ -292,14 +292,14 @@ export class ProgressBar {
// 기존 색상 클래스 제거
this.element.classList.remove(
'progress-bar--primary',
'progress-bar--success',
'progress-bar--warning',
'progress-bar--error'
'sn-progress-bar--primary',
'sn-progress-bar--success',
'sn-progress-bar--warning',
'sn-progress-bar--error'
);
// 새 색상 클래스 추가
this.element.classList.add(`progress-bar--${color}`);
this.element.classList.add(`sn-progress-bar--${color}`);
this.options.color = color;
}
@ -308,7 +308,7 @@ export class ProgressBar {
*/
setLabel(label: string) {
if (!this.labelElement) {
this.labelElement = createEl('div', { cls: 'progress-bar__label' });
this.labelElement = createEl('div', { cls: 'sn-progress-bar__label' });
this.element?.insertBefore(this.labelElement, this.element.firstChild);
}
@ -325,7 +325,7 @@ export class ProgressBar {
if (!this.progressFill) return;
if (indeterminate) {
this.progressFill.classList.add('progress-bar__fill--indeterminate');
this.progressFill.classList.add('sn-progress-bar__fill--indeterminate');
this.progressFill.removeAttribute('style');
if (this.percentageElement) {
@ -335,7 +335,7 @@ export class ProgressBar {
this.timeRemainingElement.classList.add('sn-hidden');
}
} else {
this.progressFill.classList.remove('progress-bar__fill--indeterminate');
this.progressFill.classList.remove('sn-progress-bar__fill--indeterminate');
this.updateProgress(this.currentValue);
if (this.percentageElement) {
@ -387,10 +387,10 @@ export class MultiStepProgressBar {
}
create(container: HTMLElement): HTMLElement {
this.element = createEl('div', { cls: 'multi-step-progress' });
this.element = createEl('div', { cls: 'sn-multi-step-progress' });
// 단계 표시
this.stepsContainer = createEl('div', { cls: 'multi-step-progress__steps' });
this.stepsContainer = createEl('div', { cls: 'sn-multi-step-progress__steps' });
const stepsContainer = this.stepsContainer;
if (!stepsContainer) {
container.appendChild(this.element);
@ -402,12 +402,12 @@ export class MultiStepProgressBar {
stepEl.setAttribute('data-step-id', step.id);
const stepNumber = createEl('span', {
cls: 'step__number',
cls: 'sn-step__number',
text: String(index + 1),
});
const stepLabel = createEl('span', {
cls: 'step__label',
cls: 'sn-step__label',
text: step.label,
});
@ -426,7 +426,7 @@ export class MultiStepProgressBar {
this.element.appendChild(stepsContainer);
// 전체 진행률 바
const progressContainer = createEl('div', { cls: 'multi-step-progress__bar' });
const progressContainer = createEl('div', { cls: 'sn-multi-step-progress__bar' });
this.progressBar.create(progressContainer);
this.element.appendChild(progressContainer);
@ -499,7 +499,7 @@ export class MultiStepProgressBar {
const stepEl = this.stepsContainer?.querySelector(`[data-step-id="${stepId}"]`);
if (stepEl) {
stepEl.className = `step step--${status}`;
stepEl.className = `sn-step sn-step--${status}`;
// ARIA 속성 업데이트
switch (status) {

View file

@ -25,7 +25,7 @@ export class UIComponentFactory {
});
if (icon) {
statusEl.createSpan({ text: icon, cls: 'status-icon' });
statusEl.createSpan({ text: icon, cls: 'sn-status-icon' });
}
statusEl.createSpan({ text, cls: 'status-text' });
@ -53,7 +53,7 @@ export class UIComponentFactory {
}
const progressBar = progressContainer.createDiv({
cls: 'progress-bar',
cls: 'sn-progress-bar',
attr: {
role: 'progressbar',
'aria-valuenow': String(value),
@ -64,7 +64,7 @@ export class UIComponentFactory {
});
const progressFill = progressBar.createDiv({
cls: 'progress-fill',
cls: 'sn-progress-fill',
attr: {
style: `width: ${(value / max) * 100}%`,
},
@ -73,7 +73,7 @@ export class UIComponentFactory {
// 접근성을 위한 텍스트
progressFill.createSpan({
text: `${Math.round((value / max) * 100)}%`,
cls: 'progress-text',
cls: 'sn-progress-text',
});
return progressContainer;
@ -198,7 +198,7 @@ export class UIComponentFactory {
// 탭 버튼
const tabButton = tabList.createEl('button', {
text: tab.label,
cls: `tab-button ${tab.id === activeTab ? 'active' : ''}`,
cls: `sn-tab-button ${tab.id === activeTab ? 'sn-is-active' : ''}`,
attr: {
role: 'tab',
id: `tab-${tab.id}`,
@ -210,7 +210,7 @@ export class UIComponentFactory {
// 탭 패널
const tabPanel = tabPanels.createDiv({
cls: `tab-panel ${tab.id === activeTab ? 'active' : ''}`,
cls: `sn-tab-panel ${tab.id === activeTab ? 'sn-is-active' : ''}`,
attr: {
role: 'tabpanel',
id: `panel-${tab.id}`,
@ -226,14 +226,14 @@ export class UIComponentFactory {
// 이벤트 핸들러
tabButton.onclick = () => {
// 모든 탭 비활성화
tabList.querySelectorAll('.tab-button').forEach((btn) => {
btn.classList.remove('active');
tabList.querySelectorAll('.sn-tab-button').forEach((btn) => {
btn.classList.remove('sn-is-active');
btn.setAttribute('aria-selected', 'false');
btn.setAttribute('tabindex', '-1');
});
tabPanels.querySelectorAll('.tab-panel').forEach((panel) => {
panel.classList.remove('active');
tabPanels.querySelectorAll('.sn-tab-panel').forEach((panel) => {
panel.classList.remove('sn-is-active');
panel.setAttribute('hidden', 'true');
if (panel instanceof HTMLElement) {
panel.empty();
@ -241,11 +241,11 @@ export class UIComponentFactory {
});
// 선택된 탭 활성화
tabButton.classList.add('active');
tabButton.classList.add('sn-is-active');
tabButton.setAttribute('aria-selected', 'true');
tabButton.setAttribute('tabindex', '0');
tabPanel.classList.add('active');
tabPanel.classList.add('sn-is-active');
tabPanel.removeAttribute('hidden');
tabPanel.appendChild(tab.content());
@ -269,7 +269,7 @@ export class UIComponentFactory {
}
e.preventDefault();
const newTab = tabList.querySelectorAll('.tab-button')[newIndex];
const newTab = tabList.querySelectorAll('.sn-tab-button')[newIndex];
if (newTab instanceof HTMLElement) {
newTab.click();
newTab.focus();
@ -294,7 +294,7 @@ export class UIComponentFactory {
*/
static createLoadingSpinner(containerEl: HTMLElement, text = 'Loading...'): HTMLElement {
const spinnerEl = containerEl.createDiv({
cls: 'loading-spinner',
cls: 'sn-loading-spinner',
attr: {
role: 'status',
'aria-label': text,
@ -302,7 +302,7 @@ export class UIComponentFactory {
});
spinnerEl.createDiv({ cls: 'spinner' });
spinnerEl.createSpan({ text, cls: 'spinner-text' });
spinnerEl.createSpan({ text, cls: 'sn-loading-message' });
return spinnerEl;
}

View file

@ -1,32 +1,32 @@
/* File Picker Modal Styles */
.file-picker-modal {
.sn-file-picker-modal {
max-width: 800px;
max-height: 600px;
}
.file-picker-modal .modal-content {
.sn-file-picker-modal .modal-content {
padding: 20px;
overflow-y: auto;
}
/* Header */
.file-picker-header {
.sn-file-picker-header {
margin-bottom: 20px;
text-align: center;
}
.file-picker-header h2 {
.sn-file-picker-header h2 {
margin: 0 0 10px 0;
color: var(--text-normal);
}
.file-picker-subtitle {
.sn-file-picker-subtitle {
color: var(--text-muted);
font-size: 14px;
}
/* Drag & Drop Zone */
.drag-drop-zone {
.sn-drag-drop-zone {
border: 2px dashed var(--background-modifier-border);
border-radius: 8px;
padding: 40px 20px;
@ -37,48 +37,48 @@
background: var(--background-primary);
}
.drag-drop-zone:hover {
.sn-drag-drop-zone:hover {
border-color: var(--interactive-accent);
background: var(--background-primary-alt);
}
.drag-drop-zone.dragging {
.sn-drag-drop-zone.sn-is-dragging {
border-color: var(--interactive-accent);
background: var(--background-primary-alt);
transform: scale(1.02);
}
.drop-zone-icon {
.sn-drop-zone-icon {
margin-bottom: 20px;
color: var(--text-muted);
}
.drop-zone-icon svg {
.sn-drop-zone-icon svg {
width: 64px;
height: 64px;
}
.drop-zone-text h3 {
.sn-drop-zone-text h3 {
margin: 0 0 10px 0;
color: var(--text-normal);
}
.drop-zone-subtext {
.sn-drop-zone-subtext {
color: var(--text-muted);
margin-bottom: 10px;
}
.drop-zone-formats {
.sn-drop-zone-formats {
font-size: 12px;
color: var(--text-faint);
}
.drop-zone-input {
.sn-drop-zone-input {
display: none;
}
/* Drag Overlay */
.drag-overlay {
.sn-drag-overlay {
position: absolute;
top: 0;
left: 0;
@ -93,22 +93,22 @@
transition: opacity 0.3s ease;
}
.drag-overlay.active {
.sn-drag-overlay.sn-is-active {
display: flex;
opacity: 0.95;
}
.drag-overlay-content {
.sn-drag-overlay-content {
text-align: center;
}
.drag-overlay-icon svg {
.sn-drag-overlay-icon svg {
width: 48px;
height: 48px;
color: var(--interactive-accent);
}
.drag-overlay-text {
.sn-drag-overlay-text {
margin-top: 10px;
font-size: 18px;
font-weight: 600;
@ -116,7 +116,7 @@
}
/* Drop Zone Messages */
.drop-zone-message {
.sn-drop-zone-message {
position: absolute;
bottom: 10px;
left: 50%;
@ -124,31 +124,31 @@
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
animation: slideUp 0.3s ease;
animation: sn-file-picker-slide-up 0.3s ease;
}
.drop-zone-message.success {
.sn-drop-zone-message.sn-drop-zone-message--success {
background: var(--background-modifier-success);
color: var(--text-on-accent);
}
.drop-zone-message.error {
.sn-drop-zone-message.sn-drop-zone-message--error {
background: var(--background-modifier-error);
color: var(--text-on-accent);
}
/* Tabs */
.file-picker-tabs {
.sn-file-picker-tabs {
margin: 20px 0;
}
.tab-header {
.sn-tab-header {
display: flex;
border-bottom: 2px solid var(--background-modifier-border);
margin-bottom: 20px;
}
.tab-button {
.sn-tab-button {
padding: 10px 20px;
background: none;
border: none;
@ -160,15 +160,15 @@
position: relative;
}
.tab-button:hover {
.sn-tab-button:hover {
color: var(--text-normal);
}
.tab-button.active {
.sn-tab-button.sn-is-active {
color: var(--interactive-accent);
}
.tab-button.active::after {
.sn-tab-button.sn-is-active::after {
content: '';
position: absolute;
bottom: -2px;
@ -178,27 +178,27 @@
background: var(--interactive-accent);
}
.tab-content {
.sn-tab-content {
min-height: 200px;
}
.browse-content,
.recent-content {
.sn-browse-content,
.sn-recent-content {
display: none;
}
.browse-content.active,
.recent-content.active {
.sn-browse-content.sn-is-active,
.sn-recent-content.sn-is-active {
display: block;
}
/* File Browser */
.file-browser {
.sn-file-browser {
max-height: 400px;
overflow-y: auto;
}
.file-browser-toolbar {
.sn-file-browser-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
@ -208,12 +208,12 @@
border-radius: 4px;
}
.search-container {
.sn-search-container {
flex: 1;
margin-right: 10px;
}
.search-input {
.sn-search-input {
width: 100%;
padding: 6px 10px;
border: 1px solid var(--background-modifier-border);
@ -222,13 +222,13 @@
color: var(--text-normal);
}
.sort-container {
.sn-sort-container {
display: flex;
align-items: center;
gap: 5px;
}
.sort-select {
.sn-sort-select {
padding: 6px 10px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -236,8 +236,8 @@
color: var(--text-normal);
}
.sort-order-btn,
.refresh-btn {
.sn-sort-order-btn,
.sn-refresh-btn {
padding: 6px 10px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -246,18 +246,18 @@
cursor: pointer;
}
.sort-order-btn:hover,
.refresh-btn:hover {
.sn-sort-order-btn:hover,
.sn-refresh-btn:hover {
background: var(--background-modifier-hover);
}
/* File List */
.file-browser-list {
.sn-file-browser-list {
max-height: 300px;
overflow-y: auto;
}
.folder-header {
.sn-folder-header {
display: flex;
align-items: center;
padding: 8px 12px;
@ -268,35 +268,35 @@
user-select: none;
}
.folder-header:hover {
.sn-folder-header:hover {
background: var(--background-modifier-hover);
}
.folder-header.collapsed .folder-icon {
.sn-folder-header.sn-is-collapsed .sn-folder-icon {
transform: rotate(-90deg);
}
.folder-icon {
.sn-folder-icon {
margin-right: 8px;
transition: transform 0.2s ease;
}
.folder-name {
.sn-folder-name {
flex: 1;
font-weight: 500;
}
.folder-count {
.sn-folder-count {
color: var(--text-muted);
font-size: 12px;
}
.folder-files {
.sn-folder-files {
margin-left: 20px;
margin-bottom: 10px;
}
.file-item {
.sn-file-item {
display: flex;
align-items: center;
padding: 8px 12px;
@ -305,29 +305,29 @@
transition: background 0.2s ease;
}
.file-item:hover {
.sn-file-item:hover {
background: var(--background-modifier-hover);
}
.file-item.selected {
.sn-file-item.sn-is-selected {
background: var(--background-modifier-active-hover);
}
.file-icon {
.sn-file-icon {
margin-right: 10px;
font-size: 18px;
}
.file-info {
.sn-file-info {
flex: 1;
}
.file-name {
.sn-file-name {
color: var(--text-normal);
margin-bottom: 2px;
}
.file-meta {
.sn-file-meta {
display: flex;
gap: 10px;
font-size: 12px;
@ -335,24 +335,24 @@
}
/* Recent Files */
.recent-files {
.sn-recent-files {
max-height: 400px;
overflow-y: auto;
}
.recent-files-header {
.sn-recent-files-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.recent-files-header h3 {
.sn-recent-files-header h3 {
margin: 0;
color: var(--text-normal);
}
.clear-recent-btn {
.sn-clear-recent-btn {
padding: 4px 10px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -362,17 +362,17 @@
cursor: pointer;
}
.clear-recent-btn:hover {
.sn-clear-recent-btn:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
.recent-files-list {
.sn-recent-files-list {
max-height: 300px;
overflow-y: auto;
}
.recent-file-item {
.sn-recent-file-item {
display: flex;
align-items: center;
padding: 10px 12px;
@ -382,15 +382,15 @@
transition: background 0.2s ease;
}
.recent-file-item:hover {
.sn-recent-file-item:hover {
background: var(--background-modifier-hover);
}
.recent-file-item.file-not-found {
.sn-recent-file-item.sn-is-file-not-found {
opacity: 0.5;
}
.file-order {
.sn-file-order {
width: 24px;
height: 24px;
border-radius: 50%;
@ -404,13 +404,13 @@
margin-right: 10px;
}
.file-actions {
.sn-file-actions {
display: flex;
gap: 5px;
}
.select-btn,
.remove-btn {
.sn-select-btn,
.sn-remove-btn {
padding: 4px 8px;
border: none;
background: none;
@ -419,32 +419,32 @@
border-radius: 4px;
}
.select-btn:hover,
.remove-btn:hover {
.sn-select-btn:hover,
.sn-remove-btn:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
/* Selected Files Section */
.selected-files-section {
.sn-selected-files-section {
margin: 20px 0;
padding: 15px;
background: var(--background-secondary);
border-radius: 4px;
}
.selected-files-section h3 {
.sn-selected-files-section h3 {
margin: 0 0 10px 0;
color: var(--text-normal);
font-size: 14px;
}
.selected-files-list {
.sn-selected-files-list {
max-height: 150px;
overflow-y: auto;
}
.selected-file-item {
.sn-selected-file-item {
display: flex;
align-items: center;
padding: 8px 10px;
@ -453,14 +453,14 @@
margin-bottom: 5px;
}
.selected-file-item .file-info {
.sn-selected-file-item .sn-file-info {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
}
.validation-status {
.sn-validation-status {
width: 20px;
height: 20px;
border-radius: 50%;
@ -471,17 +471,17 @@
margin-right: 10px;
}
.validation-status.valid {
.sn-validation-status.sn-is-valid {
background: var(--background-modifier-success);
color: var(--text-on-accent);
}
.validation-status.invalid {
.sn-validation-status.sn-is-invalid {
background: var(--background-modifier-error);
color: var(--text-on-accent);
}
.remove-file-btn {
.sn-remove-file-btn {
padding: 4px 8px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -491,12 +491,12 @@
cursor: pointer;
}
.remove-file-btn:hover {
.sn-remove-file-btn:hover {
background: var(--background-modifier-error);
color: var(--text-on-accent);
}
.no-files-message {
.sn-no-files-message {
color: var(--text-muted);
font-style: italic;
text-align: center;
@ -504,7 +504,7 @@
}
/* Progress Indicator */
.progress-indicator {
.sn-progress-indicator {
position: fixed;
top: 0;
left: 0;
@ -515,11 +515,11 @@
justify-content: center;
}
.progress-indicator.sn-flex {
.sn-progress-indicator.sn-flex {
display: flex;
}
.progress-overlay {
.sn-progress-overlay {
position: absolute;
top: 0;
left: 0;
@ -528,7 +528,7 @@
background: rgba(0, 0, 0, 0.5);
}
.progress-content {
.sn-progress-content {
position: relative;
background: var(--background-primary);
border-radius: 8px;
@ -538,24 +538,24 @@
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
.progress-content.error {
.sn-progress-content.sn-is-error {
border: 2px solid var(--background-modifier-error);
}
.progress-content.success {
.sn-progress-content.sn-is-success {
border: 2px solid var(--background-modifier-success);
}
.progress-spinner {
.sn-progress-spinner {
margin-bottom: 20px;
color: var(--interactive-accent);
}
.progress-spinner.is-spinning svg {
animation: spin 1s linear infinite;
.sn-progress-spinner.sn-is-spinning svg {
animation: sn-file-picker-spin 1s linear infinite;
}
@keyframes spin {
@keyframes sn-file-picker-spin {
from {
transform: rotate(0deg);
}
@ -564,11 +564,11 @@
}
}
.progress-bar-container {
.sn-progress-bar-container {
margin: 20px 0;
}
.progress-bar {
.sn-progress-bar {
height: 20px;
background: var(--background-modifier-border);
border-radius: 10px;
@ -576,21 +576,21 @@
position: relative;
}
.progress-fill {
.sn-progress-fill {
height: 100%;
background: var(--interactive-accent);
transition: width 0.3s ease;
}
.progress-fill.progress-warning {
.sn-progress-fill.sn-is-warning {
background: var(--text-warning);
}
.progress-fill.progress-success {
.sn-progress-fill.sn-is-success {
background: var(--text-success);
}
.progress-text {
.sn-progress-text {
position: absolute;
top: 50%;
left: 50%;
@ -600,12 +600,12 @@
font-weight: 600;
}
.progress-message {
.sn-progress-message {
color: var(--text-normal);
margin-bottom: 20px;
}
.progress-cancel-btn {
.sn-progress-cancel-btn {
padding: 8px 20px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
@ -614,12 +614,12 @@
cursor: pointer;
}
.progress-cancel-btn:hover {
.sn-progress-cancel-btn:hover {
background: var(--background-modifier-hover);
}
/* Empty State */
.empty-state {
.sn-empty-state {
padding: 40px 20px;
text-align: center;
color: var(--text-muted);
@ -627,14 +627,14 @@
}
/* Footer */
.file-picker-footer {
.sn-file-picker-footer {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid var(--background-modifier-border);
}
/* Animations */
@keyframes slideUp {
@keyframes sn-file-picker-slide-up {
from {
transform: translateX(-50%) translateY(10px);
opacity: 0;
@ -646,27 +646,27 @@
}
/* Scrollbar Styles */
.file-browser-list::-webkit-scrollbar,
.selected-files-list::-webkit-scrollbar,
.recent-files-list::-webkit-scrollbar {
.sn-file-browser-list::-webkit-scrollbar,
.sn-selected-files-list::-webkit-scrollbar,
.sn-recent-files-list::-webkit-scrollbar {
width: 8px;
}
.file-browser-list::-webkit-scrollbar-track,
.selected-files-list::-webkit-scrollbar-track,
.recent-files-list::-webkit-scrollbar-track {
.sn-file-browser-list::-webkit-scrollbar-track,
.sn-selected-files-list::-webkit-scrollbar-track,
.sn-recent-files-list::-webkit-scrollbar-track {
background: var(--background-secondary);
}
.file-browser-list::-webkit-scrollbar-thumb,
.selected-files-list::-webkit-scrollbar-thumb,
.recent-files-list::-webkit-scrollbar-thumb {
.sn-file-browser-list::-webkit-scrollbar-thumb,
.sn-selected-files-list::-webkit-scrollbar-thumb,
.sn-recent-files-list::-webkit-scrollbar-thumb {
background: var(--background-modifier-border);
border-radius: 4px;
}
.file-browser-list::-webkit-scrollbar-thumb:hover,
.selected-files-list::-webkit-scrollbar-thumb:hover,
.recent-files-list::-webkit-scrollbar-thumb:hover {
.sn-file-browser-list::-webkit-scrollbar-thumb:hover,
.sn-selected-files-list::-webkit-scrollbar-thumb:hover,
.sn-recent-files-list::-webkit-scrollbar-thumb:hover {
background: var(--background-modifier-border-hover);
}

View file

@ -3,7 +3,7 @@
*/
/* Toast 알림 */
.toast-container {
.sn-toast-container {
position: fixed;
z-index: 10000;
pointer-events: none;
@ -11,39 +11,39 @@
max-width: 400px;
}
.toast-container--top-right {
.sn-toast-container--top-right {
top: 0;
right: 0;
}
.toast-container--top-left {
.sn-toast-container--top-left {
top: 0;
left: 0;
}
.toast-container--bottom-right {
.sn-toast-container--bottom-right {
bottom: 0;
right: 0;
}
.toast-container--bottom-left {
.sn-toast-container--bottom-left {
bottom: 0;
left: 0;
}
.toast-container--top-center {
.sn-toast-container--top-center {
top: 0;
left: 50%;
transform: translateX(-50%);
}
.toast-container--bottom-center {
.sn-toast-container--bottom-center {
bottom: 0;
left: 50%;
transform: translateX(-50%);
}
.toast {
.sn-toast {
pointer-events: auto;
display: flex;
align-items: flex-start;
@ -59,92 +59,92 @@
transition: all 0.3s ease;
}
.toast--show {
.sn-toast--show {
opacity: 1;
transform: translateX(0);
}
.toast--hide {
.sn-toast--hide {
opacity: 0;
transform: translateX(100%);
}
.toast-container--top-left .toast,
.toast-container--bottom-left .toast {
.sn-toast-container--top-left .sn-toast,
.sn-toast-container--bottom-left .sn-toast {
transform: translateX(-100%);
}
.toast-container--top-left .toast--show,
.toast-container--bottom-left .toast--show {
.sn-toast-container--top-left .sn-toast--show,
.sn-toast-container--bottom-left .sn-toast--show {
transform: translateX(0);
}
.toast-container--top-left .toast--hide,
.toast-container--bottom-left .toast--hide {
.sn-toast-container--top-left .sn-toast--hide,
.sn-toast-container--bottom-left .sn-toast--hide {
transform: translateX(-100%);
}
.toast--success {
.sn-toast--success {
border-color: var(--text-success);
}
.toast--error {
.sn-toast--error {
border-color: var(--text-error);
}
.toast--warning {
.sn-toast--warning {
border-color: var(--text-warning);
}
.toast--info {
.sn-toast--info {
border-color: var(--interactive-accent);
}
.toast__icon {
.sn-toast__icon {
flex-shrink: 0;
width: 20px;
height: 20px;
}
.toast--success .toast__icon {
.sn-toast--success .sn-toast__icon {
color: var(--text-success);
}
.toast--error .toast__icon {
.sn-toast--error .sn-toast__icon {
color: var(--text-error);
}
.toast--warning .toast__icon {
.sn-toast--warning .sn-toast__icon {
color: var(--text-warning);
}
.toast--info .toast__icon {
.sn-toast--info .sn-toast__icon {
color: var(--interactive-accent);
}
.toast__icon svg {
.sn-toast__icon svg {
width: 100%;
height: 100%;
fill: currentColor;
}
.toast__content {
.sn-toast__content {
flex: 1;
min-width: 0;
}
.toast__title {
.sn-toast__title {
font-weight: 600;
margin-bottom: 4px;
color: var(--text-normal);
}
.toast__message {
.sn-toast__message {
color: var(--text-muted);
word-wrap: break-word;
}
.toast__close {
.sn-toast__close {
flex-shrink: 0;
width: 24px;
height: 24px;
@ -158,17 +158,17 @@
transition: color 0.2s;
}
.toast__close:hover {
.sn-toast__close:hover {
color: var(--text-normal);
}
.toast__actions {
.sn-toast__actions {
display: flex;
gap: 8px;
margin-top: 12px;
}
.toast__action {
.sn-toast__action {
padding: 4px 12px;
font-size: 14px;
border-radius: 4px;
@ -179,27 +179,27 @@
transition: all 0.2s;
}
.toast__action:hover {
.sn-toast__action:hover {
background: var(--background-modifier-hover);
}
.toast__action--primary {
.sn-toast__action--primary {
background: var(--interactive-accent);
color: var(--text-on-accent);
border-color: var(--interactive-accent);
}
.toast__action--primary:hover {
.sn-toast__action--primary:hover {
background: var(--interactive-accent-hover);
}
.toast__action--danger {
.sn-toast__action--danger {
background: var(--text-error);
color: white;
border-color: var(--text-error);
}
.toast__progress {
.sn-toast__progress {
position: absolute;
bottom: 0;
left: 0;
@ -210,14 +210,14 @@
overflow: hidden;
}
.toast__progress-fill {
.sn-toast__progress-fill {
height: 100%;
background: var(--interactive-accent);
transition: width 0.3s ease;
}
/* Modal 알림 */
.modal-overlay {
.sn-modal-overlay {
position: fixed;
top: 0;
left: 0;
@ -228,14 +228,14 @@
align-items: center;
justify-content: center;
z-index: 10001;
animation: fadeIn 0.3s ease;
animation: sn-notification-fade-in 0.3s ease;
}
.modal-overlay--hide {
animation: fadeOut 0.3s ease;
.sn-modal-overlay--hide {
animation: sn-notification-fade-out 0.3s ease;
}
@keyframes fadeIn {
@keyframes sn-notification-fade-in {
from {
opacity: 0;
}
@ -244,7 +244,7 @@
}
}
@keyframes fadeOut {
@keyframes sn-notification-fade-out {
from {
opacity: 1;
}
@ -253,7 +253,7 @@
}
}
.modal {
.sn-modal {
background: var(--background-primary);
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
@ -261,10 +261,10 @@
width: 90%;
max-height: 80vh;
overflow: auto;
animation: slideIn 0.3s ease;
animation: sn-notification-slide-in 0.3s ease;
}
@keyframes slideIn {
@keyframes sn-notification-slide-in {
from {
opacity: 0;
transform: translateY(-20px);
@ -275,7 +275,7 @@
}
}
.modal__header {
.sn-modal__header {
display: flex;
align-items: center;
justify-content: space-between;
@ -283,14 +283,14 @@
border-bottom: 1px solid var(--background-modifier-border);
}
.modal__title {
.sn-modal__title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--text-normal);
}
.modal__close {
.sn-modal__close {
width: 30px;
height: 30px;
padding: 0;
@ -303,16 +303,16 @@
transition: color 0.2s;
}
.modal__close:hover {
.sn-modal__close:hover {
color: var(--text-normal);
}
.modal__content {
.sn-modal__content {
padding: 20px;
color: var(--text-normal);
}
.modal__footer {
.sn-modal__footer {
display: flex;
gap: 12px;
justify-content: flex-end;
@ -320,7 +320,7 @@
border-top: 1px solid var(--background-modifier-border);
}
.modal__action {
.sn-modal__action {
padding: 8px 16px;
border-radius: 6px;
border: 1px solid var(--background-modifier-border);
@ -331,28 +331,28 @@
transition: all 0.2s;
}
.modal__action:hover {
.sn-modal__action:hover {
background: var(--background-modifier-hover);
}
.modal__action--primary {
.sn-modal__action--primary {
background: var(--interactive-accent);
color: var(--text-on-accent);
border-color: var(--interactive-accent);
}
.modal__action--primary:hover {
.sn-modal__action--primary:hover {
background: var(--interactive-accent-hover);
}
.modal__action--danger {
.sn-modal__action--danger {
background: var(--text-error);
color: white;
border-color: var(--text-error);
}
/* StatusBar 알림 */
.status-bar {
.sn-status-bar {
position: fixed;
bottom: 0;
left: 0;
@ -366,53 +366,53 @@
transition: transform 0.3s ease;
}
.status-bar--show {
.sn-status-bar--show {
transform: translateY(0);
}
.status-bar--success {
.sn-status-bar--success {
background: var(--text-success);
color: white;
}
.status-bar--error {
.sn-status-bar--error {
background: var(--text-error);
color: white;
}
.status-bar--warning {
.sn-status-bar--warning {
background: var(--text-warning);
color: white;
}
.status-bar--info {
.sn-status-bar--info {
background: var(--interactive-accent);
color: var(--text-on-accent);
}
/* 원형 진행률 */
.circular-progress {
.sn-circular-progress {
display: inline-block;
position: relative;
}
.semi-circular-progress {
.sn-semi-circular-progress {
display: inline-block;
position: relative;
}
/* 로딩 인디케이터 */
.loading-spinner {
.sn-loading-spinner {
display: inline-flex;
align-items: center;
gap: 12px;
}
.loading-spinner svg {
animation: spin 1s linear infinite;
.sn-loading-spinner svg {
animation: sn-notification-spin 1s linear infinite;
}
@keyframes spin {
@keyframes sn-notification-spin {
from {
transform: rotate(0deg);
}
@ -421,54 +421,54 @@
}
}
.loading-spinner--small svg {
.sn-loading-spinner--small svg {
width: 16px;
height: 16px;
}
.loading-spinner--medium svg {
.sn-loading-spinner--medium svg {
width: 24px;
height: 24px;
}
.loading-spinner--large svg {
.sn-loading-spinner--large svg {
width: 32px;
height: 32px;
}
.loading-message {
.sn-loading-message {
color: var(--text-muted);
font-size: 14px;
}
/* 펄스 로더 */
.loading-pulse {
.sn-loading-pulse {
display: inline-flex;
align-items: center;
gap: 8px;
}
.pulse-dot {
.sn-pulse-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--interactive-accent);
animation: pulse 1.5s ease-in-out infinite;
animation: sn-notification-pulse 1.5s ease-in-out infinite;
}
.pulse-dot--1 {
.sn-pulse-dot--1 {
animation-delay: 0s;
}
.pulse-dot--2 {
.sn-pulse-dot--2 {
animation-delay: 0.15s;
}
.pulse-dot--3 {
.sn-pulse-dot--3 {
animation-delay: 0.3s;
}
@keyframes pulse {
@keyframes sn-notification-pulse {
0%,
100% {
opacity: 0.3;
@ -480,34 +480,34 @@
}
}
.loading-pulse--small .pulse-dot {
.sn-loading-pulse--small .sn-pulse-dot {
width: 6px;
height: 6px;
}
.loading-pulse--large .pulse-dot {
.sn-loading-pulse--large .sn-pulse-dot {
width: 10px;
height: 10px;
}
/* 스켈레톤 로더 */
.loading-skeleton {
.sn-loading-skeleton {
width: 100%;
}
.skeleton-line {
.sn-skeleton-line {
width: 100%;
height: 20px;
background: var(--background-modifier-border);
border-radius: 4px;
}
.skeleton-animated {
.sn-skeleton-animated {
position: relative;
overflow: hidden;
}
.skeleton-animated::after {
.sn-skeleton-animated::after {
content: '';
position: absolute;
top: 0;
@ -515,76 +515,76 @@
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
animation: shimmer 2s infinite;
animation: sn-notification-shimmer 2s infinite;
}
.loading-skeleton .skeleton-line:last-child {
.sn-loading-skeleton .sn-skeleton-line:last-child {
width: 70%;
}
.loading-skeleton--height-sm .skeleton-line {
.sn-loading-skeleton--height-sm .sn-skeleton-line {
height: 14px;
}
.loading-skeleton--height-md .skeleton-line {
.sn-loading-skeleton--height-md .sn-skeleton-line {
height: 20px;
}
.loading-skeleton--height-lg .skeleton-line {
.sn-loading-skeleton--height-lg .sn-skeleton-line {
height: 28px;
}
.loading-skeleton--spacing-none .skeleton-line:not(:last-child) {
.sn-loading-skeleton--spacing-none .sn-skeleton-line:not(:last-child) {
margin-bottom: 0;
}
.loading-skeleton--spacing-sm .skeleton-line:not(:last-child) {
.sn-loading-skeleton--spacing-sm .sn-skeleton-line:not(:last-child) {
margin-bottom: 6px;
}
.loading-skeleton--spacing-md .skeleton-line:not(:last-child) {
.sn-loading-skeleton--spacing-md .sn-skeleton-line:not(:last-child) {
margin-bottom: 10px;
}
.loading-skeleton--spacing-lg .skeleton-line:not(:last-child) {
.sn-loading-skeleton--spacing-lg .sn-skeleton-line:not(:last-child) {
margin-bottom: 16px;
}
@keyframes shimmer {
@keyframes sn-notification-shimmer {
to {
left: 100%;
}
}
/* 도트 로더 */
.loading-dots {
.sn-loading-dots {
display: inline-flex;
align-items: center;
gap: 12px;
}
.dots-container {
.sn-dots-container {
display: flex;
gap: 4px;
}
.dot {
.sn-dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--text-muted);
animation: dot-bounce 1.4s ease-in-out infinite;
animation: sn-notification-dot-bounce 1.4s ease-in-out infinite;
}
.dot:nth-child(1) {
.sn-dot:nth-child(1) {
animation-delay: -0.32s;
}
.dot:nth-child(2) {
.sn-dot:nth-child(2) {
animation-delay: -0.16s;
}
@keyframes dot-bounce {
@keyframes sn-notification-dot-bounce {
0%,
60%,
100% {
@ -596,45 +596,45 @@
}
/* 상태 아이콘 */
.status-icon {
.sn-status-icon {
display: inline-flex;
align-items: center;
gap: 8px;
}
.status-icon__icon {
.sn-status-icon__icon {
width: 20px;
height: 20px;
}
.status-icon__icon svg {
.sn-status-icon__icon svg {
width: 100%;
height: 100%;
}
.status-icon--success .status-icon__icon {
.sn-status-icon--success .sn-status-icon__icon {
color: var(--text-success);
}
.status-icon--error .status-icon__icon {
.sn-status-icon--error .sn-status-icon__icon {
color: var(--text-error);
}
.status-icon--warning .status-icon__icon {
.sn-status-icon--warning .sn-status-icon__icon {
color: var(--text-warning);
}
.status-icon--info .status-icon__icon {
.sn-status-icon--info .sn-status-icon__icon {
color: var(--interactive-accent);
}
.status-icon__message {
.sn-status-icon__message {
color: var(--text-normal);
font-size: 14px;
}
/* 스크린 리더 전용 */
.sr-only {
.sn-sr-only {
position: absolute;
width: 1px;
height: 1px;

File diff suppressed because it is too large Load diff

1220
styles.css

File diff suppressed because it is too large Load diff