cynx-lib 1.3.50 → 1.3.52
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/package.json +6 -1
- package/ui/components/Layout.jsx +138 -831
- package/ui/components/Sidebar.jsx +820 -0
- package/ui/index.cjs +3 -0
- package/ui/index.js +2 -0
- package/utils/utils/db.cjs +40 -2
- package/utils/utils/db.js +43 -2
- package/vue/components/Badge.vue +64 -0
- package/vue/components/Button.vue +161 -0
- package/vue/components/Checkbox.vue +97 -0
- package/vue/components/Input.vue +341 -0
- package/vue/components/Pagination.vue +127 -0
- package/vue/components/Select.vue +755 -0
- package/vue/components/Sidebar.vue +954 -0
- package/vue/components/Skeleton.vue +91 -0
- package/vue/components/Spinner.vue +146 -0
- package/vue/components/TabBar.vue +176 -0
- package/vue/components/Table.vue +323 -0
- package/vue/components/XTable.vue +119 -0
- package/vue/composables/useAutoSkeletonRows.ts +71 -0
- package/vue/index.d.ts +58 -0
- package/vue/index.js +56 -0
package/ui/index.cjs
CHANGED
|
@@ -55,6 +55,7 @@ __export(index_exports, {
|
|
|
55
55
|
Pagination: () => Pagination,
|
|
56
56
|
PulseDot: () => PulseDot,
|
|
57
57
|
Select: () => Select,
|
|
58
|
+
Sidebar: () => Sidebar,
|
|
58
59
|
Skeleton: () => Skeleton,
|
|
59
60
|
SkeletonCard: () => SkeletonCard,
|
|
60
61
|
SkeletonRows: () => SkeletonRows,
|
|
@@ -126,8 +127,10 @@ var import_FilePicker = __toESM(require("./components/FilePicker.jsx"), 1);
|
|
|
126
127
|
var import_Divider = __toESM(require("./components/Divider.jsx"), 1);
|
|
127
128
|
var import_InfoBadge = __toESM(require("./components/InfoBadge.jsx"), 1);
|
|
128
129
|
var import_Layout = __toESM(require("./components/Layout.jsx"), 1);
|
|
130
|
+
var import_Sidebar = __toESM(require("./components/Sidebar.jsx"), 1);
|
|
129
131
|
var import_LoginPage = __toESM(require("./components/LoginPage.jsx"), 1);
|
|
130
132
|
const Layout = import_Layout.default;
|
|
133
|
+
const Sidebar = import_Sidebar.default;
|
|
131
134
|
const LoginPage = import_LoginPage.default;
|
|
132
135
|
const Button = import_Button.default;
|
|
133
136
|
const FilePicker = import_FilePicker.default;
|
package/ui/index.js
CHANGED
|
@@ -38,9 +38,11 @@ import { default as _FilePicker } from './components/FilePicker.jsx';
|
|
|
38
38
|
import { default as _Divider } from './components/Divider.jsx';
|
|
39
39
|
import { default as _InfoBadge } from './components/InfoBadge.jsx';
|
|
40
40
|
import { default as _Layout } from './components/Layout.jsx';
|
|
41
|
+
import { default as _Sidebar } from './components/Sidebar.jsx';
|
|
41
42
|
import { default as _LoginPage } from './components/LoginPage.jsx';
|
|
42
43
|
|
|
43
44
|
export const Layout = _Layout;
|
|
45
|
+
export const Sidebar = _Sidebar;
|
|
44
46
|
export const LoginPage = _LoginPage;
|
|
45
47
|
export const Button = _Button;
|
|
46
48
|
export const FilePicker = _FilePicker;
|
package/utils/utils/db.cjs
CHANGED
|
@@ -196,6 +196,7 @@ async function sync_db(options = {}) {
|
|
|
196
196
|
freshCheckTable,
|
|
197
197
|
logger: rawLogger = console,
|
|
198
198
|
dryRun = false,
|
|
199
|
+
syncColumnTypes = false,
|
|
199
200
|
maxRetries = 3,
|
|
200
201
|
retryDelayMs = 300,
|
|
201
202
|
throwOnError = false
|
|
@@ -316,16 +317,21 @@ async function sync_db(options = {}) {
|
|
|
316
317
|
log("Analyzing schema.sql and comparing with database catalogs (Zero-Lock)...");
|
|
317
318
|
const [tablesRes, colsRes, idxRes, conRes] = await Promise.all([
|
|
318
319
|
queryFn("SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE'", [tableSchema]),
|
|
319
|
-
queryFn("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = $1", [tableSchema]),
|
|
320
|
+
queryFn("SELECT table_name, column_name, data_type, udt_name FROM information_schema.columns WHERE table_schema = $1", [tableSchema]),
|
|
320
321
|
queryFn("SELECT indexname FROM pg_indexes WHERE schemaname = $1", [tableSchema]),
|
|
321
322
|
queryFn("SELECT conname FROM pg_constraint")
|
|
322
323
|
]);
|
|
323
324
|
const existingTables = new Set((tablesRes.rows || []).map((r) => String(r.table_name).toLowerCase()));
|
|
324
325
|
const existingColumns = new Set((colsRes.rows || []).map((r) => `${String(r.table_name).toLowerCase()}.${String(r.column_name).toLowerCase()}`));
|
|
326
|
+
const existingColumnTypes = new Map((colsRes.rows || []).map((r) => [
|
|
327
|
+
`${String(r.table_name).toLowerCase()}.${String(r.column_name).toLowerCase()}`,
|
|
328
|
+
{ dataType: String(r.data_type || "").toLowerCase(), udtName: String(r.udt_name || "").toLowerCase() }
|
|
329
|
+
]));
|
|
325
330
|
const existingIndexes = new Set((idxRes.rows || []).map((r) => String(r.indexname).toLowerCase()));
|
|
326
331
|
const existingConstraints = new Set((conRes.rows || []).map((r) => String(r.conname).toLowerCase()));
|
|
327
332
|
const createdTables = [];
|
|
328
333
|
const addedColumns = [];
|
|
334
|
+
const alteredColumns = [];
|
|
329
335
|
const createdIndexes = [];
|
|
330
336
|
const droppedConstraints = [];
|
|
331
337
|
const droppedIndexes = [];
|
|
@@ -373,6 +379,36 @@ async function sync_db(options = {}) {
|
|
|
373
379
|
addedColumns.push(colKey);
|
|
374
380
|
log(`[DryRun] Would add column: ${colKey}`);
|
|
375
381
|
}
|
|
382
|
+
} else if (syncColumnTypes) {
|
|
383
|
+
const currentType = existingColumnTypes.get(colKey);
|
|
384
|
+
const defUpper = col.def.toUpperCase().trim();
|
|
385
|
+
let targetType = null;
|
|
386
|
+
if (/\bTIMESTAMPTZ\b|\bTIMESTAMP\s+WITH\s+TIME\s+ZONE\b/.test(defUpper)) {
|
|
387
|
+
if (currentType && (currentType.dataType === "timestamp without time zone" || currentType.udtName === "timestamp")) {
|
|
388
|
+
targetType = "TIMESTAMPTZ";
|
|
389
|
+
}
|
|
390
|
+
} else if (/\bBIGINT\b|\bBIGSERIAL\b/.test(defUpper)) {
|
|
391
|
+
if (currentType && (currentType.dataType === "integer" || currentType.udtName === "int4")) {
|
|
392
|
+
targetType = "BIGINT";
|
|
393
|
+
}
|
|
394
|
+
} else if (/\bTEXT\b/.test(defUpper)) {
|
|
395
|
+
if (currentType && (currentType.dataType === "character varying" || currentType.udtName === "varchar")) {
|
|
396
|
+
targetType = "TEXT";
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (targetType) {
|
|
400
|
+
const sql = `ALTER TABLE ${table.name} ALTER COLUMN ${col.name} TYPE ${targetType};`;
|
|
401
|
+
if (!dryRun) {
|
|
402
|
+
const ok = await runSafeDdl(queryFn, sql, `alter column ${colKey} to ${targetType}`, safeDdlOpts);
|
|
403
|
+
if (ok) {
|
|
404
|
+
alteredColumns.push(`${colKey} (${targetType})`);
|
|
405
|
+
log(`Altered column type: ${colKey} -> ${targetType}`);
|
|
406
|
+
}
|
|
407
|
+
} else {
|
|
408
|
+
alteredColumns.push(`${colKey} (${targetType})`);
|
|
409
|
+
log(`[DryRun] Would alter column type: ${colKey} -> ${targetType}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
376
412
|
}
|
|
377
413
|
}
|
|
378
414
|
}
|
|
@@ -445,11 +481,12 @@ async function sync_db(options = {}) {
|
|
|
445
481
|
const summaryParts = [];
|
|
446
482
|
if (createdTables.length > 0) summaryParts.push(`${createdTables.length} tables created`);
|
|
447
483
|
if (addedColumns.length > 0) summaryParts.push(`${addedColumns.length} columns added`);
|
|
484
|
+
if (alteredColumns.length > 0) summaryParts.push(`${alteredColumns.length} columns altered`);
|
|
448
485
|
if (createdIndexes.length > 0) summaryParts.push(`${createdIndexes.length} indexes created`);
|
|
449
486
|
if (droppedConstraints.length > 0) summaryParts.push(`${droppedConstraints.length} constraints dropped`);
|
|
450
487
|
if (droppedIndexes.length > 0) summaryParts.push(`${droppedIndexes.length} indexes dropped`);
|
|
451
488
|
if (executedDoBlocks > 0) summaryParts.push(`${executedDoBlocks} migration blocks run`);
|
|
452
|
-
const changesCount = createdTables.length + addedColumns.length + createdIndexes.length + droppedConstraints.length + droppedIndexes.length;
|
|
489
|
+
const changesCount = createdTables.length + addedColumns.length + alteredColumns.length + createdIndexes.length + droppedConstraints.length + droppedIndexes.length;
|
|
453
490
|
const summary = summaryParts.length > 0 ? `Database synced in ${durationMs}ms: ${summaryParts.join(", ")}` : `Database schema is up to date (0 changes needed, ${durationMs}ms)`;
|
|
454
491
|
log(summary);
|
|
455
492
|
return {
|
|
@@ -457,6 +494,7 @@ async function sync_db(options = {}) {
|
|
|
457
494
|
isFreshDb: false,
|
|
458
495
|
createdTables,
|
|
459
496
|
addedColumns,
|
|
497
|
+
alteredColumns,
|
|
460
498
|
createdIndexes,
|
|
461
499
|
droppedConstraints,
|
|
462
500
|
droppedIndexes,
|
package/utils/utils/db.js
CHANGED
|
@@ -281,6 +281,7 @@ export async function sync_db(options = {}) {
|
|
|
281
281
|
freshCheckTable,
|
|
282
282
|
logger: rawLogger = console,
|
|
283
283
|
dryRun = false,
|
|
284
|
+
syncColumnTypes = false,
|
|
284
285
|
maxRetries = 3,
|
|
285
286
|
retryDelayMs = 300,
|
|
286
287
|
throwOnError = false,
|
|
@@ -409,18 +410,23 @@ export async function sync_db(options = {}) {
|
|
|
409
410
|
// ── 2. Query System Catalogs in parallel (AccessShareLock only - 0ms blocking) ──
|
|
410
411
|
const [tablesRes, colsRes, idxRes, conRes] = await Promise.all([
|
|
411
412
|
queryFn("SELECT table_name FROM information_schema.tables WHERE table_schema = $1 AND table_type = 'BASE TABLE'", [tableSchema]),
|
|
412
|
-
queryFn("SELECT table_name, column_name FROM information_schema.columns WHERE table_schema = $1", [tableSchema]),
|
|
413
|
+
queryFn("SELECT table_name, column_name, data_type, udt_name FROM information_schema.columns WHERE table_schema = $1", [tableSchema]),
|
|
413
414
|
queryFn("SELECT indexname FROM pg_indexes WHERE schemaname = $1", [tableSchema]),
|
|
414
415
|
queryFn("SELECT conname FROM pg_constraint"),
|
|
415
416
|
]);
|
|
416
417
|
|
|
417
418
|
const existingTables = new Set((tablesRes.rows || []).map((r) => String(r.table_name).toLowerCase()));
|
|
418
419
|
const existingColumns = new Set((colsRes.rows || []).map((r) => `${String(r.table_name).toLowerCase()}.${String(r.column_name).toLowerCase()}`));
|
|
420
|
+
const existingColumnTypes = new Map((colsRes.rows || []).map((r) => [
|
|
421
|
+
`${String(r.table_name).toLowerCase()}.${String(r.column_name).toLowerCase()}`,
|
|
422
|
+
{ dataType: String(r.data_type || '').toLowerCase(), udtName: String(r.udt_name || '').toLowerCase() }
|
|
423
|
+
]));
|
|
419
424
|
const existingIndexes = new Set((idxRes.rows || []).map((r) => String(r.indexname).toLowerCase()));
|
|
420
425
|
const existingConstraints = new Set((conRes.rows || []).map((r) => String(r.conname).toLowerCase()));
|
|
421
426
|
|
|
422
427
|
const createdTables = [];
|
|
423
428
|
const addedColumns = [];
|
|
429
|
+
const alteredColumns = [];
|
|
424
430
|
const createdIndexes = [];
|
|
425
431
|
const droppedConstraints = [];
|
|
426
432
|
const droppedIndexes = [];
|
|
@@ -476,6 +482,39 @@ export async function sync_db(options = {}) {
|
|
|
476
482
|
addedColumns.push(colKey);
|
|
477
483
|
log(`[DryRun] Would add column: ${colKey}`);
|
|
478
484
|
}
|
|
485
|
+
} else if (syncColumnTypes) {
|
|
486
|
+
// Check for safe column type discrepancies when syncColumnTypes flag is true
|
|
487
|
+
const currentType = existingColumnTypes.get(colKey);
|
|
488
|
+
const defUpper = col.def.toUpperCase().trim();
|
|
489
|
+
|
|
490
|
+
let targetType = null;
|
|
491
|
+
if (/\bTIMESTAMPTZ\b|\bTIMESTAMP\s+WITH\s+TIME\s+ZONE\b/.test(defUpper)) {
|
|
492
|
+
if (currentType && (currentType.dataType === 'timestamp without time zone' || currentType.udtName === 'timestamp')) {
|
|
493
|
+
targetType = 'TIMESTAMPTZ';
|
|
494
|
+
}
|
|
495
|
+
} else if (/\bBIGINT\b|\bBIGSERIAL\b/.test(defUpper)) {
|
|
496
|
+
if (currentType && (currentType.dataType === 'integer' || currentType.udtName === 'int4')) {
|
|
497
|
+
targetType = 'BIGINT';
|
|
498
|
+
}
|
|
499
|
+
} else if (/\bTEXT\b/.test(defUpper)) {
|
|
500
|
+
if (currentType && (currentType.dataType === 'character varying' || currentType.udtName === 'varchar')) {
|
|
501
|
+
targetType = 'TEXT';
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (targetType) {
|
|
506
|
+
const sql = `ALTER TABLE ${table.name} ALTER COLUMN ${col.name} TYPE ${targetType};`;
|
|
507
|
+
if (!dryRun) {
|
|
508
|
+
const ok = await runSafeDdl(queryFn, sql, `alter column ${colKey} to ${targetType}`, safeDdlOpts);
|
|
509
|
+
if (ok) {
|
|
510
|
+
alteredColumns.push(`${colKey} (${targetType})`);
|
|
511
|
+
log(`Altered column type: ${colKey} -> ${targetType}`);
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
alteredColumns.push(`${colKey} (${targetType})`);
|
|
515
|
+
log(`[DryRun] Would alter column type: ${colKey} -> ${targetType}`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
479
518
|
}
|
|
480
519
|
}
|
|
481
520
|
}
|
|
@@ -556,12 +595,13 @@ export async function sync_db(options = {}) {
|
|
|
556
595
|
const summaryParts = [];
|
|
557
596
|
if (createdTables.length > 0) summaryParts.push(`${createdTables.length} tables created`);
|
|
558
597
|
if (addedColumns.length > 0) summaryParts.push(`${addedColumns.length} columns added`);
|
|
598
|
+
if (alteredColumns.length > 0) summaryParts.push(`${alteredColumns.length} columns altered`);
|
|
559
599
|
if (createdIndexes.length > 0) summaryParts.push(`${createdIndexes.length} indexes created`);
|
|
560
600
|
if (droppedConstraints.length > 0) summaryParts.push(`${droppedConstraints.length} constraints dropped`);
|
|
561
601
|
if (droppedIndexes.length > 0) summaryParts.push(`${droppedIndexes.length} indexes dropped`);
|
|
562
602
|
if (executedDoBlocks > 0) summaryParts.push(`${executedDoBlocks} migration blocks run`);
|
|
563
603
|
|
|
564
|
-
const changesCount = createdTables.length + addedColumns.length + createdIndexes.length + droppedConstraints.length + droppedIndexes.length;
|
|
604
|
+
const changesCount = createdTables.length + addedColumns.length + alteredColumns.length + createdIndexes.length + droppedConstraints.length + droppedIndexes.length;
|
|
565
605
|
const summary = summaryParts.length > 0
|
|
566
606
|
? `Database synced in ${durationMs}ms: ${summaryParts.join(', ')}`
|
|
567
607
|
: `Database schema is up to date (0 changes needed, ${durationMs}ms)`;
|
|
@@ -573,6 +613,7 @@ export async function sync_db(options = {}) {
|
|
|
573
613
|
isFreshDb: false,
|
|
574
614
|
createdTables,
|
|
575
615
|
addedColumns,
|
|
616
|
+
alteredColumns,
|
|
576
617
|
createdIndexes,
|
|
577
618
|
droppedConstraints,
|
|
578
619
|
droppedIndexes,
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
|
|
4
|
+
const VARIANTS: Record<string, any> = {
|
|
5
|
+
ok: { bg: "rgba(39,174,96,.1)", color: "#16a34a", border: "rgba(39,174,96,.2)", dot: "#16a34a" },
|
|
6
|
+
err: { bg: "rgba(246,71,71,.1)", color: "#ef4444", border: "rgba(246,71,71,.2)", dot: "#ef4444" },
|
|
7
|
+
warn: { bg: "rgba(249,158,44,.1)", color: "#f59e0b", border: "rgba(249,158,44,.25)", dot: "#f59e0b" },
|
|
8
|
+
blue: { bg: "rgba(39,130,228,.1)", color: "#2563eb", border: "rgba(39,130,228,.2)", dot: "#2563eb" },
|
|
9
|
+
teal: { bg: "rgba(18,103,93,.1)", color: "#12675D", border: "rgba(18,103,93,.25)", dot: "#12675D" },
|
|
10
|
+
dim: { bg: "#f1f5f9", color: "#64748b", border: "#e2e8f0", dot: "#64748b" },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const SIZE_MAP: Record<string, any> = {
|
|
14
|
+
xs: { padding: "1.5px 6px", fontSize: "9.5px", dotSize: 5 },
|
|
15
|
+
sm: { padding: "2px 8px", fontSize: "11px", dotSize: 6 },
|
|
16
|
+
md: { padding: "3px 9px", fontSize: "12px", dotSize: 6.5 },
|
|
17
|
+
lg: { padding: "4px 12px", fontSize: "13px", dotSize: 8 },
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const props = withDefaults(
|
|
21
|
+
defineProps<{
|
|
22
|
+
variant?: string;
|
|
23
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
24
|
+
dot?: boolean;
|
|
25
|
+
pill?: boolean;
|
|
26
|
+
}>(),
|
|
27
|
+
{
|
|
28
|
+
variant: "dim",
|
|
29
|
+
size: "sm",
|
|
30
|
+
dot: false,
|
|
31
|
+
pill: true,
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const v = computed(() => VARIANTS[props.variant] || VARIANTS.dim);
|
|
36
|
+
const s = computed(() => SIZE_MAP[props.size] || SIZE_MAP.sm);
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<span
|
|
41
|
+
:class="[
|
|
42
|
+
'inline-flex items-center gap-1.5 font-bold leading-none select-none',
|
|
43
|
+
pill ? 'rounded-full' : 'rounded-md'
|
|
44
|
+
]"
|
|
45
|
+
:style="{
|
|
46
|
+
background: v.bg,
|
|
47
|
+
color: v.color,
|
|
48
|
+
border: `1px solid ${v.border}`,
|
|
49
|
+
padding: s.padding,
|
|
50
|
+
fontSize: s.fontSize,
|
|
51
|
+
}"
|
|
52
|
+
>
|
|
53
|
+
<span
|
|
54
|
+
v-if="dot"
|
|
55
|
+
class="rounded-full flex-shrink-0"
|
|
56
|
+
:style="{
|
|
57
|
+
width: `${s.dotSize}px`,
|
|
58
|
+
height: `${s.dotSize}px`,
|
|
59
|
+
background: v.dot,
|
|
60
|
+
}"
|
|
61
|
+
/>
|
|
62
|
+
<slot />
|
|
63
|
+
</span>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import Spinner from "./Spinner.vue";
|
|
4
|
+
|
|
5
|
+
const VARIANTS: Record<string, any> = {
|
|
6
|
+
default: {
|
|
7
|
+
background: "var(--surface, #fff)",
|
|
8
|
+
color: "var(--def-btn-text, #0f111c)",
|
|
9
|
+
border: "1px solid var(--bd, #d7d8e0)",
|
|
10
|
+
hoverBg: "var(--grey-1, #f5f5f8)",
|
|
11
|
+
hoverBorder: "var(--grey-2, #ebebee)",
|
|
12
|
+
},
|
|
13
|
+
primary: {
|
|
14
|
+
background: "var(--accent, #12675D)",
|
|
15
|
+
color: "#fff",
|
|
16
|
+
border: "1px solid transparent",
|
|
17
|
+
hoverBg: "var(--accent-hover, #0e524a)",
|
|
18
|
+
hoverShadow: "0 2px 8px rgba(18, 103, 93, .35)",
|
|
19
|
+
},
|
|
20
|
+
secondary: {
|
|
21
|
+
background: "var(--blue-bg, rgba(39,130,228,.1))",
|
|
22
|
+
color: "var(--blue, #2782e4)",
|
|
23
|
+
border: "1px solid var(--infd, rgba(39,130,228,.2))",
|
|
24
|
+
hoverBg: "var(--infd, rgba(39,130,228,.18))",
|
|
25
|
+
hoverShadow: "0 2px 8px rgba(39,130,228,.25)",
|
|
26
|
+
},
|
|
27
|
+
danger: {
|
|
28
|
+
background: "var(--danger-bg, rgba(246,71,71,.1))",
|
|
29
|
+
color: "var(--danger, #f64747)",
|
|
30
|
+
border: "1px solid var(--dd, rgba(246,71,71,.2))",
|
|
31
|
+
hoverBg: "var(--dd, rgba(246,71,71,.18))",
|
|
32
|
+
hoverShadow: "0 2px 8px rgba(246,71,71,.25)",
|
|
33
|
+
},
|
|
34
|
+
"danger-solid": {
|
|
35
|
+
background: "var(--danger, #EF4444)",
|
|
36
|
+
color: "#fff",
|
|
37
|
+
border: "1px solid transparent",
|
|
38
|
+
hoverBg: "#dc2626",
|
|
39
|
+
hoverShadow: "0 2px 8px rgba(239, 68, 68, .35)",
|
|
40
|
+
},
|
|
41
|
+
"danger-fill": {
|
|
42
|
+
background: "var(--danger, #EF4444)",
|
|
43
|
+
color: "#fff",
|
|
44
|
+
border: "1px solid transparent",
|
|
45
|
+
hoverBg: "#dc2626",
|
|
46
|
+
hoverShadow: "0 2px 8px rgba(239, 68, 68, .35)",
|
|
47
|
+
},
|
|
48
|
+
success: {
|
|
49
|
+
background: "var(--success-bg, rgba(39,174,96,.1))",
|
|
50
|
+
color: "var(--success, #27ae60)",
|
|
51
|
+
border: "1px solid var(--sd, rgba(39,174,96,.2))",
|
|
52
|
+
hoverBg: "var(--sd, rgba(39,174,96,.18))",
|
|
53
|
+
hoverShadow: "0 2px 8px rgba(39,174,96,.25)",
|
|
54
|
+
},
|
|
55
|
+
warning: {
|
|
56
|
+
background: "var(--accent-bg, rgba(249,158,44,.1))",
|
|
57
|
+
color: "var(--accent, #f99e2c)",
|
|
58
|
+
border: "1px solid var(--accent-border, rgba(249,158,44,.25))",
|
|
59
|
+
hoverBg: "var(--pd, rgba(249,158,44,.18))",
|
|
60
|
+
hoverShadow: "0 2px 8px rgba(249,158,44,.25)",
|
|
61
|
+
},
|
|
62
|
+
ghost: {
|
|
63
|
+
background: "var(--surface, #ffffff)",
|
|
64
|
+
color: "var(--t2, #797b8d)",
|
|
65
|
+
border: "1px solid var(--bd, #d7d8e0)",
|
|
66
|
+
hoverColor: "var(--t1, #1a1a2e)",
|
|
67
|
+
hoverBg: "var(--grey-1, #f5f5f8)",
|
|
68
|
+
hoverBorder: "var(--grey-2, #ebebee)",
|
|
69
|
+
},
|
|
70
|
+
outline: {
|
|
71
|
+
background: "transparent",
|
|
72
|
+
color: "var(--accent, #12675D)",
|
|
73
|
+
border: "1px solid var(--accent, #12675D)",
|
|
74
|
+
hoverBg: "var(--accent-bg, rgba(18, 103, 93, .1))",
|
|
75
|
+
},
|
|
76
|
+
link: {
|
|
77
|
+
background: "transparent",
|
|
78
|
+
color: "var(--accent, #12675D)",
|
|
79
|
+
border: "1px solid transparent",
|
|
80
|
+
paddingLeft: "4px",
|
|
81
|
+
paddingRight: "4px",
|
|
82
|
+
boxShadow: "none",
|
|
83
|
+
},
|
|
84
|
+
"no-line": {
|
|
85
|
+
background: "none",
|
|
86
|
+
border: "none",
|
|
87
|
+
boxShadow: "none",
|
|
88
|
+
color: "var(--t3, #8a96b0)",
|
|
89
|
+
paddingLeft: "14px",
|
|
90
|
+
paddingRight: "14px",
|
|
91
|
+
borderRadius: "var(--rmd, 8px)",
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const SIZES: Record<string, any> = {
|
|
96
|
+
xs: { padding: "0 6px", fontSize: 11, height: 24 },
|
|
97
|
+
sm: { padding: "0 8px", fontSize: 11.5, height: 28 },
|
|
98
|
+
md: { padding: "0 14px", fontSize: 13, height: 32 },
|
|
99
|
+
lg: { padding: "0 18px", fontSize: 14, height: 36 },
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const props = withDefaults(
|
|
103
|
+
defineProps<{
|
|
104
|
+
variant?: string;
|
|
105
|
+
size?: "xs" | "sm" | "md" | "lg";
|
|
106
|
+
disabled?: boolean;
|
|
107
|
+
loading?: boolean;
|
|
108
|
+
type?: "button" | "submit" | "reset";
|
|
109
|
+
}>(),
|
|
110
|
+
{
|
|
111
|
+
variant: "default",
|
|
112
|
+
size: "md",
|
|
113
|
+
disabled: false,
|
|
114
|
+
loading: false,
|
|
115
|
+
type: "button",
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const emit = defineEmits<{
|
|
120
|
+
(e: "click", event: MouseEvent): void;
|
|
121
|
+
}>();
|
|
122
|
+
|
|
123
|
+
const v = computed(() => VARIANTS[props.variant] || VARIANTS.default);
|
|
124
|
+
const s = computed(() => SIZES[props.size] || SIZES.md);
|
|
125
|
+
const isIconOnly = computed(() => props.variant === "icon");
|
|
126
|
+
</script>
|
|
127
|
+
|
|
128
|
+
<template>
|
|
129
|
+
<button
|
|
130
|
+
:type="type"
|
|
131
|
+
:disabled="disabled || loading"
|
|
132
|
+
:class="[`btn btn-${variant}`, 'inline-flex items-center justify-center gap-1.5 font-medium transition select-none']"
|
|
133
|
+
:style="{
|
|
134
|
+
borderRadius: 'var(--rads, 8px)',
|
|
135
|
+
fontFamily: 'var(--f, var(--sans, inherit))',
|
|
136
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
137
|
+
whiteSpace: 'nowrap',
|
|
138
|
+
letterSpacing: '-.01em',
|
|
139
|
+
outline: 'none',
|
|
140
|
+
boxShadow: 'var(--shadow, 0 1px 3px rgba(20,22,41,.1))',
|
|
141
|
+
opacity: disabled ? 0.45 : 1,
|
|
142
|
+
pointerEvents: disabled ? 'none' : undefined,
|
|
143
|
+
background: v.background,
|
|
144
|
+
color: v.color,
|
|
145
|
+
border: v.border,
|
|
146
|
+
padding: isIconOnly ? '0' : s.padding,
|
|
147
|
+
fontSize: `${s.fontSize}px`,
|
|
148
|
+
height: `${s.height}px`,
|
|
149
|
+
width: isIconOnly ? `${s.height}px` : undefined,
|
|
150
|
+
...(v.paddingLeft !== undefined && { paddingLeft: v.paddingLeft }),
|
|
151
|
+
...(v.paddingRight !== undefined && { paddingRight: v.paddingRight }),
|
|
152
|
+
...(v.boxShadow !== undefined && { boxShadow: v.boxShadow }),
|
|
153
|
+
}"
|
|
154
|
+
@click="emit('click', $event)"
|
|
155
|
+
>
|
|
156
|
+
<Spinner v-if="loading" :size="14" inline color="currentColor" />
|
|
157
|
+
<slot v-if="!loading" name="icon" />
|
|
158
|
+
<slot v-if="!loading" />
|
|
159
|
+
<slot v-if="!loading" name="iconRight" />
|
|
160
|
+
</button>
|
|
161
|
+
</template>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(
|
|
5
|
+
defineProps<{
|
|
6
|
+
modelValue?: boolean;
|
|
7
|
+
checked?: boolean;
|
|
8
|
+
indeterminate?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
size?: "sm" | "md";
|
|
11
|
+
label?: string;
|
|
12
|
+
}>(),
|
|
13
|
+
{
|
|
14
|
+
modelValue: false,
|
|
15
|
+
checked: false,
|
|
16
|
+
indeterminate: false,
|
|
17
|
+
disabled: false,
|
|
18
|
+
size: "md",
|
|
19
|
+
label: "",
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const emit = defineEmits<{
|
|
24
|
+
(e: "update:modelValue", value: boolean): void;
|
|
25
|
+
(e: "change", value: boolean): void;
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
const isChecked = computed(() => props.modelValue || props.checked);
|
|
29
|
+
const dims = computed(() =>
|
|
30
|
+
props.size === "sm" ? { w: 14, h: 14, icon: 8 } : { w: 16, h: 16, icon: 10 }
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const toggle = () => {
|
|
34
|
+
if (props.disabled) return;
|
|
35
|
+
const next = !isChecked.value;
|
|
36
|
+
emit("update:modelValue", next);
|
|
37
|
+
emit("change", next);
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<label
|
|
43
|
+
class="inline-flex items-center gap-1.5 cursor-pointer select-none m-0 p-0 leading-none transition"
|
|
44
|
+
:style="{
|
|
45
|
+
opacity: disabled ? 0.45 : 1,
|
|
46
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
47
|
+
}"
|
|
48
|
+
@click.prevent="toggle"
|
|
49
|
+
>
|
|
50
|
+
<span
|
|
51
|
+
class="relative flex-shrink-0 transition-all duration-150 flex items-center justify-center"
|
|
52
|
+
:style="{
|
|
53
|
+
width: `${dims.w}px`,
|
|
54
|
+
height: `${dims.h}px`,
|
|
55
|
+
border: `1.5px solid ${isChecked || indeterminate ? 'var(--accent, #12675D)' : 'var(--border, #e2e8f0)'}`,
|
|
56
|
+
borderRadius: size === 'sm' ? '3px' : '4px',
|
|
57
|
+
background: isChecked || indeterminate ? 'var(--accent, #12675D)' : 'var(--input-bg, #ffffff)',
|
|
58
|
+
}"
|
|
59
|
+
>
|
|
60
|
+
<!-- Indeterminate dash -->
|
|
61
|
+
<svg
|
|
62
|
+
v-if="indeterminate"
|
|
63
|
+
:width="dims.icon"
|
|
64
|
+
:height="dims.icon"
|
|
65
|
+
viewBox="0 0 10 10"
|
|
66
|
+
fill="none"
|
|
67
|
+
>
|
|
68
|
+
<line x1="2" y1="5" x2="8" y2="5" stroke="white" stroke-width="1.6" stroke-linecap="round" />
|
|
69
|
+
</svg>
|
|
70
|
+
|
|
71
|
+
<!-- Checked checkmark -->
|
|
72
|
+
<svg
|
|
73
|
+
v-else-if="isChecked"
|
|
74
|
+
:width="dims.icon"
|
|
75
|
+
:height="dims.icon"
|
|
76
|
+
viewBox="0 0 10 10"
|
|
77
|
+
fill="none"
|
|
78
|
+
>
|
|
79
|
+
<polyline
|
|
80
|
+
points="1.5,5 4,7.5 8.5,2.5"
|
|
81
|
+
stroke="white"
|
|
82
|
+
stroke-width="1.6"
|
|
83
|
+
stroke-linecap="round"
|
|
84
|
+
stroke-linejoin="round"
|
|
85
|
+
/>
|
|
86
|
+
</svg>
|
|
87
|
+
</span>
|
|
88
|
+
|
|
89
|
+
<span
|
|
90
|
+
v-if="label || $slots.default"
|
|
91
|
+
:style="{ fontSize: size === 'sm' ? '.72rem' : '.78rem' }"
|
|
92
|
+
class="text-slate-800 font-semibold"
|
|
93
|
+
>
|
|
94
|
+
<slot>{{ label }}</slot>
|
|
95
|
+
</span>
|
|
96
|
+
</label>
|
|
97
|
+
</template>
|