nexa-ui-kit 0.6.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/dist/NBadge.nexa +40 -0
- package/dist/NBottomSheet.nexa +124 -0
- package/dist/NButton.nexa +123 -0
- package/dist/NCard.nexa +74 -0
- package/dist/NInput.nexa +116 -0
- package/dist/NModal.nexa +165 -0
- package/dist/NSelect.nexa +169 -0
- package/dist/NToastContainer.nexa +86 -0
- package/dist/NTooltip.nexa +115 -0
- package/dist/components/NAlert.js +134 -0
- package/dist/components/NAlert.nexa +115 -0
- package/dist/components/NAutocomplete.js +94 -0
- package/dist/components/NAutocomplete.nexa +58 -0
- package/dist/components/NAvatar.js +75 -0
- package/dist/components/NAvatar.nexa +67 -0
- package/dist/components/NBadge.js +74 -0
- package/dist/components/NBadge.nexa +61 -0
- package/dist/components/NBottomSheet.js +149 -0
- package/dist/components/NBottomSheet.nexa +145 -0
- package/dist/components/NButton.js +284 -0
- package/dist/components/NButton.nexa +275 -0
- package/dist/components/NCard.js +117 -0
- package/dist/components/NCard.nexa +100 -0
- package/dist/components/NCheckbox.js +108 -0
- package/dist/components/NCheckbox.nexa +90 -0
- package/dist/components/NChips.js +72 -0
- package/dist/components/NChips.nexa +57 -0
- package/dist/components/NDataTable.js +252 -0
- package/dist/components/NDataTable.nexa +186 -0
- package/dist/components/NDatepicker.js +379 -0
- package/dist/components/NDatepicker.nexa +367 -0
- package/dist/components/NForm.js +132 -0
- package/dist/components/NForm.nexa +133 -0
- package/dist/components/NFormField.js +173 -0
- package/dist/components/NFormField.nexa +171 -0
- package/dist/components/NInput.js +311 -0
- package/dist/components/NInput.nexa +311 -0
- package/dist/components/NInputNumber.js +202 -0
- package/dist/components/NInputNumber.nexa +199 -0
- package/dist/components/NModal.js +221 -0
- package/dist/components/NModal.nexa +221 -0
- package/dist/components/NMultiSelect.js +156 -0
- package/dist/components/NMultiSelect.nexa +77 -0
- package/dist/components/NPaginator.js +117 -0
- package/dist/components/NPaginator.nexa +77 -0
- package/dist/components/NPassword.js +193 -0
- package/dist/components/NPassword.nexa +178 -0
- package/dist/components/NProgressBar.js +127 -0
- package/dist/components/NProgressBar.nexa +111 -0
- package/dist/components/NRadio.js +96 -0
- package/dist/components/NRadio.nexa +81 -0
- package/dist/components/NSelect.js +468 -0
- package/dist/components/NSelect.nexa +452 -0
- package/dist/components/NSkeleton.js +98 -0
- package/dist/components/NSkeleton.nexa +74 -0
- package/dist/components/NSwitch.js +92 -0
- package/dist/components/NSwitch.nexa +76 -0
- package/dist/components/NTabs.js +129 -0
- package/dist/components/NTabs.nexa +113 -0
- package/dist/components/NTag.js +108 -0
- package/dist/components/NTag.nexa +93 -0
- package/dist/components/NToastContainer.js +242 -0
- package/dist/components/NToastContainer.nexa +221 -0
- package/dist/components/NTooltip.js +163 -0
- package/dist/components/NTooltip.nexa +166 -0
- package/dist/components/NTreeMenu.js +151 -0
- package/dist/components/NTreeMenu.nexa +142 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +34 -0
- package/dist/services/FloatingOverlay.d.ts +27 -0
- package/dist/services/FloatingOverlay.js +98 -0
- package/dist/services/FormValidation.d.ts +8 -0
- package/dist/services/FormValidation.js +46 -0
- package/dist/services/ToastService.d.ts +16 -0
- package/dist/services/ToastService.js +26 -0
- package/dist/styles/theme.d.ts +1 -0
- package/dist/styles/theme.js +144 -0
- package/package.json +32 -0
- package/src/components/NAlert.nexa +115 -0
- package/src/components/NAutocomplete.nexa +58 -0
- package/src/components/NAvatar.nexa +67 -0
- package/src/components/NBadge.nexa +61 -0
- package/src/components/NBottomSheet.nexa +145 -0
- package/src/components/NButton.nexa +275 -0
- package/src/components/NCard.nexa +100 -0
- package/src/components/NCheckbox.nexa +90 -0
- package/src/components/NChips.nexa +57 -0
- package/src/components/NDataTable.nexa +186 -0
- package/src/components/NDatepicker.nexa +367 -0
- package/src/components/NForm.nexa +133 -0
- package/src/components/NFormField.nexa +171 -0
- package/src/components/NInput.nexa +311 -0
- package/src/components/NInputNumber.nexa +199 -0
- package/src/components/NModal.nexa +221 -0
- package/src/components/NMultiSelect.nexa +77 -0
- package/src/components/NPaginator.nexa +77 -0
- package/src/components/NPassword.nexa +178 -0
- package/src/components/NProgressBar.nexa +111 -0
- package/src/components/NRadio.nexa +81 -0
- package/src/components/NSelect.nexa +452 -0
- package/src/components/NSkeleton.nexa +74 -0
- package/src/components/NSwitch.nexa +76 -0
- package/src/components/NTabs.nexa +113 -0
- package/src/components/NTag.nexa +93 -0
- package/src/components/NToastContainer.nexa +221 -0
- package/src/components/NTooltip.nexa +166 -0
- package/src/components/NTreeMenu.nexa +142 -0
- package/src/index.ts +36 -0
- package/src/services/FloatingOverlay.ts +133 -0
- package/src/services/FormValidation.ts +44 -0
- package/src/services/ToastService.ts +41 -0
- package/src/shims.d.ts +5 -0
- package/src/styles/theme.ts +146 -0
- package/src/styles/tokens.css +170 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const required = (message = 'Required') => (value) => {
|
|
2
|
+
if (value === null || value === undefined)
|
|
3
|
+
return message;
|
|
4
|
+
if (typeof value === 'string' && value.trim() === '')
|
|
5
|
+
return message;
|
|
6
|
+
if (Array.isArray(value) && value.length === 0)
|
|
7
|
+
return message;
|
|
8
|
+
return null;
|
|
9
|
+
};
|
|
10
|
+
export const minLength = (length, message) => (value) => {
|
|
11
|
+
const str = value === null || value === undefined ? '' : String(value);
|
|
12
|
+
if (str.length < length)
|
|
13
|
+
return message || `Must be at least ${length} characters`;
|
|
14
|
+
return null;
|
|
15
|
+
};
|
|
16
|
+
export const maxLength = (length, message) => (value) => {
|
|
17
|
+
const str = value === null || value === undefined ? '' : String(value);
|
|
18
|
+
if (str.length > length)
|
|
19
|
+
return message || `Must be at most ${length} characters`;
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
export const pattern = (re, message = 'Invalid format') => (value) => {
|
|
23
|
+
const str = value === null || value === undefined ? '' : String(value);
|
|
24
|
+
if (!re.test(str))
|
|
25
|
+
return message;
|
|
26
|
+
return null;
|
|
27
|
+
};
|
|
28
|
+
export const email = (message = 'Invalid email') => (value) => {
|
|
29
|
+
const str = value === null || value === undefined ? '' : String(value).trim();
|
|
30
|
+
if (!str)
|
|
31
|
+
return null;
|
|
32
|
+
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
33
|
+
if (!re.test(str))
|
|
34
|
+
return message;
|
|
35
|
+
return null;
|
|
36
|
+
};
|
|
37
|
+
export const sameAs = (otherField, message) => (value, values) => {
|
|
38
|
+
if (value !== values[otherField])
|
|
39
|
+
return message || `Must match ${otherField}`;
|
|
40
|
+
return null;
|
|
41
|
+
};
|
|
42
|
+
export const asyncRule = (rule, delayMs = 0) => async (value, values) => {
|
|
43
|
+
if (delayMs > 0)
|
|
44
|
+
await new Promise(r => setTimeout(r, delayMs));
|
|
45
|
+
return rule(value, values);
|
|
46
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ToastType = 'success' | 'info' | 'warning' | 'error';
|
|
2
|
+
export interface Toast {
|
|
3
|
+
id: number;
|
|
4
|
+
message: string;
|
|
5
|
+
type: ToastType;
|
|
6
|
+
duration?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const useToast: () => {
|
|
9
|
+
toasts: import("nexa-reactivity").Signal<Toast[]>;
|
|
10
|
+
add: (message: string, type?: ToastType, duration?: number) => number;
|
|
11
|
+
remove: (id: number) => void;
|
|
12
|
+
success: (msg: string, dur?: number) => number;
|
|
13
|
+
error: (msg: string, dur?: number) => number;
|
|
14
|
+
info: (msg: string, dur?: number) => number;
|
|
15
|
+
warning: (msg: string, dur?: number) => number;
|
|
16
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { signal } from 'nexa-framework';
|
|
2
|
+
const toasts = signal([]);
|
|
3
|
+
let nextId = 1;
|
|
4
|
+
export const useToast = () => {
|
|
5
|
+
const add = (message, type = 'info', duration = 3000) => {
|
|
6
|
+
const id = nextId++;
|
|
7
|
+
const toast = { id, message, type, duration };
|
|
8
|
+
toasts.value = [...toasts.value, toast];
|
|
9
|
+
if (duration > 0) {
|
|
10
|
+
setTimeout(() => remove(id), duration);
|
|
11
|
+
}
|
|
12
|
+
return id;
|
|
13
|
+
};
|
|
14
|
+
const remove = (id) => {
|
|
15
|
+
toasts.value = toasts.value.filter((t) => t.id !== id);
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
toasts,
|
|
19
|
+
add,
|
|
20
|
+
remove,
|
|
21
|
+
success: (msg, dur) => add(msg, 'success', dur),
|
|
22
|
+
error: (msg, dur) => add(msg, 'error', dur),
|
|
23
|
+
info: (msg, dur) => add(msg, 'info', dur),
|
|
24
|
+
warning: (msg, dur) => add(msg, 'warning', dur)
|
|
25
|
+
};
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function installTheme(): void;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
let installed = false;
|
|
2
|
+
export function installTheme() {
|
|
3
|
+
if (installed)
|
|
4
|
+
return;
|
|
5
|
+
installed = true;
|
|
6
|
+
const tokenCSS = `
|
|
7
|
+
:root {
|
|
8
|
+
--n-color-primary: #3b82f6;
|
|
9
|
+
--n-color-primary-hover: #2563eb;
|
|
10
|
+
--n-color-primary-active: #1d4ed8;
|
|
11
|
+
--n-color-primary-light: rgba(59, 130, 246, 0.12);
|
|
12
|
+
--n-color-primary-glow: rgba(59, 130, 246, 0.3);
|
|
13
|
+
--n-color-success: #10b981;
|
|
14
|
+
--n-color-success-hover: #059669;
|
|
15
|
+
--n-color-success-light: rgba(16, 185, 129, 0.12);
|
|
16
|
+
--n-color-warning: #f59e0b;
|
|
17
|
+
--n-color-warning-hover: #d97706;
|
|
18
|
+
--n-color-warning-light: rgba(245, 158, 11, 0.12);
|
|
19
|
+
--n-color-danger: #ef4444;
|
|
20
|
+
--n-color-danger-hover: #dc2626;
|
|
21
|
+
--n-color-danger-light: rgba(239, 68, 68, 0.12);
|
|
22
|
+
--n-color-info: #06b6d4;
|
|
23
|
+
--n-color-info-hover: #0891b2;
|
|
24
|
+
--n-color-info-light: rgba(6, 182, 212, 0.12);
|
|
25
|
+
--n-color-surface: #0f172a;
|
|
26
|
+
--n-color-surface-alt: #1e293b;
|
|
27
|
+
--n-color-surface-hover: #334155;
|
|
28
|
+
--n-color-surface-elevated: #1e293b;
|
|
29
|
+
--n-color-bg: #020617;
|
|
30
|
+
--n-color-bg-alt: #0f172a;
|
|
31
|
+
--n-color-text: #f8fafc;
|
|
32
|
+
--n-color-text-secondary: #94a3b8;
|
|
33
|
+
--n-color-text-muted: #64748b;
|
|
34
|
+
--n-color-text-inverse: #0f172a;
|
|
35
|
+
--n-color-border: rgba(255, 255, 255, 0.08);
|
|
36
|
+
--n-color-border-hover: rgba(255, 255, 255, 0.15);
|
|
37
|
+
--n-color-border-active: rgba(255, 255, 255, 0.25);
|
|
38
|
+
--n-color-overlay: rgba(2, 6, 17, 0.7);
|
|
39
|
+
--n-color-glass: rgba(255, 255, 255, 0.04);
|
|
40
|
+
--n-color-glass-border: rgba(255, 255, 255, 0.08);
|
|
41
|
+
--n-color-glass-hover: rgba(255, 255, 255, 0.08);
|
|
42
|
+
--n-font-sans: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
43
|
+
--n-font-mono: 'JetBrains Mono', 'Fira Code', monospace;
|
|
44
|
+
--n-text-xs: 0.75rem;
|
|
45
|
+
--n-text-sm: 0.875rem;
|
|
46
|
+
--n-text-base: 1rem;
|
|
47
|
+
--n-text-lg: 1.125rem;
|
|
48
|
+
--n-text-xl: 1.25rem;
|
|
49
|
+
--n-text-2xl: 1.5rem;
|
|
50
|
+
--n-text-3xl: 2rem;
|
|
51
|
+
--n-weight-normal: 400;
|
|
52
|
+
--n-weight-medium: 500;
|
|
53
|
+
--n-weight-semibold: 600;
|
|
54
|
+
--n-weight-bold: 700;
|
|
55
|
+
--n-weight-extrabold: 800;
|
|
56
|
+
--n-leading-tight: 1.25;
|
|
57
|
+
--n-leading-normal: 1.5;
|
|
58
|
+
--n-leading-relaxed: 1.75;
|
|
59
|
+
--n-tracking-tight: -0.025em;
|
|
60
|
+
--n-tracking-normal: 0;
|
|
61
|
+
--n-tracking-wide: 0.05em;
|
|
62
|
+
--n-space-1: 0.25rem;
|
|
63
|
+
--n-space-2: 0.5rem;
|
|
64
|
+
--n-space-3: 0.75rem;
|
|
65
|
+
--n-space-4: 1rem;
|
|
66
|
+
--n-space-5: 1.25rem;
|
|
67
|
+
--n-space-6: 1.5rem;
|
|
68
|
+
--n-space-8: 2rem;
|
|
69
|
+
--n-space-10: 2.5rem;
|
|
70
|
+
--n-space-12: 3rem;
|
|
71
|
+
--n-space-16: 4rem;
|
|
72
|
+
--n-radius-sm: 6px;
|
|
73
|
+
--n-radius-md: 10px;
|
|
74
|
+
--n-radius-lg: 14px;
|
|
75
|
+
--n-radius-xl: 20px;
|
|
76
|
+
--n-radius-2xl: 28px;
|
|
77
|
+
--n-radius-full: 9999px;
|
|
78
|
+
--n-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
79
|
+
--n-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
|
|
80
|
+
--n-shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
|
|
81
|
+
--n-shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
|
82
|
+
--n-shadow-glow-primary: 0 4px 15px -3px rgba(37, 99, 235, 0.3);
|
|
83
|
+
--n-shadow-glow-danger: 0 4px 15px -3px rgba(220, 38, 38, 0.3);
|
|
84
|
+
--n-transition-fast: 0.15s ease;
|
|
85
|
+
--n-transition-normal: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
|
86
|
+
--n-transition-slow: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
|
87
|
+
--n-transition-spring: 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
88
|
+
--n-z-dropdown: 100;
|
|
89
|
+
--n-z-sticky: 200;
|
|
90
|
+
--n-z-overlay: 500;
|
|
91
|
+
--n-z-modal: 2000;
|
|
92
|
+
--n-z-toast: 3000;
|
|
93
|
+
--n-z-tooltip: 1000;
|
|
94
|
+
}
|
|
95
|
+
[data-theme="light"] {
|
|
96
|
+
--n-color-primary: #2563eb;
|
|
97
|
+
--n-color-primary-hover: #1d4ed8;
|
|
98
|
+
--n-color-primary-active: #1e40af;
|
|
99
|
+
--n-color-primary-light: rgba(37, 99, 235, 0.08);
|
|
100
|
+
--n-color-primary-glow: rgba(37, 99, 235, 0.2);
|
|
101
|
+
--n-color-success: #059669;
|
|
102
|
+
--n-color-success-light: rgba(5, 150, 105, 0.08);
|
|
103
|
+
--n-color-warning: #d97706;
|
|
104
|
+
--n-color-warning-light: rgba(217, 119, 6, 0.08);
|
|
105
|
+
--n-color-danger: #dc2626;
|
|
106
|
+
--n-color-danger-light: rgba(220, 38, 38, 0.08);
|
|
107
|
+
--n-color-info: #0891b2;
|
|
108
|
+
--n-color-info-light: rgba(8, 145, 178, 0.08);
|
|
109
|
+
--n-color-surface: #ffffff;
|
|
110
|
+
--n-color-surface-alt: #f8fafc;
|
|
111
|
+
--n-color-surface-hover: #f1f5f9;
|
|
112
|
+
--n-color-surface-elevated: #ffffff;
|
|
113
|
+
--n-color-bg: #f8fafc;
|
|
114
|
+
--n-color-bg-alt: #f1f5f9;
|
|
115
|
+
--n-color-text: #0f172a;
|
|
116
|
+
--n-color-text-secondary: #475569;
|
|
117
|
+
--n-color-text-muted: #94a3b8;
|
|
118
|
+
--n-color-text-inverse: #f8fafc;
|
|
119
|
+
--n-color-border: rgba(0, 0, 0, 0.08);
|
|
120
|
+
--n-color-border-hover: rgba(0, 0, 0, 0.15);
|
|
121
|
+
--n-color-border-active: rgba(0, 0, 0, 0.25);
|
|
122
|
+
--n-color-overlay: rgba(15, 23, 42, 0.5);
|
|
123
|
+
--n-color-glass: rgba(255, 255, 255, 0.6);
|
|
124
|
+
--n-color-glass-border: rgba(0, 0, 0, 0.06);
|
|
125
|
+
--n-color-glass-hover: rgba(255, 255, 255, 0.8);
|
|
126
|
+
--n-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
127
|
+
--n-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06);
|
|
128
|
+
--n-shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.08);
|
|
129
|
+
--n-shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.12);
|
|
130
|
+
--n-shadow-glow-primary: 0 4px 15px -3px rgba(37, 99, 235, 0.2);
|
|
131
|
+
--n-shadow-glow-danger: 0 4px 15px -3px rgba(220, 38, 38, 0.2);
|
|
132
|
+
}
|
|
133
|
+
`;
|
|
134
|
+
if (typeof document !== 'undefined') {
|
|
135
|
+
const styleId = 'nexa-ui-tokens';
|
|
136
|
+
let el = document.getElementById(styleId);
|
|
137
|
+
if (!el) {
|
|
138
|
+
el = document.createElement('style');
|
|
139
|
+
el.id = styleId;
|
|
140
|
+
document.head.appendChild(el);
|
|
141
|
+
}
|
|
142
|
+
el.textContent = tokenCSS;
|
|
143
|
+
}
|
|
144
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nexa-ui-kit",
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Premium component library for Nexa Framework",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src"
|
|
19
|
+
],
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"nexa-framework": "0.6.0",
|
|
22
|
+
"nexa-mobile": "0.6.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"cpx": "^1.5.0",
|
|
26
|
+
"nexa-compiler": "0.6.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc && node scripts/compile-nexa.js && node scripts/patch-imports.js && cpx \"src/**/*.nexa\" dist",
|
|
30
|
+
"dev": "tsc --watch"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { signal } from 'nexa-framework'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
variant: { type: String, default: 'info' },
|
|
6
|
+
title: { type: String, default: '' },
|
|
7
|
+
closable: { type: Boolean, default: false },
|
|
8
|
+
icon: { type: String, default: '' }
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const emit = defineEmits(['close'])
|
|
12
|
+
|
|
13
|
+
const visible = signal(true)
|
|
14
|
+
|
|
15
|
+
const dismiss = () => {
|
|
16
|
+
visible.value = false
|
|
17
|
+
emit('close')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const icons = { success: '✓', error: '✕', warning: '⚡', info: 'ℹ' }
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div v-if="visible.value" class="n-alert" :class="`is-${variant}`">
|
|
25
|
+
<span class="n-alert-icon">{{ icon || icons[variant] || 'ℹ' }}</span>
|
|
26
|
+
<div class="n-alert-body">
|
|
27
|
+
<span v-if="title" class="n-alert-title">{{ title }}</span>
|
|
28
|
+
<span class="n-alert-text"><slot /></span>
|
|
29
|
+
</div>
|
|
30
|
+
<button v-if="closable" class="n-alert-close" @click="dismiss">×</button>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<style scoped>
|
|
35
|
+
.n-alert {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: flex-start;
|
|
38
|
+
gap: var(--n-space-3);
|
|
39
|
+
padding: var(--n-space-4) var(--n-space-5);
|
|
40
|
+
border-radius: var(--n-radius-md);
|
|
41
|
+
border: 1px solid transparent;
|
|
42
|
+
font-size: var(--n-text-sm);
|
|
43
|
+
line-height: var(--n-leading-normal);
|
|
44
|
+
animation: n-alert-in 0.25s ease-out;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@keyframes n-alert-in {
|
|
48
|
+
from { opacity: 0; transform: translateY(-8px); }
|
|
49
|
+
to { opacity: 1; transform: translateY(0); }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.is-info {
|
|
53
|
+
background: var(--n-color-primary-light);
|
|
54
|
+
border-color: rgba(59, 130, 246, 0.2);
|
|
55
|
+
color: var(--n-color-primary);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.is-success {
|
|
59
|
+
background: var(--n-color-success-light);
|
|
60
|
+
border-color: rgba(16, 185, 129, 0.2);
|
|
61
|
+
color: var(--n-color-success);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.is-warning {
|
|
65
|
+
background: var(--n-color-warning-light);
|
|
66
|
+
border-color: rgba(245, 158, 11, 0.2);
|
|
67
|
+
color: var(--n-color-warning);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.is-error {
|
|
71
|
+
background: var(--n-color-danger-light);
|
|
72
|
+
border-color: rgba(239, 68, 68, 0.2);
|
|
73
|
+
color: var(--n-color-danger);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.n-alert-icon {
|
|
77
|
+
font-size: var(--n-text-base);
|
|
78
|
+
font-weight: var(--n-weight-bold);
|
|
79
|
+
flex-shrink: 0;
|
|
80
|
+
line-height: 1.4;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.n-alert-body {
|
|
84
|
+
flex: 1;
|
|
85
|
+
display: flex;
|
|
86
|
+
flex-direction: column;
|
|
87
|
+
gap: var(--n-space-1);
|
|
88
|
+
color: var(--n-color-text);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.n-alert-title {
|
|
92
|
+
font-weight: var(--n-weight-semibold);
|
|
93
|
+
font-size: var(--n-text-sm);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.n-alert-text {
|
|
97
|
+
color: var(--n-color-text-secondary);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.n-alert-close {
|
|
101
|
+
background: transparent;
|
|
102
|
+
border: none;
|
|
103
|
+
color: var(--n-color-text-muted);
|
|
104
|
+
font-size: var(--n-text-lg);
|
|
105
|
+
cursor: pointer;
|
|
106
|
+
padding: 0;
|
|
107
|
+
line-height: 1;
|
|
108
|
+
flex-shrink: 0;
|
|
109
|
+
transition: color var(--n-transition-fast);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.n-alert-close:hover {
|
|
113
|
+
color: var(--n-color-text);
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { signal, computed, effect, onBeforeUnmount } from 'nexa-framework'
|
|
3
|
+
import { trackFloatingOverlay } from '../services/FloatingOverlay.js'
|
|
4
|
+
const props = defineProps({ modelValue: { type: String, default: '' }, options: { type: Array, default: () => [] }, placeholder: { type: String, default: '' }, label: { type: String, default: '' }, disabled: { type: Boolean, default: false }, readonly: { type: Boolean, default: false }, clearable: { type: Boolean, default: false }, minLength: { type: Number, default: 1 }, delay: { type: Number, default: 200 }, loading: { type: Boolean, default: false }, dropdown: { type: Boolean, default: false }, emptyMessage: { type: String, default: 'No results' }, placement: { type: String, default: 'auto' } })
|
|
5
|
+
const emit = defineEmits(['update:modelValue', 'select', 'complete', 'clear'])
|
|
6
|
+
const instanceId = `n-ac-${Math.random().toString(16).slice(2)}`
|
|
7
|
+
const listboxId = `${instanceId}-listbox`
|
|
8
|
+
const inputId = `${instanceId}-input`
|
|
9
|
+
const isOpen = signal(false)
|
|
10
|
+
const query = signal('')
|
|
11
|
+
const focusedIndex = signal(-1)
|
|
12
|
+
const rootEl = signal(null)
|
|
13
|
+
const popupStyle = signal({})
|
|
14
|
+
const resolvedPlacement = signal('bottom')
|
|
15
|
+
let stopTracking = null
|
|
16
|
+
let completeTimer = null
|
|
17
|
+
const normalizeOption = (opt) => { if (opt == null) return { label: '', value: null, raw: opt }; if (typeof opt === 'string' || typeof opt === 'number') return { label: String(opt), value: opt, raw: opt }; if (typeof opt === 'object') { const label = 'label' in opt ? String(opt.label) : String(opt.value ?? ''); return { label, value: 'value' in opt ? opt.value : label, raw: opt } } return { label: String(opt), value: opt, raw: opt } }
|
|
18
|
+
const normalizedOptions = computed(() => props.options.map(normalizeOption))
|
|
19
|
+
const filteredOptions = computed(() => { const q = query.value.trim().toLowerCase(); if (!q) return normalizedOptions.value; return normalizedOptions.value.filter(opt => opt.label.toLowerCase().includes(q)) })
|
|
20
|
+
const activeId = computed(() => { const idx = focusedIndex.value; if (idx < 0) return ''; const opt = filteredOptions.value[idx]; return opt ? `${instanceId}-opt-${idx}` : '' })
|
|
21
|
+
const close = () => { if (!isOpen.value) return; isOpen.value = false; focusedIndex.value = -1; if (stopTracking) { stopTracking(); stopTracking = null } }
|
|
22
|
+
const open = (e) => { if (props.disabled || props.readonly || isOpen.value) return; isOpen.value = true; const t = e?.currentTarget || e?.target; rootEl.value = t?.closest ? t.closest(`[data-autocomplete-root="${instanceId}"]`) : null; stopTracking = trackFloatingOverlay({ isOpen: () => isOpen.value, getAnchor: () => { const root = rootEl.value; return root ? root.querySelector('.n-ac-input-wrap') : null }, getPopup: () => document.querySelector(`[data-autocomplete-popup="${instanceId}"]`), placement: props.placement, align: 'start', matchWidth: true, minWidth: 240, gap: 8, margin: 8, zIndex: 9999, onUpdate: (r) => { popupStyle.value = r.style; resolvedPlacement.value = r.placement }, isEventInside: (ev) => { const el = ev.target; if (!el || typeof el.closest !== 'function') return false; return !!(el.closest(`[data-autocomplete-root="${instanceId}"]`) || el.closest(`[data-autocomplete-popup="${instanceId}"]`)) }, onOutside: () => close() }) }
|
|
23
|
+
const requestComplete = () => { clearTimeout(completeTimer); completeTimer = setTimeout(() => emit('complete', query.value), props.delay) }
|
|
24
|
+
const onInput = (e) => { const value = e.target.value; query.value = value; emit('update:modelValue', value); if (value.trim().length >= props.minLength) { open(e); requestComplete() } else close() }
|
|
25
|
+
const onFocus = (e) => { query.value = props.modelValue || ''; if ((props.modelValue || '').trim().length >= props.minLength) { open(e); requestComplete() } }
|
|
26
|
+
const onKeydown = (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); return } if (!isOpen.value) { if (e.key === 'ArrowDown') { e.preventDefault(); open(e); focusedIndex.value = 0 } return } const items = filteredOptions.value; if (e.key === 'ArrowDown') { e.preventDefault(); focusedIndex.value = Math.min(focusedIndex.value + 1, items.length - 1); return } if (e.key === 'ArrowUp') { e.preventDefault(); focusedIndex.value = Math.max(focusedIndex.value - 1, 0); return } if (e.key === 'Enter') { e.preventDefault(); const opt = items[focusedIndex.value]; if (!opt) return; emit('update:modelValue', opt.label); emit('select', opt.raw); query.value = opt.label; close() } }
|
|
27
|
+
const selectOption = (opt) => { emit('update:modelValue', opt.label); emit('select', opt.raw); query.value = opt.label; close() }
|
|
28
|
+
const clearValue = () => { if (props.disabled || props.readonly) return; emit('update:modelValue', ''); emit('clear'); query.value = ''; close() }
|
|
29
|
+
const openAll = (e) => { if (props.disabled || props.readonly) return; query.value = props.modelValue || ''; open(e); requestComplete() }
|
|
30
|
+
effect(() => { if (!isOpen.value) return; if (focusedIndex.value < 0 && filteredOptions.value.length > 0) focusedIndex.value = 0 })
|
|
31
|
+
onBeforeUnmount(() => { clearTimeout(completeTimer); close() })
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="n-ac" :data-autocomplete-root="instanceId">
|
|
36
|
+
<label v-if="label" class="n-ac-label" :for="inputId">{{ label }}</label>
|
|
37
|
+
<div class="n-ac-input-wrap" :class="{ 'is-disabled': disabled }">
|
|
38
|
+
<input class="n-ac-input" :id="inputId" :value="modelValue" :placeholder="placeholder" :disabled="disabled" :readonly="readonly" role="combobox" aria-autocomplete="list" :aria-expanded="isOpen.value" :aria-controls="listboxId" :aria-activedescendant="activeId.value || undefined" @input="onInput" @focus="onFocus" @keydown="onKeydown" />
|
|
39
|
+
<div class="n-ac-actions">
|
|
40
|
+
<button v-if="clearable && modelValue" type="button" class="n-ac-action" :disabled="disabled || readonly" aria-label="Limpiar" @click="clearValue">✕</button>
|
|
41
|
+
<button v-if="dropdown" type="button" class="n-ac-action" :disabled="disabled || readonly" aria-label="Abrir" @click="openAll">▾</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<Teleport to="body">
|
|
45
|
+
<div v-if="isOpen.value" class="n-ac-popup" :class="{ 'is-top': resolvedPlacement.value === 'top' }" :data-autocomplete-popup="instanceId" :style="popupStyle.value">
|
|
46
|
+
<div v-if="loading" class="n-ac-loading">Loading...</div>
|
|
47
|
+
<div v-else class="n-ac-list" role="listbox" :id="listboxId">
|
|
48
|
+
<button v-for="(opt, i) in filteredOptions.value" :key="opt.value" type="button" class="n-ac-option" role="option" :id="instanceId + '-opt-' + i" :aria-selected="(i === focusedIndex.value).toString()" :class="{ 'is-focused': i === focusedIndex.value }" @mouseenter="focusedIndex.value = i" @click="selectOption(opt)">{{ opt.label }}</button>
|
|
49
|
+
<div v-if="filteredOptions.value.length === 0" class="n-ac-empty">{{ emptyMessage }}</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</Teleport>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<style scoped>
|
|
57
|
+
.n-ac{display:flex;flex-direction:column;gap:var(--n-space-2);width:100%;font-family:var(--n-font-sans)}.n-ac-label{display:block;font-size:var(--n-text-sm);font-weight:var(--n-weight-medium);color:var(--n-color-text-secondary);margin-bottom:var(--n-space-2)}.n-ac-input-wrap{position:relative;display:flex;align-items:center;background:var(--n-color-surface);border:1px solid var(--n-color-border);border-radius:var(--n-radius-md);transition:all var(--n-transition-fast)}.n-ac-input-wrap:focus-within{border-color:var(--n-color-primary);box-shadow:0 0 0 3px var(--n-color-primary-light)}.n-ac-input{width:100%;background:transparent;border:none;outline:none;padding:0.75rem 2.75rem 0.75rem 1rem;color:var(--n-color-text);font-size:var(--n-text-base);font-family:inherit}.n-ac-input::placeholder{color:var(--n-color-text-muted)}.n-ac-actions{position:absolute;right:0.5rem;display:flex;align-items:center;gap:0.15rem}.n-ac-action{background:transparent;border:none;color:var(--n-color-text-muted);cursor:pointer;padding:0.25rem;border-radius:var(--n-radius-sm);transition:all var(--n-transition-fast);line-height:1;display:flex;align-items:center}.n-ac-action:hover:not(:disabled){color:var(--n-color-text);background:var(--n-color-glass)}.n-ac-action:disabled{opacity:0.5;cursor:not-allowed}.n-ac-input-wrap.is-disabled{opacity:0.5;cursor:not-allowed;background:var(--n-color-surface-alt)}.n-ac-popup{background:var(--n-color-surface);border:1px solid var(--n-color-border);border-radius:var(--n-radius-md);box-shadow:var(--n-shadow-lg);overflow:hidden;animation:n-ac-in .2s cubic-bezier(0,1,0,1)}.n-ac-popup.is-top{animation:n-ac-in-top .2s cubic-bezier(0,1,0,1)}@keyframes n-ac-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@keyframes n-ac-in-top{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.n-ac-loading{padding:var(--n-space-3) var(--n-space-4);color:var(--n-color-text-muted);font-size:var(--n-text-sm)}.n-ac-list{max-height:260px;overflow:auto;display:flex;flex-direction:column}.n-ac-option{text-align:left;padding:0.7rem 1rem;color:var(--n-color-text-secondary);background:transparent;border:none;cursor:pointer;transition:all var(--n-transition-fast)}.n-ac-option:hover,.n-ac-option.is-focused{background:var(--n-color-glass);color:var(--n-color-text)}.n-ac-empty{padding:var(--n-space-4);color:var(--n-color-text-muted);text-align:center;font-size:var(--n-text-sm)}
|
|
58
|
+
</style>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'nexa-framework'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
label: { type: String, default: '' },
|
|
6
|
+
size: { type: String, default: 'md' },
|
|
7
|
+
variant: { type: String, default: 'primary' },
|
|
8
|
+
image: { type: String, default: '' },
|
|
9
|
+
shape: { type: String, default: 'rounded' } // rounded, circle, square
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const initials = computed(() => {
|
|
13
|
+
if (!props.label) return '?'
|
|
14
|
+
return props.label
|
|
15
|
+
.split(' ')
|
|
16
|
+
.map(w => w[0])
|
|
17
|
+
.join('')
|
|
18
|
+
.toUpperCase()
|
|
19
|
+
.slice(0, 2)
|
|
20
|
+
})
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div
|
|
25
|
+
:class="['n-avatar', `is-${size}`, `is-${variant}`, `is-${shape}`]"
|
|
26
|
+
v-bind:style="image ? { backgroundImage: `url(${image})` } : {}"
|
|
27
|
+
:aria-label="label"
|
|
28
|
+
>
|
|
29
|
+
<span v-if="!image" class="n-avatar-initials">{{ initials.value }}</span>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<style scoped>
|
|
34
|
+
.n-avatar {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
flex-shrink: 0;
|
|
39
|
+
background-size: cover;
|
|
40
|
+
background-position: center;
|
|
41
|
+
background-color: var(--n-color-surface-hover);
|
|
42
|
+
font-weight: var(--n-weight-bold);
|
|
43
|
+
color: var(--n-color-text);
|
|
44
|
+
user-select: none;
|
|
45
|
+
transition: all var(--n-transition-fast);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.is-sm { width: 32px; height: 32px; font-size: var(--n-text-xs); }
|
|
49
|
+
.is-md { width: 40px; height: 40px; font-size: var(--n-text-sm); }
|
|
50
|
+
.is-lg { width: 56px; height: 56px; font-size: var(--n-text-lg); }
|
|
51
|
+
.is-xl { width: 72px; height: 72px; font-size: var(--n-text-2xl); }
|
|
52
|
+
|
|
53
|
+
.is-circle { border-radius: var(--n-radius-full); }
|
|
54
|
+
.is-rounded { border-radius: var(--n-radius-md); }
|
|
55
|
+
.is-square { border-radius: 0; }
|
|
56
|
+
|
|
57
|
+
.is-primary { background-color: var(--n-color-primary); color: white; }
|
|
58
|
+
.is-success { background-color: var(--n-color-success); color: white; }
|
|
59
|
+
.is-danger { background-color: var(--n-color-danger); color: white; }
|
|
60
|
+
.is-warning { background-color: var(--n-color-warning); color: white; }
|
|
61
|
+
.is-info { background-color: var(--n-color-info); color: white; }
|
|
62
|
+
|
|
63
|
+
.n-avatar-initials {
|
|
64
|
+
line-height: 1;
|
|
65
|
+
font-weight: var(--n-weight-bold);
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
variant: { type: String, default: 'primary' },
|
|
4
|
+
size: { type: String, default: 'md' },
|
|
5
|
+
rounded: { type: Boolean, default: false },
|
|
6
|
+
dot: { type: Boolean, default: false },
|
|
7
|
+
position: { type: String, default: '' }
|
|
8
|
+
})
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<span v-if="dot" class="n-badge-dot" :class="[`is-${variant}`, position ? `is-${position}` : '']"></span>
|
|
13
|
+
<span v-else :class="['n-badge', `n-badge-${variant}`, `n-badge-${size}`, rounded ? 'is-rounded' : '']">
|
|
14
|
+
<slot />
|
|
15
|
+
</span>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<style scoped>
|
|
19
|
+
.n-badge {
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
padding: 0.25rem 0.6rem;
|
|
24
|
+
font-size: var(--n-text-xs);
|
|
25
|
+
font-weight: var(--n-weight-bold);
|
|
26
|
+
border-radius: var(--n-radius-sm);
|
|
27
|
+
line-height: 1;
|
|
28
|
+
font-family: var(--n-font-sans);
|
|
29
|
+
text-transform: uppercase;
|
|
30
|
+
letter-spacing: var(--n-tracking-wide);
|
|
31
|
+
transition: all var(--n-transition-fast);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.n-badge-primary { background: var(--n-color-primary); color: white; }
|
|
35
|
+
.n-badge-success { background: var(--n-color-success); color: white; }
|
|
36
|
+
.n-badge-danger { background: var(--n-color-danger); color: white; }
|
|
37
|
+
.n-badge-warning { background: var(--n-color-warning); color: white; }
|
|
38
|
+
.n-badge-info { background: var(--n-color-info); color: white; }
|
|
39
|
+
.n-badge-secondary { background: var(--n-color-surface-hover); color: var(--n-color-text-secondary); }
|
|
40
|
+
|
|
41
|
+
.n-badge-sm { padding: 0.15rem 0.4rem; font-size: 0.65rem; }
|
|
42
|
+
.n-badge-lg { padding: 0.4rem 0.8rem; font-size: var(--n-text-sm); }
|
|
43
|
+
|
|
44
|
+
.is-rounded { border-radius: var(--n-radius-full); }
|
|
45
|
+
|
|
46
|
+
.n-badge-dot {
|
|
47
|
+
display: inline-block;
|
|
48
|
+
width: 8px;
|
|
49
|
+
height: 8px;
|
|
50
|
+
border-radius: var(--n-radius-full);
|
|
51
|
+
vertical-align: middle;
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.n-badge-dot.is-primary { background: var(--n-color-primary); }
|
|
56
|
+
.n-badge-dot.is-success { background: var(--n-color-success); }
|
|
57
|
+
.n-badge-dot.is-danger { background: var(--n-color-danger); }
|
|
58
|
+
.n-badge-dot.is-warning { background: var(--n-color-warning); }
|
|
59
|
+
.n-badge-dot.is-info { background: var(--n-color-info); }
|
|
60
|
+
.n-badge-dot.is-secondary { background: var(--n-color-text-muted); }
|
|
61
|
+
</style>
|