@reopt-ai/opt-ui 1.4.1 → 1.5.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.
@@ -0,0 +1,242 @@
1
+ // src/lib/theme/presets/index.ts
2
+ var DEFAULT_DESCRIPTOR = {
3
+ id: "default",
4
+ label: "Default",
5
+ description: "Blue accent, balanced radius & shadow",
6
+ palette: {
7
+ seed: "#2563eb",
8
+ harmony: "split-complementary",
9
+ radiusScale: "medium",
10
+ shadowIntensity: "subtle",
11
+ neutralTemperature: "cool"
12
+ },
13
+ switcherSwatches: ["hsl(221 83% 53%)", "hsl(142 71% 45%)", "hsl(38 92% 50%)"]
14
+ };
15
+ var MINIMAL_DESCRIPTOR = {
16
+ id: "minimal",
17
+ label: "Minimal",
18
+ description: "Monochrome, no shadow, sharp edges",
19
+ palette: {
20
+ seed: "#333333",
21
+ harmony: "analogous",
22
+ radiusScale: "sharp",
23
+ shadowIntensity: "none",
24
+ neutralTemperature: "cool"
25
+ },
26
+ switcherSwatches: ["hsl(0 0% 20%)", "hsl(0 0% 45%)", "hsl(0 0% 70%)"]
27
+ };
28
+ var NATURAL_DESCRIPTOR = {
29
+ id: "natural",
30
+ label: "Natural",
31
+ description: "Always light \u2014 sage accent, warm cream",
32
+ forceLightOnly: true,
33
+ palette: {
34
+ seed: "#5a9376",
35
+ harmony: "complementary",
36
+ radiusScale: "medium",
37
+ shadowIntensity: "subtle",
38
+ neutralTemperature: "warm"
39
+ },
40
+ switcherSwatches: ["hsl(155 38% 45%)", "hsl(25 65% 55%)", "hsl(45 75% 50%)"]
41
+ };
42
+ var PRO_DESCRIPTOR = {
43
+ id: "pro",
44
+ label: "Pro",
45
+ description: "Neon blue accent, mono headings, ultra-dense",
46
+ palette: {
47
+ seed: "#0080ff",
48
+ harmony: "tetradic",
49
+ radiusScale: "sharp",
50
+ shadowIntensity: "subtle",
51
+ neutralTemperature: "cool"
52
+ },
53
+ switcherSwatches: [
54
+ "hsl(210 100% 55%)",
55
+ "hsl(170 90% 48%)",
56
+ "hsl(290 85% 60%)"
57
+ ]
58
+ };
59
+ var MONO_DARK_DESCRIPTOR = {
60
+ id: "mono-dark",
61
+ label: "Mono Dark",
62
+ description: "OLED-friendly black, neon cyan accent, mono headings",
63
+ palette: {
64
+ seed: "#00d4ff",
65
+ harmony: "tetradic",
66
+ radiusScale: "sharp",
67
+ shadowIntensity: "subtle",
68
+ neutralTemperature: "cool"
69
+ },
70
+ switcherSwatches: ["hsl(190 100% 50%)", "hsl(0 0% 8%)", "hsl(280 90% 65%)"]
71
+ };
72
+ var THEME_REGISTRY = {
73
+ default: DEFAULT_DESCRIPTOR,
74
+ minimal: MINIMAL_DESCRIPTOR,
75
+ natural: NATURAL_DESCRIPTOR,
76
+ pro: PRO_DESCRIPTOR,
77
+ "mono-dark": MONO_DARK_DESCRIPTOR
78
+ };
79
+ var THEME_PRESET_IDS = Object.keys(THEME_REGISTRY);
80
+ function isThemePresetId(id) {
81
+ return id in THEME_REGISTRY;
82
+ }
83
+ var FORCE_LIGHT_PRESET_IDS = new Set(
84
+ THEME_PRESET_IDS.filter((id) => THEME_REGISTRY[id].forceLightOnly)
85
+ );
86
+ var DEPRECATED_PRESET_MIGRATIONS = Object.freeze({
87
+ corporate: "default",
88
+ playful: "default"
89
+ });
90
+ function migrateDeprecatedPresetId(id) {
91
+ if (!id) return null;
92
+ if (isThemePresetId(id)) return id;
93
+ if (id in DEPRECATED_PRESET_MIGRATIONS) {
94
+ return DEPRECATED_PRESET_MIGRATIONS[id];
95
+ }
96
+ return null;
97
+ }
98
+
99
+ // src/lib/theme/theme-serialize.ts
100
+ import {
101
+ generatePalette
102
+ } from "@reopt-ai/opt-palette";
103
+ var GLOBAL_THEME_STYLE_ID = "opt-global-theme";
104
+ var THEME_SPEC_STORAGE_KEY = "opt-theme-spec";
105
+ var THEME_TOKENS_STORAGE_KEY = "opt-theme-tokens";
106
+
107
+ // src/lib/theme/theme-resolution.ts
108
+ var THEME_PRESET_VALUES = THEME_PRESET_IDS;
109
+ var THEME_MODE_VALUES = ["light", "dark", "system"];
110
+ var LEGACY_THEME_STORAGE_KEY = "opt-theme";
111
+ var THEME_PRESET_STORAGE_KEY = "opt-preset";
112
+ var THEME_MODE_STORAGE_KEY = "opt-mode";
113
+ var DARK_MQ = "(prefers-color-scheme: dark)";
114
+ var FORCE_LIGHT_PRESETS = FORCE_LIGHT_PRESET_IDS;
115
+ var ALL_COMPOUND_THEMES = THEME_PRESET_IDS.flatMap(
116
+ (id) => FORCE_LIGHT_PRESET_IDS.has(id) ? [id] : [id, `${id}-dark`]
117
+ );
118
+ function hasStorageMutation(storage) {
119
+ return typeof storage.setItem === "function" && typeof storage.removeItem === "function";
120
+ }
121
+ function isThemePreset(value) {
122
+ return typeof value === "string" && THEME_PRESET_VALUES.includes(value);
123
+ }
124
+ function isThemeMode(value) {
125
+ return typeof value === "string" && THEME_MODE_VALUES.includes(value);
126
+ }
127
+ function getCompoundTheme(preset, resolvedMode) {
128
+ if (FORCE_LIGHT_PRESETS.has(preset)) return preset;
129
+ return resolvedMode === "dark" ? `${preset}-dark` : preset;
130
+ }
131
+ function resolveThemeMode(preset, mode, systemDark) {
132
+ if (FORCE_LIGHT_PRESETS.has(preset)) return "light";
133
+ if (mode === "system") return systemDark ? "dark" : "light";
134
+ return mode;
135
+ }
136
+ function readStoredTheme(storage, defaultPreset, defaultMode, options = {}) {
137
+ const legacy = storage.getItem(LEGACY_THEME_STORAGE_KEY);
138
+ const storedPreset = storage.getItem(THEME_PRESET_STORAGE_KEY);
139
+ const storedMode = storage.getItem(THEME_MODE_STORAGE_KEY);
140
+ let preset = defaultPreset;
141
+ if (typeof legacy === "string") {
142
+ const migratedLegacy = migrateDeprecatedPresetId(legacy);
143
+ if (migratedLegacy) {
144
+ if (options.migrateLegacy && hasStorageMutation(storage)) {
145
+ storage.setItem(THEME_PRESET_STORAGE_KEY, migratedLegacy);
146
+ storage.removeItem(LEGACY_THEME_STORAGE_KEY);
147
+ }
148
+ preset = migratedLegacy;
149
+ }
150
+ } else if (typeof storedPreset === "string") {
151
+ const migrated = migrateDeprecatedPresetId(storedPreset);
152
+ if (migrated) {
153
+ preset = migrated;
154
+ if (hasStorageMutation(storage) && migrated !== storedPreset && storedPreset in DEPRECATED_PRESET_MIGRATIONS) {
155
+ storage.setItem(THEME_PRESET_STORAGE_KEY, migrated);
156
+ }
157
+ }
158
+ }
159
+ const mode = isThemeMode(storedMode) ? storedMode : defaultMode;
160
+ return { preset, mode };
161
+ }
162
+ function coerceThemeMode(mode, allowedModes, fallback = "system") {
163
+ var _a;
164
+ if (!allowedModes || allowedModes.length === 0) return mode;
165
+ if (allowedModes.includes(mode)) return mode;
166
+ if (allowedModes.includes(fallback)) return fallback;
167
+ return (_a = allowedModes[0]) != null ? _a : fallback;
168
+ }
169
+ function createThemeBootScript(defaultPreset = "default", defaultMode = "system", options = {}) {
170
+ var _a, _b;
171
+ const presetValues = JSON.stringify(THEME_PRESET_VALUES);
172
+ const modeValues = JSON.stringify(THEME_MODE_VALUES);
173
+ const allowedModes = ((_a = options.allowedModes) == null ? void 0 : _a.length) ? options.allowedModes : THEME_MODE_VALUES;
174
+ const allowedModeValues = JSON.stringify(allowedModes);
175
+ const forceLightPresets = JSON.stringify(Array.from(FORCE_LIGHT_PRESETS));
176
+ const deprecatedMap = JSON.stringify(DEPRECATED_PRESET_MIGRATIONS);
177
+ const lockedPreset = options.lockedPreset;
178
+ const resolvedDefaultPreset = lockedPreset != null ? lockedPreset : defaultPreset;
179
+ const defaultPresetLiteral = JSON.stringify(resolvedDefaultPreset);
180
+ const defaultModeLiteral = JSON.stringify(
181
+ coerceThemeMode(defaultMode, allowedModes, (_b = allowedModes[0]) != null ? _b : "system")
182
+ );
183
+ const lockedPresetLiteral = JSON.stringify(lockedPreset != null ? lockedPreset : null);
184
+ const styleId = JSON.stringify(GLOBAL_THEME_STYLE_ID);
185
+ const specKey = JSON.stringify(THEME_SPEC_STORAGE_KEY);
186
+ const tokensKey = JSON.stringify(THEME_TOKENS_STORAGE_KEY);
187
+ const presetKey = JSON.stringify(THEME_PRESET_STORAGE_KEY);
188
+ const modeKey = JSON.stringify(THEME_MODE_STORAGE_KEY);
189
+ const legacyKey = JSON.stringify(LEGACY_THEME_STORAGE_KEY);
190
+ const darkMq = JSON.stringify(DARK_MQ);
191
+ const source = `
192
+ try {
193
+ var pv=${presetValues},mv=${modeValues},am=${allowedModeValues},fp=${forceLightPresets},dm=${deprecatedMap},lk=${lockedPresetLiteral};
194
+ function mp(v){if(typeof v!=="string")return null;if(pv.indexOf(v)>=0)return v;return Object.prototype.hasOwnProperty.call(dm,v)?dm[v]:null;}
195
+ var p=${defaultPresetLiteral};
196
+ if(!lk){
197
+ var lp=localStorage.getItem(${legacyKey}),sp=localStorage.getItem(${presetKey});
198
+ var lpm=mp(lp),spm=mp(sp);
199
+ p=lpm||spm||${defaultPresetLiteral};
200
+ if(lpm){localStorage.setItem(${presetKey},lpm);localStorage.removeItem(${legacyKey});}
201
+ else if(sp&&spm&&sp!==spm){localStorage.setItem(${presetKey},spm);}
202
+ }
203
+ var sm=localStorage.getItem(${modeKey});
204
+ var m=(typeof sm==="string"&&mv.indexOf(sm)>=0&&am.indexOf(sm)>=0)?sm:${defaultModeLiteral};
205
+ var spec=null,tk=null;
206
+ if(!lk){try{var sj=localStorage.getItem(${specKey});if(sj)spec=JSON.parse(sj);}catch(e){}}
207
+ if(!lk){try{var tj=localStorage.getItem(${tokensKey});if(tj)tk=JSON.parse(tj);}catch(e){}}
208
+ if(!lk&&spec&&spec.kind==="generated"&&tk&&typeof tk.id==="string"&&tk.light&&typeof tk.light==="object"){
209
+ var fld=spec.forceLightOnly===true;
210
+ var dk=!fld&&(m==="dark"||(m==="system"&&matchMedia(${darkMq}).matches));
211
+ var tt=(dk&&tk.dark)?tk.dark:tk.light;
212
+ var cid=(dk&&tk.dark)?(tk.id+"-dark"):tk.id;
213
+ var css="";
214
+ for(var k in tt){if(Object.prototype.hasOwnProperty.call(tt,k))css+=k+":"+tt[k]+";"}
215
+ var st=document.getElementById(${styleId});
216
+ if(!st){st=document.createElement("style");st.id=${styleId};document.head.appendChild(st);}
217
+ st.textContent='[data-theme="'+cid+'"]{'+css+'}';
218
+ document.documentElement.setAttribute("data-theme",cid);
219
+ } else {
220
+ var d=fp.indexOf(p)<0&&(m==="dark"||(m==="system"&&matchMedia(${darkMq}).matches));
221
+ document.documentElement.setAttribute("data-theme",d?p+"-dark":p);
222
+ }
223
+ } catch(e){}`;
224
+ return source.replace(/\n\s*/g, "");
225
+ }
226
+ export {
227
+ ALL_COMPOUND_THEMES,
228
+ DARK_MQ,
229
+ FORCE_LIGHT_PRESETS,
230
+ LEGACY_THEME_STORAGE_KEY,
231
+ THEME_MODE_STORAGE_KEY,
232
+ THEME_MODE_VALUES,
233
+ THEME_PRESET_STORAGE_KEY,
234
+ THEME_PRESET_VALUES,
235
+ coerceThemeMode,
236
+ createThemeBootScript,
237
+ getCompoundTheme,
238
+ isThemeMode,
239
+ isThemePreset,
240
+ readStoredTheme,
241
+ resolveThemeMode
242
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reopt-ai/opt-ui",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "접근성 우선 UI 컴포넌트 라이브러리. opt-ui-primitives Core, 비즈니스 Shells, 페이지 Surfaces.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -105,6 +105,17 @@
105
105
  },
106
106
  "default": "./dist/id-registry.js"
107
107
  },
108
+ "./theme/server": {
109
+ "import": {
110
+ "types": "./dist/theme/server.d.ts",
111
+ "default": "./dist/theme/server.js"
112
+ },
113
+ "require": {
114
+ "types": "./dist/theme/server.d.cts",
115
+ "default": "./dist/theme/server.cjs"
116
+ },
117
+ "default": "./dist/theme/server.js"
118
+ },
108
119
  "./id-registry.json": "./dist/id-registry.json",
109
120
  "./tailwind.css": "./dist/tailwind.css",
110
121
  "./theme/presets/default.css": "./dist/theme/presets/default.css",
@@ -125,9 +136,9 @@
125
136
  "registry": "https://registry.npmjs.org"
126
137
  },
127
138
  "dependencies": {
128
- "@reopt-ai/opt-charts": "^1.0.0",
139
+ "@reopt-ai/opt-charts": "^1.1.0",
129
140
  "@reopt-ai/opt-palette": "^1.0.0",
130
- "@reopt-ai/opt-ui-primitives": "^1.4.2",
141
+ "@reopt-ai/opt-ui-primitives": "^1.5.0",
131
142
  "clsx": "^2.1.1",
132
143
  "lucide-react": "^0.577.0",
133
144
  "match-sorter": "^8.2.0",