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.
Files changed (74) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +567 -546
  3. package/app/app.config.ts +25 -25
  4. package/app/assets/ads/checkbox-check.svg +5 -5
  5. package/app/assets/ads/checkbox-minus.svg +5 -5
  6. package/app/assets/brand/farmarcas.svg +6 -6
  7. package/app/assets/brand/radar.svg +9 -9
  8. package/app/assets/css/component-tokens.css +403 -403
  9. package/app/assets/css/main.css +617 -617
  10. package/app/components/AdsBadge.vue +48 -48
  11. package/app/components/AdsBroadcast.vue +45 -45
  12. package/app/components/AdsButton.vue +569 -569
  13. package/app/components/AdsButtonGroup.vue +39 -39
  14. package/app/components/AdsButtonMenu.vue +48 -48
  15. package/app/components/AdsCard.vue +117 -117
  16. package/app/components/AdsCharttip.vue +78 -78
  17. package/app/components/AdsCheckbox.vue +77 -77
  18. package/app/components/AdsDatePicker.vue +473 -473
  19. package/app/components/AdsDropdownMenu.vue +19 -19
  20. package/app/components/AdsEmptyState.vue +58 -58
  21. package/app/components/AdsFarmarcasPill.vue +74 -74
  22. package/app/components/AdsFeatureCard.vue +217 -217
  23. package/app/components/AdsFileCard.vue +101 -101
  24. package/app/components/AdsFilterDropdown.vue +174 -174
  25. package/app/components/AdsFilterOption.vue +72 -72
  26. package/app/components/AdsFilterSelect.vue +202 -202
  27. package/app/components/AdsFooter.vue +25 -25
  28. package/app/components/AdsInput.vue +143 -143
  29. package/app/components/AdsListDropdown.vue +44 -44
  30. package/app/components/AdsLoading.vue +109 -109
  31. package/app/components/AdsMenuBar.vue +103 -103
  32. package/app/components/AdsMetricCard.vue +119 -119
  33. package/app/components/AdsMiniPill.vue +40 -40
  34. package/app/components/AdsModal.vue +157 -157
  35. package/app/components/AdsMonthPicker.vue +80 -80
  36. package/app/components/AdsNavbarApp.vue +67 -67
  37. package/app/components/AdsNavbarDesk.vue +62 -62
  38. package/app/components/AdsNotabilityPill.vue +27 -27
  39. package/app/components/AdsNumberInput.vue +166 -166
  40. package/app/components/AdsPagination.vue +91 -91
  41. package/app/components/AdsProductTag.vue +61 -61
  42. package/app/components/AdsRadio.vue +70 -70
  43. package/app/components/AdsSearchInput.vue +113 -113
  44. package/app/components/AdsSegmentedTabs.vue +28 -28
  45. package/app/components/AdsSideMenu.vue +101 -101
  46. package/app/components/AdsSmalltip.vue +44 -44
  47. package/app/components/AdsSteptip.vue +101 -101
  48. package/app/components/AdsSwitch.vue +59 -59
  49. package/app/components/AdsTable.vue +100 -100
  50. package/app/components/AdsTableToolbar.vue +44 -44
  51. package/app/components/AdsTabs.vue +51 -51
  52. package/app/components/AdsTextarea.vue +109 -109
  53. package/app/components/AdsToast.vue +181 -181
  54. package/app/components/AdsTooltip.vue +66 -66
  55. package/app/components/InvertedTooltip.vue +40 -40
  56. package/app/components/OverlayLoading.vue +173 -173
  57. package/app/components/ProgressBarLoading.vue +256 -256
  58. package/app/composables/useOverlayLoading.ts +66 -66
  59. package/app/locale/pt-br.ts +6 -6
  60. package/app/plugins/ads-toast.client.ts +24 -24
  61. package/app/theme/ads-theme.ts +370 -370
  62. package/app/theme/component-tokens.json +402 -402
  63. package/app/theme/component-tokens.ts +815 -815
  64. package/app/types/overlay-loading.types.ts +26 -26
  65. package/app/utils/ads-filter-select.ts +172 -172
  66. package/app/utils/ads-table.ts +204 -204
  67. package/app/utils/calendar-date.ts +128 -128
  68. package/app/utils/category-role.ts +45 -45
  69. package/app/utils/money.ts +77 -77
  70. package/app/utils/notability.ts +8 -8
  71. package/app/utils/parser.ts +134 -134
  72. package/app/utils/percentage.ts +54 -54
  73. package/nuxt.config.ts +31 -32
  74. 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>