birus-ui 0.0.0-stage → 0.3.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 (44) hide show
  1. package/README.md +232 -2
  2. package/dist/birus-ui.js +6919 -0
  3. package/dist/birus-ui.umd.cjs +10 -0
  4. package/dist/style.css +1 -0
  5. package/dist/tailwind-preset.js +70 -0
  6. package/dist/themes.css +217 -0
  7. package/package.json +41 -4
  8. package/src/components/BirusAccordion.vue +112 -0
  9. package/src/components/BirusAlert.vue +114 -0
  10. package/src/components/BirusAvatar.vue +67 -0
  11. package/src/components/BirusBadge.vue +64 -0
  12. package/src/components/BirusBreadcrumb.vue +61 -0
  13. package/src/components/BirusButton.vue +84 -0
  14. package/src/components/BirusCard.vue +89 -0
  15. package/src/components/BirusCheckbox.vue +106 -0
  16. package/src/components/BirusDialog.vue +127 -0
  17. package/src/components/BirusDrawer.vue +158 -0
  18. package/src/components/BirusDropdownMenu.vue +184 -0
  19. package/src/components/BirusEmptyState.vue +64 -0
  20. package/src/components/BirusHeading.vue +70 -0
  21. package/src/components/BirusInput.vue +197 -0
  22. package/src/components/BirusKbd.vue +43 -0
  23. package/src/components/BirusPagination.vue +132 -0
  24. package/src/components/BirusProgress.vue +70 -0
  25. package/src/components/BirusRadioGroup.vue +157 -0
  26. package/src/components/BirusSelect.vue +272 -0
  27. package/src/components/BirusSeparator.vue +72 -0
  28. package/src/components/BirusSkeleton.vue +46 -0
  29. package/src/components/BirusSlider.vue +96 -0
  30. package/src/components/BirusStatCard.vue +106 -0
  31. package/src/components/BirusStepper.vue +114 -0
  32. package/src/components/BirusSwitch.vue +76 -0
  33. package/src/components/BirusTable.vue +126 -0
  34. package/src/components/BirusTabs.vue +121 -0
  35. package/src/components/BirusText.vue +74 -0
  36. package/src/components/BirusTextarea.vue +84 -0
  37. package/src/components/BirusTimeline.vue +61 -0
  38. package/src/components/BirusToaster.vue +130 -0
  39. package/src/components/BirusTooltip.vue +63 -0
  40. package/src/composables/useBirusTheme.js +144 -0
  41. package/src/composables/useToast.js +63 -0
  42. package/src/index.js +116 -0
  43. package/src/lib-entry.js +3 -0
  44. package/src/style.css +5 -0
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Preset Tailwind compartilhável para qualquer projeto Vue ou React que adote o birusUI.
3
+ */
4
+ export const birusTailwindPreset = {
5
+ darkMode: ['class', '[data-birus-mode="dark"]'],
6
+ theme: {
7
+ extend: {
8
+ fontFamily: {
9
+ sans: ['var(--birus-font-sans, Inter, system-ui, sans-serif)'],
10
+ mono: ['var(--birus-font-mono, "JetBrains Mono", ui-monospace, monospace)'],
11
+ },
12
+ colors: {
13
+ bg: 'hsl(var(--birus-bg) / <alpha-value>)',
14
+ fg: 'hsl(var(--birus-fg) / <alpha-value>)',
15
+ surface: {
16
+ DEFAULT: 'hsl(var(--birus-surface) / <alpha-value>)',
17
+ hover: 'hsl(var(--birus-surface-hover) / <alpha-value>)',
18
+ },
19
+ card: {
20
+ DEFAULT: 'hsl(var(--birus-card) / <alpha-value>)',
21
+ fg: 'hsl(var(--birus-card-fg) / <alpha-value>)',
22
+ },
23
+ popover: {
24
+ DEFAULT: 'hsl(var(--birus-popover) / <alpha-value>)',
25
+ fg: 'hsl(var(--birus-popover-fg) / <alpha-value>)',
26
+ },
27
+ primary: {
28
+ DEFAULT: 'hsl(var(--birus-primary) / <alpha-value>)',
29
+ fg: 'hsl(var(--birus-primary-fg) / <alpha-value>)',
30
+ },
31
+ secondary: {
32
+ DEFAULT: 'hsl(var(--birus-secondary) / <alpha-value>)',
33
+ fg: 'hsl(var(--birus-secondary-fg) / <alpha-value>)',
34
+ },
35
+ muted: {
36
+ DEFAULT: 'hsl(var(--birus-muted) / <alpha-value>)',
37
+ fg: 'hsl(var(--birus-muted-fg) / <alpha-value>)',
38
+ },
39
+ accent: {
40
+ DEFAULT: 'hsl(var(--birus-accent) / <alpha-value>)',
41
+ fg: 'hsl(var(--birus-accent-fg) / <alpha-value>)',
42
+ },
43
+ destructive: {
44
+ DEFAULT: 'hsl(var(--birus-destructive) / <alpha-value>)',
45
+ fg: 'hsl(var(--birus-destructive-fg) / <alpha-value>)',
46
+ },
47
+ success: {
48
+ DEFAULT: 'hsl(var(--birus-success) / <alpha-value>)',
49
+ fg: 'hsl(var(--birus-success-fg) / <alpha-value>)',
50
+ },
51
+ warning: {
52
+ DEFAULT: 'hsl(var(--birus-warning) / <alpha-value>)',
53
+ fg: 'hsl(var(--birus-warning-fg) / <alpha-value>)',
54
+ },
55
+ info: {
56
+ DEFAULT: 'hsl(var(--birus-info) / <alpha-value>)',
57
+ fg: 'hsl(var(--birus-info-fg) / <alpha-value>)',
58
+ },
59
+ border: 'hsl(var(--birus-border) / <alpha-value>)',
60
+ input: 'hsl(var(--birus-input) / <alpha-value>)',
61
+ ring: 'hsl(var(--birus-ring) / <alpha-value>)',
62
+ },
63
+ borderRadius: {
64
+ birus: 'var(--birus-radius)',
65
+ 'birus-sm': 'calc(var(--birus-radius) * 0.65)',
66
+ 'birus-lg': 'calc(var(--birus-radius) * 1.4)',
67
+ },
68
+ },
69
+ },
70
+ };
@@ -0,0 +1,217 @@
1
+ /* birusUI Design System Tokens - CSS Variables (Light/Dark + 6 Paletas + Radius) */
2
+
3
+ :root {
4
+ --birus-bg: 0 0% 100%;
5
+ --birus-fg: 240 10% 4%;
6
+ --birus-surface: 260 25% 98%;
7
+ --birus-surface-hover: 260 25% 94%;
8
+ --birus-card: 0 0% 100%;
9
+ --birus-card-fg: 240 10% 4%;
10
+ --birus-popover: 0 0% 100%;
11
+ --birus-popover-fg: 240 10% 4%;
12
+ --birus-primary: 262 83% 58%;
13
+ --birus-primary-fg: 0 0% 100%;
14
+ --birus-secondary: 260 25% 94%;
15
+ --birus-secondary-fg: 262 45% 24%;
16
+ --birus-muted: 260 15% 95%;
17
+ --birus-muted-fg: 240 5% 44%;
18
+ --birus-accent: 262 80% 94%;
19
+ --birus-accent-fg: 262 83% 42%;
20
+ --birus-destructive: 0 84% 60%;
21
+ --birus-destructive-fg: 0 0% 100%;
22
+ --birus-success: 142 71% 45%;
23
+ --birus-success-fg: 0 0% 100%;
24
+ --birus-warning: 38 92% 50%;
25
+ --birus-warning-fg: 24 10% 10%;
26
+ --birus-info: 199 89% 48%;
27
+ --birus-info-fg: 0 0% 100%;
28
+ --birus-border: 260 16% 89%;
29
+ --birus-input: 260 16% 86%;
30
+ --birus-ring: 262 83% 58%;
31
+ --birus-radius: 0.5rem;
32
+ }
33
+
34
+ .dark,
35
+ [data-birus-mode='dark'] {
36
+ --birus-bg: 240 16% 6%;
37
+ --birus-fg: 250 20% 96%;
38
+ --birus-surface: 244 16% 9%;
39
+ --birus-surface-hover: 244 18% 13%;
40
+ --birus-card: 244 16% 9%;
41
+ --birus-card-fg: 250 20% 96%;
42
+ --birus-popover: 244 18% 11%;
43
+ --birus-popover-fg: 250 20% 96%;
44
+ --birus-primary: 263 86% 66%;
45
+ --birus-primary-fg: 0 0% 100%;
46
+ --birus-secondary: 246 18% 16%;
47
+ --birus-secondary-fg: 250 25% 92%;
48
+ --birus-muted: 246 14% 15%;
49
+ --birus-muted-fg: 245 10% 64%;
50
+ --birus-accent: 262 45% 20%;
51
+ --birus-accent-fg: 263 90% 82%;
52
+ --birus-destructive: 0 72% 51%;
53
+ --birus-destructive-fg: 0 0% 100%;
54
+ --birus-success: 142 69% 48%;
55
+ --birus-success-fg: 144 61% 8%;
56
+ --birus-warning: 38 92% 55%;
57
+ --birus-warning-fg: 24 10% 8%;
58
+ --birus-info: 199 89% 54%;
59
+ --birus-info-fg: 204 80% 8%;
60
+ --birus-border: 246 16% 18%;
61
+ --birus-input: 246 16% 22%;
62
+ --birus-ring: 263 86% 66%;
63
+ }
64
+
65
+ /* Radius Presets */
66
+ [data-birus-radius='none'] { --birus-radius: 0px; }
67
+ [data-birus-radius='sm'] { --birus-radius: 0.25rem; }
68
+ [data-birus-radius='md'] { --birus-radius: 0.5rem; }
69
+ [data-birus-radius='lg'] { --birus-radius: 0.75rem; }
70
+ [data-birus-radius='full'] { --birus-radius: 1rem; }
71
+
72
+ /* Emerald Palette */
73
+ [data-birus-theme='emerald'] {
74
+ --birus-surface: 155 25% 98%;
75
+ --birus-surface-hover: 155 24% 94%;
76
+ --birus-primary: 158 74% 39%;
77
+ --birus-secondary: 155 22% 93%;
78
+ --birus-secondary-fg: 160 40% 20%;
79
+ --birus-accent: 158 65% 92%;
80
+ --birus-accent-fg: 158 75% 28%;
81
+ --birus-border: 155 16% 88%;
82
+ --birus-input: 155 16% 84%;
83
+ --birus-ring: 158 74% 39%;
84
+ }
85
+ [data-birus-theme='emerald'][data-birus-mode='dark'] {
86
+ --birus-bg: 162 24% 5%;
87
+ --birus-surface: 162 20% 8%;
88
+ --birus-surface-hover: 162 20% 12%;
89
+ --birus-card: 162 20% 8%;
90
+ --birus-popover: 162 22% 10%;
91
+ --birus-primary: 158 72% 48%;
92
+ --birus-primary-fg: 162 40% 7%;
93
+ --birus-secondary: 160 18% 15%;
94
+ --birus-accent: 158 45% 17%;
95
+ --birus-accent-fg: 158 80% 75%;
96
+ --birus-border: 160 16% 17%;
97
+ --birus-input: 160 16% 21%;
98
+ --birus-ring: 158 72% 48%;
99
+ }
100
+
101
+ /* Ocean Palette */
102
+ [data-birus-theme='ocean'] {
103
+ --birus-surface: 205 35% 98%;
104
+ --birus-surface-hover: 205 32% 94%;
105
+ --birus-primary: 204 88% 48%;
106
+ --birus-secondary: 205 30% 93%;
107
+ --birus-secondary-fg: 210 45% 22%;
108
+ --birus-accent: 204 85% 93%;
109
+ --birus-accent-fg: 204 88% 34%;
110
+ --birus-border: 210 20% 89%;
111
+ --birus-input: 210 20% 85%;
112
+ --birus-ring: 204 88% 48%;
113
+ }
114
+ [data-birus-theme='ocean'][data-birus-mode='dark'] {
115
+ --birus-bg: 216 32% 6%;
116
+ --birus-surface: 216 28% 9%;
117
+ --birus-surface-hover: 216 26% 13%;
118
+ --birus-card: 216 28% 9%;
119
+ --birus-popover: 216 28% 11%;
120
+ --birus-primary: 201 92% 55%;
121
+ --birus-primary-fg: 216 40% 7%;
122
+ --birus-secondary: 215 22% 16%;
123
+ --birus-accent: 204 55% 18%;
124
+ --birus-accent-fg: 201 92% 78%;
125
+ --birus-border: 215 20% 18%;
126
+ --birus-input: 215 20% 22%;
127
+ --birus-ring: 201 92% 55%;
128
+ }
129
+
130
+ /* Rose Palette */
131
+ [data-birus-theme='rose'] {
132
+ --birus-surface: 345 30% 98%;
133
+ --birus-surface-hover: 345 28% 94%;
134
+ --birus-primary: 346 84% 58%;
135
+ --birus-secondary: 345 24% 94%;
136
+ --birus-secondary-fg: 345 45% 24%;
137
+ --birus-accent: 346 80% 94%;
138
+ --birus-accent-fg: 346 84% 40%;
139
+ --birus-border: 345 18% 89%;
140
+ --birus-input: 345 18% 85%;
141
+ --birus-ring: 346 84% 58%;
142
+ }
143
+ [data-birus-theme='rose'][data-birus-mode='dark'] {
144
+ --birus-bg: 346 22% 6%;
145
+ --birus-surface: 346 18% 9%;
146
+ --birus-surface-hover: 346 18% 13%;
147
+ --birus-card: 346 18% 9%;
148
+ --birus-popover: 346 20% 11%;
149
+ --birus-primary: 346 86% 62%;
150
+ --birus-primary-fg: 0 0% 100%;
151
+ --birus-secondary: 346 16% 16%;
152
+ --birus-accent: 346 48% 19%;
153
+ --birus-accent-fg: 346 90% 80%;
154
+ --birus-border: 346 16% 18%;
155
+ --birus-input: 346 16% 22%;
156
+ --birus-ring: 346 86% 62%;
157
+ }
158
+
159
+ /* Amber Palette */
160
+ [data-birus-theme='amber'] {
161
+ --birus-surface: 38 40% 98%;
162
+ --birus-surface-hover: 38 35% 94%;
163
+ --birus-primary: 36 92% 48%;
164
+ --birus-primary-fg: 28 30% 8%;
165
+ --birus-secondary: 38 30% 93%;
166
+ --birus-secondary-fg: 30 45% 20%;
167
+ --birus-accent: 38 88% 92%;
168
+ --birus-accent-fg: 32 90% 30%;
169
+ --birus-border: 36 20% 88%;
170
+ --birus-input: 36 20% 84%;
171
+ --birus-ring: 36 92% 48%;
172
+ }
173
+ [data-birus-theme='amber'][data-birus-mode='dark'] {
174
+ --birus-bg: 28 22% 5%;
175
+ --birus-surface: 28 18% 8%;
176
+ --birus-surface-hover: 28 18% 12%;
177
+ --birus-card: 28 18% 8%;
178
+ --birus-popover: 28 20% 10%;
179
+ --birus-primary: 38 94% 54%;
180
+ --birus-primary-fg: 28 35% 7%;
181
+ --birus-secondary: 28 16% 15%;
182
+ --birus-accent: 36 48% 17%;
183
+ --birus-accent-fg: 38 92% 76%;
184
+ --birus-border: 28 16% 17%;
185
+ --birus-input: 28 16% 21%;
186
+ --birus-ring: 38 94% 54%;
187
+ }
188
+
189
+ /* Zinc Minimal Palette */
190
+ [data-birus-theme='zinc'] {
191
+ --birus-surface: 240 5% 98%;
192
+ --birus-surface-hover: 240 5% 94%;
193
+ --birus-primary: 240 6% 10%;
194
+ --birus-primary-fg: 0 0% 98%;
195
+ --birus-secondary: 240 5% 94%;
196
+ --birus-secondary-fg: 240 6% 10%;
197
+ --birus-accent: 240 5% 93%;
198
+ --birus-accent-fg: 240 6% 10%;
199
+ --birus-border: 240 6% 89%;
200
+ --birus-input: 240 6% 85%;
201
+ --birus-ring: 240 6% 10%;
202
+ }
203
+ [data-birus-theme='zinc'][data-birus-mode='dark'] {
204
+ --birus-bg: 240 10% 4%;
205
+ --birus-surface: 240 8% 7%;
206
+ --birus-surface-hover: 240 7% 11%;
207
+ --birus-card: 240 8% 7%;
208
+ --birus-popover: 240 8% 9%;
209
+ --birus-primary: 0 0% 98%;
210
+ --birus-primary-fg: 240 6% 10%;
211
+ --birus-secondary: 240 5% 15%;
212
+ --birus-accent: 240 5% 18%;
213
+ --birus-accent-fg: 0 0% 98%;
214
+ --birus-border: 240 5% 17%;
215
+ --birus-input: 240 5% 21%;
216
+ --birus-ring: 240 5% 84%;
217
+ }
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "birus-ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.3.0",
4
+ "description": "birusUI — Design System & Biblioteca de 32 Componentes Acessíveis para Vue 3 em JavaScript Puro, com temas reativos e fácil customização.",
5
+ "type": "module",
6
+ "main": "./dist/birus-ui.umd.cjs",
7
+ "module": "./dist/birus-ui.js",
8
+ "style": "./dist/style.css",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/birus-ui.js",
12
+ "require": "./dist/birus-ui.umd.cjs"
13
+ },
14
+ "./style.css": "./dist/style.css",
15
+ "./themes.css": "./dist/themes.css",
16
+ "./tailwind-preset": "./dist/tailwind-preset.js"
17
+ },
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "README.md"
22
+ ],
23
+ "scripts": {
24
+ "build": "vite build",
25
+ "prepublishOnly": "vite build"
26
+ },
27
+ "keywords": [
28
+ "vue",
29
+ "vue3",
30
+ "design-system",
31
+ "ui-library",
32
+ "components",
33
+ "tailwindcss",
34
+ "cva",
35
+ "shadcn",
36
+ "birus-ui"
37
+ ],
38
+ "author": "Gabriel Reis",
39
+ "license": "MIT",
40
+ "peerDependencies": {
41
+ "vue": "^3.4.0"
42
+ }
43
+ }
@@ -0,0 +1,112 @@
1
+ <script setup>
2
+ import { computed, ref, watch } from 'vue';
3
+ import { accordionRootVariants, accordionItemVariants, cn } from '@birus-ui/core';
4
+
5
+ const props = defineProps({
6
+ items: { type: Array, default: () => [] },
7
+ type: { type: String, default: 'single' },
8
+ variant: { type: String, default: 'default' },
9
+ modelValue: { type: [String, Array], default: undefined },
10
+ defaultOpen: { type: [String, Array], default: undefined },
11
+ class: { type: String, default: '' },
12
+ });
13
+
14
+ const emit = defineEmits(['update:modelValue']);
15
+
16
+ function normalizeIds(val) {
17
+ if (!val) return [];
18
+ return Array.isArray(val) ? val : [val];
19
+ }
20
+
21
+ const internalOpenIds = ref(normalizeIds(props.modelValue ?? props.defaultOpen));
22
+
23
+ watch(
24
+ () => props.modelValue,
25
+ (next) => {
26
+ if (next !== undefined) {
27
+ internalOpenIds.value = normalizeIds(next);
28
+ }
29
+ },
30
+ );
31
+
32
+ const rootClasses = computed(() =>
33
+ cn(accordionRootVariants({ variant: props.variant }), props.class),
34
+ );
35
+
36
+ function isOpen(id) {
37
+ return internalOpenIds.value.includes(id);
38
+ }
39
+
40
+ function toggleItem(item) {
41
+ if (item.disabled) return;
42
+ if (props.type === 'multiple') {
43
+ const nextIds = isOpen(item.id)
44
+ ? internalOpenIds.value.filter((x) => x !== item.id)
45
+ : [...internalOpenIds.value, item.id];
46
+ internalOpenIds.value = nextIds;
47
+ emit('update:modelValue', nextIds);
48
+ } else {
49
+ const nextSingle = isOpen(item.id) ? '' : item.id;
50
+ internalOpenIds.value = nextSingle ? [nextSingle] : [];
51
+ emit('update:modelValue', nextSingle);
52
+ }
53
+ }
54
+ </script>
55
+
56
+ <template>
57
+ <div :class="rootClasses">
58
+ <div
59
+ v-for="item in items"
60
+ :key="item.id"
61
+ :class="accordionItemVariants({ variant })"
62
+ >
63
+ <button
64
+ type="button"
65
+ :aria-expanded="isOpen(item.id) ? 'true' : 'false'"
66
+ :disabled="item.disabled"
67
+ :class="[
68
+ 'flex w-full items-center justify-between gap-4 px-4 py-3.5 text-left transition-colors',
69
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',
70
+ item.disabled
71
+ ? 'opacity-50 cursor-not-allowed'
72
+ : 'hover:bg-surface-hover/70 cursor-pointer',
73
+ ]"
74
+ @click="toggleItem(item)"
75
+ >
76
+ <div class="min-w-0">
77
+ <div class="text-sm font-medium text-fg">{{ item.title }}</div>
78
+ <div v-if="item.subtitle" class="text-xs text-muted-fg mt-0.5">
79
+ {{ item.subtitle }}
80
+ </div>
81
+ </div>
82
+ <svg
83
+ :class="[
84
+ 'h-4 w-4 shrink-0 text-muted-fg transition-transform duration-200',
85
+ isOpen(item.id) && 'rotate-180 text-primary',
86
+ ]"
87
+ viewBox="0 0 24 24"
88
+ fill="none"
89
+ stroke="currentColor"
90
+ stroke-width="2"
91
+ >
92
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 9l6 6 6-6" />
93
+ </svg>
94
+ </button>
95
+
96
+ <div
97
+ :class="[
98
+ 'grid transition-all duration-200 ease-out',
99
+ isOpen(item.id) ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0',
100
+ ]"
101
+ >
102
+ <div class="overflow-hidden">
103
+ <div class="px-4 pb-4 pt-1 text-sm text-muted-fg leading-relaxed">
104
+ <slot :name="`item-${item.id}`" :item="item">
105
+ {{ item.content }}
106
+ </slot>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ </template>
@@ -0,0 +1,114 @@
1
+ <script setup>
2
+ import { computed, ref } from 'vue';
3
+ import { alertVariants, cn } from '@birus-ui/core';
4
+
5
+ const props = defineProps({
6
+ variant: { type: String, default: 'default' },
7
+ title: { type: String, default: '' },
8
+ description: { type: String, default: '' },
9
+ dismissible: { type: Boolean, default: false },
10
+ class: { type: String, default: '' },
11
+ });
12
+
13
+ const emit = defineEmits(['dismiss']);
14
+
15
+ const visible = ref(true);
16
+
17
+ const classes = computed(() =>
18
+ cn(alertVariants({ variant: props.variant }), props.class),
19
+ );
20
+
21
+ function handleDismiss() {
22
+ visible.value = false;
23
+ emit('dismiss');
24
+ }
25
+ </script>
26
+
27
+ <template>
28
+ <Transition
29
+ enter-active-class="transition duration-200 ease-out"
30
+ enter-from-class="opacity-0 -translate-y-1"
31
+ enter-to-class="opacity-100 translate-y-0"
32
+ leave-active-class="transition duration-150 ease-in"
33
+ leave-from-class="opacity-100 scale-100"
34
+ leave-to-class="opacity-0 scale-95"
35
+ >
36
+ <div v-if="visible" role="alert" :class="classes">
37
+ <div class="birus-alert-icon mt-0.5 shrink-0 text-primary">
38
+ <slot name="icon">
39
+ <svg
40
+ v-if="variant === 'success'"
41
+ class="h-5 w-5"
42
+ viewBox="0 0 24 24"
43
+ fill="none"
44
+ stroke="currentColor"
45
+ stroke-width="2"
46
+ >
47
+ <circle cx="12" cy="12" r="10" />
48
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4" />
49
+ </svg>
50
+ <svg
51
+ v-else-if="variant === 'warning'"
52
+ class="h-5 w-5"
53
+ viewBox="0 0 24 24"
54
+ fill="none"
55
+ stroke="currentColor"
56
+ stroke-width="2"
57
+ >
58
+ <path
59
+ stroke-linecap="round"
60
+ stroke-linejoin="round"
61
+ d="M12 9v4m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"
62
+ />
63
+ </svg>
64
+ <svg
65
+ v-else-if="variant === 'destructive'"
66
+ class="h-5 w-5"
67
+ viewBox="0 0 24 24"
68
+ fill="none"
69
+ stroke="currentColor"
70
+ stroke-width="2"
71
+ >
72
+ <circle cx="12" cy="12" r="10" />
73
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15 9l-6 6m0-6l6 6" />
74
+ </svg>
75
+ <svg
76
+ v-else
77
+ class="h-5 w-5"
78
+ viewBox="0 0 24 24"
79
+ fill="none"
80
+ stroke="currentColor"
81
+ stroke-width="2"
82
+ >
83
+ <circle cx="12" cy="12" r="10" />
84
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 16v-4m0-4h.01" />
85
+ </svg>
86
+ </slot>
87
+ </div>
88
+
89
+ <div class="flex-1 min-w-0 space-y-1">
90
+ <h5 v-if="title" class="text-sm font-semibold leading-tight tracking-tight text-fg">
91
+ {{ title }}
92
+ </h5>
93
+ <div v-if="description || $slots.default" class="text-sm text-muted-fg leading-relaxed">
94
+ <slot>{{ description }}</slot>
95
+ </div>
96
+ <div v-if="$slots.actions" class="pt-2 flex flex-wrap items-center gap-2">
97
+ <slot name="actions" />
98
+ </div>
99
+ </div>
100
+
101
+ <button
102
+ v-if="dismissible"
103
+ type="button"
104
+ class="shrink-0 rounded-birus-sm p-1 text-muted-fg hover:text-fg hover:bg-surface-hover transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
105
+ aria-label="Fechar alerta"
106
+ @click="handleDismiss"
107
+ >
108
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
109
+ <path stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12" />
110
+ </svg>
111
+ </button>
112
+ </div>
113
+ </Transition>
114
+ </template>
@@ -0,0 +1,67 @@
1
+ <script setup>
2
+ import { computed, ref, watch } from 'vue';
3
+ import { avatarVariants, avatarStatusVariants, cn } from '@birus-ui/core';
4
+
5
+ const props = defineProps({
6
+ src: { type: String, default: '' },
7
+ alt: { type: String, default: '' },
8
+ name: { type: String, default: '' },
9
+ size: { type: String, default: 'md' },
10
+ shape: { type: String, default: 'circle' },
11
+ status: { type: String, default: undefined },
12
+ class: { type: String, default: '' },
13
+ });
14
+
15
+ const imageError = ref(false);
16
+
17
+ watch(
18
+ () => props.src,
19
+ () => {
20
+ imageError.value = false;
21
+ },
22
+ );
23
+
24
+ const initials = computed(() => {
25
+ const source = (props.name || props.alt || 'BU').trim();
26
+ const parts = source.split(/\s+/).filter(Boolean);
27
+ if (parts.length === 1) {
28
+ return parts[0].slice(0, 2).toUpperCase();
29
+ }
30
+ return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
31
+ });
32
+
33
+ const containerClasses = computed(() =>
34
+ cn(avatarVariants({ size: props.size, shape: props.shape }), props.class),
35
+ );
36
+
37
+ const statusClasses = computed(() =>
38
+ props.status
39
+ ? avatarStatusVariants({ status: props.status, size: props.size })
40
+ : '',
41
+ );
42
+ </script>
43
+
44
+ <template>
45
+ <span class="relative inline-flex shrink-0">
46
+ <span :class="containerClasses">
47
+ <img
48
+ v-if="src && !imageError"
49
+ :src="src"
50
+ :alt="alt || name || 'Avatar'"
51
+ class="h-full w-full object-cover"
52
+ @error="imageError = true"
53
+ />
54
+ <span
55
+ v-else
56
+ class="bg-gradient-to-br from-primary/20 to-accent text-primary font-semibold h-full w-full flex items-center justify-center"
57
+ >
58
+ {{ initials }}
59
+ </span>
60
+ </span>
61
+ <span
62
+ v-if="status"
63
+ :class="statusClasses"
64
+ :aria-label="`Status: ${status}`"
65
+ />
66
+ </span>
67
+ </template>
@@ -0,0 +1,64 @@
1
+ <script setup>
2
+ import { computed } from 'vue';
3
+ import { badgeVariants, cn } from '@birus-ui/core';
4
+
5
+ const props = defineProps({
6
+ variant: { type: String, default: 'default' },
7
+ size: { type: String, default: 'md' },
8
+ dot: { type: Boolean, default: false },
9
+ removable: { type: Boolean, default: false },
10
+ class: { type: String, default: '' },
11
+ });
12
+
13
+ const emit = defineEmits(['remove']);
14
+
15
+ const classes = computed(() =>
16
+ cn(
17
+ badgeVariants({
18
+ variant: props.variant,
19
+ size: props.size,
20
+ }),
21
+ props.class,
22
+ ),
23
+ );
24
+
25
+ const dotColorClass = computed(() => {
26
+ switch (props.variant) {
27
+ case 'success':
28
+ return 'bg-success';
29
+ case 'warning':
30
+ return 'bg-warning';
31
+ case 'destructive':
32
+ return 'bg-destructive';
33
+ case 'info':
34
+ return 'bg-info';
35
+ case 'default':
36
+ return 'bg-primary-fg';
37
+ default:
38
+ return 'bg-primary';
39
+ }
40
+ });
41
+ </script>
42
+
43
+ <template>
44
+ <span :class="classes">
45
+ <span v-if="dot" class="relative flex h-2 w-2 shrink-0" aria-hidden="true">
46
+ <span
47
+ :class="['animate-ping absolute inline-flex h-full w-full rounded-full opacity-75', dotColorClass]"
48
+ />
49
+ <span :class="['relative inline-flex rounded-full h-2 w-2', dotColorClass]" />
50
+ </span>
51
+ <slot />
52
+ <button
53
+ v-if="removable"
54
+ type="button"
55
+ class="ml-0.5 -mr-1 inline-flex h-4 w-4 items-center justify-center rounded-full opacity-70 hover:opacity-100 hover:bg-black/10 dark:hover:bg-white/15 transition-opacity focus:outline-none"
56
+ aria-label="Remover"
57
+ @click.stop="emit('remove')"
58
+ >
59
+ <svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2">
60
+ <path stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12" />
61
+ </svg>
62
+ </button>
63
+ </span>
64
+ </template>