ads-nuxt 0.1.1 → 0.1.2
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/LICENSE +21 -21
- package/README.md +567 -546
- package/app/app.config.ts +25 -25
- package/app/assets/ads/checkbox-check.svg +5 -5
- package/app/assets/ads/checkbox-minus.svg +5 -5
- package/app/assets/brand/farmarcas.svg +6 -6
- package/app/assets/brand/radar.svg +9 -9
- package/app/assets/css/component-tokens.css +403 -403
- package/app/assets/css/main.css +617 -617
- package/app/components/AdsBadge.vue +48 -48
- package/app/components/AdsBroadcast.vue +45 -45
- package/app/components/AdsButton.vue +569 -569
- package/app/components/AdsButtonGroup.vue +39 -39
- package/app/components/AdsButtonMenu.vue +48 -48
- package/app/components/AdsCard.vue +117 -117
- package/app/components/AdsCharttip.vue +78 -78
- package/app/components/AdsCheckbox.vue +77 -77
- package/app/components/AdsDatePicker.vue +473 -473
- package/app/components/AdsDropdownMenu.vue +19 -19
- package/app/components/AdsEmptyState.vue +58 -58
- package/app/components/AdsFarmarcasPill.vue +74 -74
- package/app/components/AdsFeatureCard.vue +217 -217
- package/app/components/AdsFileCard.vue +101 -101
- package/app/components/AdsFilterDropdown.vue +174 -174
- package/app/components/AdsFilterOption.vue +72 -72
- package/app/components/AdsFilterSelect.vue +202 -202
- package/app/components/AdsFooter.vue +25 -25
- package/app/components/AdsInput.vue +143 -143
- package/app/components/AdsListDropdown.vue +44 -44
- package/app/components/AdsLoading.vue +109 -109
- package/app/components/AdsMenuBar.vue +103 -103
- package/app/components/AdsMetricCard.vue +119 -119
- package/app/components/AdsMiniPill.vue +40 -40
- package/app/components/AdsModal.vue +157 -157
- package/app/components/AdsMonthPicker.vue +80 -80
- package/app/components/AdsNavbarApp.vue +67 -67
- package/app/components/AdsNavbarDesk.vue +62 -62
- package/app/components/AdsNotabilityPill.vue +27 -27
- package/app/components/AdsNumberInput.vue +166 -166
- package/app/components/AdsPagination.vue +91 -91
- package/app/components/AdsProductTag.vue +61 -61
- package/app/components/AdsRadio.vue +70 -70
- package/app/components/AdsSearchInput.vue +113 -113
- package/app/components/AdsSegmentedTabs.vue +28 -28
- package/app/components/AdsSideMenu.vue +101 -101
- package/app/components/AdsSmalltip.vue +44 -44
- package/app/components/AdsSteptip.vue +101 -101
- package/app/components/AdsSwitch.vue +59 -59
- package/app/components/AdsTable.vue +100 -100
- package/app/components/AdsTableToolbar.vue +44 -44
- package/app/components/AdsTabs.vue +51 -51
- package/app/components/AdsTextarea.vue +109 -109
- package/app/components/AdsToast.vue +181 -181
- package/app/components/AdsTooltip.vue +66 -66
- package/app/components/InvertedTooltip.vue +40 -40
- package/app/components/OverlayLoading.vue +173 -173
- package/app/components/ProgressBarLoading.vue +256 -256
- package/app/composables/useOverlayLoading.ts +66 -66
- package/app/locale/pt-br.ts +6 -6
- package/app/plugins/ads-toast.client.ts +24 -24
- package/app/theme/ads-theme.ts +370 -370
- package/app/theme/component-tokens.json +402 -402
- package/app/theme/component-tokens.ts +815 -815
- package/app/types/overlay-loading.types.ts +26 -26
- package/app/utils/ads-filter-select.ts +172 -172
- package/app/utils/ads-table.ts +204 -204
- package/app/utils/calendar-date.ts +128 -128
- package/app/utils/category-role.ts +45 -45
- package/app/utils/money.ts +77 -77
- package/app/utils/notability.ts +8 -8
- package/app/utils/parser.ts +134 -134
- package/app/utils/percentage.ts +54 -54
- package/nuxt.config.ts +31 -32
- package/package.json +48 -46
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<component
|
|
3
|
-
:is="to ? NuxtLink : 'button'"
|
|
4
|
-
v-bind="rootAttrs"
|
|
5
|
-
class="group flex w-[var(--ads-feature-width)] max-w-full flex-col gap-[var(--ads-feature-gap)] rounded-[var(--ads-feature-radius)] border border-[var(--ads-feature-border)] bg-[var(--ads-feature-background)] p-[var(--ads-feature-padding)] text-left transition-colors duration-150 ease-out focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-feature-accent-hover)]"
|
|
6
|
-
:class="disabled ? 'cursor-not-allowed' : 'hover:border-[var(--ads-feature-accent-hover)] focus-visible:border-[var(--ads-feature-accent-hover)]'"
|
|
7
|
-
:style="accentStyle"
|
|
8
|
-
@click="onClick"
|
|
9
|
-
>
|
|
10
|
-
<span class="flex items-start gap-3">
|
|
11
|
-
<span
|
|
12
|
-
class="grid size-[var(--ads-feature-icon-box)] shrink-0 place-items-center rounded-[var(--ads-radius-sm)]"
|
|
13
|
-
:class="iconBoxClass"
|
|
14
|
-
>
|
|
15
|
-
<UIcon :name="icon" class="size-6" />
|
|
16
|
-
</span>
|
|
17
|
-
<span class="min-w-0 flex-1">
|
|
18
|
-
<span class="block text-[length:var(--ads-feature-title-size)] font-bold leading-[1.3]" :class="titleClass">
|
|
19
|
-
{{ title }}
|
|
20
|
-
</span>
|
|
21
|
-
<InvertedTooltip
|
|
22
|
-
:text="description"
|
|
23
|
-
:disabled="!isDescriptionTruncated"
|
|
24
|
-
>
|
|
25
|
-
<span
|
|
26
|
-
ref="descriptionEl"
|
|
27
|
-
class="mt-1 block line-clamp-2 text-[length:var(--ads-feature-body-size)] leading-relaxed"
|
|
28
|
-
:class="descriptionClass"
|
|
29
|
-
>
|
|
30
|
-
{{ description }}
|
|
31
|
-
</span>
|
|
32
|
-
</InvertedTooltip>
|
|
33
|
-
</span>
|
|
34
|
-
<UIcon
|
|
35
|
-
v-if="!disabled"
|
|
36
|
-
name="i-lucide-arrow-right"
|
|
37
|
-
class="size-5 shrink-0 text-[var(--ads-border-icon-support)] transition-colors duration-150 ease-out group-hover:text-[var(--ads-feature-accent-hover)] group-focus-visible:text-[var(--ads-feature-accent-hover)]"
|
|
38
|
-
/>
|
|
39
|
-
</span>
|
|
40
|
-
|
|
41
|
-
<AdsPill
|
|
42
|
-
v-if="disabled"
|
|
43
|
-
class="mt-auto"
|
|
44
|
-
tone="secondary"
|
|
45
|
-
status="neutral"
|
|
46
|
-
label="Em breve"
|
|
47
|
-
/>
|
|
48
|
-
<span v-else-if="hasFooter" class="mt-auto flex flex-wrap items-center gap-2">
|
|
49
|
-
<AdsPill
|
|
50
|
-
v-for="badge in badges"
|
|
51
|
-
:key="badge.label"
|
|
52
|
-
:tone="badge.tone ?? 'secondary'"
|
|
53
|
-
:status="badge.status ?? 'default'"
|
|
54
|
-
:label="badge.label"
|
|
55
|
-
/>
|
|
56
|
-
<span
|
|
57
|
-
v-if="footerText"
|
|
58
|
-
class="inline-flex items-center gap-1 text-xs text-[var(--ads-text-secondary)]"
|
|
59
|
-
>
|
|
60
|
-
<UIcon v-if="footerIcon" :name="footerIcon" class="size-3.5" />
|
|
61
|
-
{{ footerText }}
|
|
62
|
-
</span>
|
|
63
|
-
</span>
|
|
64
|
-
</component>
|
|
65
|
-
</template>
|
|
66
|
-
|
|
67
|
-
<script setup lang="ts">
|
|
68
|
-
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
|
69
|
-
import type { RouteLocationRaw } from "vue-router";
|
|
70
|
-
import { useResizeObserver } from "@vueuse/core";
|
|
71
|
-
import { NuxtLink } from "#components";
|
|
72
|
-
import AdsPill from "./AdsPill.vue";
|
|
73
|
-
import type { AdsPillStatus, AdsPillTone } from "./AdsPill.vue";
|
|
74
|
-
import InvertedTooltip from "./InvertedTooltip.vue";
|
|
75
|
-
|
|
76
|
-
export interface AdsFeatureCardBadge {
|
|
77
|
-
label: string;
|
|
78
|
-
tone?: AdsPillTone;
|
|
79
|
-
status?: AdsPillStatus;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Fundo e texto do AdsPill secondary. Primary segue o ícone do próprio card no catálogo. */
|
|
83
|
-
export type AdsFeatureCardColor =
|
|
84
|
-
| "primary"
|
|
85
|
-
| "success"
|
|
86
|
-
| "attention"
|
|
87
|
-
| "warning"
|
|
88
|
-
| "new"
|
|
89
|
-
| "blocked"
|
|
90
|
-
| "innovation";
|
|
91
|
-
|
|
92
|
-
const iconBoxByColor: Record<AdsFeatureCardColor, string> = {
|
|
93
|
-
primary: "bg-[var(--ads-button-secondary)] text-[var(--ads-text-brand)]",
|
|
94
|
-
success: "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-800)]",
|
|
95
|
-
attention: "bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)]",
|
|
96
|
-
warning: "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-800)]",
|
|
97
|
-
new: "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-800)]",
|
|
98
|
-
blocked: "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)]",
|
|
99
|
-
innovation: "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-800)]",
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Cor de acento usada NO HOVER para borda e seta (ambas a mesma cor).
|
|
104
|
-
* Em repouso: borda = --ads-feature-border (neutro), seta = --ads-border-icon-support (cinza).
|
|
105
|
-
*/
|
|
106
|
-
const accentByColor: Record<AdsFeatureCardColor, string> = {
|
|
107
|
-
// Hover: borda e seta no passo 500 da mesma escala do card.
|
|
108
|
-
primary: "var(--color-primary-500)",
|
|
109
|
-
success: "var(--ads-status-success-500)",
|
|
110
|
-
attention: "var(--ads-status-attention-500)",
|
|
111
|
-
warning: "var(--ads-status-warning-500)",
|
|
112
|
-
new: "var(--ads-status-new-500)",
|
|
113
|
-
blocked: "var(--ads-status-void-500)",
|
|
114
|
-
innovation: "var(--ads-status-innovation-500)",
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const props = withDefaults(
|
|
118
|
-
defineProps<{
|
|
119
|
-
title: string;
|
|
120
|
-
description: string;
|
|
121
|
-
icon: string;
|
|
122
|
-
/** Cor do ícone, da seta e da borda. Sem valor, fica na cor da marca. */
|
|
123
|
-
color?: AdsFeatureCardColor;
|
|
124
|
-
/** Vira link. Sem valor, continua botão. */
|
|
125
|
-
to?: RouteLocationRaw;
|
|
126
|
-
disabled?: boolean;
|
|
127
|
-
badges?: AdsFeatureCardBadge[];
|
|
128
|
-
footerIcon?: string;
|
|
129
|
-
footerText?: string;
|
|
130
|
-
}>(),
|
|
131
|
-
{
|
|
132
|
-
color: "primary",
|
|
133
|
-
to: undefined,
|
|
134
|
-
disabled: false,
|
|
135
|
-
badges: undefined,
|
|
136
|
-
footerIcon: undefined,
|
|
137
|
-
footerText: undefined,
|
|
138
|
-
},
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
const emit = defineEmits<{
|
|
142
|
-
click: [event: MouseEvent];
|
|
143
|
-
}>();
|
|
144
|
-
|
|
145
|
-
const hasFooter = computed(
|
|
146
|
-
() => (props.badges?.length ?? 0) > 0 || Boolean(props.footerText),
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
const descriptionEl = ref<HTMLElement | null>(null);
|
|
150
|
-
const isDescriptionTruncated = ref(false);
|
|
151
|
-
|
|
152
|
-
const syncDescriptionTruncation = () => {
|
|
153
|
-
const element = descriptionEl.value;
|
|
154
|
-
if (!element) {
|
|
155
|
-
isDescriptionTruncated.value = false;
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
isDescriptionTruncated.value = element.scrollHeight - element.clientHeight > 1;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
useResizeObserver(descriptionEl, syncDescriptionTruncation);
|
|
162
|
-
|
|
163
|
-
onMounted(() => {
|
|
164
|
-
void nextTick(syncDescriptionTruncation);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
watch(
|
|
168
|
-
() => props.description,
|
|
169
|
-
() => {
|
|
170
|
-
void nextTick(syncDescriptionTruncation);
|
|
171
|
-
},
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
const rootAttrs = computed(() =>
|
|
175
|
-
props.to
|
|
176
|
-
? {
|
|
177
|
-
to: props.to,
|
|
178
|
-
tabindex: props.disabled ? -1 : undefined,
|
|
179
|
-
"aria-disabled": props.disabled || undefined,
|
|
180
|
-
}
|
|
181
|
-
: {
|
|
182
|
-
type: "button" as const,
|
|
183
|
-
disabled: props.disabled,
|
|
184
|
-
},
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
const accentStyle = computed(() => {
|
|
188
|
-
if (props.disabled) {
|
|
189
|
-
return {};
|
|
190
|
-
}
|
|
191
|
-
return {
|
|
192
|
-
"--ads-feature-accent-hover": accentByColor[props.color],
|
|
193
|
-
};
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const iconBoxClass = computed(() =>
|
|
197
|
-
props.disabled
|
|
198
|
-
? "bg-[var(--ads-button-secondary-disabled)] text-[var(--ads-text-tertiary)]"
|
|
199
|
-
: iconBoxByColor[props.color],
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
const titleClass = computed(() =>
|
|
203
|
-
props.disabled ? "text-[var(--ads-text-tertiary)]" : "text-[var(--ads-feature-title)]",
|
|
204
|
-
);
|
|
205
|
-
|
|
206
|
-
const descriptionClass = computed(() =>
|
|
207
|
-
props.disabled ? "text-[var(--ads-text-tertiary)]" : "text-[var(--ads-feature-body)]",
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
const onClick = (event: MouseEvent) => {
|
|
211
|
-
if (!props.disabled) {
|
|
212
|
-
emit("click", event);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
event.preventDefault();
|
|
216
|
-
};
|
|
217
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="to ? NuxtLink : 'button'"
|
|
4
|
+
v-bind="rootAttrs"
|
|
5
|
+
class="group flex w-[var(--ads-feature-width)] max-w-full flex-col gap-[var(--ads-feature-gap)] rounded-[var(--ads-feature-radius)] border border-[var(--ads-feature-border)] bg-[var(--ads-feature-background)] p-[var(--ads-feature-padding)] text-left transition-colors duration-150 ease-out focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-feature-accent-hover)]"
|
|
6
|
+
:class="disabled ? 'cursor-not-allowed' : 'hover:border-[var(--ads-feature-accent-hover)] focus-visible:border-[var(--ads-feature-accent-hover)]'"
|
|
7
|
+
:style="accentStyle"
|
|
8
|
+
@click="onClick"
|
|
9
|
+
>
|
|
10
|
+
<span class="flex items-start gap-3">
|
|
11
|
+
<span
|
|
12
|
+
class="grid size-[var(--ads-feature-icon-box)] shrink-0 place-items-center rounded-[var(--ads-radius-sm)]"
|
|
13
|
+
:class="iconBoxClass"
|
|
14
|
+
>
|
|
15
|
+
<UIcon :name="icon" class="size-6" />
|
|
16
|
+
</span>
|
|
17
|
+
<span class="min-w-0 flex-1">
|
|
18
|
+
<span class="block text-[length:var(--ads-feature-title-size)] font-bold leading-[1.3]" :class="titleClass">
|
|
19
|
+
{{ title }}
|
|
20
|
+
</span>
|
|
21
|
+
<InvertedTooltip
|
|
22
|
+
:text="description"
|
|
23
|
+
:disabled="!isDescriptionTruncated"
|
|
24
|
+
>
|
|
25
|
+
<span
|
|
26
|
+
ref="descriptionEl"
|
|
27
|
+
class="mt-1 block line-clamp-2 text-[length:var(--ads-feature-body-size)] leading-relaxed"
|
|
28
|
+
:class="descriptionClass"
|
|
29
|
+
>
|
|
30
|
+
{{ description }}
|
|
31
|
+
</span>
|
|
32
|
+
</InvertedTooltip>
|
|
33
|
+
</span>
|
|
34
|
+
<UIcon
|
|
35
|
+
v-if="!disabled"
|
|
36
|
+
name="i-lucide-arrow-right"
|
|
37
|
+
class="size-5 shrink-0 text-[var(--ads-border-icon-support)] transition-colors duration-150 ease-out group-hover:text-[var(--ads-feature-accent-hover)] group-focus-visible:text-[var(--ads-feature-accent-hover)]"
|
|
38
|
+
/>
|
|
39
|
+
</span>
|
|
40
|
+
|
|
41
|
+
<AdsPill
|
|
42
|
+
v-if="disabled"
|
|
43
|
+
class="mt-auto"
|
|
44
|
+
tone="secondary"
|
|
45
|
+
status="neutral"
|
|
46
|
+
label="Em breve"
|
|
47
|
+
/>
|
|
48
|
+
<span v-else-if="hasFooter" class="mt-auto flex flex-wrap items-center gap-2">
|
|
49
|
+
<AdsPill
|
|
50
|
+
v-for="badge in badges"
|
|
51
|
+
:key="badge.label"
|
|
52
|
+
:tone="badge.tone ?? 'secondary'"
|
|
53
|
+
:status="badge.status ?? 'default'"
|
|
54
|
+
:label="badge.label"
|
|
55
|
+
/>
|
|
56
|
+
<span
|
|
57
|
+
v-if="footerText"
|
|
58
|
+
class="inline-flex items-center gap-1 text-xs text-[var(--ads-text-secondary)]"
|
|
59
|
+
>
|
|
60
|
+
<UIcon v-if="footerIcon" :name="footerIcon" class="size-3.5" />
|
|
61
|
+
{{ footerText }}
|
|
62
|
+
</span>
|
|
63
|
+
</span>
|
|
64
|
+
</component>
|
|
65
|
+
</template>
|
|
66
|
+
|
|
67
|
+
<script setup lang="ts">
|
|
68
|
+
import { computed, nextTick, onMounted, ref, watch } from "vue";
|
|
69
|
+
import type { RouteLocationRaw } from "vue-router";
|
|
70
|
+
import { useResizeObserver } from "@vueuse/core";
|
|
71
|
+
import { NuxtLink } from "#components";
|
|
72
|
+
import AdsPill from "./AdsPill.vue";
|
|
73
|
+
import type { AdsPillStatus, AdsPillTone } from "./AdsPill.vue";
|
|
74
|
+
import InvertedTooltip from "./InvertedTooltip.vue";
|
|
75
|
+
|
|
76
|
+
export interface AdsFeatureCardBadge {
|
|
77
|
+
label: string;
|
|
78
|
+
tone?: AdsPillTone;
|
|
79
|
+
status?: AdsPillStatus;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Fundo e texto do AdsPill secondary. Primary segue o ícone do próprio card no catálogo. */
|
|
83
|
+
export type AdsFeatureCardColor =
|
|
84
|
+
| "primary"
|
|
85
|
+
| "success"
|
|
86
|
+
| "attention"
|
|
87
|
+
| "warning"
|
|
88
|
+
| "new"
|
|
89
|
+
| "blocked"
|
|
90
|
+
| "innovation";
|
|
91
|
+
|
|
92
|
+
const iconBoxByColor: Record<AdsFeatureCardColor, string> = {
|
|
93
|
+
primary: "bg-[var(--ads-button-secondary)] text-[var(--ads-text-brand)]",
|
|
94
|
+
success: "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-800)]",
|
|
95
|
+
attention: "bg-[var(--ads-status-attention-100)] text-[var(--ads-status-attention-800)]",
|
|
96
|
+
warning: "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-800)]",
|
|
97
|
+
new: "bg-[var(--ads-status-new-100)] text-[var(--ads-status-new-800)]",
|
|
98
|
+
blocked: "bg-[var(--ads-status-void-100)] text-[var(--ads-status-void-800)]",
|
|
99
|
+
innovation: "bg-[var(--ads-status-innovation-100)] text-[var(--ads-status-innovation-800)]",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Cor de acento usada NO HOVER para borda e seta (ambas a mesma cor).
|
|
104
|
+
* Em repouso: borda = --ads-feature-border (neutro), seta = --ads-border-icon-support (cinza).
|
|
105
|
+
*/
|
|
106
|
+
const accentByColor: Record<AdsFeatureCardColor, string> = {
|
|
107
|
+
// Hover: borda e seta no passo 500 da mesma escala do card.
|
|
108
|
+
primary: "var(--color-primary-500)",
|
|
109
|
+
success: "var(--ads-status-success-500)",
|
|
110
|
+
attention: "var(--ads-status-attention-500)",
|
|
111
|
+
warning: "var(--ads-status-warning-500)",
|
|
112
|
+
new: "var(--ads-status-new-500)",
|
|
113
|
+
blocked: "var(--ads-status-void-500)",
|
|
114
|
+
innovation: "var(--ads-status-innovation-500)",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const props = withDefaults(
|
|
118
|
+
defineProps<{
|
|
119
|
+
title: string;
|
|
120
|
+
description: string;
|
|
121
|
+
icon: string;
|
|
122
|
+
/** Cor do ícone, da seta e da borda. Sem valor, fica na cor da marca. */
|
|
123
|
+
color?: AdsFeatureCardColor;
|
|
124
|
+
/** Vira link. Sem valor, continua botão. */
|
|
125
|
+
to?: RouteLocationRaw;
|
|
126
|
+
disabled?: boolean;
|
|
127
|
+
badges?: AdsFeatureCardBadge[];
|
|
128
|
+
footerIcon?: string;
|
|
129
|
+
footerText?: string;
|
|
130
|
+
}>(),
|
|
131
|
+
{
|
|
132
|
+
color: "primary",
|
|
133
|
+
to: undefined,
|
|
134
|
+
disabled: false,
|
|
135
|
+
badges: undefined,
|
|
136
|
+
footerIcon: undefined,
|
|
137
|
+
footerText: undefined,
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const emit = defineEmits<{
|
|
142
|
+
click: [event: MouseEvent];
|
|
143
|
+
}>();
|
|
144
|
+
|
|
145
|
+
const hasFooter = computed(
|
|
146
|
+
() => (props.badges?.length ?? 0) > 0 || Boolean(props.footerText),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const descriptionEl = ref<HTMLElement | null>(null);
|
|
150
|
+
const isDescriptionTruncated = ref(false);
|
|
151
|
+
|
|
152
|
+
const syncDescriptionTruncation = () => {
|
|
153
|
+
const element = descriptionEl.value;
|
|
154
|
+
if (!element) {
|
|
155
|
+
isDescriptionTruncated.value = false;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
isDescriptionTruncated.value = element.scrollHeight - element.clientHeight > 1;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
useResizeObserver(descriptionEl, syncDescriptionTruncation);
|
|
162
|
+
|
|
163
|
+
onMounted(() => {
|
|
164
|
+
void nextTick(syncDescriptionTruncation);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
watch(
|
|
168
|
+
() => props.description,
|
|
169
|
+
() => {
|
|
170
|
+
void nextTick(syncDescriptionTruncation);
|
|
171
|
+
},
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const rootAttrs = computed(() =>
|
|
175
|
+
props.to
|
|
176
|
+
? {
|
|
177
|
+
to: props.to,
|
|
178
|
+
tabindex: props.disabled ? -1 : undefined,
|
|
179
|
+
"aria-disabled": props.disabled || undefined,
|
|
180
|
+
}
|
|
181
|
+
: {
|
|
182
|
+
type: "button" as const,
|
|
183
|
+
disabled: props.disabled,
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const accentStyle = computed(() => {
|
|
188
|
+
if (props.disabled) {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
"--ads-feature-accent-hover": accentByColor[props.color],
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const iconBoxClass = computed(() =>
|
|
197
|
+
props.disabled
|
|
198
|
+
? "bg-[var(--ads-button-secondary-disabled)] text-[var(--ads-text-tertiary)]"
|
|
199
|
+
: iconBoxByColor[props.color],
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const titleClass = computed(() =>
|
|
203
|
+
props.disabled ? "text-[var(--ads-text-tertiary)]" : "text-[var(--ads-feature-title)]",
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const descriptionClass = computed(() =>
|
|
207
|
+
props.disabled ? "text-[var(--ads-text-tertiary)]" : "text-[var(--ads-feature-body)]",
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const onClick = (event: MouseEvent) => {
|
|
211
|
+
if (!props.disabled) {
|
|
212
|
+
emit("click", event);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
event.preventDefault();
|
|
216
|
+
};
|
|
217
|
+
</script>
|
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="flex min-h-[var(--ads-file-min-height)] flex-col justify-center gap-2 rounded-[var(--ads-file-radius)] border px-[var(--ads-file-padding-x)] py-[var(--ads-file-padding-y)]"
|
|
4
|
-
:class="surface.card"
|
|
5
|
-
:role="status === 'error' ? 'alert' : 'status'"
|
|
6
|
-
>
|
|
7
|
-
<p
|
|
8
|
-
v-if="message"
|
|
9
|
-
class="text-[length:var(--ads-file-title-size)] font-semibold leading-5"
|
|
10
|
-
:class="surface.message"
|
|
11
|
-
>
|
|
12
|
-
{{ message }}
|
|
13
|
-
</p>
|
|
14
|
-
<div class="flex items-center justify-between gap-3">
|
|
15
|
-
<div class="flex min-w-0 items-center gap-2">
|
|
16
|
-
<span
|
|
17
|
-
class="inline-flex size-[var(--ads-file-icon-box)] shrink-0 items-center justify-center rounded-[var(--ads-file-radius)]"
|
|
18
|
-
:class="surface.icon"
|
|
19
|
-
aria-hidden="true"
|
|
20
|
-
>
|
|
21
|
-
<UIcon :name="resolvedIcon" class="size-4" />
|
|
22
|
-
</span>
|
|
23
|
-
<div class="min-w-0">
|
|
24
|
-
<InvertedTooltip :text="name">
|
|
25
|
-
<p class="truncate text-[length:var(--ads-file-title-size)] font-semibold leading-5 text-[var(--ads-text-primary)]">
|
|
26
|
-
{{ name }}
|
|
27
|
-
</p>
|
|
28
|
-
</InvertedTooltip>
|
|
29
|
-
<p
|
|
30
|
-
v-if="detail"
|
|
31
|
-
class="text-[length:var(--ads-file-detail-size)] font-normal leading-4 text-[var(--ads-text-secondary)]"
|
|
32
|
-
>
|
|
33
|
-
{{ detail }}
|
|
34
|
-
</p>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
<AdsButton
|
|
38
|
-
v-if="removable"
|
|
39
|
-
type="unfilled"
|
|
40
|
-
color="neutral"
|
|
41
|
-
size="sm"
|
|
42
|
-
icon="i-lucide-trash-2"
|
|
43
|
-
:aria-label="removeLabel"
|
|
44
|
-
@click="emit('remove')"
|
|
45
|
-
/>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</template>
|
|
49
|
-
|
|
50
|
-
<script setup lang="ts">
|
|
51
|
-
import { computed } from "vue";
|
|
52
|
-
import AdsButton from "./AdsButton.vue";
|
|
53
|
-
import InvertedTooltip from "./InvertedTooltip.vue";
|
|
54
|
-
|
|
55
|
-
export type AdsFileCardStatus = "success" | "error";
|
|
56
|
-
|
|
57
|
-
const props = withDefaults(
|
|
58
|
-
defineProps<{
|
|
59
|
-
/** Nome do arquivo. */
|
|
60
|
-
name: string;
|
|
61
|
-
/** Linha de apoio, como o tamanho. */
|
|
62
|
-
detail?: string;
|
|
63
|
-
status?: AdsFileCardStatus;
|
|
64
|
-
/** Texto do problema, acima do arquivo. */
|
|
65
|
-
message?: string;
|
|
66
|
-
icon?: string;
|
|
67
|
-
removable?: boolean;
|
|
68
|
-
removeLabel?: string;
|
|
69
|
-
}>(),
|
|
70
|
-
{
|
|
71
|
-
detail: undefined,
|
|
72
|
-
status: "success",
|
|
73
|
-
message: undefined,
|
|
74
|
-
icon: undefined,
|
|
75
|
-
removable: false,
|
|
76
|
-
removeLabel: "Remover arquivo",
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
const emit = defineEmits<{
|
|
81
|
-
remove: [];
|
|
82
|
-
}>();
|
|
83
|
-
|
|
84
|
-
const surfaces: Record<AdsFileCardStatus, { card: string; icon: string; message: string; glyph: string }> = {
|
|
85
|
-
success: {
|
|
86
|
-
card: "border-[var(--ads-status-success-500)] bg-[color-mix(in_srgb,var(--ads-status-success-100)_var(--ads-opacity-light),transparent)]",
|
|
87
|
-
icon: "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-800)]",
|
|
88
|
-
message: "text-[var(--ads-status-success-800)]",
|
|
89
|
-
glyph: "i-lucide-cloud-check",
|
|
90
|
-
},
|
|
91
|
-
error: {
|
|
92
|
-
card: "border-[var(--ads-status-warning-500)] bg-[color-mix(in_srgb,var(--ads-status-warning-100)_var(--ads-opacity-light),transparent)]",
|
|
93
|
-
icon: "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-500)]",
|
|
94
|
-
message: "text-[var(--ads-status-warning-500)]",
|
|
95
|
-
glyph: "i-lucide-ban",
|
|
96
|
-
},
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const surface = computed(() => surfaces[props.status]);
|
|
100
|
-
const resolvedIcon = computed(() => props.icon || surface.value.glyph);
|
|
101
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="flex min-h-[var(--ads-file-min-height)] flex-col justify-center gap-2 rounded-[var(--ads-file-radius)] border px-[var(--ads-file-padding-x)] py-[var(--ads-file-padding-y)]"
|
|
4
|
+
:class="surface.card"
|
|
5
|
+
:role="status === 'error' ? 'alert' : 'status'"
|
|
6
|
+
>
|
|
7
|
+
<p
|
|
8
|
+
v-if="message"
|
|
9
|
+
class="text-[length:var(--ads-file-title-size)] font-semibold leading-5"
|
|
10
|
+
:class="surface.message"
|
|
11
|
+
>
|
|
12
|
+
{{ message }}
|
|
13
|
+
</p>
|
|
14
|
+
<div class="flex items-center justify-between gap-3">
|
|
15
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
16
|
+
<span
|
|
17
|
+
class="inline-flex size-[var(--ads-file-icon-box)] shrink-0 items-center justify-center rounded-[var(--ads-file-radius)]"
|
|
18
|
+
:class="surface.icon"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
>
|
|
21
|
+
<UIcon :name="resolvedIcon" class="size-4" />
|
|
22
|
+
</span>
|
|
23
|
+
<div class="min-w-0">
|
|
24
|
+
<InvertedTooltip :text="name">
|
|
25
|
+
<p class="truncate text-[length:var(--ads-file-title-size)] font-semibold leading-5 text-[var(--ads-text-primary)]">
|
|
26
|
+
{{ name }}
|
|
27
|
+
</p>
|
|
28
|
+
</InvertedTooltip>
|
|
29
|
+
<p
|
|
30
|
+
v-if="detail"
|
|
31
|
+
class="text-[length:var(--ads-file-detail-size)] font-normal leading-4 text-[var(--ads-text-secondary)]"
|
|
32
|
+
>
|
|
33
|
+
{{ detail }}
|
|
34
|
+
</p>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<AdsButton
|
|
38
|
+
v-if="removable"
|
|
39
|
+
type="unfilled"
|
|
40
|
+
color="neutral"
|
|
41
|
+
size="sm"
|
|
42
|
+
icon="i-lucide-trash-2"
|
|
43
|
+
:aria-label="removeLabel"
|
|
44
|
+
@click="emit('remove')"
|
|
45
|
+
/>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
import { computed } from "vue";
|
|
52
|
+
import AdsButton from "./AdsButton.vue";
|
|
53
|
+
import InvertedTooltip from "./InvertedTooltip.vue";
|
|
54
|
+
|
|
55
|
+
export type AdsFileCardStatus = "success" | "error";
|
|
56
|
+
|
|
57
|
+
const props = withDefaults(
|
|
58
|
+
defineProps<{
|
|
59
|
+
/** Nome do arquivo. */
|
|
60
|
+
name: string;
|
|
61
|
+
/** Linha de apoio, como o tamanho. */
|
|
62
|
+
detail?: string;
|
|
63
|
+
status?: AdsFileCardStatus;
|
|
64
|
+
/** Texto do problema, acima do arquivo. */
|
|
65
|
+
message?: string;
|
|
66
|
+
icon?: string;
|
|
67
|
+
removable?: boolean;
|
|
68
|
+
removeLabel?: string;
|
|
69
|
+
}>(),
|
|
70
|
+
{
|
|
71
|
+
detail: undefined,
|
|
72
|
+
status: "success",
|
|
73
|
+
message: undefined,
|
|
74
|
+
icon: undefined,
|
|
75
|
+
removable: false,
|
|
76
|
+
removeLabel: "Remover arquivo",
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const emit = defineEmits<{
|
|
81
|
+
remove: [];
|
|
82
|
+
}>();
|
|
83
|
+
|
|
84
|
+
const surfaces: Record<AdsFileCardStatus, { card: string; icon: string; message: string; glyph: string }> = {
|
|
85
|
+
success: {
|
|
86
|
+
card: "border-[var(--ads-status-success-500)] bg-[color-mix(in_srgb,var(--ads-status-success-100)_var(--ads-opacity-light),transparent)]",
|
|
87
|
+
icon: "bg-[var(--ads-status-success-100)] text-[var(--ads-status-success-800)]",
|
|
88
|
+
message: "text-[var(--ads-status-success-800)]",
|
|
89
|
+
glyph: "i-lucide-cloud-check",
|
|
90
|
+
},
|
|
91
|
+
error: {
|
|
92
|
+
card: "border-[var(--ads-status-warning-500)] bg-[color-mix(in_srgb,var(--ads-status-warning-100)_var(--ads-opacity-light),transparent)]",
|
|
93
|
+
icon: "bg-[var(--ads-status-warning-100)] text-[var(--ads-status-warning-500)]",
|
|
94
|
+
message: "text-[var(--ads-status-warning-500)]",
|
|
95
|
+
glyph: "i-lucide-ban",
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const surface = computed(() => surfaces[props.status]);
|
|
100
|
+
const resolvedIcon = computed(() => props.icon || surface.value.glyph);
|
|
101
|
+
</script>
|