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,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>