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,157 +1,157 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<UModal
|
|
3
|
-
v-model:open="open"
|
|
4
|
-
:dismissible="dismissible"
|
|
5
|
-
:close="false"
|
|
6
|
-
:title="title"
|
|
7
|
-
:description="description"
|
|
8
|
-
:ui="{ overlay: 'bg-neutral-950/40', content: contentClass }"
|
|
9
|
-
:content="{
|
|
10
|
-
onEscapeKeyDown: () => emit('dismiss', 'esc'),
|
|
11
|
-
onPointerDownOutside: () => emit('dismiss', 'outside'),
|
|
12
|
-
}"
|
|
13
|
-
>
|
|
14
|
-
<template #content="{ close }">
|
|
15
|
-
<div class="flex min-h-0 w-full flex-col gap-[var(--ads-space-xxs)]">
|
|
16
|
-
<div class="flex items-start gap-[var(--ads-space-nano)]">
|
|
17
|
-
<span
|
|
18
|
-
v-if="icon"
|
|
19
|
-
class="flex size-[var(--ads-space-xs)] shrink-0 items-center justify-center text-[var(--ads-text-primary)]"
|
|
20
|
-
aria-hidden="true"
|
|
21
|
-
>
|
|
22
|
-
<UIcon :name="icon" class="size-[var(--ads-space-xxs)]" />
|
|
23
|
-
</span>
|
|
24
|
-
<div class="flex min-h-[var(--ads-space-xs)] min-w-0 flex-1 items-center">
|
|
25
|
-
<slot name="title">
|
|
26
|
-
<h2 class="text-[length:var(--ads-modal-title-size)] font-bold leading-[1.3] text-[var(--ads-modal-title)]">
|
|
27
|
-
{{ title }}
|
|
28
|
-
</h2>
|
|
29
|
-
</slot>
|
|
30
|
-
</div>
|
|
31
|
-
<button
|
|
32
|
-
v-if="closable"
|
|
33
|
-
type="button"
|
|
34
|
-
class="flex size-[var(--ads-space-xs)] shrink-0 items-center justify-center rounded-[var(--ads-radius-xsm)] text-[var(--ads-text-primary)] hover:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
|
|
35
|
-
aria-label="Fechar"
|
|
36
|
-
@click="dismiss(close)"
|
|
37
|
-
>
|
|
38
|
-
<UIcon name="i-lucide-x" class="size-[18px]" />
|
|
39
|
-
</button>
|
|
40
|
-
</div>
|
|
41
|
-
|
|
42
|
-
<p v-if="description" class="text-[length:var(--ads-modal-body-size)] leading-[var(--ads-line-height-md)] text-[var(--ads-modal-body)]">
|
|
43
|
-
{{ description }}
|
|
44
|
-
</p>
|
|
45
|
-
|
|
46
|
-
<div v-if="slots.default" class="min-h-0 overflow-y-auto">
|
|
47
|
-
<slot />
|
|
48
|
-
</div>
|
|
49
|
-
|
|
50
|
-
<div v-if="slots.footer" class="flex w-full shrink-0 items-center justify-end gap-[var(--ads-space-quark)] bg-white">
|
|
51
|
-
<slot name="footer" />
|
|
52
|
-
</div>
|
|
53
|
-
<div v-else-if="showDefaultActions" class="flex w-full items-center justify-end gap-[var(--ads-space-quark)]">
|
|
54
|
-
<AdsButton
|
|
55
|
-
v-if="cancelLabel"
|
|
56
|
-
type="unfilled"
|
|
57
|
-
:label="cancelLabel"
|
|
58
|
-
@click="dismiss(close)"
|
|
59
|
-
/>
|
|
60
|
-
<AdsButton
|
|
61
|
-
v-if="confirmLabel"
|
|
62
|
-
:label="confirmLabel"
|
|
63
|
-
@click="emit('confirm')"
|
|
64
|
-
/>
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</template>
|
|
68
|
-
</UModal>
|
|
69
|
-
</template>
|
|
70
|
-
|
|
71
|
-
<script setup lang="ts">
|
|
72
|
-
import { computed, useSlots } from "vue";
|
|
73
|
-
import AdsButton from "./AdsButton.vue";
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* `dialog`: cartão centralizado (desktop, Figma `APP=No`), 532px, raio SM (8px).
|
|
77
|
-
* `sheet`: folha presa à base (mobile/app, Figma `APP=Yes`), largura cheia,
|
|
78
|
-
* arredondamento só no topo, sombra nível 2.
|
|
79
|
-
*
|
|
80
|
-
* Espaçamento, na escala ADS:
|
|
81
|
-
* - padding do cartão e faixa do cabeçalho: space-xs (24px)
|
|
82
|
-
* - vão entre cabeçalho, texto, conteúdo e ações: space-xxs (16px)
|
|
83
|
-
* - vão entre ícone, título e fechar: space-nano (8px)
|
|
84
|
-
* - ícone: space-xxs (16px). O X do desktop segue o SM do Figma (18px)
|
|
85
|
-
* - título: 20px bold, line-height 130% (Subtitle 20 do Figma)
|
|
86
|
-
*/
|
|
87
|
-
export type AdsModalVariant = "dialog" | "sheet";
|
|
88
|
-
export type AdsModalSize = "md" | "lg" | "xl" | "2xl" | "3xl";
|
|
89
|
-
export type AdsModalDismissReason = "esc" | "outside";
|
|
90
|
-
|
|
91
|
-
const props = withDefaults(
|
|
92
|
-
defineProps<{
|
|
93
|
-
title: string;
|
|
94
|
-
description?: string;
|
|
95
|
-
/** Nome Lucide, como `i-lucide-align-center`. Sem valor, o título fica só com o texto. */
|
|
96
|
-
icon?: string;
|
|
97
|
-
cancelLabel?: string;
|
|
98
|
-
confirmLabel?: string;
|
|
99
|
-
variant?: AdsModalVariant;
|
|
100
|
-
/** Largura do cartão. `md` é o diálogo de confirmação (532px). */
|
|
101
|
-
size?: AdsModalSize;
|
|
102
|
-
scrollable?: boolean;
|
|
103
|
-
dismissible?: boolean;
|
|
104
|
-
/** Sem o X. Overlay e Esc continuam no `dismissible`. */
|
|
105
|
-
closable?: boolean;
|
|
106
|
-
}>(),
|
|
107
|
-
{
|
|
108
|
-
description: undefined,
|
|
109
|
-
icon: undefined,
|
|
110
|
-
cancelLabel: "Cancelar",
|
|
111
|
-
confirmLabel: "Continuar",
|
|
112
|
-
variant: "dialog",
|
|
113
|
-
size: "md",
|
|
114
|
-
scrollable: false,
|
|
115
|
-
dismissible: true,
|
|
116
|
-
closable: true,
|
|
117
|
-
},
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* `!` força a prioridade sobre as classes padrão do tema `UModal`
|
|
122
|
-
* (ex.: `shadow-lg`, `rounded-lg`), que entram na cascata depois das nossas
|
|
123
|
-
* — sem o important, o valor do Figma perde para o default do Nuxt UI.
|
|
124
|
-
*/
|
|
125
|
-
const widths: Record<AdsModalSize, string> = {
|
|
126
|
-
md: "!w-[min(532px,calc(100%-2rem))]",
|
|
127
|
-
lg: "!w-[min(42rem,calc(100%-2rem))]",
|
|
128
|
-
xl: "!w-[min(48rem,calc(100%-2rem))]",
|
|
129
|
-
"2xl": "!w-[min(56rem,calc(100%-2rem))]",
|
|
130
|
-
"3xl": "!w-[min(64rem,calc(100%-2rem))]",
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const contentClass = computed(() => {
|
|
134
|
-
const scroll = props.scrollable ? " !max-h-[calc(100dvh-2rem)] !overflow-y-auto" : "";
|
|
135
|
-
if (props.variant === "sheet") {
|
|
136
|
-
return `!inset-x-0 !bottom-0 !top-auto !left-0 !translate-x-0 !translate-y-0 !w-full !max-w-none !max-h-[calc(100dvh-2rem)] !divide-y-0 !overflow-y-auto !bg-[var(--ads-modal-background)] !p-[var(--ads-modal-padding)] !shadow-[var(--ads-shadow-level-2)] !ring-1 !ring-[var(--ads-modal-border)] !rounded-t-[var(--ads-modal-radius)] !rounded-b-none${scroll}`;
|
|
137
|
-
}
|
|
138
|
-
return `${widths[props.size]} !max-w-none !divide-y-0 !rounded-[var(--ads-modal-radius)] !border !border-[var(--ads-modal-border)] !bg-[var(--ads-modal-background)] !p-[var(--ads-modal-padding)] !shadow-[var(--ads-shadow-level-3)] !ring-0${scroll}`;
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
const emit = defineEmits<{
|
|
142
|
-
confirm: [];
|
|
143
|
-
cancel: [];
|
|
144
|
-
dismiss: [reason: AdsModalDismissReason];
|
|
145
|
-
}>();
|
|
146
|
-
|
|
147
|
-
const open = defineModel<boolean>("open", { default: false });
|
|
148
|
-
const slots = useSlots();
|
|
149
|
-
const showDefaultActions = computed(() => !slots.footer && !slots.default);
|
|
150
|
-
|
|
151
|
-
const dismiss = (close: () => void) => {
|
|
152
|
-
if (!props.closable) return;
|
|
153
|
-
emit("cancel");
|
|
154
|
-
close();
|
|
155
|
-
open.value = false;
|
|
156
|
-
};
|
|
157
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<UModal
|
|
3
|
+
v-model:open="open"
|
|
4
|
+
:dismissible="dismissible"
|
|
5
|
+
:close="false"
|
|
6
|
+
:title="title"
|
|
7
|
+
:description="description"
|
|
8
|
+
:ui="{ overlay: 'bg-neutral-950/40', content: contentClass }"
|
|
9
|
+
:content="{
|
|
10
|
+
onEscapeKeyDown: () => emit('dismiss', 'esc'),
|
|
11
|
+
onPointerDownOutside: () => emit('dismiss', 'outside'),
|
|
12
|
+
}"
|
|
13
|
+
>
|
|
14
|
+
<template #content="{ close }">
|
|
15
|
+
<div class="flex min-h-0 w-full flex-col gap-[var(--ads-space-xxs)]">
|
|
16
|
+
<div class="flex items-start gap-[var(--ads-space-nano)]">
|
|
17
|
+
<span
|
|
18
|
+
v-if="icon"
|
|
19
|
+
class="flex size-[var(--ads-space-xs)] shrink-0 items-center justify-center text-[var(--ads-text-primary)]"
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
>
|
|
22
|
+
<UIcon :name="icon" class="size-[var(--ads-space-xxs)]" />
|
|
23
|
+
</span>
|
|
24
|
+
<div class="flex min-h-[var(--ads-space-xs)] min-w-0 flex-1 items-center">
|
|
25
|
+
<slot name="title">
|
|
26
|
+
<h2 class="text-[length:var(--ads-modal-title-size)] font-bold leading-[1.3] text-[var(--ads-modal-title)]">
|
|
27
|
+
{{ title }}
|
|
28
|
+
</h2>
|
|
29
|
+
</slot>
|
|
30
|
+
</div>
|
|
31
|
+
<button
|
|
32
|
+
v-if="closable"
|
|
33
|
+
type="button"
|
|
34
|
+
class="flex size-[var(--ads-space-xs)] shrink-0 items-center justify-center rounded-[var(--ads-radius-xsm)] text-[var(--ads-text-primary)] hover:bg-neutral-100 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
|
|
35
|
+
aria-label="Fechar"
|
|
36
|
+
@click="dismiss(close)"
|
|
37
|
+
>
|
|
38
|
+
<UIcon name="i-lucide-x" class="size-[18px]" />
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<p v-if="description" class="text-[length:var(--ads-modal-body-size)] leading-[var(--ads-line-height-md)] text-[var(--ads-modal-body)]">
|
|
43
|
+
{{ description }}
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<div v-if="slots.default" class="min-h-0 overflow-y-auto">
|
|
47
|
+
<slot />
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div v-if="slots.footer" class="flex w-full shrink-0 items-center justify-end gap-[var(--ads-space-quark)] bg-white">
|
|
51
|
+
<slot name="footer" />
|
|
52
|
+
</div>
|
|
53
|
+
<div v-else-if="showDefaultActions" class="flex w-full items-center justify-end gap-[var(--ads-space-quark)]">
|
|
54
|
+
<AdsButton
|
|
55
|
+
v-if="cancelLabel"
|
|
56
|
+
type="unfilled"
|
|
57
|
+
:label="cancelLabel"
|
|
58
|
+
@click="dismiss(close)"
|
|
59
|
+
/>
|
|
60
|
+
<AdsButton
|
|
61
|
+
v-if="confirmLabel"
|
|
62
|
+
:label="confirmLabel"
|
|
63
|
+
@click="emit('confirm')"
|
|
64
|
+
/>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</template>
|
|
68
|
+
</UModal>
|
|
69
|
+
</template>
|
|
70
|
+
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { computed, useSlots } from "vue";
|
|
73
|
+
import AdsButton from "./AdsButton.vue";
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `dialog`: cartão centralizado (desktop, Figma `APP=No`), 532px, raio SM (8px).
|
|
77
|
+
* `sheet`: folha presa à base (mobile/app, Figma `APP=Yes`), largura cheia,
|
|
78
|
+
* arredondamento só no topo, sombra nível 2.
|
|
79
|
+
*
|
|
80
|
+
* Espaçamento, na escala ADS:
|
|
81
|
+
* - padding do cartão e faixa do cabeçalho: space-xs (24px)
|
|
82
|
+
* - vão entre cabeçalho, texto, conteúdo e ações: space-xxs (16px)
|
|
83
|
+
* - vão entre ícone, título e fechar: space-nano (8px)
|
|
84
|
+
* - ícone: space-xxs (16px). O X do desktop segue o SM do Figma (18px)
|
|
85
|
+
* - título: 20px bold, line-height 130% (Subtitle 20 do Figma)
|
|
86
|
+
*/
|
|
87
|
+
export type AdsModalVariant = "dialog" | "sheet";
|
|
88
|
+
export type AdsModalSize = "md" | "lg" | "xl" | "2xl" | "3xl";
|
|
89
|
+
export type AdsModalDismissReason = "esc" | "outside";
|
|
90
|
+
|
|
91
|
+
const props = withDefaults(
|
|
92
|
+
defineProps<{
|
|
93
|
+
title: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
/** Nome Lucide, como `i-lucide-align-center`. Sem valor, o título fica só com o texto. */
|
|
96
|
+
icon?: string;
|
|
97
|
+
cancelLabel?: string;
|
|
98
|
+
confirmLabel?: string;
|
|
99
|
+
variant?: AdsModalVariant;
|
|
100
|
+
/** Largura do cartão. `md` é o diálogo de confirmação (532px). */
|
|
101
|
+
size?: AdsModalSize;
|
|
102
|
+
scrollable?: boolean;
|
|
103
|
+
dismissible?: boolean;
|
|
104
|
+
/** Sem o X. Overlay e Esc continuam no `dismissible`. */
|
|
105
|
+
closable?: boolean;
|
|
106
|
+
}>(),
|
|
107
|
+
{
|
|
108
|
+
description: undefined,
|
|
109
|
+
icon: undefined,
|
|
110
|
+
cancelLabel: "Cancelar",
|
|
111
|
+
confirmLabel: "Continuar",
|
|
112
|
+
variant: "dialog",
|
|
113
|
+
size: "md",
|
|
114
|
+
scrollable: false,
|
|
115
|
+
dismissible: true,
|
|
116
|
+
closable: true,
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* `!` força a prioridade sobre as classes padrão do tema `UModal`
|
|
122
|
+
* (ex.: `shadow-lg`, `rounded-lg`), que entram na cascata depois das nossas
|
|
123
|
+
* — sem o important, o valor do Figma perde para o default do Nuxt UI.
|
|
124
|
+
*/
|
|
125
|
+
const widths: Record<AdsModalSize, string> = {
|
|
126
|
+
md: "!w-[min(532px,calc(100%-2rem))]",
|
|
127
|
+
lg: "!w-[min(42rem,calc(100%-2rem))]",
|
|
128
|
+
xl: "!w-[min(48rem,calc(100%-2rem))]",
|
|
129
|
+
"2xl": "!w-[min(56rem,calc(100%-2rem))]",
|
|
130
|
+
"3xl": "!w-[min(64rem,calc(100%-2rem))]",
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const contentClass = computed(() => {
|
|
134
|
+
const scroll = props.scrollable ? " !max-h-[calc(100dvh-2rem)] !overflow-y-auto" : "";
|
|
135
|
+
if (props.variant === "sheet") {
|
|
136
|
+
return `!inset-x-0 !bottom-0 !top-auto !left-0 !translate-x-0 !translate-y-0 !w-full !max-w-none !max-h-[calc(100dvh-2rem)] !divide-y-0 !overflow-y-auto !bg-[var(--ads-modal-background)] !p-[var(--ads-modal-padding)] !shadow-[var(--ads-shadow-level-2)] !ring-1 !ring-[var(--ads-modal-border)] !rounded-t-[var(--ads-modal-radius)] !rounded-b-none${scroll}`;
|
|
137
|
+
}
|
|
138
|
+
return `${widths[props.size]} !max-w-none !divide-y-0 !rounded-[var(--ads-modal-radius)] !border !border-[var(--ads-modal-border)] !bg-[var(--ads-modal-background)] !p-[var(--ads-modal-padding)] !shadow-[var(--ads-shadow-level-3)] !ring-0${scroll}`;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const emit = defineEmits<{
|
|
142
|
+
confirm: [];
|
|
143
|
+
cancel: [];
|
|
144
|
+
dismiss: [reason: AdsModalDismissReason];
|
|
145
|
+
}>();
|
|
146
|
+
|
|
147
|
+
const open = defineModel<boolean>("open", { default: false });
|
|
148
|
+
const slots = useSlots();
|
|
149
|
+
const showDefaultActions = computed(() => !slots.footer && !slots.default);
|
|
150
|
+
|
|
151
|
+
const dismiss = (close: () => void) => {
|
|
152
|
+
if (!props.closable) return;
|
|
153
|
+
emit("cancel");
|
|
154
|
+
close();
|
|
155
|
+
open.value = false;
|
|
156
|
+
};
|
|
157
|
+
</script>
|
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div
|
|
3
|
-
class="flex h-[284px] w-[290px] flex-col gap-3 rounded-[var(--ads-radius-sm)] bg-[var(--ads-surface-card)] p-4 shadow-[var(--ads-shadow-level-1)]"
|
|
4
|
-
role="dialog"
|
|
5
|
-
aria-label="Selecionar mês"
|
|
6
|
-
>
|
|
7
|
-
<div class="flex items-center justify-between">
|
|
8
|
-
<button
|
|
9
|
-
type="button"
|
|
10
|
-
class="inline-flex size-6 items-center justify-center text-[var(--ads-text-primary)]"
|
|
11
|
-
aria-label="Ano anterior"
|
|
12
|
-
@click="year -= 1"
|
|
13
|
-
>
|
|
14
|
-
<UIcon name="i-lucide-chevron-left" class="size-6" />
|
|
15
|
-
</button>
|
|
16
|
-
<p class="text-sm font-bold leading-[1.3] text-[var(--ads-text-primary)]">{{ year }}</p>
|
|
17
|
-
<button
|
|
18
|
-
type="button"
|
|
19
|
-
class="inline-flex size-6 items-center justify-center text-[var(--ads-text-primary)]"
|
|
20
|
-
aria-label="Próximo ano"
|
|
21
|
-
@click="year += 1"
|
|
22
|
-
>
|
|
23
|
-
<UIcon name="i-lucide-chevron-right" class="size-6" />
|
|
24
|
-
</button>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
|
-
<div class="grid min-h-0 flex-1 grid-cols-3 grid-rows-4 gap-1">
|
|
28
|
-
<button
|
|
29
|
-
v-for="(month, index) in ADS_FILTER_MONTHS"
|
|
30
|
-
:key="month.short"
|
|
31
|
-
type="button"
|
|
32
|
-
class="flex items-center justify-center rounded-[var(--ads-radius-xsm)] text-sm leading-[1.3] transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--ads-date-focus-border)] disabled:cursor-not-allowed"
|
|
33
|
-
:class="
|
|
34
|
-
index === selectedMonthIndex
|
|
35
|
-
? 'bg-[var(--ads-surface-brand)] font-bold text-[var(--ads-text-contrast)]'
|
|
36
|
-
: 'text-[var(--ads-text-primary)] enabled:hover:bg-[var(--ads-month-hover-bg)]'
|
|
37
|
-
"
|
|
38
|
-
:aria-label="month.full"
|
|
39
|
-
:aria-pressed="index === selectedMonthIndex"
|
|
40
|
-
@click="onSelect(index)"
|
|
41
|
-
>
|
|
42
|
-
{{ month.short }}
|
|
43
|
-
</button>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
</template>
|
|
47
|
-
|
|
48
|
-
<script setup lang="ts">
|
|
49
|
-
import { computed, ref, watch } from "vue";
|
|
50
|
-
import {
|
|
51
|
-
ADS_FILTER_MONTHS,
|
|
52
|
-
formatMonthValue,
|
|
53
|
-
parseMonthValue,
|
|
54
|
-
} from "../utils/ads-filter-select";
|
|
55
|
-
|
|
56
|
-
const model = defineModel<string>({ default: "" });
|
|
57
|
-
const emit = defineEmits<{ pick: [] }>();
|
|
58
|
-
|
|
59
|
-
const initial = parseMonthValue(model.value);
|
|
60
|
-
const year = ref(initial?.year ?? new Date().getFullYear());
|
|
61
|
-
|
|
62
|
-
watch(
|
|
63
|
-
() => model.value,
|
|
64
|
-
(value) => {
|
|
65
|
-
const parsed = parseMonthValue(value);
|
|
66
|
-
if (parsed) year.value = parsed.year;
|
|
67
|
-
},
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const selectedMonthIndex = computed(() => {
|
|
71
|
-
const parsed = parseMonthValue(model.value);
|
|
72
|
-
if (!parsed || parsed.year !== year.value) return -1;
|
|
73
|
-
return parsed.monthIndex;
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const onSelect = (monthIndex: number) => {
|
|
77
|
-
model.value = formatMonthValue(year.value, monthIndex);
|
|
78
|
-
emit("pick");
|
|
79
|
-
};
|
|
80
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="flex h-[284px] w-[290px] flex-col gap-3 rounded-[var(--ads-radius-sm)] bg-[var(--ads-surface-card)] p-4 shadow-[var(--ads-shadow-level-1)]"
|
|
4
|
+
role="dialog"
|
|
5
|
+
aria-label="Selecionar mês"
|
|
6
|
+
>
|
|
7
|
+
<div class="flex items-center justify-between">
|
|
8
|
+
<button
|
|
9
|
+
type="button"
|
|
10
|
+
class="inline-flex size-6 items-center justify-center text-[var(--ads-text-primary)]"
|
|
11
|
+
aria-label="Ano anterior"
|
|
12
|
+
@click="year -= 1"
|
|
13
|
+
>
|
|
14
|
+
<UIcon name="i-lucide-chevron-left" class="size-6" />
|
|
15
|
+
</button>
|
|
16
|
+
<p class="text-sm font-bold leading-[1.3] text-[var(--ads-text-primary)]">{{ year }}</p>
|
|
17
|
+
<button
|
|
18
|
+
type="button"
|
|
19
|
+
class="inline-flex size-6 items-center justify-center text-[var(--ads-text-primary)]"
|
|
20
|
+
aria-label="Próximo ano"
|
|
21
|
+
@click="year += 1"
|
|
22
|
+
>
|
|
23
|
+
<UIcon name="i-lucide-chevron-right" class="size-6" />
|
|
24
|
+
</button>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="grid min-h-0 flex-1 grid-cols-3 grid-rows-4 gap-1">
|
|
28
|
+
<button
|
|
29
|
+
v-for="(month, index) in ADS_FILTER_MONTHS"
|
|
30
|
+
:key="month.short"
|
|
31
|
+
type="button"
|
|
32
|
+
class="flex items-center justify-center rounded-[var(--ads-radius-xsm)] text-sm leading-[1.3] transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--ads-date-focus-border)] disabled:cursor-not-allowed"
|
|
33
|
+
:class="
|
|
34
|
+
index === selectedMonthIndex
|
|
35
|
+
? 'bg-[var(--ads-surface-brand)] font-bold text-[var(--ads-text-contrast)]'
|
|
36
|
+
: 'text-[var(--ads-text-primary)] enabled:hover:bg-[var(--ads-month-hover-bg)]'
|
|
37
|
+
"
|
|
38
|
+
:aria-label="month.full"
|
|
39
|
+
:aria-pressed="index === selectedMonthIndex"
|
|
40
|
+
@click="onSelect(index)"
|
|
41
|
+
>
|
|
42
|
+
{{ month.short }}
|
|
43
|
+
</button>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script setup lang="ts">
|
|
49
|
+
import { computed, ref, watch } from "vue";
|
|
50
|
+
import {
|
|
51
|
+
ADS_FILTER_MONTHS,
|
|
52
|
+
formatMonthValue,
|
|
53
|
+
parseMonthValue,
|
|
54
|
+
} from "../utils/ads-filter-select";
|
|
55
|
+
|
|
56
|
+
const model = defineModel<string>({ default: "" });
|
|
57
|
+
const emit = defineEmits<{ pick: [] }>();
|
|
58
|
+
|
|
59
|
+
const initial = parseMonthValue(model.value);
|
|
60
|
+
const year = ref(initial?.year ?? new Date().getFullYear());
|
|
61
|
+
|
|
62
|
+
watch(
|
|
63
|
+
() => model.value,
|
|
64
|
+
(value) => {
|
|
65
|
+
const parsed = parseMonthValue(value);
|
|
66
|
+
if (parsed) year.value = parsed.year;
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const selectedMonthIndex = computed(() => {
|
|
71
|
+
const parsed = parseMonthValue(model.value);
|
|
72
|
+
if (!parsed || parsed.year !== year.value) return -1;
|
|
73
|
+
return parsed.monthIndex;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const onSelect = (monthIndex: number) => {
|
|
77
|
+
model.value = formatMonthValue(year.value, monthIndex);
|
|
78
|
+
emit("pick");
|
|
79
|
+
};
|
|
80
|
+
</script>
|
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<nav
|
|
3
|
-
class="flex h-[var(--ads-navbar-app-height)] w-full items-center justify-center gap-[var(--ads-navbar-app-gap)] border-t border-ads-neutro-claro-20 bg-[var(--ads-surface-card)] px-4"
|
|
4
|
-
aria-label="Navegação do aplicativo"
|
|
5
|
-
>
|
|
6
|
-
<component
|
|
7
|
-
:is="item.to ? NuxtLink : 'button'"
|
|
8
|
-
v-for="item in items"
|
|
9
|
-
:key="item.id"
|
|
10
|
-
v-bind="item.to ? { to: item.to } : { type: 'button' }"
|
|
11
|
-
class="flex w-[var(--ads-navbar-app-item-width)] flex-col items-center gap-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
|
|
12
|
-
:aria-current="item.id === activeId ? 'page' : undefined"
|
|
13
|
-
@click="emit('update:activeId', item.id)"
|
|
14
|
-
>
|
|
15
|
-
<span
|
|
16
|
-
class="flex size-[var(--ads-navbar-app-well)] items-center justify-center rounded-[var(--ads-radius-xsm)]"
|
|
17
|
-
:class="item.id === activeId ? 'bg-[var(--ads-navbar-app-active)] text-[var(--ads-text-contrast)]' : 'text-[var(--ads-text-secondary)]'"
|
|
18
|
-
>
|
|
19
|
-
<UIcon :name="item.icon" class="size-[18px]" />
|
|
20
|
-
</span>
|
|
21
|
-
<span
|
|
22
|
-
class="text-[length:var(--ads-navbar-app-font-size)] font-bold leading-4"
|
|
23
|
-
:class="item.id === activeId ? 'text-[var(--ads-navbar-app-active)]' : 'text-[var(--ads-text-secondary)]'"
|
|
24
|
-
>{{ item.label }}</span>
|
|
25
|
-
</component>
|
|
26
|
-
</nav>
|
|
27
|
-
</template>
|
|
28
|
-
|
|
29
|
-
<script lang="ts">
|
|
30
|
-
import type { RouteLocationRaw } from "vue-router";
|
|
31
|
-
|
|
32
|
-
export type AdsNavbarAppItem = {
|
|
33
|
-
id: string;
|
|
34
|
-
label: string;
|
|
35
|
-
icon: string;
|
|
36
|
-
to?: RouteLocationRaw;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export const adsNavbarAppItems: AdsNavbarAppItem[] = [
|
|
40
|
-
{ id: "home", label: "Home", icon: "i-lucide-home" },
|
|
41
|
-
{ id: "agenda", label: "Agenda", icon: "i-lucide-calendar-days" },
|
|
42
|
-
{ id: "apps", label: "Apps", icon: "i-lucide-layout-grid" },
|
|
43
|
-
{ id: "tickets", label: "Tickets", icon: "i-lucide-messages-square" },
|
|
44
|
-
{ id: "perfil", label: "Perfil", icon: "i-lucide-circle-user" },
|
|
45
|
-
];
|
|
46
|
-
</script>
|
|
47
|
-
|
|
48
|
-
<script setup lang="ts">
|
|
49
|
-
import { NuxtLink } from "#components";
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
withDefaults(
|
|
53
|
-
defineProps<{
|
|
54
|
-
items?: AdsNavbarAppItem[];
|
|
55
|
-
activeId?: string;
|
|
56
|
-
}>(),
|
|
57
|
-
{
|
|
58
|
-
items: () => adsNavbarAppItems,
|
|
59
|
-
activeId: "home",
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const emit = defineEmits<{
|
|
64
|
-
"update:activeId": [id: string];
|
|
65
|
-
}>();
|
|
66
|
-
|
|
67
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<nav
|
|
3
|
+
class="flex h-[var(--ads-navbar-app-height)] w-full items-center justify-center gap-[var(--ads-navbar-app-gap)] border-t border-ads-neutro-claro-20 bg-[var(--ads-surface-card)] px-4"
|
|
4
|
+
aria-label="Navegação do aplicativo"
|
|
5
|
+
>
|
|
6
|
+
<component
|
|
7
|
+
:is="item.to ? NuxtLink : 'button'"
|
|
8
|
+
v-for="item in items"
|
|
9
|
+
:key="item.id"
|
|
10
|
+
v-bind="item.to ? { to: item.to } : { type: 'button' }"
|
|
11
|
+
class="flex w-[var(--ads-navbar-app-item-width)] flex-col items-center gap-1 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
|
|
12
|
+
:aria-current="item.id === activeId ? 'page' : undefined"
|
|
13
|
+
@click="emit('update:activeId', item.id)"
|
|
14
|
+
>
|
|
15
|
+
<span
|
|
16
|
+
class="flex size-[var(--ads-navbar-app-well)] items-center justify-center rounded-[var(--ads-radius-xsm)]"
|
|
17
|
+
:class="item.id === activeId ? 'bg-[var(--ads-navbar-app-active)] text-[var(--ads-text-contrast)]' : 'text-[var(--ads-text-secondary)]'"
|
|
18
|
+
>
|
|
19
|
+
<UIcon :name="item.icon" class="size-[18px]" />
|
|
20
|
+
</span>
|
|
21
|
+
<span
|
|
22
|
+
class="text-[length:var(--ads-navbar-app-font-size)] font-bold leading-4"
|
|
23
|
+
:class="item.id === activeId ? 'text-[var(--ads-navbar-app-active)]' : 'text-[var(--ads-text-secondary)]'"
|
|
24
|
+
>{{ item.label }}</span>
|
|
25
|
+
</component>
|
|
26
|
+
</nav>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<script lang="ts">
|
|
30
|
+
import type { RouteLocationRaw } from "vue-router";
|
|
31
|
+
|
|
32
|
+
export type AdsNavbarAppItem = {
|
|
33
|
+
id: string;
|
|
34
|
+
label: string;
|
|
35
|
+
icon: string;
|
|
36
|
+
to?: RouteLocationRaw;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const adsNavbarAppItems: AdsNavbarAppItem[] = [
|
|
40
|
+
{ id: "home", label: "Home", icon: "i-lucide-home" },
|
|
41
|
+
{ id: "agenda", label: "Agenda", icon: "i-lucide-calendar-days" },
|
|
42
|
+
{ id: "apps", label: "Apps", icon: "i-lucide-layout-grid" },
|
|
43
|
+
{ id: "tickets", label: "Tickets", icon: "i-lucide-messages-square" },
|
|
44
|
+
{ id: "perfil", label: "Perfil", icon: "i-lucide-circle-user" },
|
|
45
|
+
];
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<script setup lang="ts">
|
|
49
|
+
import { NuxtLink } from "#components";
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
withDefaults(
|
|
53
|
+
defineProps<{
|
|
54
|
+
items?: AdsNavbarAppItem[];
|
|
55
|
+
activeId?: string;
|
|
56
|
+
}>(),
|
|
57
|
+
{
|
|
58
|
+
items: () => adsNavbarAppItems,
|
|
59
|
+
activeId: "home",
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const emit = defineEmits<{
|
|
64
|
+
"update:activeId": [id: string];
|
|
65
|
+
}>();
|
|
66
|
+
|
|
67
|
+
</script>
|