ads-nuxt 0.1.1 → 0.1.3

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 +577 -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 +55 -46
@@ -1,39 +1,39 @@
1
- <template>
2
- <div class="inline-flex" role="radiogroup" :aria-label="ariaLabel">
3
- <button
4
- v-for="(option, index) in options"
5
- :key="option.value"
6
- type="button"
7
- role="radio"
8
- class="-ml-px h-[var(--ads-button-group-height)] border border-[var(--ads-button-group-border)] px-4 text-[length:var(--ads-button-group-font-size)] text-[var(--ads-text-primary)] first:ml-0 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
9
- :class="[
10
- index === 0 ? 'rounded-l-[var(--ads-radius-xsm)]' : '',
11
- index === options.length - 1 ? 'rounded-r-[var(--ads-radius-xsm)]' : '',
12
- model === option.value ? 'z-[1] bg-[var(--ads-button-group-selected)] font-bold' : 'bg-[var(--ads-surface-card)] font-normal',
13
- ]"
14
- :aria-checked="model === option.value"
15
- @click="model = option.value"
16
- >
17
- {{ option.label }}
18
- </button>
19
- </div>
20
- </template>
21
-
22
- <script setup lang="ts">
23
- export type AdsButtonGroupOption = {
24
- value: string;
25
- label: string;
26
- };
27
-
28
- withDefaults(
29
- defineProps<{
30
- options: AdsButtonGroupOption[];
31
- ariaLabel?: string;
32
- }>(),
33
- {
34
- ariaLabel: "Grupo de botões",
35
- },
36
- );
37
-
38
- const model = defineModel<string>({ required: true });
39
- </script>
1
+ <template>
2
+ <div class="inline-flex" role="radiogroup" :aria-label="ariaLabel">
3
+ <button
4
+ v-for="(option, index) in options"
5
+ :key="option.value"
6
+ type="button"
7
+ role="radio"
8
+ class="-ml-px h-[var(--ads-button-group-height)] border border-[var(--ads-button-group-border)] px-4 text-[length:var(--ads-button-group-font-size)] text-[var(--ads-text-primary)] first:ml-0 focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
9
+ :class="[
10
+ index === 0 ? 'rounded-l-[var(--ads-radius-xsm)]' : '',
11
+ index === options.length - 1 ? 'rounded-r-[var(--ads-radius-xsm)]' : '',
12
+ model === option.value ? 'z-[1] bg-[var(--ads-button-group-selected)] font-bold' : 'bg-[var(--ads-surface-card)] font-normal',
13
+ ]"
14
+ :aria-checked="model === option.value"
15
+ @click="model = option.value"
16
+ >
17
+ {{ option.label }}
18
+ </button>
19
+ </div>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ export type AdsButtonGroupOption = {
24
+ value: string;
25
+ label: string;
26
+ };
27
+
28
+ withDefaults(
29
+ defineProps<{
30
+ options: AdsButtonGroupOption[];
31
+ ariaLabel?: string;
32
+ }>(),
33
+ {
34
+ ariaLabel: "Grupo de botões",
35
+ },
36
+ );
37
+
38
+ const model = defineModel<string>({ required: true });
39
+ </script>
@@ -1,48 +1,48 @@
1
- <template>
2
- <div
3
- class="flex w-[var(--ads-button-menu-width)] flex-col overflow-hidden rounded-[var(--ads-radius-xsm)] bg-[var(--ads-surface-card)] shadow-[var(--ads-shadow-level-1)]"
4
- role="menu"
5
- >
6
- <button
7
- v-for="option in options"
8
- :key="option.value"
9
- type="button"
10
- role="menuitem"
11
- class="flex flex-col border-b-[0.5px] border-[var(--ads-button-menu-border)] px-4 pt-2 pb-4 text-left last:border-b-0 hover:bg-ads-neutro-claro-20 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
12
- @click="emit('select', option.value)"
13
- >
14
- <span class="flex h-10 items-center gap-2 text-[length:var(--ads-button-menu-title-size)] leading-[1.3] font-medium text-[var(--ads-button-menu-title)]">
15
- <UIcon v-if="showIcons" :name="option.icon || 'i-lucide-sparkles'" class="size-4 shrink-0" />
16
- {{ option.title }}
17
- </span>
18
- <span v-if="showSubtitles && option.subtitle" class="line-clamp-2 text-[length:var(--ads-button-menu-subtitle-size)] leading-[1.2] text-[var(--ads-button-menu-subtitle)]">
19
- {{ option.subtitle }}
20
- </span>
21
- </button>
22
- </div>
23
- </template>
24
-
25
- <script setup lang="ts">
26
- export type AdsButtonMenuOption = {
27
- value: string;
28
- title: string;
29
- subtitle?: string;
30
- icon?: string;
31
- };
32
-
33
- withDefaults(
34
- defineProps<{
35
- options: AdsButtonMenuOption[];
36
- showSubtitles?: boolean;
37
- showIcons?: boolean;
38
- }>(),
39
- {
40
- showSubtitles: true,
41
- showIcons: true,
42
- },
43
- );
44
-
45
- const emit = defineEmits<{
46
- select: [value: string];
47
- }>();
48
- </script>
1
+ <template>
2
+ <div
3
+ class="flex w-[var(--ads-button-menu-width)] flex-col overflow-hidden rounded-[var(--ads-radius-xsm)] bg-[var(--ads-surface-card)] shadow-[var(--ads-shadow-level-1)]"
4
+ role="menu"
5
+ >
6
+ <button
7
+ v-for="option in options"
8
+ :key="option.value"
9
+ type="button"
10
+ role="menuitem"
11
+ class="flex flex-col border-b-[0.5px] border-[var(--ads-button-menu-border)] px-4 pt-2 pb-4 text-left last:border-b-0 hover:bg-ads-neutro-claro-20 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
12
+ @click="emit('select', option.value)"
13
+ >
14
+ <span class="flex h-10 items-center gap-2 text-[length:var(--ads-button-menu-title-size)] leading-[1.3] font-medium text-[var(--ads-button-menu-title)]">
15
+ <UIcon v-if="showIcons" :name="option.icon || 'i-lucide-sparkles'" class="size-4 shrink-0" />
16
+ {{ option.title }}
17
+ </span>
18
+ <span v-if="showSubtitles && option.subtitle" class="line-clamp-2 text-[length:var(--ads-button-menu-subtitle-size)] leading-[1.2] text-[var(--ads-button-menu-subtitle)]">
19
+ {{ option.subtitle }}
20
+ </span>
21
+ </button>
22
+ </div>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ export type AdsButtonMenuOption = {
27
+ value: string;
28
+ title: string;
29
+ subtitle?: string;
30
+ icon?: string;
31
+ };
32
+
33
+ withDefaults(
34
+ defineProps<{
35
+ options: AdsButtonMenuOption[];
36
+ showSubtitles?: boolean;
37
+ showIcons?: boolean;
38
+ }>(),
39
+ {
40
+ showSubtitles: true,
41
+ showIcons: true,
42
+ },
43
+ );
44
+
45
+ const emit = defineEmits<{
46
+ select: [value: string];
47
+ }>();
48
+ </script>
@@ -1,117 +1,117 @@
1
- <template>
2
- <component
3
- :is="as"
4
- :type="as === 'button' ? 'button' : undefined"
5
- :disabled="as === 'button' ? disabled : undefined"
6
- class="h-full border p-[var(--ads-card-padding)] text-left"
7
- :class="[radiusClass, surfaceClass]"
8
- >
9
- <div
10
- v-if="showHeader"
11
- class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
12
- >
13
- <div class="min-w-0 flex-1">
14
- <slot name="header">
15
- <p class="flex items-center justify-between gap-2 font-bold text-[var(--ads-card-title)]">
16
- <span class="min-w-0">{{ title }}</span>
17
- <slot name="hint" />
18
- </p>
19
- </slot>
20
- </div>
21
- <div v-if="$slots.actions" class="flex shrink-0 flex-wrap items-center gap-2">
22
- <slot name="actions" />
23
- </div>
24
- </div>
25
-
26
- <div
27
- v-if="description || $slots.default"
28
- :class="showHeader ? 'mt-2' : undefined"
29
- >
30
- <slot>
31
- <p class="text-[length:var(--ads-card-body-size)] leading-relaxed text-[var(--ads-card-body)]">
32
- {{ description }}
33
- </p>
34
- </slot>
35
- </div>
36
-
37
- <div
38
- v-if="footerText || $slots.footer"
39
- class="flex flex-wrap items-start justify-between gap-3"
40
- :class="hasContentAboveFooter ? 'mt-4' : undefined"
41
- >
42
- <slot name="footer">
43
- <p class="text-xs text-[var(--ads-text-secondary)]">
44
- {{ footerText }}
45
- </p>
46
- </slot>
47
- </div>
48
- </component>
49
- </template>
50
-
51
- <script setup lang="ts">
52
- import { computed, useSlots } from "vue";
53
-
54
- const props = withDefaults(
55
- defineProps<{
56
- /** Título em negrito. O slot header substitui. */
57
- title?: string;
58
- /** Texto de apoio. O slot padrão substitui. */
59
- description?: string;
60
- /** Linha curta no rodapé. O slot footer substitui. */
61
- footerText?: string;
62
- /** `button` quando o cartão é a própria ação. */
63
- as?: "div" | "button";
64
- disabled?: boolean;
65
- /** Realce de item escolhido, como um filtro ativo. */
66
- selected?: boolean;
67
- /** `sm` (8px) em cartões grandes; `md` (12px) é o padrão. */
68
- radius?: "sm" | "md";
69
- }>(),
70
- {
71
- as: "div",
72
- disabled: false,
73
- selected: false,
74
- radius: "md",
75
- },
76
- );
77
-
78
- const radiusClass = computed(() =>
79
- props.radius === "sm"
80
- ? "rounded-[var(--ads-card-radius-sm)]"
81
- : "rounded-[var(--ads-card-radius)]",
82
- );
83
-
84
- const surfaceClass = computed(() => {
85
- const classes = [
86
- props.selected
87
- ? "border-[var(--ads-card-selected-border)] bg-[var(--ads-card-selected-background)]"
88
- : "border-[var(--ads-card-border)] bg-[var(--ads-card-background)]",
89
- ];
90
-
91
- if (props.as === "button") {
92
- classes.push(
93
- "cursor-pointer transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-primary)] disabled:cursor-not-allowed disabled:opacity-50",
94
- );
95
- if (!props.selected && !props.disabled) {
96
- classes.push("hover:bg-[var(--color-neutral-100)]");
97
- }
98
- }
99
-
100
- return classes;
101
- });
102
-
103
- const slots = useSlots();
104
-
105
- const showHeader = computed(
106
- () =>
107
- Boolean(props.title) ||
108
- Boolean(slots.header || slots.actions),
109
- );
110
-
111
- const hasContentAboveFooter = computed(
112
- () =>
113
- showHeader.value ||
114
- Boolean(props.description) ||
115
- Boolean(slots.default),
116
- );
117
- </script>
1
+ <template>
2
+ <component
3
+ :is="as"
4
+ :type="as === 'button' ? 'button' : undefined"
5
+ :disabled="as === 'button' ? disabled : undefined"
6
+ class="h-full border p-[var(--ads-card-padding)] text-left"
7
+ :class="[radiusClass, surfaceClass]"
8
+ >
9
+ <div
10
+ v-if="showHeader"
11
+ class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"
12
+ >
13
+ <div class="min-w-0 flex-1">
14
+ <slot name="header">
15
+ <p class="flex items-center justify-between gap-2 font-bold text-[var(--ads-card-title)]">
16
+ <span class="min-w-0">{{ title }}</span>
17
+ <slot name="hint" />
18
+ </p>
19
+ </slot>
20
+ </div>
21
+ <div v-if="$slots.actions" class="flex shrink-0 flex-wrap items-center gap-2">
22
+ <slot name="actions" />
23
+ </div>
24
+ </div>
25
+
26
+ <div
27
+ v-if="description || $slots.default"
28
+ :class="showHeader ? 'mt-2' : undefined"
29
+ >
30
+ <slot>
31
+ <p class="text-[length:var(--ads-card-body-size)] leading-relaxed text-[var(--ads-card-body)]">
32
+ {{ description }}
33
+ </p>
34
+ </slot>
35
+ </div>
36
+
37
+ <div
38
+ v-if="footerText || $slots.footer"
39
+ class="flex flex-wrap items-start justify-between gap-3"
40
+ :class="hasContentAboveFooter ? 'mt-4' : undefined"
41
+ >
42
+ <slot name="footer">
43
+ <p class="text-xs text-[var(--ads-text-secondary)]">
44
+ {{ footerText }}
45
+ </p>
46
+ </slot>
47
+ </div>
48
+ </component>
49
+ </template>
50
+
51
+ <script setup lang="ts">
52
+ import { computed, useSlots } from "vue";
53
+
54
+ const props = withDefaults(
55
+ defineProps<{
56
+ /** Título em negrito. O slot header substitui. */
57
+ title?: string;
58
+ /** Texto de apoio. O slot padrão substitui. */
59
+ description?: string;
60
+ /** Linha curta no rodapé. O slot footer substitui. */
61
+ footerText?: string;
62
+ /** `button` quando o cartão é a própria ação. */
63
+ as?: "div" | "button";
64
+ disabled?: boolean;
65
+ /** Realce de item escolhido, como um filtro ativo. */
66
+ selected?: boolean;
67
+ /** `sm` (8px) em cartões grandes; `md` (12px) é o padrão. */
68
+ radius?: "sm" | "md";
69
+ }>(),
70
+ {
71
+ as: "div",
72
+ disabled: false,
73
+ selected: false,
74
+ radius: "md",
75
+ },
76
+ );
77
+
78
+ const radiusClass = computed(() =>
79
+ props.radius === "sm"
80
+ ? "rounded-[var(--ads-card-radius-sm)]"
81
+ : "rounded-[var(--ads-card-radius)]",
82
+ );
83
+
84
+ const surfaceClass = computed(() => {
85
+ const classes = [
86
+ props.selected
87
+ ? "border-[var(--ads-card-selected-border)] bg-[var(--ads-card-selected-background)]"
88
+ : "border-[var(--ads-card-border)] bg-[var(--ads-card-background)]",
89
+ ];
90
+
91
+ if (props.as === "button") {
92
+ classes.push(
93
+ "cursor-pointer transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-primary)] disabled:cursor-not-allowed disabled:opacity-50",
94
+ );
95
+ if (!props.selected && !props.disabled) {
96
+ classes.push("hover:bg-[var(--color-neutral-100)]");
97
+ }
98
+ }
99
+
100
+ return classes;
101
+ });
102
+
103
+ const slots = useSlots();
104
+
105
+ const showHeader = computed(
106
+ () =>
107
+ Boolean(props.title) ||
108
+ Boolean(slots.header || slots.actions),
109
+ );
110
+
111
+ const hasContentAboveFooter = computed(
112
+ () =>
113
+ showHeader.value ||
114
+ Boolean(props.description) ||
115
+ Boolean(slots.default),
116
+ );
117
+ </script>
@@ -1,78 +1,78 @@
1
- <template>
2
- <DefinePanel>
3
- <div class="flex min-w-[var(--ads-charttip-min-width)] flex-col gap-4 rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] p-4 text-[var(--ads-tooltip-title)] shadow-[var(--ads-shadow-level-1)]">
4
- <p v-if="title" class="text-center text-base leading-[1.3] font-bold">{{ title }}</p>
5
- <ul class="flex flex-col gap-2">
6
- <li v-for="(row, index) in rows" :key="index" class="flex items-center gap-2 text-sm leading-[1.3]">
7
- <span class="size-[var(--ads-charttip-swatch)] shrink-0 rounded-[var(--ads-radius-xsm)]" :class="row.colorClass" :style="row.color ? { background: row.color } : undefined" />
8
- <span class="min-w-0 flex-1">{{ row.label }}</span>
9
- <span>{{ row.value }}</span>
10
- </li>
11
- </ul>
12
- </div>
13
- </DefinePanel>
14
-
15
- <UTooltip
16
- v-if="hasTrigger"
17
- :delay-duration="delayDuration"
18
- :content="{ side, align: 'center', sideOffset: 8 }"
19
- :ui="tooltipUi"
20
- >
21
- <slot />
22
- <template #content>
23
- <ReusePanel />
24
- </template>
25
- </UTooltip>
26
- <ReusePanel v-else />
27
- </template>
28
-
29
- <script setup lang="ts">
30
- import { computed, useSlots } from "vue";
31
- import { createReusableTemplate } from "@vueuse/core";
32
-
33
- export type AdsCharttipRow = {
34
- label: string;
35
- value: string;
36
- /** Classe de fundo, como `bg-ads-grafico-c1-400`. */
37
- colorClass?: string;
38
- /** Cor sem token. Preferir `colorClass`. */
39
- color?: string;
40
- };
41
-
42
- const props = withDefaults(
43
- defineProps<{
44
- title?: string;
45
- rows?: AdsCharttipRow[];
46
- /** Figma `Property 1`. Com slot, escolhe o lado em que o painel abre, como no AdsTooltip. A seta não é desenhada. */
47
- wing?: "up" | "down" | "left" | "right";
48
- delayDuration?: number;
49
- }>(),
50
- {
51
- title: "Título",
52
- rows: () => [
53
- { label: "Série 1", value: "12", colorClass: "bg-ads-grafico-c1-400" },
54
- { label: "Série 2", value: "8", colorClass: "bg-ads-innovation-500" },
55
- { label: "Série 3", value: "5", colorClass: "bg-ads-grafico-c3-400" },
56
- ],
57
- wing: "down",
58
- delayDuration: 150,
59
- },
60
- );
61
-
62
- const [DefinePanel, ReusePanel] = createReusableTemplate();
63
-
64
- const slots = useSlots();
65
- const hasTrigger = computed(() => Boolean(slots.default));
66
-
67
- const side = computed(() => {
68
- if (props.wing === "down") return "bottom" as const;
69
- if (props.wing === "left") return "left" as const;
70
- if (props.wing === "right") return "right" as const;
71
- return "top" as const;
72
- });
73
-
74
- /** O painel já tem fundo, raio e sombra: o conteúdo do UTooltip só posiciona. */
75
- const tooltipUi = {
76
- content: "z-[70] h-auto rounded-none bg-transparent p-0 shadow-none ring-0",
77
- };
78
- </script>
1
+ <template>
2
+ <DefinePanel>
3
+ <div class="flex min-w-[var(--ads-charttip-min-width)] flex-col gap-4 rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] p-4 text-[var(--ads-tooltip-title)] shadow-[var(--ads-shadow-level-1)]">
4
+ <p v-if="title" class="text-center text-base leading-[1.3] font-bold">{{ title }}</p>
5
+ <ul class="flex flex-col gap-2">
6
+ <li v-for="(row, index) in rows" :key="index" class="flex items-center gap-2 text-sm leading-[1.3]">
7
+ <span class="size-[var(--ads-charttip-swatch)] shrink-0 rounded-[var(--ads-radius-xsm)]" :class="row.colorClass" :style="row.color ? { background: row.color } : undefined" />
8
+ <span class="min-w-0 flex-1">{{ row.label }}</span>
9
+ <span>{{ row.value }}</span>
10
+ </li>
11
+ </ul>
12
+ </div>
13
+ </DefinePanel>
14
+
15
+ <UTooltip
16
+ v-if="hasTrigger"
17
+ :delay-duration="delayDuration"
18
+ :content="{ side, align: 'center', sideOffset: 8 }"
19
+ :ui="tooltipUi"
20
+ >
21
+ <slot />
22
+ <template #content>
23
+ <ReusePanel />
24
+ </template>
25
+ </UTooltip>
26
+ <ReusePanel v-else />
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { computed, useSlots } from "vue";
31
+ import { createReusableTemplate } from "@vueuse/core";
32
+
33
+ export type AdsCharttipRow = {
34
+ label: string;
35
+ value: string;
36
+ /** Classe de fundo, como `bg-ads-grafico-c1-400`. */
37
+ colorClass?: string;
38
+ /** Cor sem token. Preferir `colorClass`. */
39
+ color?: string;
40
+ };
41
+
42
+ const props = withDefaults(
43
+ defineProps<{
44
+ title?: string;
45
+ rows?: AdsCharttipRow[];
46
+ /** Figma `Property 1`. Com slot, escolhe o lado em que o painel abre, como no AdsTooltip. A seta não é desenhada. */
47
+ wing?: "up" | "down" | "left" | "right";
48
+ delayDuration?: number;
49
+ }>(),
50
+ {
51
+ title: "Título",
52
+ rows: () => [
53
+ { label: "Série 1", value: "12", colorClass: "bg-ads-grafico-c1-400" },
54
+ { label: "Série 2", value: "8", colorClass: "bg-ads-innovation-500" },
55
+ { label: "Série 3", value: "5", colorClass: "bg-ads-grafico-c3-400" },
56
+ ],
57
+ wing: "down",
58
+ delayDuration: 150,
59
+ },
60
+ );
61
+
62
+ const [DefinePanel, ReusePanel] = createReusableTemplate();
63
+
64
+ const slots = useSlots();
65
+ const hasTrigger = computed(() => Boolean(slots.default));
66
+
67
+ const side = computed(() => {
68
+ if (props.wing === "down") return "bottom" as const;
69
+ if (props.wing === "left") return "left" as const;
70
+ if (props.wing === "right") return "right" as const;
71
+ return "top" as const;
72
+ });
73
+
74
+ /** O painel já tem fundo, raio e sombra: o conteúdo do UTooltip só posiciona. */
75
+ const tooltipUi = {
76
+ content: "z-[70] h-auto rounded-none bg-transparent p-0 shadow-none ring-0",
77
+ };
78
+ </script>