@icen.ai/ui 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/README.md +58 -0
- package/dist/base.css +55 -0
- package/dist/behaviors/copy.mjs +62 -0
- package/dist/behaviors/tabs.mjs +63 -0
- package/dist/behaviors/theme.mjs +80 -0
- package/dist/behaviors/toast.mjs +35 -0
- package/dist/components/btn.css +23 -0
- package/dist/components/form.css +31 -0
- package/dist/components/panel.css +19 -0
- package/dist/components/pill.css +28 -0
- package/dist/components/stat.css +18 -0
- package/dist/components/table.css +19 -0
- package/dist/components/tabs.css +26 -0
- package/dist/components/toast.css +27 -0
- package/dist/index.mjs +280 -0
- package/dist/registry.json +26 -0
- package/dist/tokens/colors.css +1318 -0
- package/dist/tokens/style-profiles.css +540 -0
- package/dist/tokens/typography.css +49 -0
- package/dist/tokens.css +1917 -0
- package/dist/ui.css +2190 -0
- package/package.json +37 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
name: "@icen.ai/ui",
|
|
4
|
+
version: "0.1.0",
|
|
5
|
+
description: "Icen Design System \u2014 \u8BBE\u8BA1 tokens\uFF086 \u9884\u8BBE \xD7 \u660E\u6697\uFF09\u3001\u65E0\u6846\u67B6\u7EC4\u4EF6 CSS \u4E0E\u884C\u4E3A JS",
|
|
6
|
+
repository: {
|
|
7
|
+
type: "git",
|
|
8
|
+
url: "git+https://github.com/icen-ai/ui.git"
|
|
9
|
+
},
|
|
10
|
+
homepage: "https://ui.icen.ai",
|
|
11
|
+
type: "module",
|
|
12
|
+
exports: {
|
|
13
|
+
".": "./dist/index.mjs",
|
|
14
|
+
"./behaviors/*": "./dist/behaviors/*.mjs",
|
|
15
|
+
"./tokens.css": "./dist/tokens.css",
|
|
16
|
+
"./ui.css": "./dist/ui.css",
|
|
17
|
+
"./base.css": "./dist/base.css",
|
|
18
|
+
"./tokens/*": "./dist/tokens/*",
|
|
19
|
+
"./components/*": "./dist/components/*",
|
|
20
|
+
"./registry.json": "./dist/registry.json"
|
|
21
|
+
},
|
|
22
|
+
files: [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
license: "MIT",
|
|
26
|
+
publishConfig: {
|
|
27
|
+
access: "public"
|
|
28
|
+
},
|
|
29
|
+
scripts: {
|
|
30
|
+
build: "tsup && bun scripts/build-css.ts",
|
|
31
|
+
prepublishOnly: "bun run build"
|
|
32
|
+
},
|
|
33
|
+
devDependencies: {
|
|
34
|
+
"@types/node": "^20.14.0",
|
|
35
|
+
tsup: "^8.3.0",
|
|
36
|
+
typescript: "^5.5.0"
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/behaviors/theme.ts
|
|
41
|
+
var PRESETS = ["clay", "piano", "art", "vangogh", "ink", "retro"];
|
|
42
|
+
var STYLES = ["modern", "retro", "terminal"];
|
|
43
|
+
var STORAGE_KEY = "icen.ui.theme";
|
|
44
|
+
var FALLBACK = { preset: "clay", dark: false, style: "modern" };
|
|
45
|
+
function isBrowser() {
|
|
46
|
+
return typeof document !== "undefined";
|
|
47
|
+
}
|
|
48
|
+
function getDefaultTheme() {
|
|
49
|
+
if (!isBrowser() || typeof window.matchMedia !== "function") {
|
|
50
|
+
return { ...FALLBACK };
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
preset: "clay",
|
|
54
|
+
dark: window.matchMedia("(prefers-color-scheme: dark)").matches,
|
|
55
|
+
style: "modern"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function isThemeState(v) {
|
|
59
|
+
if (typeof v !== "object" || v === null) return false;
|
|
60
|
+
const t = v;
|
|
61
|
+
return typeof t.preset === "string" && PRESETS.includes(t.preset) && typeof t.dark === "boolean" && typeof t.style === "string" && STYLES.includes(t.style);
|
|
62
|
+
}
|
|
63
|
+
function getTheme() {
|
|
64
|
+
if (!isBrowser()) return getDefaultTheme();
|
|
65
|
+
try {
|
|
66
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
67
|
+
if (!raw) return getDefaultTheme();
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
if (!isThemeState(parsed)) return getDefaultTheme();
|
|
70
|
+
return { preset: parsed.preset, dark: parsed.dark, style: parsed.style };
|
|
71
|
+
} catch {
|
|
72
|
+
return getDefaultTheme();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function applyTheme(t) {
|
|
76
|
+
if (!isBrowser()) return;
|
|
77
|
+
const cls = document.documentElement.classList;
|
|
78
|
+
for (const p of PRESETS) cls.remove(p);
|
|
79
|
+
for (const s of STYLES) cls.remove(`style-${s}`);
|
|
80
|
+
cls.remove("dark");
|
|
81
|
+
cls.add(t.preset);
|
|
82
|
+
if (t.dark) cls.add("dark");
|
|
83
|
+
cls.add(`style-${t.style}`);
|
|
84
|
+
}
|
|
85
|
+
function setTheme(patch) {
|
|
86
|
+
const merged = { ...getTheme(), ...patch };
|
|
87
|
+
const next = {
|
|
88
|
+
preset: PRESETS.includes(merged.preset) ? merged.preset : FALLBACK.preset,
|
|
89
|
+
dark: merged.dark === true,
|
|
90
|
+
style: STYLES.includes(merged.style) ? merged.style : FALLBACK.style
|
|
91
|
+
};
|
|
92
|
+
applyTheme(next);
|
|
93
|
+
if (isBrowser()) {
|
|
94
|
+
try {
|
|
95
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(next));
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return next;
|
|
100
|
+
}
|
|
101
|
+
function toggleDark() {
|
|
102
|
+
return setTheme({ dark: !getTheme().dark });
|
|
103
|
+
}
|
|
104
|
+
function initTheme() {
|
|
105
|
+
const t = getTheme();
|
|
106
|
+
applyTheme(t);
|
|
107
|
+
return t;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/behaviors/toast.ts
|
|
111
|
+
function ensureContainer() {
|
|
112
|
+
const existing = document.body.querySelector(".toast-container");
|
|
113
|
+
if (existing) return existing;
|
|
114
|
+
const el = document.createElement("div");
|
|
115
|
+
el.className = "toast-container";
|
|
116
|
+
document.body.appendChild(el);
|
|
117
|
+
return el;
|
|
118
|
+
}
|
|
119
|
+
function show(message, kind = "info", ms = 2600) {
|
|
120
|
+
if (typeof document === "undefined") return;
|
|
121
|
+
const el = document.createElement("div");
|
|
122
|
+
el.className = kind === "info" ? "toast" : `toast ${kind}`;
|
|
123
|
+
el.textContent = message;
|
|
124
|
+
ensureContainer().appendChild(el);
|
|
125
|
+
let removed = false;
|
|
126
|
+
const remove = () => {
|
|
127
|
+
if (removed) return;
|
|
128
|
+
removed = true;
|
|
129
|
+
el.remove();
|
|
130
|
+
};
|
|
131
|
+
window.setTimeout(() => {
|
|
132
|
+
el.classList.add("leaving");
|
|
133
|
+
el.addEventListener("transitionend", remove, { once: true });
|
|
134
|
+
window.setTimeout(remove, 400);
|
|
135
|
+
}, ms);
|
|
136
|
+
}
|
|
137
|
+
var toast = Object.assign(show, {
|
|
138
|
+
ok: (message, ms) => show(message, "ok", ms),
|
|
139
|
+
err: (message, ms) => show(message, "err", ms),
|
|
140
|
+
warn: (message, ms) => show(message, "warn", ms)
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/behaviors/tabs.ts
|
|
144
|
+
function tabNames(container) {
|
|
145
|
+
return Array.from(container.querySelectorAll("[data-tab]")).map((t) => t.getAttribute("data-tab")).filter((n) => n !== null);
|
|
146
|
+
}
|
|
147
|
+
function currentHash() {
|
|
148
|
+
return window.location.hash.replace(/^#/, "");
|
|
149
|
+
}
|
|
150
|
+
function activate(container, name, writeHash) {
|
|
151
|
+
let matched = false;
|
|
152
|
+
container.querySelectorAll("[data-tab]").forEach((t) => {
|
|
153
|
+
const on = t.getAttribute("data-tab") === name;
|
|
154
|
+
if (on) matched = true;
|
|
155
|
+
t.classList.toggle("active", on);
|
|
156
|
+
});
|
|
157
|
+
if (!matched) return;
|
|
158
|
+
container.querySelectorAll("[data-tab-panel]").forEach((p) => {
|
|
159
|
+
p.hidden = p.getAttribute("data-tab-panel") !== name;
|
|
160
|
+
});
|
|
161
|
+
if (writeHash && typeof window !== "undefined") {
|
|
162
|
+
window.history.replaceState(null, "", `#${name}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function setup(container) {
|
|
166
|
+
const el = container;
|
|
167
|
+
if (el.__icenTabsInit) return;
|
|
168
|
+
el.__icenTabsInit = true;
|
|
169
|
+
const useHash = container.hasAttribute("data-tabs-hash");
|
|
170
|
+
const names = tabNames(container);
|
|
171
|
+
if (names.length === 0) return;
|
|
172
|
+
container.addEventListener("click", (ev) => {
|
|
173
|
+
const target = ev.target;
|
|
174
|
+
if (!(target instanceof Element)) return;
|
|
175
|
+
const btn = target.closest("[data-tab]");
|
|
176
|
+
if (!btn || btn.closest("[data-tabs]") !== container) return;
|
|
177
|
+
const name = btn.getAttribute("data-tab");
|
|
178
|
+
if (name) activate(container, name, useHash);
|
|
179
|
+
});
|
|
180
|
+
let initial;
|
|
181
|
+
if (useHash) {
|
|
182
|
+
const h = currentHash();
|
|
183
|
+
if (names.includes(h)) initial = h;
|
|
184
|
+
} else {
|
|
185
|
+
const n = container.querySelector("[data-tab].active")?.getAttribute("data-tab");
|
|
186
|
+
if (n && names.includes(n)) initial = n;
|
|
187
|
+
}
|
|
188
|
+
activate(container, initial ?? names[0], false);
|
|
189
|
+
if (useHash && typeof window !== "undefined") {
|
|
190
|
+
window.addEventListener("hashchange", () => {
|
|
191
|
+
const h = currentHash();
|
|
192
|
+
if (names.includes(h)) activate(container, h, false);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function initTabs(root = document) {
|
|
197
|
+
if (typeof document === "undefined") return;
|
|
198
|
+
const containers = [];
|
|
199
|
+
if (root instanceof Element && root.matches("[data-tabs]")) containers.push(root);
|
|
200
|
+
containers.push(...Array.from(root.querySelectorAll("[data-tabs]")));
|
|
201
|
+
for (const c of containers) setup(c);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/behaviors/copy.ts
|
|
205
|
+
var initialized = /* @__PURE__ */ new WeakSet();
|
|
206
|
+
var flashing = /* @__PURE__ */ new WeakMap();
|
|
207
|
+
function legacyCopy(text) {
|
|
208
|
+
const ta = document.createElement("textarea");
|
|
209
|
+
ta.value = text;
|
|
210
|
+
ta.setAttribute("readonly", "");
|
|
211
|
+
ta.style.position = "fixed";
|
|
212
|
+
ta.style.opacity = "0";
|
|
213
|
+
document.body.appendChild(ta);
|
|
214
|
+
ta.select();
|
|
215
|
+
let ok = false;
|
|
216
|
+
try {
|
|
217
|
+
ok = document.execCommand("copy");
|
|
218
|
+
} catch {
|
|
219
|
+
ok = false;
|
|
220
|
+
}
|
|
221
|
+
ta.remove();
|
|
222
|
+
return ok;
|
|
223
|
+
}
|
|
224
|
+
async function copyText(text) {
|
|
225
|
+
try {
|
|
226
|
+
if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
227
|
+
await navigator.clipboard.writeText(text);
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
return legacyCopy(text);
|
|
233
|
+
}
|
|
234
|
+
function flashDone(btn) {
|
|
235
|
+
const prev = flashing.get(btn);
|
|
236
|
+
if (prev) window.clearTimeout(prev.timer);
|
|
237
|
+
const original = prev ? prev.original : btn.textContent;
|
|
238
|
+
btn.textContent = "\u5DF2\u590D\u5236";
|
|
239
|
+
btn.classList.add("done");
|
|
240
|
+
const timer = window.setTimeout(() => {
|
|
241
|
+
btn.textContent = original;
|
|
242
|
+
btn.classList.remove("done");
|
|
243
|
+
flashing.delete(btn);
|
|
244
|
+
}, 1400);
|
|
245
|
+
flashing.set(btn, { original, timer });
|
|
246
|
+
}
|
|
247
|
+
function onClick(root, ev) {
|
|
248
|
+
const target = ev.target;
|
|
249
|
+
if (!(target instanceof Element)) return;
|
|
250
|
+
const btn = target.closest(".copy-btn[data-copy]");
|
|
251
|
+
if (!btn || !root.contains(btn)) return;
|
|
252
|
+
const text = btn.getAttribute("data-copy") ?? "";
|
|
253
|
+
void copyText(text).then((ok) => {
|
|
254
|
+
if (ok) flashDone(btn);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
function initCopy(root = document) {
|
|
258
|
+
if (typeof document === "undefined") return;
|
|
259
|
+
if (initialized.has(root)) return;
|
|
260
|
+
initialized.add(root);
|
|
261
|
+
root.addEventListener("click", (ev) => onClick(root, ev));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/index.ts
|
|
265
|
+
var VERSION = package_default.version;
|
|
266
|
+
export {
|
|
267
|
+
PRESETS,
|
|
268
|
+
STORAGE_KEY,
|
|
269
|
+
STYLES,
|
|
270
|
+
VERSION,
|
|
271
|
+
applyTheme,
|
|
272
|
+
getDefaultTheme,
|
|
273
|
+
getTheme,
|
|
274
|
+
initCopy,
|
|
275
|
+
initTabs,
|
|
276
|
+
initTheme,
|
|
277
|
+
setTheme,
|
|
278
|
+
toast,
|
|
279
|
+
toggleDark
|
|
280
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@icen.ai/ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"tokens": [
|
|
5
|
+
"colors.css",
|
|
6
|
+
"style-profiles.css",
|
|
7
|
+
"typography.css"
|
|
8
|
+
],
|
|
9
|
+
"base": "base.css",
|
|
10
|
+
"components": [
|
|
11
|
+
"btn",
|
|
12
|
+
"form",
|
|
13
|
+
"panel",
|
|
14
|
+
"pill",
|
|
15
|
+
"stat",
|
|
16
|
+
"table",
|
|
17
|
+
"tabs",
|
|
18
|
+
"toast"
|
|
19
|
+
],
|
|
20
|
+
"behaviors": [
|
|
21
|
+
"tabs",
|
|
22
|
+
"theme",
|
|
23
|
+
"toast",
|
|
24
|
+
"copy"
|
|
25
|
+
]
|
|
26
|
+
}
|