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,61 @@
1
+ <script setup>
2
+ import { breadcrumbItemVariants, cn } from '@birus-ui/core';
3
+
4
+ const props = defineProps({
5
+ items: { type: Array, default: () => [] },
6
+ separator: { type: String, default: 'chevron' }, // 'chevron' | 'slash' | 'dot'
7
+ size: { type: String, default: 'md' },
8
+ class: { type: String, default: '' },
9
+ });
10
+
11
+ const emit = defineEmits(['navigate']);
12
+
13
+ function isLast(index) {
14
+ return index === props.items.length - 1;
15
+ }
16
+
17
+ function handleClick(item, index) {
18
+ if (isLast(index) || item.current) return;
19
+ emit('navigate', item);
20
+ }
21
+ </script>
22
+
23
+ <template>
24
+ <nav aria-label="Breadcrumb" :class="cn('flex items-center flex-wrap gap-1.5', props.class)">
25
+ <ol class="inline-flex items-center flex-wrap gap-1.5">
26
+ <li
27
+ v-for="(item, index) in items"
28
+ :key="item.value || item.label || index"
29
+ class="inline-flex items-center gap-1.5"
30
+ >
31
+ <button
32
+ type="button"
33
+ :aria-current="isLast(index) || item.current ? 'page' : undefined"
34
+ :class="breadcrumbItemVariants({ size, active: isLast(index) || Boolean(item.current) })"
35
+ @click="handleClick(item, index)"
36
+ >
37
+ <span>{{ item.label }}</span>
38
+ </button>
39
+
40
+ <span
41
+ v-if="!isLast(index)"
42
+ class="text-muted-fg/60 select-none inline-flex items-center"
43
+ aria-hidden="true"
44
+ >
45
+ <span v-if="separator === 'slash'" class="text-xs px-0.5">/</span>
46
+ <span v-else-if="separator === 'dot'" class="text-xs px-0.5">•</span>
47
+ <svg
48
+ v-else
49
+ class="h-3.5 w-3.5"
50
+ viewBox="0 0 24 24"
51
+ fill="none"
52
+ stroke="currentColor"
53
+ stroke-width="2"
54
+ >
55
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 18l6-6-6-6" />
56
+ </svg>
57
+ </span>
58
+ </li>
59
+ </ol>
60
+ </nav>
61
+ </template>
@@ -0,0 +1,84 @@
1
+ <script setup>
2
+ import { computed } from 'vue';
3
+ import { buttonVariants, cn } from '@birus-ui/core';
4
+
5
+ const props = defineProps({
6
+ variant: { type: String, default: 'default' },
7
+ size: { type: String, default: 'md' },
8
+ block: { type: Boolean, default: false },
9
+ pill: { type: Boolean, default: false },
10
+ loading: { type: Boolean, default: false },
11
+ disabled: { type: Boolean, default: false },
12
+ shortcut: { type: String, default: '' },
13
+ as: { type: String, default: 'button' },
14
+ type: { type: String, default: 'button' },
15
+ class: { type: String, default: '' },
16
+ });
17
+
18
+ const emit = defineEmits(['click']);
19
+
20
+ const isDisabled = computed(() => props.disabled || props.loading);
21
+
22
+ const classes = computed(() =>
23
+ cn(
24
+ buttonVariants({
25
+ variant: props.variant,
26
+ size: props.size,
27
+ block: props.block,
28
+ pill: props.pill,
29
+ }),
30
+ props.class,
31
+ ),
32
+ );
33
+
34
+ function handleClick(event) {
35
+ if (isDisabled.value) {
36
+ event.preventDefault();
37
+ return;
38
+ }
39
+ emit('click', event);
40
+ }
41
+ </script>
42
+
43
+ <template>
44
+ <component
45
+ :is="as"
46
+ :type="as === 'button' ? type : undefined"
47
+ :disabled="as === 'button' ? isDisabled : undefined"
48
+ :aria-disabled="isDisabled ? 'true' : undefined"
49
+ :aria-busy="loading ? 'true' : undefined"
50
+ :class="classes"
51
+ @click="handleClick"
52
+ >
53
+ <svg
54
+ v-if="loading"
55
+ class="h-4 w-4 animate-spin shrink-0"
56
+ viewBox="0 0 24 24"
57
+ fill="none"
58
+ aria-hidden="true"
59
+ >
60
+ <circle
61
+ class="opacity-25"
62
+ cx="12"
63
+ cy="12"
64
+ r="10"
65
+ stroke="currentColor"
66
+ stroke-width="3"
67
+ />
68
+ <path
69
+ class="opacity-90"
70
+ fill="currentColor"
71
+ d="M4 12a8 8 0 018-8v3a5 5 0 00-5 5H4z"
72
+ />
73
+ </svg>
74
+ <slot v-else name="prefix" />
75
+ <slot />
76
+ <slot name="suffix" />
77
+ <kbd
78
+ v-if="shortcut && size !== 'icon'"
79
+ class="ml-1 inline-flex items-center rounded border border-current/20 bg-black/10 dark:bg-white/10 px-1.5 py-0.5 text-[10px] font-mono leading-none opacity-85"
80
+ >
81
+ {{ shortcut }}
82
+ </kbd>
83
+ </component>
84
+ </template>
@@ -0,0 +1,89 @@
1
+ <script setup>
2
+ import { computed, useSlots } from 'vue';
3
+ import { cardVariants, cn } from '@birus-ui/core';
4
+ import BirusBadge from './BirusBadge.vue';
5
+
6
+ const props = defineProps({
7
+ variant: { type: String, default: 'default' },
8
+ title: { type: String, default: '' },
9
+ description: { type: String, default: '' },
10
+ badge: { type: String, default: '' },
11
+ badgeVariant: { type: String, default: 'secondary' },
12
+ padding: { type: String, default: 'md' },
13
+ accentTop: { type: Boolean, default: false },
14
+ class: { type: String, default: '' },
15
+ });
16
+
17
+ const slots = useSlots();
18
+
19
+ const classes = computed(() =>
20
+ cn(
21
+ 'relative',
22
+ cardVariants({ variant: props.variant }),
23
+ props.class,
24
+ ),
25
+ );
26
+
27
+ const paddingClass = computed(() => {
28
+ switch (props.padding) {
29
+ case 'none':
30
+ return 'p-0';
31
+ case 'sm':
32
+ return 'p-4';
33
+ case 'lg':
34
+ return 'p-7';
35
+ default:
36
+ return 'p-5';
37
+ }
38
+ });
39
+
40
+ const hasHeader = computed(
41
+ () => Boolean(slots.header) || Boolean(props.title) || Boolean(props.description) || Boolean(props.badge),
42
+ );
43
+ </script>
44
+
45
+ <template>
46
+ <div :class="classes">
47
+ <div
48
+ v-if="accentTop"
49
+ class="h-1 w-full bg-gradient-to-r from-primary via-info to-primary"
50
+ aria-hidden="true"
51
+ />
52
+
53
+ <div v-if="$slots.media" class="w-full overflow-hidden border-b border-border">
54
+ <slot name="media" />
55
+ </div>
56
+
57
+ <div v-if="hasHeader" :class="[paddingClass, $slots.default ? 'pb-3' : '']">
58
+ <slot name="header">
59
+ <div class="flex items-start justify-between gap-3">
60
+ <div class="space-y-1">
61
+ <h3 v-if="title" class="text-base font-semibold tracking-tight text-card-fg">
62
+ {{ title }}
63
+ </h3>
64
+ <p v-if="description" class="text-sm text-muted-fg leading-relaxed">
65
+ {{ description }}
66
+ </p>
67
+ </div>
68
+ <BirusBadge v-if="badge" :variant="badgeVariant" size="sm" class="shrink-0">
69
+ {{ badge }}
70
+ </BirusBadge>
71
+ </div>
72
+ </slot>
73
+ </div>
74
+
75
+ <div v-if="$slots.default" :class="[paddingClass, hasHeader ? 'pt-0' : '']">
76
+ <slot />
77
+ </div>
78
+
79
+ <div
80
+ v-if="$slots.footer"
81
+ :class="[
82
+ paddingClass,
83
+ 'pt-3.5 border-t border-border/80 bg-surface/50 flex items-center justify-between gap-3',
84
+ ]"
85
+ >
86
+ <slot name="footer" />
87
+ </div>
88
+ </div>
89
+ </template>
@@ -0,0 +1,106 @@
1
+ <script setup>
2
+ import { computed } from 'vue';
3
+ import { checkboxBoxVariants, cn } from '@birus-ui/core';
4
+
5
+ const props = defineProps({
6
+ modelValue: { type: Boolean, default: false },
7
+ indeterminate: { type: Boolean, default: false },
8
+ size: { type: String, default: 'md' },
9
+ label: { type: String, default: '' },
10
+ description: { type: String, default: '' },
11
+ disabled: { type: Boolean, default: false },
12
+ id: { type: String, default: undefined },
13
+ class: { type: String, default: '' },
14
+ });
15
+
16
+ const emit = defineEmits(['update:modelValue']);
17
+
18
+ const uid = props.id || `birus-checkbox-${Math.random().toString(36).slice(2, 9)}`;
19
+ const isCheckedOrMixed = computed(() => props.modelValue || props.indeterminate);
20
+
21
+ const disabledClass = computed(() =>
22
+ props.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
23
+ );
24
+
25
+ const boxClasses = computed(() =>
26
+ cn(
27
+ checkboxBoxVariants({
28
+ size: props.size,
29
+ checked: isCheckedOrMixed.value,
30
+ }),
31
+ disabledClass.value,
32
+ props.class,
33
+ ),
34
+ );
35
+
36
+ const iconSizeClass = computed(() => {
37
+ switch (props.size) {
38
+ case 'sm':
39
+ return 'h-3 w-3';
40
+ case 'lg':
41
+ return 'h-4 w-4';
42
+ default:
43
+ return 'h-3.5 w-3.5';
44
+ }
45
+ });
46
+
47
+ function toggle() {
48
+ if (props.disabled) return;
49
+ emit('update:modelValue', !props.modelValue);
50
+ }
51
+ </script>
52
+
53
+ <template>
54
+ <div class="inline-flex items-start gap-2.5">
55
+ <button
56
+ :id="uid"
57
+ type="button"
58
+ role="checkbox"
59
+ :aria-checked="indeterminate ? 'mixed' : modelValue ? 'true' : 'false'"
60
+ :aria-labelledby="label ? `${uid}-label` : undefined"
61
+ :disabled="disabled"
62
+ :class="boxClasses"
63
+ @click="toggle"
64
+ @keydown.enter.prevent="toggle"
65
+ >
66
+ <svg
67
+ v-if="indeterminate"
68
+ :class="iconSizeClass"
69
+ viewBox="0 0 24 24"
70
+ fill="none"
71
+ stroke="currentColor"
72
+ stroke-width="3"
73
+ >
74
+ <path stroke-linecap="round" d="M5 12h14" />
75
+ </svg>
76
+ <svg
77
+ v-else-if="modelValue"
78
+ :class="iconSizeClass"
79
+ viewBox="0 0 24 24"
80
+ fill="none"
81
+ stroke="currentColor"
82
+ stroke-width="3"
83
+ >
84
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
85
+ </svg>
86
+ </button>
87
+
88
+ <div v-if="label || description || $slots.default" class="flex flex-col gap-0.5">
89
+ <label
90
+ v-if="label || $slots.default"
91
+ :id="`${uid}-label`"
92
+ :for="uid"
93
+ :class="[
94
+ 'text-sm font-medium leading-snug text-fg select-none',
95
+ disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
96
+ ]"
97
+ @click.prevent="toggle"
98
+ >
99
+ <slot>{{ label }}</slot>
100
+ </label>
101
+ <p v-if="description" class="text-xs text-muted-fg leading-relaxed">
102
+ {{ description }}
103
+ </p>
104
+ </div>
105
+ </div>
106
+ </template>
@@ -0,0 +1,127 @@
1
+ <script setup>
2
+ import { computed, onBeforeUnmount, watch } from 'vue';
3
+ import { dialogContentVariants, cn } from '@birus-ui/core';
4
+
5
+ const props = defineProps({
6
+ modelValue: { type: Boolean, required: true },
7
+ title: { type: String, default: '' },
8
+ description: { type: String, default: '' },
9
+ size: { type: String, default: 'md' },
10
+ closeOnOverlay: { type: Boolean, default: true },
11
+ closeOnEscape: { type: Boolean, default: true },
12
+ showCloseButton: { type: Boolean, default: true },
13
+ class: { type: String, default: '' },
14
+ });
15
+
16
+ const emit = defineEmits(['update:modelValue', 'close']);
17
+
18
+ const contentClasses = computed(() =>
19
+ cn(dialogContentVariants({ size: props.size }), props.class),
20
+ );
21
+
22
+ function close() {
23
+ emit('update:modelValue', false);
24
+ emit('close');
25
+ }
26
+
27
+ function open() {
28
+ emit('update:modelValue', true);
29
+ }
30
+
31
+ function handleOverlayClick() {
32
+ if (props.closeOnOverlay) {
33
+ close();
34
+ }
35
+ }
36
+
37
+ function handleKeydown(event) {
38
+ if (event.key === 'Escape' && props.closeOnEscape && props.modelValue) {
39
+ event.stopPropagation();
40
+ close();
41
+ }
42
+ }
43
+
44
+ watch(
45
+ () => props.modelValue,
46
+ (isOpen) => {
47
+ if (typeof document === 'undefined') return;
48
+ if (isOpen) {
49
+ document.body.style.overflow = 'hidden';
50
+ window.addEventListener('keydown', handleKeydown);
51
+ } else {
52
+ document.body.style.overflow = '';
53
+ window.removeEventListener('keydown', handleKeydown);
54
+ }
55
+ },
56
+ { immediate: true },
57
+ );
58
+
59
+ onBeforeUnmount(() => {
60
+ if (typeof document !== 'undefined') {
61
+ document.body.style.overflow = '';
62
+ window.removeEventListener('keydown', handleKeydown);
63
+ }
64
+ });
65
+ </script>
66
+
67
+ <template>
68
+ <slot name="trigger" :open="open" />
69
+
70
+ <Teleport to="body">
71
+ <Transition
72
+ enter-active-class="transition duration-200 ease-out"
73
+ enter-from-class="opacity-0"
74
+ enter-to-class="opacity-100"
75
+ leave-active-class="transition duration-150 ease-in"
76
+ leave-from-class="opacity-100"
77
+ leave-to-class="opacity-0"
78
+ >
79
+ <div
80
+ v-if="modelValue"
81
+ class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/65 backdrop-blur-sm"
82
+ @click.self="handleOverlayClick"
83
+ >
84
+ <div
85
+ role="dialog"
86
+ aria-modal="true"
87
+ :aria-label="title || 'Diálogo'"
88
+ :class="contentClasses"
89
+ >
90
+ <button
91
+ v-if="showCloseButton"
92
+ type="button"
93
+ class="absolute right-4 top-4 rounded-birus-sm p-1.5 text-muted-fg hover:text-fg hover:bg-surface-hover transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring"
94
+ aria-label="Fechar modal"
95
+ @click="close"
96
+ >
97
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
98
+ <path stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12" />
99
+ </svg>
100
+ </button>
101
+
102
+ <div v-if="$slots.header || title || description" class="mb-4 pr-6 space-y-1.5">
103
+ <slot name="header">
104
+ <h2 v-if="title" class="text-lg font-semibold text-card-fg leading-snug">
105
+ {{ title }}
106
+ </h2>
107
+ <p v-if="description" class="text-sm text-muted-fg leading-relaxed">
108
+ {{ description }}
109
+ </p>
110
+ </slot>
111
+ </div>
112
+
113
+ <div class="text-sm text-card-fg">
114
+ <slot :close="close" />
115
+ </div>
116
+
117
+ <div
118
+ v-if="$slots.footer"
119
+ class="mt-6 flex flex-wrap items-center justify-end gap-2.5 pt-4 border-t border-border"
120
+ >
121
+ <slot name="footer" :close="close" />
122
+ </div>
123
+ </div>
124
+ </div>
125
+ </Transition>
126
+ </Teleport>
127
+ </template>
@@ -0,0 +1,158 @@
1
+ <script setup>
2
+ import { computed, onBeforeUnmount, watch } from 'vue';
3
+ import { drawerPanelVariants, cn } from '@birus-ui/core';
4
+
5
+ const props = defineProps({
6
+ modelValue: { type: Boolean, default: false },
7
+ title: { type: String, default: '' },
8
+ description: { type: String, default: '' },
9
+ side: { type: String, default: 'right' }, // 'right' | 'left' | 'top' | 'bottom'
10
+ size: { type: String, default: 'md' }, // 'sm' | 'md' | 'lg' | 'xl'
11
+ closeOnOverlay: { type: Boolean, default: true },
12
+ closeOnEscape: { type: Boolean, default: true },
13
+ class: { type: String, default: '' },
14
+ });
15
+
16
+ const emit = defineEmits(['update:modelValue', 'close']);
17
+
18
+ const panelClasses = computed(() =>
19
+ cn(
20
+ drawerPanelVariants({
21
+ side: props.side,
22
+ size: props.size,
23
+ }),
24
+ props.class,
25
+ ),
26
+ );
27
+
28
+ const enterFromClass = computed(() => {
29
+ switch (props.side) {
30
+ case 'left':
31
+ return '-translate-x-full';
32
+ case 'top':
33
+ return '-translate-y-full';
34
+ case 'bottom':
35
+ return 'translate-y-full';
36
+ default:
37
+ return 'translate-x-full';
38
+ }
39
+ });
40
+
41
+ function close() {
42
+ emit('update:modelValue', false);
43
+ emit('close');
44
+ }
45
+
46
+ function open() {
47
+ emit('update:modelValue', true);
48
+ }
49
+
50
+ function handleOverlayClick() {
51
+ if (props.closeOnOverlay) close();
52
+ }
53
+
54
+ function handleKeydown(event) {
55
+ if (event.key === 'Escape' && props.closeOnEscape && props.modelValue) {
56
+ event.stopPropagation();
57
+ close();
58
+ }
59
+ }
60
+
61
+ watch(
62
+ () => props.modelValue,
63
+ (isOpen) => {
64
+ if (typeof document === 'undefined') return;
65
+ if (isOpen) {
66
+ document.body.style.overflow = 'hidden';
67
+ window.addEventListener('keydown', handleKeydown);
68
+ } else {
69
+ document.body.style.overflow = '';
70
+ window.removeEventListener('keydown', handleKeydown);
71
+ }
72
+ },
73
+ { immediate: true },
74
+ );
75
+
76
+ onBeforeUnmount(() => {
77
+ if (typeof document !== 'undefined') {
78
+ document.body.style.overflow = '';
79
+ window.removeEventListener('keydown', handleKeydown);
80
+ }
81
+ });
82
+ </script>
83
+
84
+ <template>
85
+ <slot name="trigger" :open="open" />
86
+
87
+ <Teleport to="body">
88
+ <Transition
89
+ enter-active-class="transition-opacity duration-200 ease-out"
90
+ enter-from-class="opacity-0"
91
+ enter-to-class="opacity-100"
92
+ leave-active-class="transition-opacity duration-150 ease-in"
93
+ leave-from-class="opacity-100"
94
+ leave-to-class="opacity-0"
95
+ >
96
+ <div
97
+ v-if="modelValue"
98
+ class="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm"
99
+ @click.self="handleOverlayClick"
100
+ >
101
+ <Transition
102
+ appear
103
+ enter-active-class="transition-transform duration-200 ease-out"
104
+ :enter-from-class="enterFromClass"
105
+ enter-to-class="translate-x-0 translate-y-0"
106
+ leave-active-class="transition-transform duration-150 ease-in"
107
+ leave-from-class="translate-x-0 translate-y-0"
108
+ :leave-to-class="enterFromClass"
109
+ >
110
+ <div
111
+ v-if="modelValue"
112
+ role="dialog"
113
+ aria-modal="true"
114
+ :aria-label="title || 'Painel lateral'"
115
+ :class="panelClasses"
116
+ >
117
+ <!-- Header -->
118
+ <div class="flex items-start justify-between gap-4 border-b border-border p-5">
119
+ <div class="space-y-1">
120
+ <slot name="header">
121
+ <h2 v-if="title" class="text-lg font-semibold text-card-fg leading-snug">
122
+ {{ title }}
123
+ </h2>
124
+ <p v-if="description" class="text-sm text-muted-fg">
125
+ {{ description }}
126
+ </p>
127
+ </slot>
128
+ </div>
129
+ <button
130
+ type="button"
131
+ class="rounded-birus-sm p-1.5 text-muted-fg hover:text-fg hover:bg-surface-hover transition-colors"
132
+ aria-label="Fechar painel"
133
+ @click="close"
134
+ >
135
+ <svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
136
+ <path stroke-linecap="round" stroke-linejoin="round" d="M18 6L6 18M6 6l12 12" />
137
+ </svg>
138
+ </button>
139
+ </div>
140
+
141
+ <!-- Body -->
142
+ <div class="flex-1 overflow-y-auto p-5 text-sm text-card-fg">
143
+ <slot :close="close" />
144
+ </div>
145
+
146
+ <!-- Footer -->
147
+ <div
148
+ v-if="$slots.footer"
149
+ class="flex flex-wrap items-center justify-end gap-2.5 border-t border-border bg-surface/60 p-4"
150
+ >
151
+ <slot name="footer" :close="close" />
152
+ </div>
153
+ </div>
154
+ </Transition>
155
+ </div>
156
+ </Transition>
157
+ </Teleport>
158
+ </template>