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,174 +1,174 @@
1
- <template>
2
- <div
3
- class="overflow-hidden rounded-[var(--ads-radius-sm)] border border-ads-neutro-claro-50 bg-[var(--ads-surface-card)] shadow-[var(--ads-shadow-level-1)]"
4
- :class="widthClass"
5
- >
6
- <div v-if="searchable" class="px-4 pt-4 pb-2">
7
- <UInput
8
- v-model="query"
9
- :placeholder="searchPlaceholder"
10
- aria-label="Pesquisar opções"
11
- icon="i-lucide-search"
12
- color="neutral"
13
- variant="outline"
14
- size="md"
15
- class="w-full"
16
- :ui="searchFieldUi"
17
- >
18
- <template v-if="query" #trailing>
19
- <button
20
- type="button"
21
- class="text-[var(--ads-text-secondary)]"
22
- aria-label="Limpar busca"
23
- @click="query = ''"
24
- >
25
- <UIcon name="i-lucide-x" class="size-4" />
26
- </button>
27
- </template>
28
- </UInput>
29
- </div>
30
-
31
- <p
32
- v-if="groups.empty"
33
- class="flex h-16 items-center ps-6 text-sm leading-[1.3] text-[var(--ads-text-secondary)]"
34
- >
35
- {{ emptyLabel }}
36
- </p>
37
-
38
- <template v-else>
39
- <div v-if="groups.exact.length">
40
- <template v-for="(option, index) in groups.exact" :key="option.value">
41
- <AdsFilterOption
42
- :mode="type"
43
- :label="option.label"
44
- :selected="isSelected(option.value)"
45
- @select="onSelect(option.value)"
46
- />
47
- <div
48
- v-if="index < groups.exact.length - 1"
49
- class="h-px bg-[var(--ads-button-secondary-disabled)]"
50
- :class="dividerInsetClass"
51
- role="presentation"
52
- />
53
- </template>
54
- </div>
55
-
56
- <div
57
- v-if="showSelectAll || groups.matches.length"
58
- class="overflow-y-auto overscroll-contain [scrollbar-color:var(--color-ads-neutro-claro-50)_transparent] [scrollbar-width:thin]"
59
- :class="
60
- groups.exact.length
61
- ? 'max-h-[244px] border-t-[0.5px] border-[var(--ads-border-icon)]'
62
- : 'max-h-[294px]'
63
- "
64
- >
65
- <div :role="type === 'radio' ? 'listbox' : 'group'" :aria-label="listLabel">
66
- <template v-if="showSelectAll">
67
- <AdsFilterOption
68
- mode="checkbox"
69
- :label="selectAllLabel"
70
- :selected="checkState === 'all'"
71
- :indeterminate="checkState === 'partial'"
72
- @select="onSelectAll"
73
- />
74
- <div
75
- v-if="groups.matches.length"
76
- class="h-px bg-[var(--ads-button-secondary-disabled)]"
77
- :class="dividerInsetClass"
78
- role="presentation"
79
- />
80
- </template>
81
- <template v-for="(option, index) in groups.matches" :key="option.value">
82
- <AdsFilterOption
83
- :mode="type"
84
- :label="option.label"
85
- :selected="isSelected(option.value)"
86
- @select="onSelect(option.value)"
87
- />
88
- <div
89
- v-if="index < groups.matches.length - 1"
90
- class="h-px bg-[var(--ads-button-secondary-disabled)]"
91
- :class="dividerInsetClass"
92
- role="presentation"
93
- />
94
- </template>
95
- </div>
96
- </div>
97
- </template>
98
- </div>
99
- </template>
100
-
101
- <script setup lang="ts">
102
- import { computed } from "vue";
103
- import AdsFilterOption from "./AdsFilterOption.vue";
104
- import {
105
- groupFilterOptions,
106
- selectionCheckState,
107
- toggleFilterValue,
108
- toggleSelectAll,
109
- type AdsFilterOption as FilterOption,
110
- } from "../utils/ads-filter-select";
111
-
112
- const props = withDefaults(
113
- defineProps<{
114
- type: "checkbox" | "radio";
115
- options: FilterOption[];
116
- searchable?: boolean;
117
- selectAll?: boolean;
118
- selectAllLabel?: string;
119
- emptyLabel?: string;
120
- searchPlaceholder?: string;
121
- listLabel?: string;
122
- widthClass?: string;
123
- }>(),
124
- {
125
- searchable: true,
126
- selectAll: true,
127
- selectAllLabel: "Selecionar todos",
128
- emptyLabel: "Nenhum resultado",
129
- searchPlaceholder: "Pesquisar",
130
- listLabel: "Opções",
131
- widthClass: "w-[270px]",
132
- },
133
- );
134
-
135
- const model = defineModel<string | string[]>({ required: true });
136
- const query = defineModel<string>("query", { default: "" });
137
- const emit = defineEmits<{ pick: [] }>();
138
-
139
- const groups = computed(() => groupFilterOptions(props.options, query.value));
140
- const selectedList = computed(() =>
141
- Array.isArray(model.value) ? model.value : model.value ? [model.value] : [],
142
- );
143
- const checkState = computed(() =>
144
- selectionCheckState(selectedList.value, props.options),
145
- );
146
- const showSelectAll = computed(
147
- () => props.type === "checkbox" && props.selectAll && query.value.trim() === "",
148
- );
149
- /** O separador começa no mesmo eixo do conteúdo da opção: texto (rádio) ou caixa (checkbox). */
150
- const dividerInsetClass = computed(() => (props.type === "checkbox" ? "mx-4" : "mx-6"));
151
-
152
- const isSelected = (value: string) => selectedList.value.includes(value);
153
-
154
- const onSelect = (value: string) => {
155
- if (props.type === "radio") {
156
- model.value = value;
157
- emit("pick");
158
- return;
159
- }
160
- model.value = toggleFilterValue(selectedList.value, value);
161
- };
162
-
163
- const onSelectAll = () => {
164
- model.value = toggleSelectAll(selectedList.value, props.options);
165
- };
166
-
167
- /**
168
- * Busca do painel ADS: pílula.
169
- */
170
- const searchFieldUi = {
171
- base: "h-10 rounded-[var(--ads-radius-pill)] bg-[var(--ads-surface-card)] text-sm text-[var(--ads-text-secondary)] !outline-none ring-1 ring-inset ring-ads-neutro-claro-50 enabled:hover:ring-[var(--ads-filter-hover-border)] placeholder:text-[var(--ads-text-tertiary)] focus-visible:!outline-none focus-visible:ring-(length:--ads-border-width-slim) focus-visible:ring-[var(--ads-text-brand)]",
172
- leadingIcon: "text-[var(--ads-text-tertiary)]",
173
- };
174
- </script>
1
+ <template>
2
+ <div
3
+ class="overflow-hidden rounded-[var(--ads-radius-sm)] border border-ads-neutro-claro-50 bg-[var(--ads-surface-card)] shadow-[var(--ads-shadow-level-1)]"
4
+ :class="widthClass"
5
+ >
6
+ <div v-if="searchable" class="px-4 pt-4 pb-2">
7
+ <UInput
8
+ v-model="query"
9
+ :placeholder="searchPlaceholder"
10
+ aria-label="Pesquisar opções"
11
+ icon="i-lucide-search"
12
+ color="neutral"
13
+ variant="outline"
14
+ size="md"
15
+ class="w-full"
16
+ :ui="searchFieldUi"
17
+ >
18
+ <template v-if="query" #trailing>
19
+ <button
20
+ type="button"
21
+ class="text-[var(--ads-text-secondary)]"
22
+ aria-label="Limpar busca"
23
+ @click="query = ''"
24
+ >
25
+ <UIcon name="i-lucide-x" class="size-4" />
26
+ </button>
27
+ </template>
28
+ </UInput>
29
+ </div>
30
+
31
+ <p
32
+ v-if="groups.empty"
33
+ class="flex h-16 items-center ps-6 text-sm leading-[1.3] text-[var(--ads-text-secondary)]"
34
+ >
35
+ {{ emptyLabel }}
36
+ </p>
37
+
38
+ <template v-else>
39
+ <div v-if="groups.exact.length">
40
+ <template v-for="(option, index) in groups.exact" :key="option.value">
41
+ <AdsFilterOption
42
+ :mode="type"
43
+ :label="option.label"
44
+ :selected="isSelected(option.value)"
45
+ @select="onSelect(option.value)"
46
+ />
47
+ <div
48
+ v-if="index < groups.exact.length - 1"
49
+ class="h-px bg-[var(--ads-button-secondary-disabled)]"
50
+ :class="dividerInsetClass"
51
+ role="presentation"
52
+ />
53
+ </template>
54
+ </div>
55
+
56
+ <div
57
+ v-if="showSelectAll || groups.matches.length"
58
+ class="overflow-y-auto overscroll-contain [scrollbar-color:var(--color-ads-neutro-claro-50)_transparent] [scrollbar-width:thin]"
59
+ :class="
60
+ groups.exact.length
61
+ ? 'max-h-[244px] border-t-[0.5px] border-[var(--ads-border-icon)]'
62
+ : 'max-h-[294px]'
63
+ "
64
+ >
65
+ <div :role="type === 'radio' ? 'listbox' : 'group'" :aria-label="listLabel">
66
+ <template v-if="showSelectAll">
67
+ <AdsFilterOption
68
+ mode="checkbox"
69
+ :label="selectAllLabel"
70
+ :selected="checkState === 'all'"
71
+ :indeterminate="checkState === 'partial'"
72
+ @select="onSelectAll"
73
+ />
74
+ <div
75
+ v-if="groups.matches.length"
76
+ class="h-px bg-[var(--ads-button-secondary-disabled)]"
77
+ :class="dividerInsetClass"
78
+ role="presentation"
79
+ />
80
+ </template>
81
+ <template v-for="(option, index) in groups.matches" :key="option.value">
82
+ <AdsFilterOption
83
+ :mode="type"
84
+ :label="option.label"
85
+ :selected="isSelected(option.value)"
86
+ @select="onSelect(option.value)"
87
+ />
88
+ <div
89
+ v-if="index < groups.matches.length - 1"
90
+ class="h-px bg-[var(--ads-button-secondary-disabled)]"
91
+ :class="dividerInsetClass"
92
+ role="presentation"
93
+ />
94
+ </template>
95
+ </div>
96
+ </div>
97
+ </template>
98
+ </div>
99
+ </template>
100
+
101
+ <script setup lang="ts">
102
+ import { computed } from "vue";
103
+ import AdsFilterOption from "./AdsFilterOption.vue";
104
+ import {
105
+ groupFilterOptions,
106
+ selectionCheckState,
107
+ toggleFilterValue,
108
+ toggleSelectAll,
109
+ type AdsFilterOption as FilterOption,
110
+ } from "../utils/ads-filter-select";
111
+
112
+ const props = withDefaults(
113
+ defineProps<{
114
+ type: "checkbox" | "radio";
115
+ options: FilterOption[];
116
+ searchable?: boolean;
117
+ selectAll?: boolean;
118
+ selectAllLabel?: string;
119
+ emptyLabel?: string;
120
+ searchPlaceholder?: string;
121
+ listLabel?: string;
122
+ widthClass?: string;
123
+ }>(),
124
+ {
125
+ searchable: true,
126
+ selectAll: true,
127
+ selectAllLabel: "Selecionar todos",
128
+ emptyLabel: "Nenhum resultado",
129
+ searchPlaceholder: "Pesquisar",
130
+ listLabel: "Opções",
131
+ widthClass: "w-[270px]",
132
+ },
133
+ );
134
+
135
+ const model = defineModel<string | string[]>({ required: true });
136
+ const query = defineModel<string>("query", { default: "" });
137
+ const emit = defineEmits<{ pick: [] }>();
138
+
139
+ const groups = computed(() => groupFilterOptions(props.options, query.value));
140
+ const selectedList = computed(() =>
141
+ Array.isArray(model.value) ? model.value : model.value ? [model.value] : [],
142
+ );
143
+ const checkState = computed(() =>
144
+ selectionCheckState(selectedList.value, props.options),
145
+ );
146
+ const showSelectAll = computed(
147
+ () => props.type === "checkbox" && props.selectAll && query.value.trim() === "",
148
+ );
149
+ /** O separador começa no mesmo eixo do conteúdo da opção: texto (rádio) ou caixa (checkbox). */
150
+ const dividerInsetClass = computed(() => (props.type === "checkbox" ? "mx-4" : "mx-6"));
151
+
152
+ const isSelected = (value: string) => selectedList.value.includes(value);
153
+
154
+ const onSelect = (value: string) => {
155
+ if (props.type === "radio") {
156
+ model.value = value;
157
+ emit("pick");
158
+ return;
159
+ }
160
+ model.value = toggleFilterValue(selectedList.value, value);
161
+ };
162
+
163
+ const onSelectAll = () => {
164
+ model.value = toggleSelectAll(selectedList.value, props.options);
165
+ };
166
+
167
+ /**
168
+ * Busca do painel ADS: pílula.
169
+ */
170
+ const searchFieldUi = {
171
+ base: "h-10 rounded-[var(--ads-radius-pill)] bg-[var(--ads-surface-card)] text-sm text-[var(--ads-text-secondary)] !outline-none ring-1 ring-inset ring-ads-neutro-claro-50 enabled:hover:ring-[var(--ads-filter-hover-border)] placeholder:text-[var(--ads-text-tertiary)] focus-visible:!outline-none focus-visible:ring-(length:--ads-border-width-slim) focus-visible:ring-[var(--ads-text-brand)]",
172
+ leadingIcon: "text-[var(--ads-text-tertiary)]",
173
+ };
174
+ </script>
@@ -1,72 +1,72 @@
1
- <template>
2
- <button
3
- type="button"
4
- class="flex h-[50px] w-full items-center gap-2 py-4 text-left hover:bg-neutral-100"
5
- :class="mode === 'checkbox' ? 'ps-4' : 'ps-6'"
6
- :role="mode === 'checkbox' ? 'checkbox' : 'option'"
7
- :aria-checked="checkedState === undefined ? undefined : String(checkedState)"
8
- :aria-selected="mode === 'radio' ? String(selected) : undefined"
9
- @click="emit('select')"
10
- >
11
- <span
12
- v-if="mode === 'checkbox'"
13
- class="flex size-4 shrink-0 items-center justify-center rounded-[var(--ads-radius-xsm)] border-2 border-[var(--ads-text-brand)]"
14
- :class="selected || indeterminate ? 'bg-[var(--ads-surface-brand)]' : 'bg-[var(--ads-surface-card)]'"
15
- >
16
- <UIcon
17
- v-if="indeterminate"
18
- name="i-lucide-minus"
19
- class="size-3 text-[var(--ads-text-contrast)]"
20
- />
21
- <UIcon
22
- v-else-if="selected"
23
- name="i-lucide-check"
24
- class="size-3 text-[var(--ads-text-contrast)]"
25
- />
26
- </span>
27
- <InvertedTooltip
28
- :text="label"
29
- :disabled="!needsTooltip"
30
- :content="{ side: 'bottom', align: 'start', sideOffset: 4 }"
31
- >
32
- <span
33
- class="block min-w-0 truncate text-sm leading-[1.3] text-[var(--ads-text-secondary)]"
34
- :class="mode === 'radio' && selected ? 'font-bold' : 'font-normal'"
35
- >
36
- {{ displayLabel }}
37
- </span>
38
- </InvertedTooltip>
39
- </button>
40
- </template>
41
-
42
- <script setup lang="ts">
43
- import { computed } from "vue";
44
- import InvertedTooltip from "./InvertedTooltip.vue";
45
- import {
46
- filterLabelNeedsTooltip,
47
- truncateFilterLabel,
48
- } from "../utils/ads-filter-select";
49
-
50
- const props = withDefaults(
51
- defineProps<{
52
- label: string;
53
- mode: "checkbox" | "radio";
54
- selected?: boolean;
55
- indeterminate?: boolean;
56
- }>(),
57
- {
58
- selected: false,
59
- indeterminate: false,
60
- },
61
- );
62
-
63
- const emit = defineEmits<{ select: [] }>();
64
-
65
- const displayLabel = computed(() => truncateFilterLabel(props.label));
66
- const needsTooltip = computed(() => filterLabelNeedsTooltip(props.label));
67
- const checkedState = computed(() => {
68
- if (props.mode !== "checkbox") return undefined;
69
- if (props.indeterminate) return "mixed";
70
- return props.selected;
71
- });
72
- </script>
1
+ <template>
2
+ <button
3
+ type="button"
4
+ class="flex h-[50px] w-full items-center gap-2 py-4 text-left hover:bg-neutral-100"
5
+ :class="mode === 'checkbox' ? 'ps-4' : 'ps-6'"
6
+ :role="mode === 'checkbox' ? 'checkbox' : 'option'"
7
+ :aria-checked="checkedState === undefined ? undefined : String(checkedState)"
8
+ :aria-selected="mode === 'radio' ? String(selected) : undefined"
9
+ @click="emit('select')"
10
+ >
11
+ <span
12
+ v-if="mode === 'checkbox'"
13
+ class="flex size-4 shrink-0 items-center justify-center rounded-[var(--ads-radius-xsm)] border-2 border-[var(--ads-text-brand)]"
14
+ :class="selected || indeterminate ? 'bg-[var(--ads-surface-brand)]' : 'bg-[var(--ads-surface-card)]'"
15
+ >
16
+ <UIcon
17
+ v-if="indeterminate"
18
+ name="i-lucide-minus"
19
+ class="size-3 text-[var(--ads-text-contrast)]"
20
+ />
21
+ <UIcon
22
+ v-else-if="selected"
23
+ name="i-lucide-check"
24
+ class="size-3 text-[var(--ads-text-contrast)]"
25
+ />
26
+ </span>
27
+ <InvertedTooltip
28
+ :text="label"
29
+ :disabled="!needsTooltip"
30
+ :content="{ side: 'bottom', align: 'start', sideOffset: 4 }"
31
+ >
32
+ <span
33
+ class="block min-w-0 truncate text-sm leading-[1.3] text-[var(--ads-text-secondary)]"
34
+ :class="mode === 'radio' && selected ? 'font-bold' : 'font-normal'"
35
+ >
36
+ {{ displayLabel }}
37
+ </span>
38
+ </InvertedTooltip>
39
+ </button>
40
+ </template>
41
+
42
+ <script setup lang="ts">
43
+ import { computed } from "vue";
44
+ import InvertedTooltip from "./InvertedTooltip.vue";
45
+ import {
46
+ filterLabelNeedsTooltip,
47
+ truncateFilterLabel,
48
+ } from "../utils/ads-filter-select";
49
+
50
+ const props = withDefaults(
51
+ defineProps<{
52
+ label: string;
53
+ mode: "checkbox" | "radio";
54
+ selected?: boolean;
55
+ indeterminate?: boolean;
56
+ }>(),
57
+ {
58
+ selected: false,
59
+ indeterminate: false,
60
+ },
61
+ );
62
+
63
+ const emit = defineEmits<{ select: [] }>();
64
+
65
+ const displayLabel = computed(() => truncateFilterLabel(props.label));
66
+ const needsTooltip = computed(() => filterLabelNeedsTooltip(props.label));
67
+ const checkedState = computed(() => {
68
+ if (props.mode !== "checkbox") return undefined;
69
+ if (props.indeterminate) return "mixed";
70
+ return props.selected;
71
+ });
72
+ </script>