atomsystem 0.1.48 → 0.1.50

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.
Files changed (69) hide show
  1. package/README.md +147 -55
  2. package/atom.design.md +155 -0
  3. package/atom.uxPattern.md +431 -0
  4. package/components/ui/Badge.tsx +20 -0
  5. package/components/ui/Button.tsx +33 -0
  6. package/components/ui/Card.tsx +20 -0
  7. package/components/ui/Input.tsx +35 -0
  8. package/components/ui/Modal.tsx +54 -0
  9. package/components/ui/index.ts +5 -0
  10. package/css/atom-ui.css +216 -0
  11. package/dist/cli.cjs +105 -0
  12. package/dist/cli.cjs.map +1 -0
  13. package/dist/cli.d.cts +1 -0
  14. package/dist/cli.d.ts +1 -0
  15. package/dist/cli.js +97 -0
  16. package/dist/cli.js.map +1 -0
  17. package/dist/index.cjs +7897 -0
  18. package/dist/index.cjs.map +1 -0
  19. package/dist/index.d.cts +218 -0
  20. package/dist/index.d.ts +218 -0
  21. package/dist/index.js +7845 -451
  22. package/dist/index.js.map +1 -0
  23. package/package.json +120 -81
  24. package/presets/default.design.md +155 -0
  25. package/presets/uxPattern.md +431 -0
  26. package/skills/atomsystem.skill.md +104 -0
  27. package/tokens/token.json +319 -0
  28. package/tokens/tokens.json +318 -976
  29. package/app/templates/Animations/Animation.tsx +0 -282
  30. package/app/templates/Badge/Badge.tsx +0 -28
  31. package/app/templates/Badge/badge.css +0 -28
  32. package/app/templates/Button/Button.tsx +0 -31
  33. package/app/templates/Button/button.css +0 -135
  34. package/app/templates/Card/Card.tsx +0 -50
  35. package/app/templates/Card/card.css +0 -17
  36. package/app/templates/Checkbox/Checkbox.tsx +0 -55
  37. package/app/templates/Checkbox/checkbox.css +0 -38
  38. package/app/templates/CodeBlock/CodeBlock.tsx +0 -68
  39. package/app/templates/Collapse/Collapse.tsx +0 -207
  40. package/app/templates/Collapse/collapse.css +0 -24
  41. package/app/templates/Dropdown/Dropdown.css +0 -112
  42. package/app/templates/Dropdown/Dropdown.tsx +0 -103
  43. package/app/templates/Input/Input.tsx +0 -29
  44. package/app/templates/Input/input.css +0 -24
  45. package/app/templates/Label/Label.tsx +0 -22
  46. package/app/templates/Modal/Modal.tsx +0 -71
  47. package/app/templates/Modal/modal.css +0 -98
  48. package/app/templates/Navigation/Navigation.tsx +0 -169
  49. package/app/templates/Navigation/navigation.css +0 -59
  50. package/app/templates/Pagination/Pagination.tsx +0 -104
  51. package/app/templates/Pagination/pagination.css +0 -66
  52. package/app/templates/Radio/Radio.tsx +0 -45
  53. package/app/templates/Radio/radio.css +0 -29
  54. package/app/templates/Select/Select.tsx +0 -143
  55. package/app/templates/Select/select.css +0 -47
  56. package/app/templates/Sidebar/Sidebar.tsx +0 -139
  57. package/app/templates/Sidebar/sidebar.css +0 -4
  58. package/app/templates/TOC/TOC.tsx +0 -165
  59. package/app/templates/Tab/Tab.tsx +0 -117
  60. package/app/templates/Tab/tab.css +0 -56
  61. package/app/templates/Table/Table.tsx +0 -54
  62. package/app/templates/Table/table.css +0 -58
  63. package/app/templates/Theme/Theme.tsx +0 -122
  64. package/app/templates/Toast/Toast.tsx +0 -106
  65. package/app/templates/Toast/toast.css +0 -69
  66. package/build/build-tokens.js +0 -223
  67. package/build/css/_variables.css +0 -251
  68. package/build/typescript/theme.ts +0 -6
  69. package/css/text.css +0 -18
@@ -1,54 +0,0 @@
1
- /** 테이블에 표시할 데이터 배열. 각 객체는 하나의 행을 나타냅니다.
2
- * 객체의 키는 테이블 헤더가 되고, 값은 해당 셀의 내용이 됩니다.
3
- */
4
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
- const Table = ({ tableData }: { tableData: Record<string, any>[] }) => {
6
- // tableData가 제공되지 않았거나 비어있는 경우, 데이터 없음 메시지를 표시합니다.
7
- if (!tableData || tableData.length === 0) {
8
- return (
9
- <div className="table-empty">
10
- <p>표시할 데이터가 없습니다.</p>
11
- </div>
12
- );
13
- }
14
-
15
- const headers = Object.keys(tableData[0]);
16
-
17
- return (
18
- <div className="table-container">
19
- <table className="table">
20
- <thead className="table-header">
21
- <tr>
22
- {/* 헤더 배열을 순회하며 각 헤더에 대한 <th>태그를 생성합니다. */}
23
- {headers.map((header, index) => {
24
- return (
25
- <th key={index}>
26
- {header}
27
- </th>
28
- );
29
- })}
30
- </tr>
31
- </thead>
32
- {/* 테이블 바디 부분 */}
33
- <tbody className="table-body">
34
- {/* tableData 배열을 순회하며 각 데이터 객체(행)에 대한 <tr> 태그를 생성합니다. */}
35
- {tableData.map((row, rowIndex) => {
36
- return (
37
- <tr key={rowIndex} className="table-row">
38
- {/* 각 행 내에서 헤더 배열을 다시 순회하며 해당 헤더에 맞는 셀(<td>)을 생성합니다. */}
39
- {headers.map((header, colIndex) => (
40
- <td key={colIndex} className="table-cell">
41
- {/* 현재 행 객체(row)에서 해당 헤더(키)에 해당하는 값을 가져와 셀에 표시합니다. */}
42
- {row[header]}
43
- </td>
44
- ))}
45
- </tr>
46
- );
47
- })}
48
- </tbody>
49
- </table>
50
- </div>
51
- );
52
- };
53
-
54
- export default Table;
@@ -1,58 +0,0 @@
1
- .table-container {
2
- overflow: auto;
3
- width: 100%;
4
- }
5
-
6
- .table {
7
- min-width: 100%;
8
- border-collapse: collapse;
9
- border-radius: var(--radius-common);
10
- border: 1px solid var(--color-table-header-border);
11
- overflow: hidden;
12
- }
13
-
14
- .table-header {
15
- background-color: var(--color-table-header-bg);
16
- }
17
-
18
- .table-header th {
19
- padding: var(--spacing-common-y) var(--spacing-common-x);
20
- text-align: center;
21
- color: var(--color-table-header-text);
22
- font-weight: var(--font-weight-header);
23
- font-size: var(--text-body);
24
- letter-spacing: 0.05em;
25
- border-bottom: 1px solid var(--color-table-header-border);
26
- }
27
-
28
- .table-body {
29
- /* Body styles if needed */
30
- }
31
-
32
- .table-row {
33
- background-color: var(--color-table-body-bg);
34
- transition: background-color 0.2s ease;
35
- }
36
-
37
- .table-row:nth-child(even) {
38
- background-color: var(--color-table-body-bgEven);
39
- }
40
-
41
- .table-row:hover {
42
- filter: brightness(0.95);
43
- }
44
-
45
- .table-cell {
46
- padding: var(--spacing-sm-y) var(--spacing-common-x);
47
- color: var(--color-table-body-text);
48
- font-size: var(--text-body);
49
- border-bottom: 1px solid var(--color-table-body-border);
50
- text-align: left;
51
- }
52
-
53
- .table-empty {
54
- padding: var(--spacing-common-x);
55
- text-align: center;
56
- color: var(--color-text-standard);
57
- font-size: var(--text-body);
58
- }
@@ -1,122 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useState } from "react";
4
- import { Radio } from "../Radio/Radio";
5
-
6
- import { themes } from "@/build/typescript/theme";
7
- import { Button } from "../Button/Button";
8
- import { ModalContent } from "../Modal/Modal";
9
-
10
- export const ThemeSelector = () => {
11
- const [selectedThemes, setSelectedThemes] = useState<Record<string, string>>({});
12
- const [tempThemes, setTempThemes] = useState<Record<string, string>>({});
13
- const [isThemeLoading, setThemeLoading] = useState(false);
14
- const [isModalOpen, setIsModalOpen] = useState(false);
15
-
16
- useEffect(() => {
17
- const chageThemeValue = () => {
18
- const newThemes: Record<string, string> = {};
19
- Object.entries(themes).forEach(([group, groupThemes]) => {
20
- const storedKey = group === "global" ? "theme" : `theme-${group}`;
21
- const storedTheme = localStorage.getItem(storedKey) || groupThemes[0];
22
- newThemes[group] = storedTheme;
23
- });
24
- setSelectedThemes(newThemes);
25
- setTempThemes(newThemes);
26
- };
27
-
28
- chageThemeValue();
29
- setThemeLoading(true);
30
- }, []);
31
-
32
- const handleTempThemeChange = (group: string, theme: string) => {
33
- setTempThemes((prev) => ({ ...prev, [group]: theme }));
34
- };
35
-
36
- const handleApply = () => {
37
- if (typeof window !== "undefined") {
38
- Object.entries(tempThemes).forEach(([group, theme]) => {
39
- const storedKey = group === "global" ? "theme" : `theme-${group}`;
40
- localStorage.setItem(storedKey, theme);
41
- });
42
- window.dispatchEvent(new Event("storage"));
43
- setSelectedThemes(tempThemes);
44
- setIsModalOpen(false);
45
- }
46
- };
47
-
48
- const handleOpenModal = () => {
49
- setTempThemes(selectedThemes); // Reset to current applied themes
50
- setIsModalOpen(true);
51
- };
52
-
53
- if (isThemeLoading === false) {
54
- return null;
55
- }
56
-
57
- return (
58
- <>
59
- <Button type="primary" onClick={handleOpenModal} style={{ width: "100%" }}>
60
- <div className="flex items-center justify-center gap-2">
61
- <span className="material-symbols-outlined" style={{ fontSize: "18px" }}>palette</span>
62
- <span>테마 설정</span>
63
- </div>
64
- </Button>
65
- <ModalContent
66
- isOpen={isModalOpen}
67
- onRequestClose={() => setIsModalOpen(false)}
68
- modalTitle="테마 설정"
69
- modalFooter={
70
- <div className="flex justify-end gap-2 w-full mt-4">
71
- <Button type="gray" onClick={() => setIsModalOpen(false)}>취소</Button>
72
- <Button type="primary" onClick={handleApply}>적용</Button>
73
- </div>
74
- }
75
- >
76
- <div className="flex flex-col gap-6 p-4">
77
- {Object.entries(themes).map(([group, groupThemes]) => (
78
- <div key={group} className="flex flex-col gap-3">
79
- <p className="font-bold capitalize">{group} Theme</p>
80
- <div className="flex gap-4 flex-wrap">
81
- {groupThemes.map((theme) => (
82
- <Radio
83
- name={`theme-${group}-modal`}
84
- value={theme}
85
- key={theme}
86
- onChange={() => handleTempThemeChange(group, theme)}
87
- checked={theme === tempThemes[group]}
88
- />
89
- ))}
90
- </div>
91
- </div>
92
- ))}
93
- </div>
94
- </ModalContent>
95
- </>
96
- );
97
- };
98
-
99
- export const ThemeProvider = () => {
100
- const [isThemeLoading, setThemeLoading] = useState(false);
101
-
102
- useEffect(() => {
103
- const listenStorageChange = () => {
104
- Object.entries(themes).forEach(([group, groupThemes]) => {
105
- const storedKey = group === "global" ? "theme" : `theme-${group}`;
106
- const currentTheme = localStorage.getItem(storedKey) || groupThemes[0];
107
-
108
- document.documentElement.classList.remove(...groupThemes);
109
- document.documentElement.classList.add(currentTheme);
110
- });
111
- };
112
-
113
- listenStorageChange();
114
- window.addEventListener("storage", listenStorageChange);
115
- setThemeLoading(true);
116
- return () => window.removeEventListener("storage", listenStorageChange);
117
- }, []);
118
-
119
- if (isThemeLoading === false) {
120
- return null;
121
- }
122
- };
@@ -1,106 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useState, useRef } from "react";
4
- import gsap from "gsap";
5
- import { useGSAP } from "@gsap/react";
6
-
7
- gsap.registerPlugin(useGSAP);
8
-
9
- // ✅ Toast Message 타입 정의
10
- export type ToastMessage = {
11
- id: string;
12
- status?: "standard" | "primary" | "secondary" | "success" | "danger" | "warning" | "info" | "error";
13
- message: string;
14
- desc?: string;
15
- };
16
-
17
- // ✅ 전역 메시지 저장소와 구독 시스템
18
- let messages: ToastMessage[] = [];
19
- let listener: ((messages: ToastMessage[]) => void) | null = null;
20
-
21
- export const addToast = (msg: Omit<ToastMessage, "id">) => {
22
- const id = Math.random().toString(36).substring(2) + Date.now().toString(36);
23
- messages = [...messages, { id, ...msg }];
24
- // messages 값 listener에 반영
25
- listener?.(messages);
26
- };
27
-
28
- const removeToast = (id: string) => {
29
- messages = messages.filter((m) => m.id !== id);
30
- listener?.(messages);
31
- };
32
-
33
- // 콜백으로 message value 추적적
34
- const subscribeToast = (cb: (messages: ToastMessage[]) => void) => {
35
- listener = cb;
36
- cb(messages);
37
- };
38
-
39
- const unsubscribeToast = () => {
40
- listener = null;
41
- };
42
-
43
- // ✅ useToast 훅 제공
44
- export const useToast = () => {
45
- return addToast;
46
- };
47
-
48
- // ✅ ToastContainer 컴포넌트 (내부 ToastItem 포함)
49
- export const Toast = () => {
50
- const [messages, setMessages] = useState<ToastMessage[]>([]);
51
-
52
- useEffect(() => {
53
- subscribeToast(setMessages);
54
- return () => unsubscribeToast();
55
- }, []);
56
-
57
- return (
58
- <div className="toast-container">
59
- {messages.map((message) => (
60
- <ToastItem
61
- key={message.id}
62
- message={message}
63
- remove={() => removeToast(message.id)}
64
- />
65
- ))}
66
- </div>
67
- );
68
- };
69
-
70
- const ToastItem = ({
71
- message,
72
- remove,
73
- }: {
74
- message: ToastMessage;
75
- remove: () => void;
76
- }) => {
77
- const elRef = useRef<HTMLDivElement>(null);
78
-
79
- useGSAP(() => {
80
- if (!elRef.current) return;
81
-
82
- const tl = gsap.timeline({ onComplete: remove });
83
- tl.from(elRef.current, {
84
- opacity: 0,
85
- y: 50,
86
- ease: "power4.out",
87
- });
88
-
89
- tl.to(elRef.current, {
90
- delay: 3,
91
- y: 50,
92
- opacity: 0,
93
- ease: "power4.out",
94
- });
95
- }, []);
96
-
97
- return (
98
- <div
99
- ref={elRef}
100
- className={`toast-item ${message.status || "standard"}`}
101
- >
102
- <p>{message.message}</p>
103
- {message.desc ? <p className="toast-desc">{message.desc}</p> : null}
104
- </div>
105
- );
106
- };
@@ -1,69 +0,0 @@
1
- .toast-container {
2
- position: fixed;
3
- display: flex;
4
- flex-direction: column;
5
- gap: var(--spacing-sm-y);
6
- bottom: 24px;
7
- right: 24px;
8
- z-index: 50;
9
- }
10
-
11
- .toast-item {
12
- background-color: var(--toast-bg, var(--color-bg-card));
13
- border: 1px solid var(--toast-border, var(--border-standard));
14
- border-radius: var(--radius-common);
15
- color: var(--toast-color, var(--text-standard));
16
- padding: var(--spacing-common-x);
17
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
18
- min-width: 250px;
19
- }
20
-
21
- /* Status Variants */
22
- .toast-item.standard {
23
- --toast-bg: var(--color-bg-card);
24
- --toast-border: var(--border-standard);
25
- --toast-color: var(--text-standard);
26
- }
27
-
28
- .toast-item.primary {
29
- --toast-bg: var(--color-primary);
30
- --toast-border: var(--color-primary);
31
- --toast-color: var(--color-white);
32
- }
33
-
34
- .toast-item.secondary {
35
- --toast-bg: var(--color-secondary);
36
- --toast-border: var(--color-secondary);
37
- --toast-color: var(--color-white);
38
- }
39
-
40
- .toast-item.success {
41
- --toast-bg: var(--color-success);
42
- --toast-border: var(--color-success);
43
- --toast-color: var(--color-white);
44
- }
45
-
46
- .toast-item.danger,
47
- .toast-item.error {
48
- --toast-bg: var(--color-danger);
49
- --toast-border: var(--color-danger);
50
- --toast-color: var(--color-white);
51
- }
52
-
53
- .toast-item.warning {
54
- --toast-bg: var(--color-warning);
55
- --toast-border: var(--color-warning);
56
- --toast-color: var(--color-white);
57
- }
58
-
59
- .toast-item.info {
60
- --toast-bg: var(--color-info);
61
- --toast-border: var(--color-info);
62
- --toast-color: var(--color-white);
63
- }
64
-
65
- .toast-desc {
66
- font-size: var(--text-sm);
67
- margin-top: var(--spacing-gap-small);
68
- opacity: 0.9;
69
- }
@@ -1,223 +0,0 @@
1
- import StyleDictionary from "style-dictionary";
2
-
3
-
4
- // 1. Style Dictionary 인스턴스 초기화
5
- const myStyleDictionary = new StyleDictionary({
6
- source: ["tokens/tokens.json"],
7
- preprocessors: ['flatten-global'],
8
- hooks: {
9
- preprocessors: {
10
- 'flatten-global': (dictionary) => {
11
- // 1. global 객체를 찾습니다.
12
- const { global, ...rest } = dictionary;
13
-
14
- // 2. global 내부의 내용을 루트로 끌어올려 병합합니다.
15
- // 이렇게 하면 {palette.white} 참조가 루트 레벨에서 해석 가능해집니다.
16
- return {
17
- ...global,
18
- ...rest
19
- };
20
- }
21
- }
22
- },
23
- platforms: {
24
- css: {
25
- transformGroup: "css", // CSS 기본 변환 적용 (예: size/rem, color/hex 등)
26
- buildPath: "build/css/",
27
- files: [
28
- {
29
- destination: "_variables.css",
30
- format: "myCustomFormat", // 아래에서 정의한 커스텀 포맷 사용
31
- options: {
32
- outputReferences: true, // CSS 변수 참조 활성화 (var(--name))
33
- },
34
- },
35
- ],
36
- },
37
- typescript: {
38
- transformGroup: "js",
39
- buildPath: "build/typescript/",
40
- files: [
41
- {
42
- destination: "theme.ts",
43
- format: "theme",
44
- },
45
- ],
46
- },
47
- },
48
- log: {
49
- verbosity: 'verbose', // 어떤 reference를 못 찾는지 정확히 찍어줍니다.
50
- },
51
- });
52
-
53
- // 2. 커스텀 포맷 등록
54
- // 토큰 그룹을 CSS 변수 블록으로 변환합니다.
55
- // - 'global' 카테고리 -> @theme 블록
56
- // - 그 외 카테고리 -> .{category} 클래스 블록
57
- myStyleDictionary.registerFormat({
58
- name: "myCustomFormat",
59
- format: function ({ dictionary, options }) {
60
- // [헬퍼 함수]
61
- // 1. getName: 토큰의 계층(type, item, subitem)을 하이픈(-)으로 연결하여 CSS 변수명 생성
62
- // 예: palette.gray.500 -> palette-gray-500
63
- const getName = (token) => token.path.slice(1).join("-");
64
-
65
- // 2. getSemanticName: 시멘틱 토큰명을 생성 (예: color-card)
66
- // 이름 규칙: $type(color)-...path -> color-bg-card
67
- const getSemanticName = (token) => {
68
- if (token.$type === "color") {
69
- return [token.$type, ...token.path.slice(1)].join("-");
70
- } else {
71
- return null;
72
- }
73
- };
74
-
75
- // [참조 맵 생성]
76
- // {global.palette.white} 같은 참조 경로를 실제 토큰 객체로 찾기 위한 Map 생성
77
- const tokenMap = new Map(
78
- dictionary.allTokens.map((t) => [t.path.join("."), t])
79
- );
80
-
81
- // [토큰 그룹화]
82
- // tokens.json의 최상위 카테고리(global, white, dark)별로 토큰을 묶음
83
- const grouped = dictionary.allTokens.reduce((acc, token) => {
84
- const category = token.attributes.category;
85
- if (!acc[category]) acc[category] = [];
86
- acc[category].push(token);
87
- return acc;
88
- }, {});
89
-
90
- // [시멘틱 변수 수집 로직]
91
- // global이 아닌 테마별 토큰(white, dark 등)을 순회하며 시멘틱 변수 정의를 수집함
92
- // 목적: @theme 블록 안에 "--color-card: var(--card);" 형태의 매핑을 한 번만 선언하기 위함.
93
- // 각 테마 파일(.white, .dark)에서는 --card 변수만 재정의하면 됨.
94
- const themeTokens = dictionary.allTokens.filter(t => t.attributes.category !== 'palette');
95
- const semanticMap = new Map();
96
-
97
- themeTokens.forEach(token => {
98
- const semanticName = getSemanticName(token); // 예: color-card
99
- const varName = getName(token); // 예: card
100
-
101
- // 중복 선언 방지: 동일한 시멘틱 이름이 여러 테마에 있어도 한 번만 수집
102
- if (semanticName && !semanticMap.has(semanticName)) {
103
- semanticMap.set(semanticName, varName);
104
- }
105
- });
106
-
107
- // 수집된 시멘틱 변수들을 CSS 문자열로 변환
108
- const semanticCss = Array.from(semanticMap.entries())
109
- .map(([name, val]) => ` --${name}: var(--${val});`)
110
- .join('\n');
111
-
112
-
113
- // [최종 CSS 생성]
114
- // 그룹별로 순회하며 CSS 블록 생성
115
- return Object.entries(grouped)
116
- .map(([category, tokens]) => {
117
- // 선택자 결정:
118
- // - 'global' 카테고리는 Tailwind CSS v4의 '@theme' 블록으로 변환
119
- // - 나머지(white, dark)는 클래스 선택자(예: .white)로 변환
120
- const selector = category === "palette" ? "@theme" : `.${category}`;
121
-
122
-
123
- // 정렬
124
- const typeOrder = ["color", "fontSizes", "fontWeights", "fontFamily", "borderRadius"];
125
-
126
- const cssVariables = tokens
127
- .sort((a, b) => {
128
- const indexA = typeOrder.indexOf(a.$type);
129
- const indexB = typeOrder.indexOf(b.$type);
130
- if (indexA !== -1 && indexB !== -1) return indexA - indexB;
131
- if (indexA !== -1) return -1;
132
- if (indexB !== -1) return 1;
133
- return 0;
134
- })
135
- .map((token) => {
136
- const name = getName(token);
137
- // 기본 값. 참조가 있다면 원본 값("{global.palette...}")을 가져옴
138
- let value = token.$value || token.value;
139
- const originalValue = token.original.$value || token.original.value;
140
-
141
- // [수동 참조 해결]
142
- // 값이 "{...}" 형태의 참조인 경우, 해당 참조를 var(--이름) 형태로 변환
143
- if (
144
- options?.outputReferences &&
145
- typeof originalValue === "string" && // 값이 문자열이고
146
- originalValue.includes("{") // '{'를 포함하면 참조로 간주
147
- ) {
148
- value = originalValue.replace(/{([^}]+)}/g, (match, pathStr) => {
149
- const refToken = tokenMap.get(pathStr);
150
- if (refToken) {
151
- return `var(--${getName(refToken)})`;
152
- }
153
- return match;
154
- });
155
- }
156
-
157
- if (category === "palette") {
158
- // $type을 확인하여 borderRadius나 dimension이면 px 추가
159
- if(token.$type === "borderRadius" | token.$type === "fontSizes" | token.$type === "spacing" | token.$type === "sizing" | token.$type === "dimension") {
160
- if (!String(value).endsWith("px")) {
161
- return `--${name}: ${value}px;`;
162
- }
163
- }
164
- return `--${name}: ${value};`;
165
- }
166
- // 그 외 카테고리(테마)
167
- if (token.$type === "color") {
168
- return `--${name}: ${value};`;
169
- }
170
- if (token.$type === "spacing" || token.$type === "sizing") {
171
- if (!String(value).endsWith("px") && !String(value).includes("var(")) {
172
- return `--${name}: ${value}px;`;
173
- }
174
- return `--${name}: ${value};`;
175
- }
176
-
177
- return null;
178
- })
179
- .filter(Boolean)
180
- .join("\n");
181
-
182
- // global(@theme)인 경우에만 수집해둔 시멘틱 변수(semanticCss)를 추가
183
- // @theme: Tailwind 유틸리티 클래스 생성에 필요 (bg-primary, text-secondary 등)
184
- // Tailwind CSS v4의 @theme은 빌드 시 자동으로 :root에도 등록되므로 별도 :root 선언 불필요
185
- if (selector === "@theme") {
186
- return `@theme {\n${cssVariables}\n\n${semanticCss}\n}`;
187
- }
188
- return `${selector} {\n${cssVariables}\n} `;
189
- })
190
- .join("\n\n");
191
- },
192
- });
193
-
194
- // 3. theme와 관련된 커스텀 포맷 등록
195
- // theme.ts 파일을 위한 포맷 등록
196
- myStyleDictionary.registerFormat({
197
- name: "theme",
198
- format: function ({ dictionary }) {
199
- const groupedThemes = dictionary.allTokens.reduce((acc, token) => {
200
- const category = token.attributes.category;
201
- if (category === "palette") return acc;
202
-
203
- if (category.includes("-")) {
204
- const prefix = category.split("-")[0];
205
- if (!acc[prefix]) acc[prefix] = [];
206
- if (!acc[prefix].includes(category)) {
207
- acc[prefix].push(category);
208
- }
209
- } else {
210
- if (!acc.global) acc.global = [];
211
- if (!acc.global.includes(category)) {
212
- acc.global.push(category);
213
- }
214
- }
215
- return acc;
216
- }, { global: [] });
217
-
218
- return `export const themes: Record<string, string[]> = ${JSON.stringify(groupedThemes, null, 2)};`;
219
- },
220
- });
221
-
222
- // 3. 모든 플랫폼 빌드
223
- await myStyleDictionary.buildAllPlatforms();