ads-nuxt 0.1.0 → 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 -1
- 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,103 +1,103 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<nav
|
|
3
|
-
class="flex w-[var(--ads-menu-width)] flex-col items-center gap-[var(--ads-menu-gap)] bg-[var(--ads-surface-card)] px-0 py-[var(--ads-menu-padding-y)] shadow-[0_2px_4px_-1px_color-mix(in_srgb,var(--color-ads-neutro-medio-60)_75%,transparent)]"
|
|
4
|
-
aria-label="Menu principal"
|
|
5
|
-
>
|
|
6
|
-
<template v-for="item in items" :key="item.id">
|
|
7
|
-
<span
|
|
8
|
-
v-if="item.dividerBefore"
|
|
9
|
-
class="h-px w-12 bg-[var(--ads-menu-divider)]"
|
|
10
|
-
role="separator"
|
|
11
|
-
/>
|
|
12
|
-
<component
|
|
13
|
-
:is="item.to ? NuxtLink : 'button'"
|
|
14
|
-
v-bind="item.to ? { to: item.to } : { type: 'button' }"
|
|
15
|
-
class="flex w-full flex-col items-center gap-1 rounded-[var(--ads-radius-xsm)] px-1 py-0 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
|
|
16
|
-
:class="stateOf(item) === 'open' ? 'shadow-[inset_4px_0_0_var(--ads-text-brand)]' : ''"
|
|
17
|
-
:aria-current="stateOf(item) === 'regular' ? undefined : 'page'"
|
|
18
|
-
@click="onSelect(item.id)"
|
|
19
|
-
>
|
|
20
|
-
<span class="flex size-[var(--ads-menu-icon-box)] items-center justify-center rounded-[var(--ads-radius-xsm)] p-1">
|
|
21
|
-
<UIcon :name="item.icon" class="size-[var(--ads-menu-icon)]" :class="iconTone(item)" />
|
|
22
|
-
</span>
|
|
23
|
-
<span
|
|
24
|
-
class="max-w-full text-[length:var(--ads-menu-label-size)] font-bold leading-[1.2] whitespace-pre-line"
|
|
25
|
-
:class="labelTone(item)"
|
|
26
|
-
>{{ item.label }}</span>
|
|
27
|
-
</component>
|
|
28
|
-
</template>
|
|
29
|
-
</nav>
|
|
30
|
-
</template>
|
|
31
|
-
|
|
32
|
-
<script lang="ts">
|
|
33
|
-
import type { RouteLocationRaw } from "vue-router";
|
|
34
|
-
|
|
35
|
-
export type AdsMenuBarState = "regular" | "selected" | "open";
|
|
36
|
-
|
|
37
|
-
export type AdsMenuBarItem = {
|
|
38
|
-
id: string;
|
|
39
|
-
label: string;
|
|
40
|
-
icon: string;
|
|
41
|
-
to?: RouteLocationRaw;
|
|
42
|
-
dividerBefore?: boolean;
|
|
43
|
-
state?: AdsMenuBarState;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/** Ordem do Menu bar default no Figma. Home usa o mesmo tom dos demais (o frame default pinta o rótulo em dark/500). */
|
|
47
|
-
export const adsMenuBarItems: AdsMenuBarItem[] = [
|
|
48
|
-
{ id: "home", label: "Home", icon: "i-lucide-home" },
|
|
49
|
-
{ id: "gestao", label: "Gestão", icon: "i-lucide-folder-tree", dividerBefore: true },
|
|
50
|
-
{ id: "financas", label: "Finanças", icon: "i-lucide-hand-coins" },
|
|
51
|
-
{ id: "compras", label: "Compras", icon: "i-lucide-shopping-cart" },
|
|
52
|
-
{ id: "trade", label: "Trade", icon: "i-lucide-store" },
|
|
53
|
-
{ id: "marketing", label: "Marketing", icon: "i-lucide-megaphone" },
|
|
54
|
-
{ id: "capacitacao", label: "Capacitação", icon: "i-lucide-book-open" },
|
|
55
|
-
{ id: "relatorios", label: "Relatórios", icon: "i-lucide-bar-chart-big" },
|
|
56
|
-
{ id: "promocao", label: "Promoção", icon: "i-lucide-trophy" },
|
|
57
|
-
{ id: "encontro", label: "Encontro\nFarmarcas", icon: "i-lucide-contact" },
|
|
58
|
-
{ id: "campanhas", label: "Campanhas", icon: "i-lucide-goal", dividerBefore: false },
|
|
59
|
-
];
|
|
60
|
-
</script>
|
|
61
|
-
|
|
62
|
-
<script setup lang="ts">
|
|
63
|
-
import { NuxtLink } from "#components";
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const props = withDefaults(
|
|
68
|
-
defineProps<{
|
|
69
|
-
items?: AdsMenuBarItem[];
|
|
70
|
-
activeId?: string;
|
|
71
|
-
openId?: string;
|
|
72
|
-
}>(),
|
|
73
|
-
{
|
|
74
|
-
items: () => adsMenuBarItems,
|
|
75
|
-
activeId: "",
|
|
76
|
-
openId: "",
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
const emit = defineEmits<{
|
|
81
|
-
"update:activeId": [id: string];
|
|
82
|
-
select: [id: string];
|
|
83
|
-
}>();
|
|
84
|
-
|
|
85
|
-
const stateOf = (item: AdsMenuBarItem): AdsMenuBarState => {
|
|
86
|
-
if (item.state) return item.state;
|
|
87
|
-
if (props.openId === item.id) return "open";
|
|
88
|
-
if (props.activeId === item.id) return "selected";
|
|
89
|
-
return "regular";
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const iconTone = (item: AdsMenuBarItem) =>
|
|
93
|
-
stateOf(item) === "regular" ? "text-ads-neutro-escuro-500" : "text-[var(--ads-text-brand)]";
|
|
94
|
-
|
|
95
|
-
const labelTone = (item: AdsMenuBarItem) =>
|
|
96
|
-
stateOf(item) === "regular" ? "text-[var(--ads-text-secondary)]" : "text-[var(--ads-text-brand)]";
|
|
97
|
-
|
|
98
|
-
const onSelect = (id: string) => {
|
|
99
|
-
emit("update:activeId", id);
|
|
100
|
-
emit("select", id);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
class="flex w-[var(--ads-menu-width)] flex-col items-center gap-[var(--ads-menu-gap)] bg-[var(--ads-surface-card)] px-0 py-[var(--ads-menu-padding-y)] shadow-[0_2px_4px_-1px_color-mix(in_srgb,var(--color-ads-neutro-medio-60)_75%,transparent)]"
|
|
4
|
+
aria-label="Menu principal"
|
|
5
|
+
>
|
|
6
|
+
<template v-for="item in items" :key="item.id">
|
|
7
|
+
<span
|
|
8
|
+
v-if="item.dividerBefore"
|
|
9
|
+
class="h-px w-12 bg-[var(--ads-menu-divider)]"
|
|
10
|
+
role="separator"
|
|
11
|
+
/>
|
|
12
|
+
<component
|
|
13
|
+
:is="item.to ? NuxtLink : 'button'"
|
|
14
|
+
v-bind="item.to ? { to: item.to } : { type: 'button' }"
|
|
15
|
+
class="flex w-full flex-col items-center gap-1 rounded-[var(--ads-radius-xsm)] px-1 py-0 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
|
|
16
|
+
:class="stateOf(item) === 'open' ? 'shadow-[inset_4px_0_0_var(--ads-text-brand)]' : ''"
|
|
17
|
+
:aria-current="stateOf(item) === 'regular' ? undefined : 'page'"
|
|
18
|
+
@click="onSelect(item.id)"
|
|
19
|
+
>
|
|
20
|
+
<span class="flex size-[var(--ads-menu-icon-box)] items-center justify-center rounded-[var(--ads-radius-xsm)] p-1">
|
|
21
|
+
<UIcon :name="item.icon" class="size-[var(--ads-menu-icon)]" :class="iconTone(item)" />
|
|
22
|
+
</span>
|
|
23
|
+
<span
|
|
24
|
+
class="max-w-full text-[length:var(--ads-menu-label-size)] font-bold leading-[1.2] whitespace-pre-line"
|
|
25
|
+
:class="labelTone(item)"
|
|
26
|
+
>{{ item.label }}</span>
|
|
27
|
+
</component>
|
|
28
|
+
</template>
|
|
29
|
+
</nav>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script lang="ts">
|
|
33
|
+
import type { RouteLocationRaw } from "vue-router";
|
|
34
|
+
|
|
35
|
+
export type AdsMenuBarState = "regular" | "selected" | "open";
|
|
36
|
+
|
|
37
|
+
export type AdsMenuBarItem = {
|
|
38
|
+
id: string;
|
|
39
|
+
label: string;
|
|
40
|
+
icon: string;
|
|
41
|
+
to?: RouteLocationRaw;
|
|
42
|
+
dividerBefore?: boolean;
|
|
43
|
+
state?: AdsMenuBarState;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Ordem do Menu bar default no Figma. Home usa o mesmo tom dos demais (o frame default pinta o rótulo em dark/500). */
|
|
47
|
+
export const adsMenuBarItems: AdsMenuBarItem[] = [
|
|
48
|
+
{ id: "home", label: "Home", icon: "i-lucide-home" },
|
|
49
|
+
{ id: "gestao", label: "Gestão", icon: "i-lucide-folder-tree", dividerBefore: true },
|
|
50
|
+
{ id: "financas", label: "Finanças", icon: "i-lucide-hand-coins" },
|
|
51
|
+
{ id: "compras", label: "Compras", icon: "i-lucide-shopping-cart" },
|
|
52
|
+
{ id: "trade", label: "Trade", icon: "i-lucide-store" },
|
|
53
|
+
{ id: "marketing", label: "Marketing", icon: "i-lucide-megaphone" },
|
|
54
|
+
{ id: "capacitacao", label: "Capacitação", icon: "i-lucide-book-open" },
|
|
55
|
+
{ id: "relatorios", label: "Relatórios", icon: "i-lucide-bar-chart-big" },
|
|
56
|
+
{ id: "promocao", label: "Promoção", icon: "i-lucide-trophy" },
|
|
57
|
+
{ id: "encontro", label: "Encontro\nFarmarcas", icon: "i-lucide-contact" },
|
|
58
|
+
{ id: "campanhas", label: "Campanhas", icon: "i-lucide-goal", dividerBefore: false },
|
|
59
|
+
];
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
import { NuxtLink } from "#components";
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
const props = withDefaults(
|
|
68
|
+
defineProps<{
|
|
69
|
+
items?: AdsMenuBarItem[];
|
|
70
|
+
activeId?: string;
|
|
71
|
+
openId?: string;
|
|
72
|
+
}>(),
|
|
73
|
+
{
|
|
74
|
+
items: () => adsMenuBarItems,
|
|
75
|
+
activeId: "",
|
|
76
|
+
openId: "",
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
const emit = defineEmits<{
|
|
81
|
+
"update:activeId": [id: string];
|
|
82
|
+
select: [id: string];
|
|
83
|
+
}>();
|
|
84
|
+
|
|
85
|
+
const stateOf = (item: AdsMenuBarItem): AdsMenuBarState => {
|
|
86
|
+
if (item.state) return item.state;
|
|
87
|
+
if (props.openId === item.id) return "open";
|
|
88
|
+
if (props.activeId === item.id) return "selected";
|
|
89
|
+
return "regular";
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const iconTone = (item: AdsMenuBarItem) =>
|
|
93
|
+
stateOf(item) === "regular" ? "text-ads-neutro-escuro-500" : "text-[var(--ads-text-brand)]";
|
|
94
|
+
|
|
95
|
+
const labelTone = (item: AdsMenuBarItem) =>
|
|
96
|
+
stateOf(item) === "regular" ? "text-[var(--ads-text-secondary)]" : "text-[var(--ads-text-brand)]";
|
|
97
|
+
|
|
98
|
+
const onSelect = (id: string) => {
|
|
99
|
+
emit("update:activeId", id);
|
|
100
|
+
emit("select", id);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
</script>
|
|
@@ -1,119 +1,119 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<component
|
|
3
|
-
:is="as"
|
|
4
|
-
:type="as === 'button' ? 'button' : undefined"
|
|
5
|
-
:disabled="as === 'button' ? disabled : undefined"
|
|
6
|
-
class="flex h-full min-w-0 flex-col gap-[var(--ads-metric-gap)] border p-[var(--ads-metric-padding)] text-left font-normal"
|
|
7
|
-
:class="[radiusClass, surfaceClass]"
|
|
8
|
-
>
|
|
9
|
-
<div class="flex flex-col items-start gap-1">
|
|
10
|
-
<div class="flex min-w-0 items-center gap-2">
|
|
11
|
-
<p :class="labelClass">{{ title }}</p>
|
|
12
|
-
<InvertedTooltip v-if="hint" :text="hint" class="shrink-0">
|
|
13
|
-
<UIcon
|
|
14
|
-
name="i-lucide-help-circle"
|
|
15
|
-
size="xs"
|
|
16
|
-
class="align-middle text-[var(--ads-border-icon-support)]"
|
|
17
|
-
/>
|
|
18
|
-
</InvertedTooltip>
|
|
19
|
-
</div>
|
|
20
|
-
<slot name="status" />
|
|
21
|
-
</div>
|
|
22
|
-
|
|
23
|
-
<div class="flex min-w-0 items-center gap-2">
|
|
24
|
-
<p class="min-w-0" :class="empty ? emptyValueClass : valueClass">
|
|
25
|
-
{{ value }}
|
|
26
|
-
</p>
|
|
27
|
-
<span
|
|
28
|
-
v-if="!empty && slots.delta"
|
|
29
|
-
class="inline-flex shrink-0 items-center gap-1"
|
|
30
|
-
:class="deltaClass"
|
|
31
|
-
>
|
|
32
|
-
<slot name="delta" />
|
|
33
|
-
</span>
|
|
34
|
-
</div>
|
|
35
|
-
|
|
36
|
-
<div
|
|
37
|
-
v-if="caption || slots.footer"
|
|
38
|
-
class="mt-auto flex items-end justify-between gap-2"
|
|
39
|
-
>
|
|
40
|
-
<p v-if="caption" :class="captionClass">{{ caption }}</p>
|
|
41
|
-
<slot name="footer" />
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
<div v-if="slots.default" class="min-h-0 flex-1">
|
|
45
|
-
<slot />
|
|
46
|
-
</div>
|
|
47
|
-
</component>
|
|
48
|
-
</template>
|
|
49
|
-
|
|
50
|
-
<script setup lang="ts">
|
|
51
|
-
import { computed, useSlots } from "vue";
|
|
52
|
-
import InvertedTooltip from "./InvertedTooltip.vue";
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Número em destaque.
|
|
56
|
-
* Rótulo: 14px, medium. Valor: 20px, bold. Variação: 14px, bold. Legenda: 12px, regular.
|
|
57
|
-
*/
|
|
58
|
-
const labelClass =
|
|
59
|
-
"text-[length:var(--ads-metric-label-size)] font-medium leading-[var(--ads-line-height-sm)] text-[var(--ads-metric-label)]";
|
|
60
|
-
const valueClass =
|
|
61
|
-
"text-[length:var(--ads-metric-value-size)] font-bold leading-[var(--ads-line-height-sm)] text-[var(--ads-metric-value)] tabular-nums";
|
|
62
|
-
const emptyValueClass =
|
|
63
|
-
"text-[length:var(--ads-font-size-xs)] font-medium leading-[var(--ads-line-height-sm)] text-[var(--ads-text-tertiary)]";
|
|
64
|
-
const deltaClass =
|
|
65
|
-
"text-[length:var(--ads-font-size-xxs)] font-bold leading-[var(--ads-line-height-sm)] tabular-nums";
|
|
66
|
-
const captionClass =
|
|
67
|
-
"text-[length:var(--ads-metric-caption-size)] font-normal leading-[var(--ads-line-height-md)] text-[var(--ads-metric-label)]";
|
|
68
|
-
|
|
69
|
-
const props = withDefaults(
|
|
70
|
-
defineProps<{
|
|
71
|
-
/** Rótulo acima do número. */
|
|
72
|
-
title: string;
|
|
73
|
-
/** Número ou frase principal. */
|
|
74
|
-
value: string;
|
|
75
|
-
/** Texto do ícone de ajuda ao lado do rótulo. */
|
|
76
|
-
hint?: string;
|
|
77
|
-
/** Linha curta abaixo do número, como o valor atual. */
|
|
78
|
-
caption?: string;
|
|
79
|
-
/** Valor ausente: 16px medium, cor tertiary. */
|
|
80
|
-
empty?: boolean;
|
|
81
|
-
/** `button` quando o cartão é a própria ação. */
|
|
82
|
-
as?: "div" | "button";
|
|
83
|
-
disabled?: boolean;
|
|
84
|
-
/** Realce de item escolhido, como um filtro ativo. */
|
|
85
|
-
selected?: boolean;
|
|
86
|
-
}>(),
|
|
87
|
-
{
|
|
88
|
-
hint: undefined,
|
|
89
|
-
caption: undefined,
|
|
90
|
-
empty: false,
|
|
91
|
-
as: "div",
|
|
92
|
-
disabled: false,
|
|
93
|
-
selected: false,
|
|
94
|
-
},
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
const slots = useSlots();
|
|
98
|
-
|
|
99
|
-
const radiusClass = "rounded-[var(--ads-metric-radius)]";
|
|
100
|
-
|
|
101
|
-
const surfaceClass = computed(() => {
|
|
102
|
-
const classes = [
|
|
103
|
-
props.selected
|
|
104
|
-
? "border-[var(--color-neutral-300)] bg-[var(--color-neutral-100)]"
|
|
105
|
-
: "border-[var(--ads-metric-border)] bg-[var(--ads-metric-background)]",
|
|
106
|
-
];
|
|
107
|
-
|
|
108
|
-
if (props.as === "button") {
|
|
109
|
-
classes.push(
|
|
110
|
-
"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",
|
|
111
|
-
);
|
|
112
|
-
if (!props.selected && !props.disabled) {
|
|
113
|
-
classes.push("hover:bg-[var(--color-neutral-100)]");
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return classes;
|
|
118
|
-
});
|
|
119
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="as"
|
|
4
|
+
:type="as === 'button' ? 'button' : undefined"
|
|
5
|
+
:disabled="as === 'button' ? disabled : undefined"
|
|
6
|
+
class="flex h-full min-w-0 flex-col gap-[var(--ads-metric-gap)] border p-[var(--ads-metric-padding)] text-left font-normal"
|
|
7
|
+
:class="[radiusClass, surfaceClass]"
|
|
8
|
+
>
|
|
9
|
+
<div class="flex flex-col items-start gap-1">
|
|
10
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
11
|
+
<p :class="labelClass">{{ title }}</p>
|
|
12
|
+
<InvertedTooltip v-if="hint" :text="hint" class="shrink-0">
|
|
13
|
+
<UIcon
|
|
14
|
+
name="i-lucide-help-circle"
|
|
15
|
+
size="xs"
|
|
16
|
+
class="align-middle text-[var(--ads-border-icon-support)]"
|
|
17
|
+
/>
|
|
18
|
+
</InvertedTooltip>
|
|
19
|
+
</div>
|
|
20
|
+
<slot name="status" />
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="flex min-w-0 items-center gap-2">
|
|
24
|
+
<p class="min-w-0" :class="empty ? emptyValueClass : valueClass">
|
|
25
|
+
{{ value }}
|
|
26
|
+
</p>
|
|
27
|
+
<span
|
|
28
|
+
v-if="!empty && slots.delta"
|
|
29
|
+
class="inline-flex shrink-0 items-center gap-1"
|
|
30
|
+
:class="deltaClass"
|
|
31
|
+
>
|
|
32
|
+
<slot name="delta" />
|
|
33
|
+
</span>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div
|
|
37
|
+
v-if="caption || slots.footer"
|
|
38
|
+
class="mt-auto flex items-end justify-between gap-2"
|
|
39
|
+
>
|
|
40
|
+
<p v-if="caption" :class="captionClass">{{ caption }}</p>
|
|
41
|
+
<slot name="footer" />
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div v-if="slots.default" class="min-h-0 flex-1">
|
|
45
|
+
<slot />
|
|
46
|
+
</div>
|
|
47
|
+
</component>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
import { computed, useSlots } from "vue";
|
|
52
|
+
import InvertedTooltip from "./InvertedTooltip.vue";
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Número em destaque.
|
|
56
|
+
* Rótulo: 14px, medium. Valor: 20px, bold. Variação: 14px, bold. Legenda: 12px, regular.
|
|
57
|
+
*/
|
|
58
|
+
const labelClass =
|
|
59
|
+
"text-[length:var(--ads-metric-label-size)] font-medium leading-[var(--ads-line-height-sm)] text-[var(--ads-metric-label)]";
|
|
60
|
+
const valueClass =
|
|
61
|
+
"text-[length:var(--ads-metric-value-size)] font-bold leading-[var(--ads-line-height-sm)] text-[var(--ads-metric-value)] tabular-nums";
|
|
62
|
+
const emptyValueClass =
|
|
63
|
+
"text-[length:var(--ads-font-size-xs)] font-medium leading-[var(--ads-line-height-sm)] text-[var(--ads-text-tertiary)]";
|
|
64
|
+
const deltaClass =
|
|
65
|
+
"text-[length:var(--ads-font-size-xxs)] font-bold leading-[var(--ads-line-height-sm)] tabular-nums";
|
|
66
|
+
const captionClass =
|
|
67
|
+
"text-[length:var(--ads-metric-caption-size)] font-normal leading-[var(--ads-line-height-md)] text-[var(--ads-metric-label)]";
|
|
68
|
+
|
|
69
|
+
const props = withDefaults(
|
|
70
|
+
defineProps<{
|
|
71
|
+
/** Rótulo acima do número. */
|
|
72
|
+
title: string;
|
|
73
|
+
/** Número ou frase principal. */
|
|
74
|
+
value: string;
|
|
75
|
+
/** Texto do ícone de ajuda ao lado do rótulo. */
|
|
76
|
+
hint?: string;
|
|
77
|
+
/** Linha curta abaixo do número, como o valor atual. */
|
|
78
|
+
caption?: string;
|
|
79
|
+
/** Valor ausente: 16px medium, cor tertiary. */
|
|
80
|
+
empty?: boolean;
|
|
81
|
+
/** `button` quando o cartão é a própria ação. */
|
|
82
|
+
as?: "div" | "button";
|
|
83
|
+
disabled?: boolean;
|
|
84
|
+
/** Realce de item escolhido, como um filtro ativo. */
|
|
85
|
+
selected?: boolean;
|
|
86
|
+
}>(),
|
|
87
|
+
{
|
|
88
|
+
hint: undefined,
|
|
89
|
+
caption: undefined,
|
|
90
|
+
empty: false,
|
|
91
|
+
as: "div",
|
|
92
|
+
disabled: false,
|
|
93
|
+
selected: false,
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const slots = useSlots();
|
|
98
|
+
|
|
99
|
+
const radiusClass = "rounded-[var(--ads-metric-radius)]";
|
|
100
|
+
|
|
101
|
+
const surfaceClass = computed(() => {
|
|
102
|
+
const classes = [
|
|
103
|
+
props.selected
|
|
104
|
+
? "border-[var(--color-neutral-300)] bg-[var(--color-neutral-100)]"
|
|
105
|
+
: "border-[var(--ads-metric-border)] bg-[var(--ads-metric-background)]",
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
if (props.as === "button") {
|
|
109
|
+
classes.push(
|
|
110
|
+
"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",
|
|
111
|
+
);
|
|
112
|
+
if (!props.selected && !props.disabled) {
|
|
113
|
+
classes.push("hover:bg-[var(--color-neutral-100)]");
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return classes;
|
|
118
|
+
});
|
|
119
|
+
</script>
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<span
|
|
3
|
-
class="inline-flex h-[var(--ads-mini-height)] shrink-0 items-center justify-center rounded-full px-[var(--ads-mini-padding-x)] py-1 text-[length:var(--ads-mini-font-size)] [font-weight:var(--ads-mini-weight)] leading-[1.1] whitespace-nowrap"
|
|
4
|
-
:class="surfaceClass"
|
|
5
|
-
>
|
|
6
|
-
{{ resolvedLabel }}
|
|
7
|
-
</span>
|
|
8
|
-
</template>
|
|
9
|
-
|
|
10
|
-
<script setup lang="ts">
|
|
11
|
-
import { computed } from "vue";
|
|
12
|
-
|
|
13
|
-
type AdsMiniPillType = "nao-iniciado" | "pausado" | "novo";
|
|
14
|
-
|
|
15
|
-
const props = withDefaults(
|
|
16
|
-
defineProps<{
|
|
17
|
-
type?: AdsMiniPillType;
|
|
18
|
-
label?: string;
|
|
19
|
-
}>(),
|
|
20
|
-
{
|
|
21
|
-
type: "nao-iniciado",
|
|
22
|
-
label: undefined,
|
|
23
|
-
},
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
const surfaces: Record<AdsMiniPillType, string> = {
|
|
27
|
-
"nao-iniciado": "bg-[var(--ads-mini-idle-bg)] text-[var(--ads-mini-idle-text)]",
|
|
28
|
-
pausado: "bg-[var(--ads-mini-paused-bg)] text-[var(--ads-mini-paused-text)]",
|
|
29
|
-
novo: "bg-[var(--ads-mini-new-bg)] text-[var(--ads-mini-new-text)]",
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const labels: Record<AdsMiniPillType, string> = {
|
|
33
|
-
"nao-iniciado": "Não iniciado",
|
|
34
|
-
pausado: "Pausado",
|
|
35
|
-
novo: "Novo",
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const surfaceClass = computed(() => surfaces[props.type]);
|
|
39
|
-
const resolvedLabel = computed(() => props.label ?? labels[props.type]);
|
|
40
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<span
|
|
3
|
+
class="inline-flex h-[var(--ads-mini-height)] shrink-0 items-center justify-center rounded-full px-[var(--ads-mini-padding-x)] py-1 text-[length:var(--ads-mini-font-size)] [font-weight:var(--ads-mini-weight)] leading-[1.1] whitespace-nowrap"
|
|
4
|
+
:class="surfaceClass"
|
|
5
|
+
>
|
|
6
|
+
{{ resolvedLabel }}
|
|
7
|
+
</span>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { computed } from "vue";
|
|
12
|
+
|
|
13
|
+
type AdsMiniPillType = "nao-iniciado" | "pausado" | "novo";
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(
|
|
16
|
+
defineProps<{
|
|
17
|
+
type?: AdsMiniPillType;
|
|
18
|
+
label?: string;
|
|
19
|
+
}>(),
|
|
20
|
+
{
|
|
21
|
+
type: "nao-iniciado",
|
|
22
|
+
label: undefined,
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const surfaces: Record<AdsMiniPillType, string> = {
|
|
27
|
+
"nao-iniciado": "bg-[var(--ads-mini-idle-bg)] text-[var(--ads-mini-idle-text)]",
|
|
28
|
+
pausado: "bg-[var(--ads-mini-paused-bg)] text-[var(--ads-mini-paused-text)]",
|
|
29
|
+
novo: "bg-[var(--ads-mini-new-bg)] text-[var(--ads-mini-new-text)]",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const labels: Record<AdsMiniPillType, string> = {
|
|
33
|
+
"nao-iniciado": "Não iniciado",
|
|
34
|
+
pausado: "Pausado",
|
|
35
|
+
novo: "Novo",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const surfaceClass = computed(() => surfaces[props.type]);
|
|
39
|
+
const resolvedLabel = computed(() => props.label ?? labels[props.type]);
|
|
40
|
+
</script>
|