@motion-proto/live-tokens 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.
Files changed (68) hide show
  1. package/README.md +41 -0
  2. package/dist-plugin/index.cjs +444 -0
  3. package/dist-plugin/index.d.cts +12 -0
  4. package/dist-plugin/index.d.ts +12 -0
  5. package/dist-plugin/index.js +407 -0
  6. package/package.json +86 -0
  7. package/src/components/Badge.svelte +82 -0
  8. package/src/components/Button.svelte +333 -0
  9. package/src/components/Card.svelte +83 -0
  10. package/src/components/CollapsibleSection.svelte +82 -0
  11. package/src/components/DetailNav.svelte +78 -0
  12. package/src/components/Dialog.svelte +269 -0
  13. package/src/components/InlineEditActions.svelte +73 -0
  14. package/src/components/Notification.svelte +308 -0
  15. package/src/components/ProgressBar.svelte +99 -0
  16. package/src/components/RadioButton.svelte +87 -0
  17. package/src/components/SectionDivider.svelte +121 -0
  18. package/src/components/TabBar.svelte +92 -0
  19. package/src/components/Toggle.svelte +86 -0
  20. package/src/components/Tooltip.svelte +64 -0
  21. package/src/lib/ColumnsOverlay.svelte +120 -0
  22. package/src/lib/LiveEditorOverlay.svelte +467 -0
  23. package/src/lib/columnsOverlay.ts +26 -0
  24. package/src/lib/cssVarSync.ts +72 -0
  25. package/src/lib/editorConfig.ts +9 -0
  26. package/src/lib/editorConfigStore.ts +14 -0
  27. package/src/lib/index.ts +51 -0
  28. package/src/lib/oklch.ts +129 -0
  29. package/src/lib/pageSource.ts +6 -0
  30. package/src/lib/tokenInit.ts +29 -0
  31. package/src/lib/tokenService.ts +144 -0
  32. package/src/lib/tokenTypes.ts +45 -0
  33. package/src/pages/Admin.svelte +100 -0
  34. package/src/pages/ShowcasePage.svelte +146 -0
  35. package/src/showcase/BackupBrowser.svelte +617 -0
  36. package/src/showcase/BezierCurveEditor.svelte +648 -0
  37. package/src/showcase/ColorEditPanel.svelte +498 -0
  38. package/src/showcase/ComponentsTab.svelte +107 -0
  39. package/src/showcase/EditorDialog.svelte +137 -0
  40. package/src/showcase/PaletteEditor.svelte +2579 -0
  41. package/src/showcase/PaletteSelector.svelte +627 -0
  42. package/src/showcase/SurfacesTab.svelte +409 -0
  43. package/src/showcase/TextTab.svelte +205 -0
  44. package/src/showcase/TokenFileManager.svelte +683 -0
  45. package/src/showcase/TokenMap.svelte +54 -0
  46. package/src/showcase/VariablesTab.svelte +2657 -0
  47. package/src/showcase/VisualsTab.svelte +233 -0
  48. package/src/showcase/curveEngine.ts +190 -0
  49. package/src/showcase/demos/BadgeDemo.svelte +58 -0
  50. package/src/showcase/demos/CardDemo.svelte +52 -0
  51. package/src/showcase/demos/ChoiceButtonsDemo.svelte +194 -0
  52. package/src/showcase/demos/CollapsibleSectionDemo.svelte +56 -0
  53. package/src/showcase/demos/DialogDemo.svelte +42 -0
  54. package/src/showcase/demos/InlineEditActionsDemo.svelte +27 -0
  55. package/src/showcase/demos/NotificationDemo.svelte +149 -0
  56. package/src/showcase/demos/ProgressBarDemo.svelte +56 -0
  57. package/src/showcase/demos/RadioButtonDemo.svelte +58 -0
  58. package/src/showcase/demos/SectionDividerDemo.svelte +79 -0
  59. package/src/showcase/demos/StandardButtonsDemo.svelte +457 -0
  60. package/src/showcase/demos/TabBarDemo.svelte +60 -0
  61. package/src/showcase/demos/TooltipDemo.svelte +54 -0
  62. package/src/showcase/editor.css +93 -0
  63. package/src/showcase/index.ts +17 -0
  64. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  65. package/src/styles/fonts/Domine/OFL.txt +97 -0
  66. package/src/styles/fonts/Domine/README.txt +66 -0
  67. package/src/styles/fonts.css +18 -0
  68. package/src/styles/form-controls.css +190 -0
@@ -0,0 +1,407 @@
1
+ // src/vite-plugin/tokenFileApi.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ function tokenFileApi(opts) {
5
+ const TOKENS_DIR = path.resolve(opts.tokensDir);
6
+ const CSS_PATH = path.resolve(opts.variablesCssPath);
7
+ const TOKENS_BACKUP_DIR = opts.tokensBackupDir ? path.resolve(opts.tokensBackupDir) : path.join(TOKENS_DIR, "_backups");
8
+ const CSS_BACKUP_DIR = opts.cssBackupDir ? path.resolve(opts.cssBackupDir) : path.join(path.dirname(CSS_PATH), "_backups");
9
+ const API_BASE = opts.apiBase ?? "/api";
10
+ const ACTIVE_FILE = path.join(TOKENS_DIR, "_active.json");
11
+ const PRODUCTION_FILE = path.join(TOKENS_DIR, "_production.json");
12
+ function ensureTokensDir() {
13
+ if (!fs.existsSync(TOKENS_DIR)) {
14
+ fs.mkdirSync(TOKENS_DIR, { recursive: true });
15
+ }
16
+ if (!fs.existsSync(path.join(TOKENS_DIR, "default.json"))) {
17
+ const defaultTokens = {
18
+ name: "Default",
19
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
20
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
21
+ editorConfigs: {},
22
+ cssVariables: {}
23
+ };
24
+ fs.writeFileSync(path.join(TOKENS_DIR, "default.json"), JSON.stringify(defaultTokens, null, 2));
25
+ }
26
+ if (!fs.existsSync(ACTIVE_FILE)) {
27
+ fs.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: "default" }));
28
+ }
29
+ if (!fs.existsSync(PRODUCTION_FILE)) {
30
+ const activeFile = getActiveFileName();
31
+ fs.writeFileSync(PRODUCTION_FILE, JSON.stringify({ productionFile: activeFile }));
32
+ }
33
+ }
34
+ function backupTokenFile(filePath, fileName) {
35
+ if (!fs.existsSync(filePath)) return;
36
+ if (!fs.existsSync(TOKENS_BACKUP_DIR)) {
37
+ fs.mkdirSync(TOKENS_BACKUP_DIR, { recursive: true });
38
+ }
39
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
40
+ const backupPath = path.join(TOKENS_BACKUP_DIR, `${fileName}_${timestamp}.json`);
41
+ fs.copyFileSync(filePath, backupPath);
42
+ const allBackups = fs.readdirSync(TOKENS_BACKUP_DIR).filter((f) => f.startsWith(`${fileName}_`) && f.endsWith(".json")).sort().reverse();
43
+ for (const old of allBackups.slice(10)) {
44
+ fs.unlinkSync(path.join(TOKENS_BACKUP_DIR, old));
45
+ }
46
+ }
47
+ function backupCssFile() {
48
+ if (!fs.existsSync(CSS_PATH)) return;
49
+ if (!fs.existsSync(CSS_BACKUP_DIR)) {
50
+ fs.mkdirSync(CSS_BACKUP_DIR, { recursive: true });
51
+ }
52
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
53
+ const backupPath = path.join(CSS_BACKUP_DIR, `variables_${timestamp}.css`);
54
+ fs.copyFileSync(CSS_PATH, backupPath);
55
+ const allBackups = fs.readdirSync(CSS_BACKUP_DIR).filter((f) => f.startsWith("variables_") && f.endsWith(".css")).sort().reverse();
56
+ for (const old of allBackups.slice(10)) {
57
+ fs.unlinkSync(path.join(CSS_BACKUP_DIR, old));
58
+ }
59
+ }
60
+ function getActiveFileName() {
61
+ try {
62
+ const data = JSON.parse(fs.readFileSync(ACTIVE_FILE, "utf-8"));
63
+ return data.activeFile || "default";
64
+ } catch {
65
+ return "default";
66
+ }
67
+ }
68
+ function getProductionFileName() {
69
+ try {
70
+ const data = JSON.parse(fs.readFileSync(PRODUCTION_FILE, "utf-8"));
71
+ return data.productionFile || "default";
72
+ } catch {
73
+ return "default";
74
+ }
75
+ }
76
+ function readBody(req) {
77
+ return new Promise((resolve, reject) => {
78
+ let body = "";
79
+ req.on("data", (chunk) => body += chunk);
80
+ req.on("end", () => resolve(body));
81
+ req.on("error", reject);
82
+ });
83
+ }
84
+ function jsonResponse(res, status, data) {
85
+ res.statusCode = status;
86
+ res.setHeader("Content-Type", "application/json");
87
+ res.end(JSON.stringify(data));
88
+ }
89
+ function sanitize(name) {
90
+ return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
91
+ }
92
+ function syncTokensToCss(fileName) {
93
+ const tokenPath = path.join(TOKENS_DIR, `${fileName}.json`);
94
+ if (!fs.existsSync(tokenPath)) return;
95
+ const tokenData = JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
96
+ const cssVars = tokenData.cssVariables || {};
97
+ if (Object.keys(cssVars).length === 0) return;
98
+ const cssContent = fs.readFileSync(CSS_PATH, "utf-8");
99
+ const remaining = new Set(Object.keys(cssVars));
100
+ const updatedContent = cssContent.replace(
101
+ /^(\s*)(--[\w-]+):\s*(.+);/gm,
102
+ (_match, indent, varName, oldValue) => {
103
+ if (cssVars[varName] !== void 0) {
104
+ remaining.delete(varName);
105
+ return `${indent}${varName}: ${cssVars[varName]};`;
106
+ }
107
+ return `${indent}${varName}: ${oldValue.trim()};`;
108
+ }
109
+ );
110
+ let finalContent = updatedContent;
111
+ if (remaining.size > 0) {
112
+ const newVars = [...remaining].map((name) => ` ${name}: ${cssVars[name]};`).join("\n");
113
+ finalContent = finalContent.replace(
114
+ /\n\}(\s*)$/,
115
+ `
116
+
117
+ /* Token additions */
118
+ ${newVars}
119
+ }$1`
120
+ );
121
+ }
122
+ backupCssFile();
123
+ fs.writeFileSync(CSS_PATH, finalContent);
124
+ console.log(`[syncTokensToCss] Wrote ${Object.keys(cssVars).length} variables from "${fileName}" into variables.css`);
125
+ }
126
+ const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
127
+ const TOKENS_ROUTE = `${API_BASE}/tokens`;
128
+ const TOKENS_ACTIVE_ROUTE = `${API_BASE}/tokens/active`;
129
+ const TOKENS_PRODUCTION_ROUTE = `${API_BASE}/tokens/production`;
130
+ const BACKUPS_ROUTE = `${API_BASE}/backups`;
131
+ const CURRENT_CSS_ROUTE = `${API_BASE}/current-css`;
132
+ const TOKEN_BY_NAME_REGEX = new RegExp(`^${escapedBase}/tokens/([a-z0-9\\-_]+)$`);
133
+ const BACKUP_GET_REGEX = new RegExp(`^${escapedBase}/backups/(tokens|css)/(.+)$`);
134
+ const BACKUP_RESTORE_REGEX = new RegExp(`^${escapedBase}/backups/(tokens|css)/(.+)/restore$`);
135
+ return {
136
+ name: "token-file-api",
137
+ configureServer(server) {
138
+ ensureTokensDir();
139
+ server.middlewares.use(async (req, res, next) => {
140
+ const url = req.url || "";
141
+ if (url === TOKENS_ROUTE && req.method === "GET") {
142
+ try {
143
+ const activeFile = getActiveFileName();
144
+ const files = fs.readdirSync(TOKENS_DIR).filter((f) => f.endsWith(".json") && !f.startsWith("_")).map((f) => {
145
+ const filePath = path.join(TOKENS_DIR, f);
146
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
147
+ const fileName = f.replace(".json", "");
148
+ return {
149
+ name: data.name || fileName,
150
+ fileName,
151
+ updatedAt: data.updatedAt || "",
152
+ isActive: fileName === activeFile
153
+ };
154
+ });
155
+ jsonResponse(res, 200, { files });
156
+ } catch (err) {
157
+ jsonResponse(res, 500, { error: err.message });
158
+ }
159
+ return;
160
+ }
161
+ if (url === TOKENS_ACTIVE_ROUTE && req.method === "GET") {
162
+ try {
163
+ const activeFile = getActiveFileName();
164
+ const filePath = path.join(TOKENS_DIR, `${activeFile}.json`);
165
+ if (!fs.existsSync(filePath)) {
166
+ jsonResponse(res, 404, { error: "Active token file not found" });
167
+ return;
168
+ }
169
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
170
+ data._fileName = activeFile;
171
+ jsonResponse(res, 200, data);
172
+ } catch (err) {
173
+ jsonResponse(res, 500, { error: err.message });
174
+ }
175
+ return;
176
+ }
177
+ if (url === TOKENS_ACTIVE_ROUTE && req.method === "PUT") {
178
+ try {
179
+ const body = JSON.parse(await readBody(req));
180
+ const fileName = sanitize(body.name || "default");
181
+ if (!fs.existsSync(path.join(TOKENS_DIR, `${fileName}.json`))) {
182
+ jsonResponse(res, 404, { error: "Token file not found" });
183
+ return;
184
+ }
185
+ fs.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: fileName }));
186
+ jsonResponse(res, 200, { ok: true, activeFile: fileName });
187
+ } catch (err) {
188
+ jsonResponse(res, 500, { error: err.message });
189
+ }
190
+ return;
191
+ }
192
+ if (url === TOKENS_PRODUCTION_ROUTE && req.method === "GET") {
193
+ try {
194
+ const prodFile = getProductionFileName();
195
+ const filePath = path.join(TOKENS_DIR, `${prodFile}.json`);
196
+ if (!fs.existsSync(filePath)) {
197
+ jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
198
+ return;
199
+ }
200
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
201
+ jsonResponse(res, 200, {
202
+ fileName: prodFile,
203
+ name: data.name || prodFile,
204
+ updatedAt: data.updatedAt || "",
205
+ cssVariables: data.cssVariables || {}
206
+ });
207
+ } catch (err) {
208
+ jsonResponse(res, 500, { error: err.message });
209
+ }
210
+ return;
211
+ }
212
+ if (url === TOKENS_PRODUCTION_ROUTE && req.method === "PUT") {
213
+ try {
214
+ const body = JSON.parse(await readBody(req));
215
+ const fileName = sanitize(body.name || "default");
216
+ if (!fs.existsSync(path.join(TOKENS_DIR, `${fileName}.json`))) {
217
+ jsonResponse(res, 404, { error: "Token file not found" });
218
+ return;
219
+ }
220
+ fs.writeFileSync(PRODUCTION_FILE, JSON.stringify({ productionFile: fileName }));
221
+ syncTokensToCss(fileName);
222
+ const data = JSON.parse(fs.readFileSync(path.join(TOKENS_DIR, `${fileName}.json`), "utf-8"));
223
+ jsonResponse(res, 200, {
224
+ ok: true,
225
+ fileName,
226
+ name: data.name || fileName,
227
+ updatedAt: data.updatedAt || ""
228
+ });
229
+ } catch (err) {
230
+ jsonResponse(res, 500, { error: err.message });
231
+ }
232
+ return;
233
+ }
234
+ if (url === BACKUPS_ROUTE && req.method === "GET") {
235
+ try {
236
+ let fileTimestampToISO2 = function(ts) {
237
+ return ts.replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z/, "T$1:$2:$3.$4Z");
238
+ };
239
+ var fileTimestampToISO = fileTimestampToISO2;
240
+ const backups = [];
241
+ if (fs.existsSync(TOKENS_BACKUP_DIR)) {
242
+ for (const f of fs.readdirSync(TOKENS_BACKUP_DIR)) {
243
+ if (!f.endsWith(".json")) continue;
244
+ const match2 = f.match(/^(.+)_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.json$/);
245
+ if (!match2) continue;
246
+ const stat = fs.statSync(path.join(TOKENS_BACKUP_DIR, f));
247
+ backups.push({
248
+ type: "tokens",
249
+ file: f,
250
+ name: match2[1],
251
+ timestamp: fileTimestampToISO2(match2[2]),
252
+ size: stat.size
253
+ });
254
+ }
255
+ }
256
+ if (fs.existsSync(CSS_BACKUP_DIR)) {
257
+ for (const f of fs.readdirSync(CSS_BACKUP_DIR)) {
258
+ if (!f.endsWith(".css")) continue;
259
+ const match2 = f.match(/^(.+)_(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)\.css$/);
260
+ if (!match2) continue;
261
+ const stat = fs.statSync(path.join(CSS_BACKUP_DIR, f));
262
+ backups.push({
263
+ type: "css",
264
+ file: f,
265
+ name: match2[1],
266
+ timestamp: fileTimestampToISO2(match2[2]),
267
+ size: stat.size
268
+ });
269
+ }
270
+ }
271
+ backups.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
272
+ jsonResponse(res, 200, { backups });
273
+ } catch (err) {
274
+ jsonResponse(res, 500, { error: err.message });
275
+ }
276
+ return;
277
+ }
278
+ {
279
+ const restoreMatch = url.match(BACKUP_RESTORE_REGEX);
280
+ if (restoreMatch && req.method === "POST") {
281
+ const [, type, file] = restoreMatch;
282
+ try {
283
+ if (type === "css") {
284
+ const backupPath = path.join(CSS_BACKUP_DIR, decodeURIComponent(file));
285
+ if (!backupPath.startsWith(CSS_BACKUP_DIR) || !fs.existsSync(backupPath)) {
286
+ jsonResponse(res, 404, { error: "Backup not found" });
287
+ return;
288
+ }
289
+ backupCssFile();
290
+ fs.copyFileSync(backupPath, CSS_PATH);
291
+ jsonResponse(res, 200, { ok: true, restored: file });
292
+ } else {
293
+ const backupPath = path.join(TOKENS_BACKUP_DIR, decodeURIComponent(file));
294
+ if (!backupPath.startsWith(TOKENS_BACKUP_DIR) || !fs.existsSync(backupPath)) {
295
+ jsonResponse(res, 404, { error: "Backup not found" });
296
+ return;
297
+ }
298
+ const nameMatch = decodeURIComponent(file).match(/^(.+)_\d{4}-/);
299
+ if (!nameMatch) {
300
+ jsonResponse(res, 400, { error: "Cannot determine token file name from backup" });
301
+ return;
302
+ }
303
+ const tokenFilePath = path.join(TOKENS_DIR, `${nameMatch[1]}.json`);
304
+ backupTokenFile(tokenFilePath, nameMatch[1]);
305
+ fs.copyFileSync(backupPath, tokenFilePath);
306
+ jsonResponse(res, 200, { ok: true, restored: file });
307
+ }
308
+ } catch (err) {
309
+ jsonResponse(res, 500, { error: err.message });
310
+ }
311
+ return;
312
+ }
313
+ }
314
+ {
315
+ const backupMatch = url.match(BACKUP_GET_REGEX);
316
+ if (backupMatch && req.method === "GET") {
317
+ const [, type, file] = backupMatch;
318
+ const dir = type === "css" ? CSS_BACKUP_DIR : TOKENS_BACKUP_DIR;
319
+ const filePath = path.join(dir, decodeURIComponent(file));
320
+ if (!filePath.startsWith(dir) || !fs.existsSync(filePath)) {
321
+ jsonResponse(res, 404, { error: "Backup not found" });
322
+ return;
323
+ }
324
+ const content = fs.readFileSync(filePath, "utf-8");
325
+ jsonResponse(res, 200, { content, type, file });
326
+ return;
327
+ }
328
+ }
329
+ if (url === CURRENT_CSS_ROUTE && req.method === "GET") {
330
+ try {
331
+ const content = fs.readFileSync(CSS_PATH, "utf-8");
332
+ jsonResponse(res, 200, { content });
333
+ } catch (err) {
334
+ jsonResponse(res, 500, { error: err.message });
335
+ }
336
+ return;
337
+ }
338
+ const match = url.match(TOKEN_BY_NAME_REGEX);
339
+ if (match) {
340
+ const fileName = match[1];
341
+ const filePath = path.join(TOKENS_DIR, `${fileName}.json`);
342
+ if (req.method === "GET") {
343
+ try {
344
+ if (!fs.existsSync(filePath)) {
345
+ jsonResponse(res, 404, { error: "Not found" });
346
+ return;
347
+ }
348
+ const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
349
+ data._fileName = fileName;
350
+ jsonResponse(res, 200, data);
351
+ } catch (err) {
352
+ jsonResponse(res, 500, { error: err.message });
353
+ }
354
+ return;
355
+ }
356
+ if (req.method === "PUT") {
357
+ try {
358
+ const body = JSON.parse(await readBody(req));
359
+ body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
360
+ if (fs.existsSync(filePath)) {
361
+ try {
362
+ const existing = JSON.parse(fs.readFileSync(filePath, "utf-8"));
363
+ if (existing.createdAt) body.createdAt = existing.createdAt;
364
+ } catch {
365
+ }
366
+ }
367
+ if (!body.createdAt) body.createdAt = body.updatedAt;
368
+ backupTokenFile(filePath, fileName);
369
+ fs.writeFileSync(filePath, JSON.stringify(body, null, 2));
370
+ if (fileName === getProductionFileName()) {
371
+ syncTokensToCss(fileName);
372
+ }
373
+ jsonResponse(res, 200, { ok: true, fileName });
374
+ } catch (err) {
375
+ jsonResponse(res, 500, { error: err.message });
376
+ }
377
+ return;
378
+ }
379
+ if (req.method === "DELETE") {
380
+ if (fileName === "default") {
381
+ jsonResponse(res, 403, { error: "Cannot delete the default token file" });
382
+ return;
383
+ }
384
+ try {
385
+ if (fs.existsSync(filePath)) {
386
+ fs.unlinkSync(filePath);
387
+ if (getActiveFileName() === fileName) {
388
+ fs.writeFileSync(ACTIVE_FILE, JSON.stringify({ activeFile: "default" }));
389
+ }
390
+ }
391
+ jsonResponse(res, 200, { ok: true });
392
+ } catch (err) {
393
+ jsonResponse(res, 500, { error: err.message });
394
+ }
395
+ return;
396
+ }
397
+ jsonResponse(res, 405, { error: "Method not allowed" });
398
+ return;
399
+ }
400
+ next();
401
+ });
402
+ }
403
+ };
404
+ }
405
+ export {
406
+ tokenFileApi
407
+ };
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@motion-proto/live-tokens",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Design token editor with live CSS variable editing. Svelte 4 + Vite.",
6
+ "keywords": ["svelte", "vite", "design-tokens", "css-variables", "editor"],
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/motionproto/live-tokens.git"
11
+ },
12
+ "homepage": "https://github.com/motionproto/live-tokens",
13
+ "bugs": "https://github.com/motionproto/live-tokens/issues",
14
+ "files": [
15
+ "src/lib",
16
+ "src/showcase",
17
+ "src/components",
18
+ "src/pages/Admin.svelte",
19
+ "src/pages/ShowcasePage.svelte",
20
+ "src/showcase/editor.css",
21
+ "src/styles/form-controls.css",
22
+ "src/styles/fonts.css",
23
+ "src/styles/fonts",
24
+ "dist-plugin"
25
+ ],
26
+ "exports": {
27
+ ".": {
28
+ "svelte": "./src/lib/index.ts",
29
+ "types": "./src/lib/index.ts",
30
+ "default": "./src/lib/index.ts"
31
+ },
32
+ "./components": {
33
+ "svelte": "./src/showcase/index.ts",
34
+ "types": "./src/showcase/index.ts",
35
+ "default": "./src/showcase/index.ts"
36
+ },
37
+ "./admin": {
38
+ "svelte": "./src/pages/Admin.svelte"
39
+ },
40
+ "./showcase-page": {
41
+ "svelte": "./src/pages/ShowcasePage.svelte"
42
+ },
43
+ "./overlay": {
44
+ "svelte": "./src/lib/LiveEditorOverlay.svelte"
45
+ },
46
+ "./columns-overlay": {
47
+ "svelte": "./src/lib/ColumnsOverlay.svelte"
48
+ },
49
+ "./vite-plugin": {
50
+ "types": "./dist-plugin/index.d.ts",
51
+ "import": "./dist-plugin/index.js",
52
+ "require": "./dist-plugin/index.cjs"
53
+ },
54
+ "./styles/editor.css": "./src/showcase/editor.css",
55
+ "./styles/form-controls.css": "./src/styles/form-controls.css",
56
+ "./styles/fonts.css": "./src/styles/fonts.css"
57
+ },
58
+ "scripts": {
59
+ "dev": "vite",
60
+ "build": "vite build",
61
+ "preview": "vite preview",
62
+ "check": "svelte-check --tsconfig ./tsconfig.json",
63
+ "build:plugin": "tsup src/vite-plugin/index.ts --out-dir dist-plugin --format esm,cjs --dts --external vite --platform node --clean",
64
+ "build:lib": "npm run build:plugin",
65
+ "prepublishOnly": "npm run build:lib"
66
+ },
67
+ "peerDependencies": {
68
+ "svelte": "^4.2",
69
+ "vite": "^5.0"
70
+ },
71
+ "devDependencies": {
72
+ "@sveltejs/vite-plugin-svelte": "^3.1.2",
73
+ "@tsconfig/svelte": "^5.0.8",
74
+ "@types/node": "^24.12.0",
75
+ "svelte": "^4.2.20",
76
+ "svelte-check": "^3.8.6",
77
+ "tsup": "^8.5.1",
78
+ "typescript": "~5.9.3",
79
+ "vite": "^5.4.21"
80
+ },
81
+ "dependencies": {
82
+ "@fortawesome/fontawesome-free": "^7.2.0",
83
+ "sass": "^1.98.0",
84
+ "svelte-preprocess": "^6.0.3"
85
+ }
86
+ }
@@ -0,0 +1,82 @@
1
+ <script lang="ts">
2
+ export let variant: 'info' | 'accent' | 'trait' | 'hero' = 'info';
3
+ export let size: 'default' | 'small' = 'default';
4
+ export let icon: string | undefined = undefined;
5
+ </script>
6
+
7
+ <span class="badge badge-{variant}" class:badge-small={size === 'small'}>
8
+ {#if icon}
9
+ <span class="icon"><i class={icon}></i></span>
10
+ {:else}
11
+ <span class="icon"><slot name="icon" /></span>
12
+ {/if}
13
+ <slot />
14
+ </span>
15
+
16
+ <style>
17
+ .badge {
18
+ display: inline-flex;
19
+ align-items: center;
20
+ gap: var(--space-6);
21
+ font-size: var(--font-sm);
22
+ line-height: 1;
23
+ padding: var(--space-6) var(--space-12);
24
+ border-radius: var(--radius-full);
25
+ font-family: var(--font-sans);
26
+ font-weight: var(--font-weight-medium);
27
+ white-space: nowrap;
28
+ }
29
+
30
+ .icon {
31
+ display: inline-flex;
32
+ align-items: center;
33
+ font-size: 1em;
34
+ }
35
+
36
+ .icon:empty {
37
+ display: none;
38
+ }
39
+
40
+ .badge-small {
41
+ font-size: var(--font-xs);
42
+ padding: var(--space-6) var(--space-12);
43
+ gap: var(--space-4);
44
+ }
45
+
46
+ /* Info (default) */
47
+ .badge-info {
48
+ color: var(--text-primary);
49
+ background: var(--surface-neutral-higher);
50
+ border: 1px solid var(--border-neutral-default);
51
+ box-shadow: var(--shadow-sm);
52
+ }
53
+
54
+
55
+ /* Accent */
56
+ .badge-accent {
57
+ color: var(--color-accent-300);
58
+ background: var(--surface-neutral-higher);
59
+ border: 1px solid var(--border-accent);
60
+ box-shadow: var(--shadow-sm);
61
+ text-transform: capitalize;
62
+ }
63
+
64
+
65
+ /* Hero */
66
+ .badge-hero {
67
+ color: #fff;
68
+ background: rgba(0, 0, 0, 0.35);
69
+ backdrop-filter: blur(8px);
70
+ border: 1px solid rgba(255, 255, 255, 0.12);
71
+ font-weight: var(--font-weight-semibold);
72
+ letter-spacing: 0.02em;
73
+ }
74
+
75
+ /* Trait */
76
+ .badge-trait {
77
+ color: var(--text-primary);
78
+ background: var(--surface-primary-high);
79
+ border: 1px solid var(--border-primary-strong);
80
+ text-transform: capitalize;
81
+ }
82
+ </style>