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.
Files changed (114) hide show
  1. package/dist/NBadge.nexa +40 -0
  2. package/dist/NBottomSheet.nexa +124 -0
  3. package/dist/NButton.nexa +123 -0
  4. package/dist/NCard.nexa +74 -0
  5. package/dist/NInput.nexa +116 -0
  6. package/dist/NModal.nexa +165 -0
  7. package/dist/NSelect.nexa +169 -0
  8. package/dist/NToastContainer.nexa +86 -0
  9. package/dist/NTooltip.nexa +115 -0
  10. package/dist/components/NAlert.js +134 -0
  11. package/dist/components/NAlert.nexa +115 -0
  12. package/dist/components/NAutocomplete.js +94 -0
  13. package/dist/components/NAutocomplete.nexa +58 -0
  14. package/dist/components/NAvatar.js +75 -0
  15. package/dist/components/NAvatar.nexa +67 -0
  16. package/dist/components/NBadge.js +74 -0
  17. package/dist/components/NBadge.nexa +61 -0
  18. package/dist/components/NBottomSheet.js +149 -0
  19. package/dist/components/NBottomSheet.nexa +145 -0
  20. package/dist/components/NButton.js +284 -0
  21. package/dist/components/NButton.nexa +275 -0
  22. package/dist/components/NCard.js +117 -0
  23. package/dist/components/NCard.nexa +100 -0
  24. package/dist/components/NCheckbox.js +108 -0
  25. package/dist/components/NCheckbox.nexa +90 -0
  26. package/dist/components/NChips.js +72 -0
  27. package/dist/components/NChips.nexa +57 -0
  28. package/dist/components/NDataTable.js +252 -0
  29. package/dist/components/NDataTable.nexa +186 -0
  30. package/dist/components/NDatepicker.js +379 -0
  31. package/dist/components/NDatepicker.nexa +367 -0
  32. package/dist/components/NForm.js +132 -0
  33. package/dist/components/NForm.nexa +133 -0
  34. package/dist/components/NFormField.js +173 -0
  35. package/dist/components/NFormField.nexa +171 -0
  36. package/dist/components/NInput.js +311 -0
  37. package/dist/components/NInput.nexa +311 -0
  38. package/dist/components/NInputNumber.js +202 -0
  39. package/dist/components/NInputNumber.nexa +199 -0
  40. package/dist/components/NModal.js +221 -0
  41. package/dist/components/NModal.nexa +221 -0
  42. package/dist/components/NMultiSelect.js +156 -0
  43. package/dist/components/NMultiSelect.nexa +77 -0
  44. package/dist/components/NPaginator.js +117 -0
  45. package/dist/components/NPaginator.nexa +77 -0
  46. package/dist/components/NPassword.js +193 -0
  47. package/dist/components/NPassword.nexa +178 -0
  48. package/dist/components/NProgressBar.js +127 -0
  49. package/dist/components/NProgressBar.nexa +111 -0
  50. package/dist/components/NRadio.js +96 -0
  51. package/dist/components/NRadio.nexa +81 -0
  52. package/dist/components/NSelect.js +468 -0
  53. package/dist/components/NSelect.nexa +452 -0
  54. package/dist/components/NSkeleton.js +98 -0
  55. package/dist/components/NSkeleton.nexa +74 -0
  56. package/dist/components/NSwitch.js +92 -0
  57. package/dist/components/NSwitch.nexa +76 -0
  58. package/dist/components/NTabs.js +129 -0
  59. package/dist/components/NTabs.nexa +113 -0
  60. package/dist/components/NTag.js +108 -0
  61. package/dist/components/NTag.nexa +93 -0
  62. package/dist/components/NToastContainer.js +242 -0
  63. package/dist/components/NToastContainer.nexa +221 -0
  64. package/dist/components/NTooltip.js +163 -0
  65. package/dist/components/NTooltip.nexa +166 -0
  66. package/dist/components/NTreeMenu.js +151 -0
  67. package/dist/components/NTreeMenu.nexa +142 -0
  68. package/dist/index.d.ts +32 -0
  69. package/dist/index.js +34 -0
  70. package/dist/services/FloatingOverlay.d.ts +27 -0
  71. package/dist/services/FloatingOverlay.js +98 -0
  72. package/dist/services/FormValidation.d.ts +8 -0
  73. package/dist/services/FormValidation.js +46 -0
  74. package/dist/services/ToastService.d.ts +16 -0
  75. package/dist/services/ToastService.js +26 -0
  76. package/dist/styles/theme.d.ts +1 -0
  77. package/dist/styles/theme.js +144 -0
  78. package/package.json +32 -0
  79. package/src/components/NAlert.nexa +115 -0
  80. package/src/components/NAutocomplete.nexa +58 -0
  81. package/src/components/NAvatar.nexa +67 -0
  82. package/src/components/NBadge.nexa +61 -0
  83. package/src/components/NBottomSheet.nexa +145 -0
  84. package/src/components/NButton.nexa +275 -0
  85. package/src/components/NCard.nexa +100 -0
  86. package/src/components/NCheckbox.nexa +90 -0
  87. package/src/components/NChips.nexa +57 -0
  88. package/src/components/NDataTable.nexa +186 -0
  89. package/src/components/NDatepicker.nexa +367 -0
  90. package/src/components/NForm.nexa +133 -0
  91. package/src/components/NFormField.nexa +171 -0
  92. package/src/components/NInput.nexa +311 -0
  93. package/src/components/NInputNumber.nexa +199 -0
  94. package/src/components/NModal.nexa +221 -0
  95. package/src/components/NMultiSelect.nexa +77 -0
  96. package/src/components/NPaginator.nexa +77 -0
  97. package/src/components/NPassword.nexa +178 -0
  98. package/src/components/NProgressBar.nexa +111 -0
  99. package/src/components/NRadio.nexa +81 -0
  100. package/src/components/NSelect.nexa +452 -0
  101. package/src/components/NSkeleton.nexa +74 -0
  102. package/src/components/NSwitch.nexa +76 -0
  103. package/src/components/NTabs.nexa +113 -0
  104. package/src/components/NTag.nexa +93 -0
  105. package/src/components/NToastContainer.nexa +221 -0
  106. package/src/components/NTooltip.nexa +166 -0
  107. package/src/components/NTreeMenu.nexa +142 -0
  108. package/src/index.ts +36 -0
  109. package/src/services/FloatingOverlay.ts +133 -0
  110. package/src/services/FormValidation.ts +44 -0
  111. package/src/services/ToastService.ts +41 -0
  112. package/src/shims.d.ts +5 -0
  113. package/src/styles/theme.ts +146 -0
  114. package/src/styles/tokens.css +170 -0
@@ -0,0 +1,169 @@
1
+ <script setup>
2
+ import { signal, computed, onMounted, onUnmounted } from 'nexa-framework'
3
+
4
+ const props = defineProps({
5
+ modelValue: { type: [String, Number], default: '' },
6
+ options: { type: Array, default: () => [] }, // [{ label, value }]
7
+ placeholder: { type: String, default: 'Select option...' },
8
+ label: { type: String, default: '' }
9
+ })
10
+
11
+ const emit = defineEmits(['update:modelValue'])
12
+
13
+ const isOpen = signal(false)
14
+ const selectRef = signal(null)
15
+
16
+ const selectedOption = computed(() => {
17
+ return props.options.find(opt => opt.value === props.modelValue) || null
18
+ })
19
+
20
+ const toggle = () => {
21
+ isOpen.value = !isOpen.value
22
+ }
23
+
24
+ const select = (option) => {
25
+ emit('update:modelValue', option.value)
26
+ isOpen.value = false
27
+ }
28
+
29
+ const handleClickOutside = (e) => {
30
+ if (isOpen.value && !e.target.closest('.n-select')) {
31
+ isOpen.value = false
32
+ }
33
+ }
34
+
35
+ onMounted(() => {
36
+ document.addEventListener('click', handleClickOutside)
37
+ })
38
+
39
+ onUnmounted(() => {
40
+ document.removeEventListener('click', handleClickOutside)
41
+ })
42
+ </script>
43
+
44
+ <template>
45
+ <div class="n-select" :class="{ 'is-open': isOpen.value }">
46
+ <label v-if="label" class="n-select-label">{{ label }}</label>
47
+
48
+ <div class="n-select-trigger" @click="toggle">
49
+ <span v-if="selectedOption.value" class="n-select-value">
50
+ {{ selectedOption.value.label }}
51
+ </span>
52
+ <span v-else class="n-select-placeholder">
53
+ {{ placeholder }}
54
+ </span>
55
+ <span class="n-select-arrow">▾</span>
56
+ </div>
57
+
58
+ <div v-if="isOpen.value" class="n-select-dropdown">
59
+ <div
60
+ v-for="opt in options"
61
+ :key="opt.value"
62
+ class="n-select-option"
63
+ :class="{ 'is-selected': opt.value === modelValue }"
64
+ @click="select(opt)"
65
+ >
66
+ {{ opt.label }}
67
+ </div>
68
+ <div v-if="options.length === 0" class="n-select-empty">
69
+ No options available
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </template>
74
+
75
+ <style scoped>
76
+ .n-select {
77
+ position: relative;
78
+ width: 100%;
79
+ font-family: 'Inter', sans-serif;
80
+ }
81
+
82
+ .n-select-label {
83
+ display: block;
84
+ font-size: 0.875rem;
85
+ font-weight: 500;
86
+ color: #94a3b8;
87
+ margin-bottom: 0.5rem;
88
+ }
89
+
90
+ .n-select-trigger {
91
+ background: rgba(30, 41, 59, 0.5);
92
+ border: 1px solid rgba(255, 255, 255, 0.1);
93
+ border-radius: 12px;
94
+ padding: 0.75rem 1rem;
95
+ display: flex;
96
+ justify-content: space-between;
97
+ align-items: center;
98
+ cursor: pointer;
99
+ transition: all 0.2s;
100
+ color: #f8fafc;
101
+ }
102
+
103
+ .n-select:hover .n-select-trigger {
104
+ border-color: rgba(59, 130, 246, 0.5);
105
+ background: rgba(30, 41, 59, 0.8);
106
+ }
107
+
108
+ .n-select.is-open .n-select-trigger {
109
+ border-color: #3b82f6;
110
+ box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.15);
111
+ }
112
+
113
+ .n-select-placeholder {
114
+ color: #64748b;
115
+ }
116
+
117
+ .n-select-arrow {
118
+ color: #64748b;
119
+ transition: transform 0.2s;
120
+ }
121
+
122
+ .n-select.is-open .n-select-arrow {
123
+ transform: rotate(180deg);
124
+ }
125
+
126
+ .n-select-dropdown {
127
+ position: absolute;
128
+ top: calc(100% + 0.5rem);
129
+ left: 0;
130
+ width: 100%;
131
+ background: #0f172a;
132
+ border: 1px solid rgba(255, 255, 255, 0.1);
133
+ border-radius: 12px;
134
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
135
+ z-index: 100;
136
+ overflow: hidden;
137
+ animation: dropdown-in 0.2s cubic-bezier(0, 1, 0, 1);
138
+ }
139
+
140
+ @keyframes dropdown-in {
141
+ from { opacity: 0; transform: translateY(-10px); }
142
+ to { opacity: 1; transform: translateY(0); }
143
+ }
144
+
145
+ .n-select-option {
146
+ padding: 0.75rem 1rem;
147
+ color: #94a3b8;
148
+ cursor: pointer;
149
+ transition: all 0.2s;
150
+ }
151
+
152
+ .n-select-option:hover {
153
+ background: rgba(255, 255, 255, 0.05);
154
+ color: #f8fafc;
155
+ }
156
+
157
+ .n-select-option.is-selected {
158
+ background: rgba(59, 130, 246, 0.1);
159
+ color: #3b82f6;
160
+ font-weight: 600;
161
+ }
162
+
163
+ .n-select-empty {
164
+ padding: 1rem;
165
+ color: #64748b;
166
+ text-align: center;
167
+ font-size: 0.875rem;
168
+ }
169
+ </style>
@@ -0,0 +1,86 @@
1
+ <script setup>
2
+ import { useToast } from '../services/ToastService.js'
3
+
4
+ const { toasts, remove } = useToast()
5
+ </script>
6
+
7
+ <template>
8
+ <Teleport to="body">
9
+ <div class="n-toast-container">
10
+ <div
11
+ v-for="toast in toasts.value"
12
+ :key="toast.id"
13
+ class="n-toast"
14
+ :class="`is-${toast.type}`"
15
+ @click="remove(toast.id)"
16
+ >
17
+ <span class="n-toast-icon">
18
+ {{ toast.type === 'success' ? '✓' : toast.type === 'error' ? '✕' : 'ℹ' }}
19
+ </span>
20
+ <span class="n-toast-message">{{ toast.message }}</span>
21
+ </div>
22
+ </div>
23
+ </Teleport>
24
+ </template>
25
+
26
+ <style scoped>
27
+ .n-toast-container {
28
+ position: fixed;
29
+ top: 1.5rem;
30
+ right: 1.5rem;
31
+ z-index: 3000;
32
+ display: flex;
33
+ flex-direction: column;
34
+ gap: 0.75rem;
35
+ pointer-events: none;
36
+ }
37
+
38
+ .n-toast {
39
+ pointer-events: auto;
40
+ min-width: 280px;
41
+ max-width: 400px;
42
+ background: #0f172a;
43
+ border: 1px solid rgba(255, 255, 255, 0.1);
44
+ border-radius: 12px;
45
+ padding: 1rem 1.25rem;
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 1rem;
49
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3);
50
+ cursor: pointer;
51
+ animation: toast-in 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
52
+ transition: all 0.2s;
53
+ }
54
+
55
+ @keyframes toast-in {
56
+ from { opacity: 0; transform: translateX(50px) scale(0.9); }
57
+ to { opacity: 1; transform: translateX(0) scale(1); }
58
+ }
59
+
60
+ .n-toast:hover {
61
+ transform: scale(1.02);
62
+ background: #1e293b;
63
+ }
64
+
65
+ .n-toast-icon {
66
+ width: 24px;
67
+ height: 24px;
68
+ border-radius: 50%;
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ font-size: 0.8rem;
73
+ font-weight: 800;
74
+ }
75
+
76
+ .is-success .n-toast-icon { background: #10b981; color: white; }
77
+ .is-error .n-toast-icon { background: #ef4444; color: white; }
78
+ .is-info .n-toast-icon { background: #3b82f6; color: white; }
79
+ .is-warning .n-toast-icon { background: #f59e0b; color: white; }
80
+
81
+ .n-toast-message {
82
+ color: #f8fafc;
83
+ font-size: 0.9rem;
84
+ font-weight: 500;
85
+ }
86
+ </style>
@@ -0,0 +1,115 @@
1
+ <script setup>
2
+ import { signal } from 'nexa-framework'
3
+
4
+ const props = defineProps({
5
+ text: { type: String, default: '' },
6
+ position: { type: String, default: 'top' } // top, bottom, left, right
7
+ })
8
+
9
+ const isVisible = signal(false)
10
+ </script>
11
+
12
+ <template>
13
+ <div
14
+ class="n-tooltip-wrapper"
15
+ @mouseenter="isVisible.value = true"
16
+ @mouseleave="isVisible.value = false"
17
+ >
18
+ <slot />
19
+
20
+ <div
21
+ v-if="isVisible.value"
22
+ class="n-tooltip"
23
+ :class="`is-${position}`"
24
+ >
25
+ {{ text }}
26
+ </div>
27
+ </div>
28
+ </template>
29
+
30
+ <style scoped>
31
+ .n-tooltip-wrapper {
32
+ position: relative;
33
+ display: inline-block;
34
+ }
35
+
36
+ .n-tooltip {
37
+ position: absolute;
38
+ background: #0f172a;
39
+ color: #f8fafc;
40
+ padding: 0.5rem 0.75rem;
41
+ border-radius: 6px;
42
+ font-size: 0.75rem;
43
+ font-weight: 500;
44
+ white-space: nowrap;
45
+ z-index: 1000;
46
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
47
+ border: 1px solid rgba(255, 255, 255, 0.1);
48
+ pointer-events: none;
49
+ animation: tooltip-in 0.2s ease-out;
50
+ }
51
+
52
+ @keyframes tooltip-in {
53
+ from { opacity: 0; transform: scale(0.95); }
54
+ to { opacity: 1; transform: scale(1); }
55
+ }
56
+
57
+ .is-top {
58
+ bottom: calc(100% + 0.5rem);
59
+ left: 50%;
60
+ transform: translateX(-50%);
61
+ }
62
+
63
+ .is-bottom {
64
+ top: calc(100% + 0.5rem);
65
+ left: 50%;
66
+ transform: translateX(-50%);
67
+ }
68
+
69
+ .is-left {
70
+ right: calc(100% + 0.5rem);
71
+ top: 50%;
72
+ transform: translateY(-50%);
73
+ }
74
+
75
+ .is-right {
76
+ left: calc(100% + 0.5rem);
77
+ top: 50%;
78
+ transform: translateY(-50%);
79
+ }
80
+
81
+ /* Small arrow */
82
+ .n-tooltip::after {
83
+ content: '';
84
+ position: absolute;
85
+ border: 5px solid transparent;
86
+ }
87
+
88
+ .is-top::after {
89
+ top: 100%;
90
+ left: 50%;
91
+ transform: translateX(-50%);
92
+ border-top-color: #0f172a;
93
+ }
94
+
95
+ .is-bottom::after {
96
+ bottom: 100%;
97
+ left: 50%;
98
+ transform: translateX(-50%);
99
+ border-bottom-color: #0f172a;
100
+ }
101
+
102
+ .is-left::after {
103
+ left: 100%;
104
+ top: 50%;
105
+ transform: translateY(-50%);
106
+ border-left-color: #0f172a;
107
+ }
108
+
109
+ .is-right::after {
110
+ right: 100%;
111
+ top: 50%;
112
+ transform: translateY(-50%);
113
+ border-right-color: #0f172a;
114
+ }
115
+ </style>
@@ -0,0 +1,134 @@
1
+ import { signal, h, hText, effect, defineComponent, registerComponent, reloadComponent, injectStyle } from 'nexa-framework'
2
+
3
+ const _sfc_main = defineComponent({
4
+ __scopeId: 'data-v-22a3a541',
5
+ __hmrId: 'NAlert_nexa',
6
+ props: {
7
+ variant: { type: String, default: 'info' },
8
+ title: { type: String, default: '' },
9
+ closable: { type: Boolean, default: false },
10
+ icon: { type: String, default: '' }
11
+ },
12
+ emits: ['close'],
13
+ setup(props, setupContext) {
14
+ const { emit, slots, slots: $slots } = setupContext
15
+ const visible = signal(true)
16
+ const dismiss = () => {
17
+ visible.value = false
18
+ emit('close')
19
+ }
20
+ const icons = { success: '✓', error: '✕', warning: '⚡', info: 'ℹ' }
21
+ return { visible, dismiss, icons, $slots, emit }
22
+ }
23
+ })
24
+ // Injected render function
25
+ _sfc_main.render = function(ctx) {
26
+ const { visible, dismiss, icons, $slots, emit, variant, title, closable, icon, Fragment: _ntc_Fragment } = ctx
27
+ return (visible.value) ? h('div', { class: ["n-alert", `is-${variant}`], "data-v-22a3a541": "" }, [
28
+ "\n ",
29
+ h('span', { class: "n-alert-icon", "data-v-22a3a541": "" }, [
30
+ icon || icons[variant] || 'ℹ'
31
+ ]),
32
+ "\n ",
33
+ h('div', { class: "n-alert-body", "data-v-22a3a541": "" }, [
34
+ "\n ",
35
+ (title) ? h('span', { class: "n-alert-title", "data-v-22a3a541": "" }, [
36
+ title
37
+ ]) : null,
38
+ h('span', { class: "n-alert-text", "data-v-22a3a541": "" }, [
39
+ ctx.$slots.default ? ctx.$slots.default() : null
40
+ ]),
41
+ "\n "
42
+ ]),
43
+ "\n ",
44
+ (closable) ? h('button', { class: "n-alert-close", onClick: dismiss, "data-v-22a3a541": "" }, [
45
+ "×"
46
+ ]) : null
47
+ ]) : null
48
+ }
49
+ _sfc_main.__scopeId = 'data-v-22a3a541'
50
+ _sfc_main.__hmrId = 'NAlert_nexa'
51
+
52
+ export default _sfc_main
53
+
54
+ const __style = `.n-alert[data-v-22a3a541]{
55
+ display: flex;
56
+ align-items: flex-start;
57
+ gap: var(--n-space-3);
58
+ padding: var(--n-space-4) var(--n-space-5);
59
+ border-radius: var(--n-radius-md);
60
+ border: 1px solid transparent;
61
+ font-size: var(--n-text-sm);
62
+ line-height: var(--n-leading-normal);
63
+ animation: n-alert-in 0.25s ease-out;
64
+ }
65
+
66
+ @keyframes n-alert-in {
67
+ from[data-v-22a3a541]{ opacity: 0; transform: translateY(-8px); }
68
+ to[data-v-22a3a541]{ opacity: 1; transform: translateY(0); }
69
+ }
70
+
71
+ .is-info[data-v-22a3a541]{
72
+ background: var(--n-color-primary-light);
73
+ border-color: rgba(59, 130, 246, 0.2);
74
+ color: var(--n-color-primary);
75
+ }
76
+
77
+ .is-success[data-v-22a3a541]{
78
+ background: var(--n-color-success-light);
79
+ border-color: rgba(16, 185, 129, 0.2);
80
+ color: var(--n-color-success);
81
+ }
82
+
83
+ .is-warning[data-v-22a3a541]{
84
+ background: var(--n-color-warning-light);
85
+ border-color: rgba(245, 158, 11, 0.2);
86
+ color: var(--n-color-warning);
87
+ }
88
+
89
+ .is-error[data-v-22a3a541]{
90
+ background: var(--n-color-danger-light);
91
+ border-color: rgba(239, 68, 68, 0.2);
92
+ color: var(--n-color-danger);
93
+ }
94
+
95
+ .n-alert-icon[data-v-22a3a541]{
96
+ font-size: var(--n-text-base);
97
+ font-weight: var(--n-weight-bold);
98
+ flex-shrink: 0;
99
+ line-height: 1.4;
100
+ }
101
+
102
+ .n-alert-body{
103
+ flex: 1;
104
+ display: flex;
105
+ flex-direction: column;
106
+ gap: var(--n-space-1);
107
+ color: var(--n-color-text);
108
+ }
109
+
110
+ .n-alert-title[data-v-22a3a541]{
111
+ font-weight: var(--n-weight-semibold);
112
+ font-size: var(--n-text-sm);
113
+ }
114
+
115
+ .n-alert-text[data-v-22a3a541]{
116
+ color: var(--n-color-text-secondary);
117
+ }
118
+
119
+ .n-alert-close[data-v-22a3a541]{
120
+ background: transparent;
121
+ border: none;
122
+ color: var(--n-color-text-muted);
123
+ font-size: var(--n-text-lg);
124
+ cursor: pointer;
125
+ padding: 0;
126
+ line-height: 1;
127
+ flex-shrink: 0;
128
+ transition: color var(--n-transition-fast);
129
+ }
130
+
131
+ .n-alert-close[data-v-22a3a541]:hover{
132
+ color: var(--n-color-text);
133
+ }`
134
+ injectStyle('data-v-22a3a541', __style)
@@ -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">&times;</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>