@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/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,6 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ declare const meta: Meta;
3
+ export default meta;
4
+ export declare const Spacing: StoryObj;
5
+ export declare const Radius: StoryObj;
6
+ //# sourceMappingURL=ThemeTokens.stories.d.ts.map
@@ -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
+ }
@@ -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
+ }