@designbasekorea/theme 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/theme.css +388 -0
- package/dist/css/theme.css.map +1 -0
- package/dist/css/tokens/tokens.css +385 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +176 -0
- package/dist/index.js +187 -0
- package/dist/index.umd.js +193 -0
- package/dist/stories/ThemeTokens.stories.d.ts +6 -0
- package/dist/stories/ThemeTokens.stories.d.ts.map +1 -0
- package/package.json +48 -0
- package/scss/_reset.scss +245 -0
- package/scss/_semantic.scss +629 -0
- package/scss/_utilities.scss +919 -0
- package/scss/_variables.scss +330 -0
- package/scss/index.scss +12 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Designbase Theme 메인 엔트리 포인트
|
|
5
|
+
*
|
|
6
|
+
* 목적: 테마 관련 TypeScript 유틸리티와 타입 정의 제공
|
|
7
|
+
* 기능: CSS 변수 헬퍼, 테마 전환, 유틸리티 함수
|
|
8
|
+
* 사용법: import { setTheme, getTheme } from '@designbase/theme'
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 현재 활성 테마 가져오기
|
|
12
|
+
*/
|
|
13
|
+
function getTheme() {
|
|
14
|
+
if (typeof document === 'undefined') {
|
|
15
|
+
return 'light';
|
|
16
|
+
}
|
|
17
|
+
const element = document.documentElement;
|
|
18
|
+
const themeAttr = element.getAttribute('data-theme');
|
|
19
|
+
if (themeAttr === 'dark') {
|
|
20
|
+
return themeAttr;
|
|
21
|
+
}
|
|
22
|
+
return 'light';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 테마 설정
|
|
26
|
+
*/
|
|
27
|
+
function setTheme(theme) {
|
|
28
|
+
if (typeof document === 'undefined') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
32
|
+
// 로컬 스토리지에 저장
|
|
33
|
+
try {
|
|
34
|
+
localStorage.setItem('designbase-theme', theme);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
// 로컬 스토리지를 사용할 수 없는 환경
|
|
38
|
+
console.warn('Cannot save theme preference to localStorage');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 저장된 테마 불러오기
|
|
43
|
+
*/
|
|
44
|
+
function loadSavedTheme() {
|
|
45
|
+
if (typeof document === 'undefined') {
|
|
46
|
+
return 'light';
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const savedTheme = localStorage.getItem('designbase-theme');
|
|
50
|
+
if (savedTheme && ['light', 'dark'].includes(savedTheme)) {
|
|
51
|
+
return savedTheme;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// 로컬 스토리지를 사용할 수 없는 환경
|
|
56
|
+
}
|
|
57
|
+
// 시스템 다크 모드 확인
|
|
58
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
59
|
+
return 'dark';
|
|
60
|
+
}
|
|
61
|
+
return 'light';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 테마 자동 초기화
|
|
65
|
+
*/
|
|
66
|
+
function initializeTheme() {
|
|
67
|
+
const savedTheme = loadSavedTheme();
|
|
68
|
+
setTheme(savedTheme);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* CSS 변수 값 가져오기
|
|
72
|
+
*/
|
|
73
|
+
function getCSSVariable(variableName) {
|
|
74
|
+
if (typeof document === 'undefined') {
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
const computedStyle = getComputedStyle(document.documentElement);
|
|
78
|
+
return computedStyle.getPropertyValue(`--${variableName}`).trim();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* CSS 변수 설정
|
|
82
|
+
*/
|
|
83
|
+
function setCSSVariable(variableName, value) {
|
|
84
|
+
if (typeof document === 'undefined') {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
document.documentElement.style.setProperty(`--${variableName}`, value);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* 테마 토글
|
|
91
|
+
*/
|
|
92
|
+
function toggleTheme() {
|
|
93
|
+
const currentTheme = getTheme();
|
|
94
|
+
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
95
|
+
setTheme(newTheme);
|
|
96
|
+
return newTheme;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* 테마 변경 감지 훅 (React 등에서 사용)
|
|
100
|
+
*/
|
|
101
|
+
function createThemeWatcher(callback) {
|
|
102
|
+
if (typeof document === 'undefined') {
|
|
103
|
+
return () => { };
|
|
104
|
+
}
|
|
105
|
+
const observer = new MutationObserver((mutations) => {
|
|
106
|
+
mutations.forEach((mutation) => {
|
|
107
|
+
if (mutation.type === 'attributes' &&
|
|
108
|
+
mutation.attributeName === 'data-theme') {
|
|
109
|
+
const newTheme = getTheme();
|
|
110
|
+
callback(newTheme);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
observer.observe(document.documentElement, {
|
|
115
|
+
attributes: true,
|
|
116
|
+
attributeFilter: ['data-theme'],
|
|
117
|
+
});
|
|
118
|
+
// cleanup 함수 반환
|
|
119
|
+
return () => observer.disconnect();
|
|
120
|
+
}
|
|
121
|
+
// 브라우저 환경에서 자동 초기화
|
|
122
|
+
if (typeof document !== 'undefined') {
|
|
123
|
+
// DOM이 로드된 후 실행
|
|
124
|
+
if (document.readyState === 'loading') {
|
|
125
|
+
document.addEventListener('DOMContentLoaded', initializeTheme);
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
initializeTheme();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 토큰 CSS 파일들을 동적으로 로드
|
|
133
|
+
* @param basePath 토큰 CSS 파일의 기본 경로 (기본값: @designbase/tokens/dist/css)
|
|
134
|
+
*/
|
|
135
|
+
function loadTokens(basePath = '@designbase/tokens/dist/css') {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
if (typeof document === 'undefined') {
|
|
138
|
+
resolve();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// 1) 테마 CSS가 이미 토큰을 병합 포함하는지 빠른 확인
|
|
142
|
+
const themeLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
|
|
143
|
+
.find((l) => typeof l.getAttribute === 'function' && /theme\.css$/.test(l.getAttribute('href') || ''));
|
|
144
|
+
if (themeLink) {
|
|
145
|
+
// theme.css 상단에 토큰이 병합되어 배포되는 경우 추가 로드 불필요
|
|
146
|
+
resolve();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
// 2) 병합되어 있지 않다면 단일 tokens.css 만 로드 (dark는 data-theme="dark"로 오버라이드됨)
|
|
150
|
+
const href = `${basePath}/tokens.css`;
|
|
151
|
+
const existingLink = document.querySelector(`link[href="${href}"]`);
|
|
152
|
+
if (existingLink) {
|
|
153
|
+
resolve();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const link = document.createElement('link');
|
|
157
|
+
link.rel = 'stylesheet';
|
|
158
|
+
link.href = href;
|
|
159
|
+
link.onload = () => resolve();
|
|
160
|
+
link.onerror = () => {
|
|
161
|
+
console.warn(`Failed to load token CSS: ${href}`);
|
|
162
|
+
resolve();
|
|
163
|
+
};
|
|
164
|
+
document.head.appendChild(link);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* 토큰 CSS가 로드되었는지 확인
|
|
169
|
+
*/
|
|
170
|
+
function areTokensLoaded() {
|
|
171
|
+
if (typeof document === 'undefined') {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
const tokenLinks = document.querySelectorAll('link[href*="tokens"]');
|
|
175
|
+
return tokenLinks.length > 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
exports.areTokensLoaded = areTokensLoaded;
|
|
179
|
+
exports.createThemeWatcher = createThemeWatcher;
|
|
180
|
+
exports.getCSSVariable = getCSSVariable;
|
|
181
|
+
exports.getTheme = getTheme;
|
|
182
|
+
exports.initializeTheme = initializeTheme;
|
|
183
|
+
exports.loadSavedTheme = loadSavedTheme;
|
|
184
|
+
exports.loadTokens = loadTokens;
|
|
185
|
+
exports.setCSSVariable = setCSSVariable;
|
|
186
|
+
exports.setTheme = setTheme;
|
|
187
|
+
exports.toggleTheme = toggleTheme;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.DesignbaseTheme = {}));
|
|
5
|
+
})(this, (function (exports) { 'use strict';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Designbase Theme 메인 엔트리 포인트
|
|
9
|
+
*
|
|
10
|
+
* 목적: 테마 관련 TypeScript 유틸리티와 타입 정의 제공
|
|
11
|
+
* 기능: CSS 변수 헬퍼, 테마 전환, 유틸리티 함수
|
|
12
|
+
* 사용법: import { setTheme, getTheme } from '@designbase/theme'
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 현재 활성 테마 가져오기
|
|
16
|
+
*/
|
|
17
|
+
function getTheme() {
|
|
18
|
+
if (typeof document === 'undefined') {
|
|
19
|
+
return 'light';
|
|
20
|
+
}
|
|
21
|
+
const element = document.documentElement;
|
|
22
|
+
const themeAttr = element.getAttribute('data-theme');
|
|
23
|
+
if (themeAttr === 'dark') {
|
|
24
|
+
return themeAttr;
|
|
25
|
+
}
|
|
26
|
+
return 'light';
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 테마 설정
|
|
30
|
+
*/
|
|
31
|
+
function setTheme(theme) {
|
|
32
|
+
if (typeof document === 'undefined') {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
36
|
+
// 로컬 스토리지에 저장
|
|
37
|
+
try {
|
|
38
|
+
localStorage.setItem('designbase-theme', theme);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// 로컬 스토리지를 사용할 수 없는 환경
|
|
42
|
+
console.warn('Cannot save theme preference to localStorage');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 저장된 테마 불러오기
|
|
47
|
+
*/
|
|
48
|
+
function loadSavedTheme() {
|
|
49
|
+
if (typeof document === 'undefined') {
|
|
50
|
+
return 'light';
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const savedTheme = localStorage.getItem('designbase-theme');
|
|
54
|
+
if (savedTheme && ['light', 'dark'].includes(savedTheme)) {
|
|
55
|
+
return savedTheme;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
// 로컬 스토리지를 사용할 수 없는 환경
|
|
60
|
+
}
|
|
61
|
+
// 시스템 다크 모드 확인
|
|
62
|
+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
63
|
+
return 'dark';
|
|
64
|
+
}
|
|
65
|
+
return 'light';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 테마 자동 초기화
|
|
69
|
+
*/
|
|
70
|
+
function initializeTheme() {
|
|
71
|
+
const savedTheme = loadSavedTheme();
|
|
72
|
+
setTheme(savedTheme);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* CSS 변수 값 가져오기
|
|
76
|
+
*/
|
|
77
|
+
function getCSSVariable(variableName) {
|
|
78
|
+
if (typeof document === 'undefined') {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
const computedStyle = getComputedStyle(document.documentElement);
|
|
82
|
+
return computedStyle.getPropertyValue(`--${variableName}`).trim();
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* CSS 변수 설정
|
|
86
|
+
*/
|
|
87
|
+
function setCSSVariable(variableName, value) {
|
|
88
|
+
if (typeof document === 'undefined') {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
document.documentElement.style.setProperty(`--${variableName}`, value);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 테마 토글
|
|
95
|
+
*/
|
|
96
|
+
function toggleTheme() {
|
|
97
|
+
const currentTheme = getTheme();
|
|
98
|
+
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
|
|
99
|
+
setTheme(newTheme);
|
|
100
|
+
return newTheme;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 테마 변경 감지 훅 (React 등에서 사용)
|
|
104
|
+
*/
|
|
105
|
+
function createThemeWatcher(callback) {
|
|
106
|
+
if (typeof document === 'undefined') {
|
|
107
|
+
return () => { };
|
|
108
|
+
}
|
|
109
|
+
const observer = new MutationObserver((mutations) => {
|
|
110
|
+
mutations.forEach((mutation) => {
|
|
111
|
+
if (mutation.type === 'attributes' &&
|
|
112
|
+
mutation.attributeName === 'data-theme') {
|
|
113
|
+
const newTheme = getTheme();
|
|
114
|
+
callback(newTheme);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
observer.observe(document.documentElement, {
|
|
119
|
+
attributes: true,
|
|
120
|
+
attributeFilter: ['data-theme'],
|
|
121
|
+
});
|
|
122
|
+
// cleanup 함수 반환
|
|
123
|
+
return () => observer.disconnect();
|
|
124
|
+
}
|
|
125
|
+
// 브라우저 환경에서 자동 초기화
|
|
126
|
+
if (typeof document !== 'undefined') {
|
|
127
|
+
// DOM이 로드된 후 실행
|
|
128
|
+
if (document.readyState === 'loading') {
|
|
129
|
+
document.addEventListener('DOMContentLoaded', initializeTheme);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
initializeTheme();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 토큰 CSS 파일들을 동적으로 로드
|
|
137
|
+
* @param basePath 토큰 CSS 파일의 기본 경로 (기본값: @designbase/tokens/dist/css)
|
|
138
|
+
*/
|
|
139
|
+
function loadTokens(basePath = '@designbase/tokens/dist/css') {
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
if (typeof document === 'undefined') {
|
|
142
|
+
resolve();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// 1) 테마 CSS가 이미 토큰을 병합 포함하는지 빠른 확인
|
|
146
|
+
const themeLink = Array.from(document.querySelectorAll('link[rel="stylesheet"]'))
|
|
147
|
+
.find((l) => typeof l.getAttribute === 'function' && /theme\.css$/.test(l.getAttribute('href') || ''));
|
|
148
|
+
if (themeLink) {
|
|
149
|
+
// theme.css 상단에 토큰이 병합되어 배포되는 경우 추가 로드 불필요
|
|
150
|
+
resolve();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// 2) 병합되어 있지 않다면 단일 tokens.css 만 로드 (dark는 data-theme="dark"로 오버라이드됨)
|
|
154
|
+
const href = `${basePath}/tokens.css`;
|
|
155
|
+
const existingLink = document.querySelector(`link[href="${href}"]`);
|
|
156
|
+
if (existingLink) {
|
|
157
|
+
resolve();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const link = document.createElement('link');
|
|
161
|
+
link.rel = 'stylesheet';
|
|
162
|
+
link.href = href;
|
|
163
|
+
link.onload = () => resolve();
|
|
164
|
+
link.onerror = () => {
|
|
165
|
+
console.warn(`Failed to load token CSS: ${href}`);
|
|
166
|
+
resolve();
|
|
167
|
+
};
|
|
168
|
+
document.head.appendChild(link);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 토큰 CSS가 로드되었는지 확인
|
|
173
|
+
*/
|
|
174
|
+
function areTokensLoaded() {
|
|
175
|
+
if (typeof document === 'undefined') {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
const tokenLinks = document.querySelectorAll('link[href*="tokens"]');
|
|
179
|
+
return tokenLinks.length > 0;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
exports.areTokensLoaded = areTokensLoaded;
|
|
183
|
+
exports.createThemeWatcher = createThemeWatcher;
|
|
184
|
+
exports.getCSSVariable = getCSSVariable;
|
|
185
|
+
exports.getTheme = getTheme;
|
|
186
|
+
exports.initializeTheme = initializeTheme;
|
|
187
|
+
exports.loadSavedTheme = loadSavedTheme;
|
|
188
|
+
exports.loadTokens = loadTokens;
|
|
189
|
+
exports.setCSSVariable = setCSSVariable;
|
|
190
|
+
exports.setTheme = setTheme;
|
|
191
|
+
exports.toggleTheme = toggleTheme;
|
|
192
|
+
|
|
193
|
+
}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThemeTokens.stories.d.ts","sourceRoot":"","sources":["../../src/stories/ThemeTokens.stories.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,QAAA,MAAM,IAAI,EAAE,IAEX,CAAC;AAEF,eAAe,IAAI,CAAC;AAoCpB,eAAO,MAAM,OAAO,EAAE,QAkDrB,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,QAsBpB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@designbasekorea/theme",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Designbase 테마 시스템 - CSS 변수, 테마 프리셋, 유틸리티 클래스를 제공",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"style": "dist/css/theme.css",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.esm.js",
|
|
13
|
+
"require": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.esm.js"
|
|
15
|
+
},
|
|
16
|
+
"./css": "./dist/css/theme.css"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"scss"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "npm run build:css && node scripts/merge-tokens-into-theme.js && rollup -c",
|
|
24
|
+
"build:css": "sass scss/index.scss dist/css/theme.css --style=compressed",
|
|
25
|
+
"dev": "npm run build:css -- --watch",
|
|
26
|
+
"clean": "rimraf dist",
|
|
27
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
28
|
+
"test": "jest"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@rollup/plugin-commonjs": "^25.0.4",
|
|
33
|
+
"@rollup/plugin-node-resolve": "^15.2.1",
|
|
34
|
+
"@rollup/plugin-typescript": "^11.1.3",
|
|
35
|
+
"rimraf": "^5.0.1",
|
|
36
|
+
"rollup": "^3.29.2",
|
|
37
|
+
"sass": "^1.68.0",
|
|
38
|
+
"typescript": "^5.2.2"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "https://github.com/designbasekorea/Designbase-Library.git",
|
|
46
|
+
"directory": "packages/theme"
|
|
47
|
+
}
|
|
48
|
+
}
|
package/scss/_reset.scss
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSS Reset & Normalize
|
|
3
|
+
*
|
|
4
|
+
* 목적: 브라우저별 기본 스타일 차이를 제거하고 일관된 기준점 제공
|
|
5
|
+
* 기능: Modern CSS reset + Figma 플러그인 최적화
|
|
6
|
+
* 참고: Josh Comeau's CSS Reset + 피그마 플러그인 특화 스타일
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
1. Use a more-intuitive box-sizing model.
|
|
11
|
+
*/
|
|
12
|
+
*,
|
|
13
|
+
*::before,
|
|
14
|
+
*::after {
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
2. Remove default margin
|
|
20
|
+
*/
|
|
21
|
+
* {
|
|
22
|
+
margin: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
3. Allow percentage-based heights in the application
|
|
27
|
+
*/
|
|
28
|
+
html,
|
|
29
|
+
body {
|
|
30
|
+
height: 100%;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
Typographic tweaks!
|
|
35
|
+
4. Add accessible line-height
|
|
36
|
+
5. Improve text rendering
|
|
37
|
+
*/
|
|
38
|
+
body {
|
|
39
|
+
line-height: 1.5;
|
|
40
|
+
-webkit-font-smoothing: antialiased;
|
|
41
|
+
-moz-osx-font-smoothing: grayscale;
|
|
42
|
+
text-rendering: optimizeLegibility;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
6. Improve media defaults
|
|
47
|
+
*/
|
|
48
|
+
img,
|
|
49
|
+
picture,
|
|
50
|
+
video,
|
|
51
|
+
canvas,
|
|
52
|
+
svg {
|
|
53
|
+
display: block;
|
|
54
|
+
max-width: 100%;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/*
|
|
58
|
+
7. Remove built-in form typography styles
|
|
59
|
+
*/
|
|
60
|
+
input,
|
|
61
|
+
button,
|
|
62
|
+
textarea,
|
|
63
|
+
select {
|
|
64
|
+
font: inherit;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/*
|
|
68
|
+
8. Avoid text overflows
|
|
69
|
+
*/
|
|
70
|
+
p,
|
|
71
|
+
h1,
|
|
72
|
+
h2,
|
|
73
|
+
h3,
|
|
74
|
+
h4,
|
|
75
|
+
h5,
|
|
76
|
+
h6 {
|
|
77
|
+
overflow-wrap: break-word;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
9. Create a root stacking context
|
|
82
|
+
*/
|
|
83
|
+
#root,
|
|
84
|
+
#__next {
|
|
85
|
+
isolation: isolate;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/*
|
|
89
|
+
10. Figma Plugin specific resets
|
|
90
|
+
*/
|
|
91
|
+
body {
|
|
92
|
+
font-family: var(--font-family-primary, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
|
|
93
|
+
font-size: var(--font-size-sm, 14px);
|
|
94
|
+
color: var(--color-text-primary, #111827);
|
|
95
|
+
background-color: var(--color-bg-primary, #ffffff);
|
|
96
|
+
|
|
97
|
+
/* Prevent text selection in UI elements (can be overridden) */
|
|
98
|
+
-webkit-user-select: none;
|
|
99
|
+
-moz-user-select: none;
|
|
100
|
+
user-select: none;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/*
|
|
104
|
+
11. Button resets
|
|
105
|
+
*/
|
|
106
|
+
button {
|
|
107
|
+
background: none;
|
|
108
|
+
border: none;
|
|
109
|
+
padding: 0;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
outline: none;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
12. List resets
|
|
116
|
+
*/
|
|
117
|
+
ul,
|
|
118
|
+
ol {
|
|
119
|
+
list-style: none;
|
|
120
|
+
padding: 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
13. Link resets
|
|
125
|
+
*/
|
|
126
|
+
a {
|
|
127
|
+
color: inherit;
|
|
128
|
+
text-decoration: none;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/*
|
|
132
|
+
14. Form element resets
|
|
133
|
+
*/
|
|
134
|
+
input,
|
|
135
|
+
textarea,
|
|
136
|
+
select {
|
|
137
|
+
background: transparent;
|
|
138
|
+
border: none;
|
|
139
|
+
outline: none;
|
|
140
|
+
appearance: none;
|
|
141
|
+
|
|
142
|
+
&::placeholder {
|
|
143
|
+
color: var(--color-text-muted, #6b7280);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/*
|
|
148
|
+
15. Table resets
|
|
149
|
+
*/
|
|
150
|
+
table {
|
|
151
|
+
border-collapse: collapse;
|
|
152
|
+
border-spacing: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/*
|
|
156
|
+
16. Accessibility: Respect user's motion preferences
|
|
157
|
+
*/
|
|
158
|
+
@media (prefers-reduced-motion: reduce) {
|
|
159
|
+
|
|
160
|
+
*,
|
|
161
|
+
*::before,
|
|
162
|
+
*::after {
|
|
163
|
+
animation-duration: 0.01ms !important;
|
|
164
|
+
animation-iteration-count: 1 !important;
|
|
165
|
+
transition-duration: 0.01ms !important;
|
|
166
|
+
scroll-behavior: auto !important;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/*
|
|
171
|
+
17. Focus management for accessibility
|
|
172
|
+
*/
|
|
173
|
+
*:focus {
|
|
174
|
+
outline: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
*:focus-visible {
|
|
178
|
+
outline: 2px solid var(--color-border-focus, #3b82f6);
|
|
179
|
+
outline-offset: 2px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/*
|
|
183
|
+
18. Scrollbar styling (Webkit)
|
|
184
|
+
*/
|
|
185
|
+
::-webkit-scrollbar {
|
|
186
|
+
width: 8px;
|
|
187
|
+
height: 8px;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
::-webkit-scrollbar-track {
|
|
191
|
+
background: var(--color-bg-secondary, #f3f4f6);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
::-webkit-scrollbar-thumb {
|
|
195
|
+
background: var(--color-border-secondary, #d1d5db);
|
|
196
|
+
border-radius: var(--border-radius-base, 4px);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
::-webkit-scrollbar-thumb:hover {
|
|
200
|
+
background: var(--color-border-primary, #e5e7eb);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/*
|
|
204
|
+
19. Selection styling
|
|
205
|
+
*/
|
|
206
|
+
::selection {
|
|
207
|
+
background-color: var(--color-text-link, #2563eb);
|
|
208
|
+
color: var(--color-text-inverse, #ffffff);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/*
|
|
212
|
+
20. Print styles
|
|
213
|
+
*/
|
|
214
|
+
@media print {
|
|
215
|
+
|
|
216
|
+
*,
|
|
217
|
+
*::before,
|
|
218
|
+
*::after {
|
|
219
|
+
background: transparent !important;
|
|
220
|
+
color: black !important;
|
|
221
|
+
box-shadow: none !important;
|
|
222
|
+
text-shadow: none !important;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
a,
|
|
226
|
+
a:visited {
|
|
227
|
+
text-decoration: underline;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
img {
|
|
231
|
+
page-break-inside: avoid;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
p,
|
|
235
|
+
h2,
|
|
236
|
+
h3 {
|
|
237
|
+
orphans: 3;
|
|
238
|
+
widows: 3;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
h2,
|
|
242
|
+
h3 {
|
|
243
|
+
page-break-after: avoid;
|
|
244
|
+
}
|
|
245
|
+
}
|