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,113 +1,113 @@
1
- <template>
2
- <div class="flex w-full min-w-0 flex-col gap-0.5">
3
- <div
4
- class="flex items-center gap-2 rounded-[var(--ads-search-radius)] transition-colors px-[var(--ads-search-padding-x)]"
5
- :class="[fieldClass, size === 'sm' ? 'h-[var(--ads-search-sm-height)]' : 'h-[var(--ads-search-md-height)]']"
6
- :data-state="visualState"
7
- >
8
- <UIcon :name="icon" :class="iconClass" />
9
- <input
10
- v-model="model"
11
- type="search"
12
- autocomplete="off"
13
- class="min-w-0 flex-1 bg-transparent leading-[1.3] outline-none placeholder:text-current disabled:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden"
14
- :class="size === 'sm' ? 'text-[length:var(--ads-search-sm-font-size)]' : 'text-[length:var(--ads-search-md-font-size)]'"
15
- :placeholder="placeholder"
16
- :disabled="isDisabled"
17
- :aria-label="ariaLabel || placeholder"
18
- :aria-describedby="hint ? hintId : undefined"
19
- >
20
- <button
21
- v-if="showClear"
22
- type="button"
23
- class="inline-flex shrink-0"
24
- aria-label="Limpar busca"
25
- @click="model = ''"
26
- >
27
- <UIcon name="i-lucide-x" :class="iconClass" />
28
- </button>
29
- </div>
30
- <p
31
- v-if="hint"
32
- :id="hintId"
33
- :class="hintClass"
34
- >
35
- {{ hint }}
36
- </p>
37
- </div>
38
- </template>
39
-
40
- <script setup lang="ts">
41
- import { computed, useId } from "vue";
42
-
43
- export type AdsSearchInputState = "default" | "active" | "focus" | "disabled";
44
- export type AdsSearchInputSize = "md" | "sm";
45
-
46
- const props = withDefaults(
47
- defineProps<{
48
- placeholder?: string;
49
- ariaLabel?: string;
50
- hint?: string;
51
- disabled?: boolean;
52
- icon?: string;
53
- size?: AdsSearchInputSize;
54
- /** Estado congelado para a documentação. `null` segue o campo. */
55
- state?: AdsSearchInputState | null;
56
- }>(),
57
- {
58
- placeholder: "Pesquisar",
59
- ariaLabel: "",
60
- hint: "",
61
- disabled: false,
62
- icon: "i-lucide-search",
63
- size: "md",
64
- state: null,
65
- },
66
- );
67
-
68
- const model = defineModel<string>({ default: "" });
69
-
70
- const hintId = useId();
71
-
72
- const isDisabled = computed(() => props.disabled || props.state === "disabled");
73
-
74
- const visualState = computed<AdsSearchInputState>(() => {
75
- if (isDisabled.value) return "disabled";
76
- if (props.state) return props.state;
77
- return model.value ? "active" : "default";
78
- });
79
-
80
- const showClear = computed(() => visualState.value === "active" || visualState.value === "focus");
81
-
82
- const iconClass = computed(() =>
83
- props.size === "sm"
84
- ? "size-4 shrink-0 text-base"
85
- : "size-[18px] shrink-0 text-[18px]",
86
- );
87
-
88
- const hintClass = computed(() => {
89
- const tone = visualState.value === "disabled"
90
- ? "text-[var(--ads-text-tertiary)]"
91
- : "text-[var(--ads-text-secondary)]";
92
- const type = props.size === "sm" ? "text-[10px] leading-[1.1]" : "text-xs leading-[1.2]";
93
- return `${type} ${tone}`;
94
- });
95
-
96
- const fieldClass = computed(() => {
97
- const tone = visualState.value === "default" || visualState.value === "disabled"
98
- ? "text-[var(--ads-search-placeholder)]"
99
- : "text-[var(--ads-search-text)]";
100
- const interactiveFocus = props.state === null && !isDisabled.value;
101
- const focus = interactiveFocus
102
- ? "hover:not-focus-within:border-[var(--ads-search-hover-border)] focus-within:border-(length:--ads-border-width-slim) focus-within:border-[var(--ads-search-focus-border)]"
103
- : "";
104
-
105
- if (visualState.value === "disabled") {
106
- return `border border-[var(--ads-search-disabled-border)] bg-[var(--ads-search-disabled-background)] ${tone}`;
107
- }
108
- if (visualState.value === "focus") {
109
- return `border-(length:--ads-border-width-slim) border-[var(--ads-search-focus-border)] bg-[var(--ads-search-background)] ${tone}`;
110
- }
111
- return `border border-[var(--ads-search-border)] bg-[var(--ads-search-background)] ${tone} ${focus}`;
112
- });
113
- </script>
1
+ <template>
2
+ <div class="flex w-full min-w-0 flex-col gap-0.5">
3
+ <div
4
+ class="flex items-center gap-2 rounded-[var(--ads-search-radius)] transition-colors px-[var(--ads-search-padding-x)]"
5
+ :class="[fieldClass, size === 'sm' ? 'h-[var(--ads-search-sm-height)]' : 'h-[var(--ads-search-md-height)]']"
6
+ :data-state="visualState"
7
+ >
8
+ <UIcon :name="icon" :class="iconClass" />
9
+ <input
10
+ v-model="model"
11
+ type="search"
12
+ autocomplete="off"
13
+ class="min-w-0 flex-1 bg-transparent leading-[1.3] outline-none placeholder:text-current disabled:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden"
14
+ :class="size === 'sm' ? 'text-[length:var(--ads-search-sm-font-size)]' : 'text-[length:var(--ads-search-md-font-size)]'"
15
+ :placeholder="placeholder"
16
+ :disabled="isDisabled"
17
+ :aria-label="ariaLabel || placeholder"
18
+ :aria-describedby="hint ? hintId : undefined"
19
+ >
20
+ <button
21
+ v-if="showClear"
22
+ type="button"
23
+ class="inline-flex shrink-0"
24
+ aria-label="Limpar busca"
25
+ @click="model = ''"
26
+ >
27
+ <UIcon name="i-lucide-x" :class="iconClass" />
28
+ </button>
29
+ </div>
30
+ <p
31
+ v-if="hint"
32
+ :id="hintId"
33
+ :class="hintClass"
34
+ >
35
+ {{ hint }}
36
+ </p>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { computed, useId } from "vue";
42
+
43
+ export type AdsSearchInputState = "default" | "active" | "focus" | "disabled";
44
+ export type AdsSearchInputSize = "md" | "sm";
45
+
46
+ const props = withDefaults(
47
+ defineProps<{
48
+ placeholder?: string;
49
+ ariaLabel?: string;
50
+ hint?: string;
51
+ disabled?: boolean;
52
+ icon?: string;
53
+ size?: AdsSearchInputSize;
54
+ /** Estado congelado para a documentação. `null` segue o campo. */
55
+ state?: AdsSearchInputState | null;
56
+ }>(),
57
+ {
58
+ placeholder: "Pesquisar",
59
+ ariaLabel: "",
60
+ hint: "",
61
+ disabled: false,
62
+ icon: "i-lucide-search",
63
+ size: "md",
64
+ state: null,
65
+ },
66
+ );
67
+
68
+ const model = defineModel<string>({ default: "" });
69
+
70
+ const hintId = useId();
71
+
72
+ const isDisabled = computed(() => props.disabled || props.state === "disabled");
73
+
74
+ const visualState = computed<AdsSearchInputState>(() => {
75
+ if (isDisabled.value) return "disabled";
76
+ if (props.state) return props.state;
77
+ return model.value ? "active" : "default";
78
+ });
79
+
80
+ const showClear = computed(() => visualState.value === "active" || visualState.value === "focus");
81
+
82
+ const iconClass = computed(() =>
83
+ props.size === "sm"
84
+ ? "size-4 shrink-0 text-base"
85
+ : "size-[18px] shrink-0 text-[18px]",
86
+ );
87
+
88
+ const hintClass = computed(() => {
89
+ const tone = visualState.value === "disabled"
90
+ ? "text-[var(--ads-text-tertiary)]"
91
+ : "text-[var(--ads-text-secondary)]";
92
+ const type = props.size === "sm" ? "text-[10px] leading-[1.1]" : "text-xs leading-[1.2]";
93
+ return `${type} ${tone}`;
94
+ });
95
+
96
+ const fieldClass = computed(() => {
97
+ const tone = visualState.value === "default" || visualState.value === "disabled"
98
+ ? "text-[var(--ads-search-placeholder)]"
99
+ : "text-[var(--ads-search-text)]";
100
+ const interactiveFocus = props.state === null && !isDisabled.value;
101
+ const focus = interactiveFocus
102
+ ? "hover:not-focus-within:border-[var(--ads-search-hover-border)] focus-within:border-(length:--ads-border-width-slim) focus-within:border-[var(--ads-search-focus-border)]"
103
+ : "";
104
+
105
+ if (visualState.value === "disabled") {
106
+ return `border border-[var(--ads-search-disabled-border)] bg-[var(--ads-search-disabled-background)] ${tone}`;
107
+ }
108
+ if (visualState.value === "focus") {
109
+ return `border-(length:--ads-border-width-slim) border-[var(--ads-search-focus-border)] bg-[var(--ads-search-background)] ${tone}`;
110
+ }
111
+ return `border border-[var(--ads-search-border)] bg-[var(--ads-search-background)] ${tone} ${focus}`;
112
+ });
113
+ </script>
@@ -1,28 +1,28 @@
1
- <template>
2
- <UTabs
3
- v-model="model"
4
- :items="items"
5
- :content="false"
6
- variant="pill"
7
- color="neutral"
8
- :ui="adsSegmentedTabsUi"
9
- :aria-label="ariaLabel"
10
- />
11
- </template>
12
-
13
- <script setup lang="ts" generic="T extends string | number">
14
- import type { TabsItem } from "@nuxt/ui";
15
- import { adsSegmentedTabsUi } from "../theme/ads-theme";
16
-
17
- withDefaults(
18
- defineProps<{
19
- items: TabsItem[];
20
- ariaLabel?: string;
21
- }>(),
22
- {
23
- ariaLabel: undefined,
24
- },
25
- );
26
-
27
- const model = defineModel<T>({ required: true });
28
- </script>
1
+ <template>
2
+ <UTabs
3
+ v-model="model"
4
+ :items="items"
5
+ :content="false"
6
+ variant="pill"
7
+ color="neutral"
8
+ :ui="adsSegmentedTabsUi"
9
+ :aria-label="ariaLabel"
10
+ />
11
+ </template>
12
+
13
+ <script setup lang="ts" generic="T extends string | number">
14
+ import type { TabsItem } from "@nuxt/ui";
15
+ import { adsSegmentedTabsUi } from "../theme/ads-theme";
16
+
17
+ withDefaults(
18
+ defineProps<{
19
+ items: TabsItem[];
20
+ ariaLabel?: string;
21
+ }>(),
22
+ {
23
+ ariaLabel: undefined,
24
+ },
25
+ );
26
+
27
+ const model = defineModel<T>({ required: true });
28
+ </script>
@@ -1,101 +1,101 @@
1
- <template>
2
- <nav
3
- class="flex w-[var(--ads-side-width)] flex-col border-r-[0.5px] border-[var(--ads-side-border)] bg-[var(--ads-surface-card)]"
4
- aria-label="Menu lateral"
5
- >
6
- <template v-for="item in items" :key="item.id">
7
- <component
8
- :is="item.to && !item.children?.length ? NuxtLink : 'button'"
9
- v-bind="item.to && !item.children?.length ? { to: item.to } : { type: 'button' }"
10
- class="flex h-[var(--ads-side-row-height)] items-center gap-1 border-b-[0.5px] border-[var(--ads-side-border)] py-2 pe-1 text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
11
- :class="rowClass(item, false)"
12
- :aria-expanded="item.children?.length ? isOpen(item.id) : undefined"
13
- @click="onRow(item)"
14
- >
15
- <span class="flex min-w-0 flex-1 items-center gap-2">
16
- <UIcon :name="item.icon || 'i-lucide-sun'" class="size-4 shrink-0 text-ads-neutro-escuro-500" />
17
- <span class="line-clamp-2 text-xs font-bold leading-[1.2] text-[var(--ads-text-secondary)]">{{ item.label }}</span>
18
- </span>
19
- <span
20
- v-if="item.badge"
21
- class="size-[var(--ads-side-badge)] shrink-0 rounded-full bg-[var(--ads-funcional-opportunity-200)]"
22
- aria-hidden="true"
23
- />
24
- <AdsMiniPill v-if="item.pill" type="novo" :label="item.pill" />
25
- <UIcon
26
- v-if="item.children?.length"
27
- name="i-lucide-chevron-down"
28
- class="size-4 shrink-0 text-ads-neutro-medio-100 transition-transform duration-150"
29
- :class="isOpen(item.id) ? '' : '-rotate-90'"
30
- />
31
- </component>
32
- <component
33
- :is="child.to ? NuxtLink : 'button'"
34
- v-for="child in isOpen(item.id) ? item.children : []"
35
- :key="child.id"
36
- v-bind="child.to ? { to: child.to } : { type: 'button' }"
37
- class="flex h-[var(--ads-side-row-height)] items-center gap-2 border-b-[0.5px] border-[var(--ads-side-border)] py-2 pe-1 ps-6 text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
38
- :class="rowClass(child, true)"
39
- @click="emit('select', child.id)"
40
- >
41
- <UIcon :name="child.icon || 'i-lucide-sun'" class="size-4 shrink-0 text-ads-neutro-escuro-500" />
42
- <span class="line-clamp-2 min-w-0 flex-1 text-xs font-bold leading-[1.2] text-[var(--ads-text-secondary)]">{{ child.label }}</span>
43
- <AdsMiniPill v-if="child.pill" type="novo" :label="child.pill" />
44
- </component>
45
- </template>
46
- </nav>
47
- </template>
48
-
49
- <script setup lang="ts">
50
- import { ref } from "vue";
51
- import type { RouteLocationRaw } from "vue-router";
52
- import { NuxtLink } from "#components";
53
- import AdsMiniPill from "./AdsMiniPill.vue";
54
-
55
- export type AdsSideMenuItem = {
56
- id: string;
57
- label: string;
58
- icon?: string;
59
- to?: RouteLocationRaw;
60
- children?: AdsSideMenuItem[];
61
- /** Texto da mini pill. Vazio esconde. */
62
- pill?: string;
63
- /** Ponto de 8px (Figma Show Badge no item mãe). */
64
- badge?: boolean;
65
- };
66
-
67
- const props = withDefaults(
68
- defineProps<{
69
- items: AdsSideMenuItem[];
70
- /** Pais abertos no primeiro render. O Figma não desenha o clique. */
71
- defaultOpen?: string[];
72
- }>(),
73
- {
74
- defaultOpen: () => [],
75
- },
76
- );
77
-
78
- const emit = defineEmits<{
79
- select: [id: string];
80
- }>();
81
-
82
- const openIds = ref(new Set(props.defaultOpen));
83
-
84
- const isOpen = (id: string) => openIds.value.has(id);
85
-
86
- const rowClass = (item: AdsSideMenuItem, child: boolean) => {
87
- if (child) return "bg-[var(--ads-side-child-bg)]";
88
- if (item.children?.length && isOpen(item.id)) return "bg-[var(--ads-side-open-bg)] ps-4";
89
- return "bg-[var(--ads-surface-card)] ps-4";
90
- };
91
-
92
- const onRow = (item: AdsSideMenuItem) => {
93
- if (item.children?.length) {
94
- const next = new Set(openIds.value);
95
- if (next.has(item.id)) next.delete(item.id);
96
- else next.add(item.id);
97
- openIds.value = next;
98
- }
99
- emit("select", item.id);
100
- };
101
- </script>
1
+ <template>
2
+ <nav
3
+ class="flex w-[var(--ads-side-width)] flex-col border-r-[0.5px] border-[var(--ads-side-border)] bg-[var(--ads-surface-card)]"
4
+ aria-label="Menu lateral"
5
+ >
6
+ <template v-for="item in items" :key="item.id">
7
+ <component
8
+ :is="item.to && !item.children?.length ? NuxtLink : 'button'"
9
+ v-bind="item.to && !item.children?.length ? { to: item.to } : { type: 'button' }"
10
+ class="flex h-[var(--ads-side-row-height)] items-center gap-1 border-b-[0.5px] border-[var(--ads-side-border)] py-2 pe-1 text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
11
+ :class="rowClass(item, false)"
12
+ :aria-expanded="item.children?.length ? isOpen(item.id) : undefined"
13
+ @click="onRow(item)"
14
+ >
15
+ <span class="flex min-w-0 flex-1 items-center gap-2">
16
+ <UIcon :name="item.icon || 'i-lucide-sun'" class="size-4 shrink-0 text-ads-neutro-escuro-500" />
17
+ <span class="line-clamp-2 text-xs font-bold leading-[1.2] text-[var(--ads-text-secondary)]">{{ item.label }}</span>
18
+ </span>
19
+ <span
20
+ v-if="item.badge"
21
+ class="size-[var(--ads-side-badge)] shrink-0 rounded-full bg-[var(--ads-funcional-opportunity-200)]"
22
+ aria-hidden="true"
23
+ />
24
+ <AdsMiniPill v-if="item.pill" type="novo" :label="item.pill" />
25
+ <UIcon
26
+ v-if="item.children?.length"
27
+ name="i-lucide-chevron-down"
28
+ class="size-4 shrink-0 text-ads-neutro-medio-100 transition-transform duration-150"
29
+ :class="isOpen(item.id) ? '' : '-rotate-90'"
30
+ />
31
+ </component>
32
+ <component
33
+ :is="child.to ? NuxtLink : 'button'"
34
+ v-for="child in isOpen(item.id) ? item.children : []"
35
+ :key="child.id"
36
+ v-bind="child.to ? { to: child.to } : { type: 'button' }"
37
+ class="flex h-[var(--ads-side-row-height)] items-center gap-2 border-b-[0.5px] border-[var(--ads-side-border)] py-2 pe-1 ps-6 text-left focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[var(--ads-text-brand)]"
38
+ :class="rowClass(child, true)"
39
+ @click="emit('select', child.id)"
40
+ >
41
+ <UIcon :name="child.icon || 'i-lucide-sun'" class="size-4 shrink-0 text-ads-neutro-escuro-500" />
42
+ <span class="line-clamp-2 min-w-0 flex-1 text-xs font-bold leading-[1.2] text-[var(--ads-text-secondary)]">{{ child.label }}</span>
43
+ <AdsMiniPill v-if="child.pill" type="novo" :label="child.pill" />
44
+ </component>
45
+ </template>
46
+ </nav>
47
+ </template>
48
+
49
+ <script setup lang="ts">
50
+ import { ref } from "vue";
51
+ import type { RouteLocationRaw } from "vue-router";
52
+ import { NuxtLink } from "#components";
53
+ import AdsMiniPill from "./AdsMiniPill.vue";
54
+
55
+ export type AdsSideMenuItem = {
56
+ id: string;
57
+ label: string;
58
+ icon?: string;
59
+ to?: RouteLocationRaw;
60
+ children?: AdsSideMenuItem[];
61
+ /** Texto da mini pill. Vazio esconde. */
62
+ pill?: string;
63
+ /** Ponto de 8px (Figma Show Badge no item mãe). */
64
+ badge?: boolean;
65
+ };
66
+
67
+ const props = withDefaults(
68
+ defineProps<{
69
+ items: AdsSideMenuItem[];
70
+ /** Pais abertos no primeiro render. O Figma não desenha o clique. */
71
+ defaultOpen?: string[];
72
+ }>(),
73
+ {
74
+ defaultOpen: () => [],
75
+ },
76
+ );
77
+
78
+ const emit = defineEmits<{
79
+ select: [id: string];
80
+ }>();
81
+
82
+ const openIds = ref(new Set(props.defaultOpen));
83
+
84
+ const isOpen = (id: string) => openIds.value.has(id);
85
+
86
+ const rowClass = (item: AdsSideMenuItem, child: boolean) => {
87
+ if (child) return "bg-[var(--ads-side-child-bg)]";
88
+ if (item.children?.length && isOpen(item.id)) return "bg-[var(--ads-side-open-bg)] ps-4";
89
+ return "bg-[var(--ads-surface-card)] ps-4";
90
+ };
91
+
92
+ const onRow = (item: AdsSideMenuItem) => {
93
+ if (item.children?.length) {
94
+ const next = new Set(openIds.value);
95
+ if (next.has(item.id)) next.delete(item.id);
96
+ else next.add(item.id);
97
+ openIds.value = next;
98
+ }
99
+ emit("select", item.id);
100
+ };
101
+ </script>
@@ -1,44 +1,44 @@
1
- <template>
2
- <UTooltip
3
- v-if="hasTrigger"
4
- arrow
5
- :delay-duration="delayDuration"
6
- :content="{ side: 'top', align: 'center', sideOffset: 4 }"
7
- :ui="tooltipUi"
8
- >
9
- <slot />
10
- <template #content>
11
- <span class="text-[length:var(--ads-tooltip-small-font-size)] leading-[1.1] text-[var(--ads-tooltip-title)]">{{ text }}</span>
12
- </template>
13
- </UTooltip>
14
- <p
15
- v-else
16
- class="inline-flex rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] p-[var(--ads-tooltip-small-padding)] text-[length:var(--ads-tooltip-small-font-size)] leading-[1.1] text-[var(--ads-tooltip-title)] shadow-[var(--ads-shadow-level-1)]"
17
- >
18
- {{ text }}
19
- </p>
20
- </template>
21
-
22
- <script setup lang="ts">
23
- import { computed, useSlots } from "vue";
24
-
25
- withDefaults(
26
- defineProps<{
27
- text?: string;
28
- delayDuration?: number;
29
- }>(),
30
- {
31
- text: "Clique para ver mais",
32
- delayDuration: 150,
33
- },
34
- );
35
-
36
- const slots = useSlots();
37
- const hasTrigger = computed(() => Boolean(slots.default));
38
-
39
- const tooltipUi = {
40
- content:
41
- "z-[70] rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] p-[var(--ads-tooltip-small-padding)] shadow-[var(--ads-shadow-level-1)] ring-0",
42
- arrow: "fill-[var(--ads-tooltip-bg)]",
43
- };
44
- </script>
1
+ <template>
2
+ <UTooltip
3
+ v-if="hasTrigger"
4
+ arrow
5
+ :delay-duration="delayDuration"
6
+ :content="{ side: 'top', align: 'center', sideOffset: 4 }"
7
+ :ui="tooltipUi"
8
+ >
9
+ <slot />
10
+ <template #content>
11
+ <span class="text-[length:var(--ads-tooltip-small-font-size)] leading-[1.1] text-[var(--ads-tooltip-title)]">{{ text }}</span>
12
+ </template>
13
+ </UTooltip>
14
+ <p
15
+ v-else
16
+ class="inline-flex rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] p-[var(--ads-tooltip-small-padding)] text-[length:var(--ads-tooltip-small-font-size)] leading-[1.1] text-[var(--ads-tooltip-title)] shadow-[var(--ads-shadow-level-1)]"
17
+ >
18
+ {{ text }}
19
+ </p>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import { computed, useSlots } from "vue";
24
+
25
+ withDefaults(
26
+ defineProps<{
27
+ text?: string;
28
+ delayDuration?: number;
29
+ }>(),
30
+ {
31
+ text: "Clique para ver mais",
32
+ delayDuration: 150,
33
+ },
34
+ );
35
+
36
+ const slots = useSlots();
37
+ const hasTrigger = computed(() => Boolean(slots.default));
38
+
39
+ const tooltipUi = {
40
+ content:
41
+ "z-[70] rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] p-[var(--ads-tooltip-small-padding)] shadow-[var(--ads-shadow-level-1)] ring-0",
42
+ arrow: "fill-[var(--ads-tooltip-bg)]",
43
+ };
44
+ </script>