ads-nuxt 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +567 -1
- package/app/app.config.ts +25 -25
- package/app/assets/ads/checkbox-check.svg +5 -5
- package/app/assets/ads/checkbox-minus.svg +5 -5
- package/app/assets/brand/farmarcas.svg +6 -6
- package/app/assets/brand/radar.svg +9 -9
- package/app/assets/css/component-tokens.css +403 -403
- package/app/assets/css/main.css +617 -617
- package/app/components/AdsBadge.vue +48 -48
- package/app/components/AdsBroadcast.vue +45 -45
- package/app/components/AdsButton.vue +569 -569
- package/app/components/AdsButtonGroup.vue +39 -39
- package/app/components/AdsButtonMenu.vue +48 -48
- package/app/components/AdsCard.vue +117 -117
- package/app/components/AdsCharttip.vue +78 -78
- package/app/components/AdsCheckbox.vue +77 -77
- package/app/components/AdsDatePicker.vue +473 -473
- package/app/components/AdsDropdownMenu.vue +19 -19
- package/app/components/AdsEmptyState.vue +58 -58
- package/app/components/AdsFarmarcasPill.vue +74 -74
- package/app/components/AdsFeatureCard.vue +217 -217
- package/app/components/AdsFileCard.vue +101 -101
- package/app/components/AdsFilterDropdown.vue +174 -174
- package/app/components/AdsFilterOption.vue +72 -72
- package/app/components/AdsFilterSelect.vue +202 -202
- package/app/components/AdsFooter.vue +25 -25
- package/app/components/AdsInput.vue +143 -143
- package/app/components/AdsListDropdown.vue +44 -44
- package/app/components/AdsLoading.vue +109 -109
- package/app/components/AdsMenuBar.vue +103 -103
- package/app/components/AdsMetricCard.vue +119 -119
- package/app/components/AdsMiniPill.vue +40 -40
- package/app/components/AdsModal.vue +157 -157
- package/app/components/AdsMonthPicker.vue +80 -80
- package/app/components/AdsNavbarApp.vue +67 -67
- package/app/components/AdsNavbarDesk.vue +62 -62
- package/app/components/AdsNotabilityPill.vue +27 -27
- package/app/components/AdsNumberInput.vue +166 -166
- package/app/components/AdsPagination.vue +91 -91
- package/app/components/AdsProductTag.vue +61 -61
- package/app/components/AdsRadio.vue +70 -70
- package/app/components/AdsSearchInput.vue +113 -113
- package/app/components/AdsSegmentedTabs.vue +28 -28
- package/app/components/AdsSideMenu.vue +101 -101
- package/app/components/AdsSmalltip.vue +44 -44
- package/app/components/AdsSteptip.vue +101 -101
- package/app/components/AdsSwitch.vue +59 -59
- package/app/components/AdsTable.vue +100 -100
- package/app/components/AdsTableToolbar.vue +44 -44
- package/app/components/AdsTabs.vue +51 -51
- package/app/components/AdsTextarea.vue +109 -109
- package/app/components/AdsToast.vue +181 -181
- package/app/components/AdsTooltip.vue +66 -66
- package/app/components/InvertedTooltip.vue +40 -40
- package/app/components/OverlayLoading.vue +173 -173
- package/app/components/ProgressBarLoading.vue +256 -256
- package/app/composables/useOverlayLoading.ts +66 -66
- package/app/locale/pt-br.ts +6 -6
- package/app/plugins/ads-toast.client.ts +24 -24
- package/app/theme/ads-theme.ts +370 -370
- package/app/theme/component-tokens.json +402 -402
- package/app/theme/component-tokens.ts +815 -815
- package/app/types/overlay-loading.types.ts +26 -26
- package/app/utils/ads-filter-select.ts +172 -172
- package/app/utils/ads-table.ts +204 -204
- package/app/utils/calendar-date.ts +128 -128
- package/app/utils/category-role.ts +45 -45
- package/app/utils/money.ts +77 -77
- package/app/utils/notability.ts +8 -8
- package/app/utils/parser.ts +134 -134
- package/app/utils/percentage.ts +54 -54
- package/nuxt.config.ts +31 -32
- package/package.json +48 -46
|
@@ -1,101 +1,101 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<DefinePanel>
|
|
3
|
-
<div class="flex w-[var(--ads-steptip-width)] flex-col gap-3 rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] p-4 text-[var(--ads-tooltip-title)] shadow-[var(--ads-shadow-level-1)]">
|
|
4
|
-
<div class="flex items-start justify-between gap-3">
|
|
5
|
-
<p class="text-base leading-[1.3] font-bold">{{ title }}</p>
|
|
6
|
-
<p class="shrink-0 text-xs leading-[1.2] font-medium">{{ step }}/{{ totalSteps }}</p>
|
|
7
|
-
</div>
|
|
8
|
-
<p class="text-sm leading-[1.3]">{{ description }}</p>
|
|
9
|
-
<div class="flex items-center justify-between gap-3">
|
|
10
|
-
<button
|
|
11
|
-
type="button"
|
|
12
|
-
class="text-base text-[var(--ads-steptip-skip)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-tooltip-title)]"
|
|
13
|
-
@click="emit('skip')"
|
|
14
|
-
>
|
|
15
|
-
{{ skipLabel }}
|
|
16
|
-
</button>
|
|
17
|
-
<div class="flex items-center gap-3">
|
|
18
|
-
<button
|
|
19
|
-
v-if="step > 1"
|
|
20
|
-
type="button"
|
|
21
|
-
class="text-base font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-contrast)]"
|
|
22
|
-
@click="emit('back')"
|
|
23
|
-
>
|
|
24
|
-
{{ backLabel }}
|
|
25
|
-
</button>
|
|
26
|
-
<button
|
|
27
|
-
type="button"
|
|
28
|
-
class="text-base font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-contrast)]"
|
|
29
|
-
@click="step >= totalSteps ? emit('finish') : emit('next')"
|
|
30
|
-
>
|
|
31
|
-
{{ step >= totalSteps ? finishLabel : nextLabel }}
|
|
32
|
-
</button>
|
|
33
|
-
</div>
|
|
34
|
-
</div>
|
|
35
|
-
</div>
|
|
36
|
-
</DefinePanel>
|
|
37
|
-
|
|
38
|
-
<UTooltip
|
|
39
|
-
v-if="hasTrigger"
|
|
40
|
-
:delay-duration="delayDuration"
|
|
41
|
-
:content="{ side, align: 'center', sideOffset: 8 }"
|
|
42
|
-
:ui="tooltipUi"
|
|
43
|
-
>
|
|
44
|
-
<slot />
|
|
45
|
-
<template #content>
|
|
46
|
-
<ReusePanel />
|
|
47
|
-
</template>
|
|
48
|
-
</UTooltip>
|
|
49
|
-
<ReusePanel v-else />
|
|
50
|
-
</template>
|
|
51
|
-
|
|
52
|
-
<script setup lang="ts">
|
|
53
|
-
import { computed, useSlots } from "vue";
|
|
54
|
-
import { createReusableTemplate } from "@vueuse/core";
|
|
55
|
-
|
|
56
|
-
const [DefinePanel, ReusePanel] = createReusableTemplate();
|
|
57
|
-
|
|
58
|
-
const props = withDefaults(
|
|
59
|
-
defineProps<{
|
|
60
|
-
title?: string;
|
|
61
|
-
description?: string;
|
|
62
|
-
step?: number;
|
|
63
|
-
totalSteps?: number;
|
|
64
|
-
skipLabel?: string;
|
|
65
|
-
backLabel?: string;
|
|
66
|
-
nextLabel?: string;
|
|
67
|
-
finishLabel?: string;
|
|
68
|
-
/** Lado do gatilho onde o passo abre. Só vale com slot. */
|
|
69
|
-
side?: "top" | "right" | "bottom" | "left";
|
|
70
|
-
delayDuration?: number;
|
|
71
|
-
}>(),
|
|
72
|
-
{
|
|
73
|
-
title: "Título do passo",
|
|
74
|
-
description: "Texto que orienta a pessoa neste passo.",
|
|
75
|
-
step: 1,
|
|
76
|
-
totalSteps: 3,
|
|
77
|
-
skipLabel: "pular",
|
|
78
|
-
backLabel: "voltar",
|
|
79
|
-
nextLabel: "próximo",
|
|
80
|
-
finishLabel: "concluir",
|
|
81
|
-
side: "bottom",
|
|
82
|
-
delayDuration: 150,
|
|
83
|
-
},
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
const emit = defineEmits<{
|
|
87
|
-
skip: [];
|
|
88
|
-
back: [];
|
|
89
|
-
next: [];
|
|
90
|
-
finish: [];
|
|
91
|
-
}>();
|
|
92
|
-
|
|
93
|
-
const slots = useSlots();
|
|
94
|
-
const hasTrigger = computed(() => Boolean(slots.default));
|
|
95
|
-
const side = computed(() => props.side);
|
|
96
|
-
|
|
97
|
-
/** O painel já tem fundo, raio e sombra: o conteúdo do UTooltip só posiciona. */
|
|
98
|
-
const tooltipUi = {
|
|
99
|
-
content: "z-[70] h-auto rounded-none bg-transparent p-0 shadow-none ring-0",
|
|
100
|
-
};
|
|
101
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<DefinePanel>
|
|
3
|
+
<div class="flex w-[var(--ads-steptip-width)] flex-col gap-3 rounded-[var(--ads-tooltip-radius)] bg-[var(--ads-tooltip-bg)] p-4 text-[var(--ads-tooltip-title)] shadow-[var(--ads-shadow-level-1)]">
|
|
4
|
+
<div class="flex items-start justify-between gap-3">
|
|
5
|
+
<p class="text-base leading-[1.3] font-bold">{{ title }}</p>
|
|
6
|
+
<p class="shrink-0 text-xs leading-[1.2] font-medium">{{ step }}/{{ totalSteps }}</p>
|
|
7
|
+
</div>
|
|
8
|
+
<p class="text-sm leading-[1.3]">{{ description }}</p>
|
|
9
|
+
<div class="flex items-center justify-between gap-3">
|
|
10
|
+
<button
|
|
11
|
+
type="button"
|
|
12
|
+
class="text-base text-[var(--ads-steptip-skip)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-tooltip-title)]"
|
|
13
|
+
@click="emit('skip')"
|
|
14
|
+
>
|
|
15
|
+
{{ skipLabel }}
|
|
16
|
+
</button>
|
|
17
|
+
<div class="flex items-center gap-3">
|
|
18
|
+
<button
|
|
19
|
+
v-if="step > 1"
|
|
20
|
+
type="button"
|
|
21
|
+
class="text-base font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-contrast)]"
|
|
22
|
+
@click="emit('back')"
|
|
23
|
+
>
|
|
24
|
+
{{ backLabel }}
|
|
25
|
+
</button>
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
class="text-base font-medium focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-contrast)]"
|
|
29
|
+
@click="step >= totalSteps ? emit('finish') : emit('next')"
|
|
30
|
+
>
|
|
31
|
+
{{ step >= totalSteps ? finishLabel : nextLabel }}
|
|
32
|
+
</button>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</DefinePanel>
|
|
37
|
+
|
|
38
|
+
<UTooltip
|
|
39
|
+
v-if="hasTrigger"
|
|
40
|
+
:delay-duration="delayDuration"
|
|
41
|
+
:content="{ side, align: 'center', sideOffset: 8 }"
|
|
42
|
+
:ui="tooltipUi"
|
|
43
|
+
>
|
|
44
|
+
<slot />
|
|
45
|
+
<template #content>
|
|
46
|
+
<ReusePanel />
|
|
47
|
+
</template>
|
|
48
|
+
</UTooltip>
|
|
49
|
+
<ReusePanel v-else />
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script setup lang="ts">
|
|
53
|
+
import { computed, useSlots } from "vue";
|
|
54
|
+
import { createReusableTemplate } from "@vueuse/core";
|
|
55
|
+
|
|
56
|
+
const [DefinePanel, ReusePanel] = createReusableTemplate();
|
|
57
|
+
|
|
58
|
+
const props = withDefaults(
|
|
59
|
+
defineProps<{
|
|
60
|
+
title?: string;
|
|
61
|
+
description?: string;
|
|
62
|
+
step?: number;
|
|
63
|
+
totalSteps?: number;
|
|
64
|
+
skipLabel?: string;
|
|
65
|
+
backLabel?: string;
|
|
66
|
+
nextLabel?: string;
|
|
67
|
+
finishLabel?: string;
|
|
68
|
+
/** Lado do gatilho onde o passo abre. Só vale com slot. */
|
|
69
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
70
|
+
delayDuration?: number;
|
|
71
|
+
}>(),
|
|
72
|
+
{
|
|
73
|
+
title: "Título do passo",
|
|
74
|
+
description: "Texto que orienta a pessoa neste passo.",
|
|
75
|
+
step: 1,
|
|
76
|
+
totalSteps: 3,
|
|
77
|
+
skipLabel: "pular",
|
|
78
|
+
backLabel: "voltar",
|
|
79
|
+
nextLabel: "próximo",
|
|
80
|
+
finishLabel: "concluir",
|
|
81
|
+
side: "bottom",
|
|
82
|
+
delayDuration: 150,
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const emit = defineEmits<{
|
|
87
|
+
skip: [];
|
|
88
|
+
back: [];
|
|
89
|
+
next: [];
|
|
90
|
+
finish: [];
|
|
91
|
+
}>();
|
|
92
|
+
|
|
93
|
+
const slots = useSlots();
|
|
94
|
+
const hasTrigger = computed(() => Boolean(slots.default));
|
|
95
|
+
const side = computed(() => props.side);
|
|
96
|
+
|
|
97
|
+
/** O painel já tem fundo, raio e sombra: o conteúdo do UTooltip só posiciona. */
|
|
98
|
+
const tooltipUi = {
|
|
99
|
+
content: "z-[70] h-auto rounded-none bg-transparent p-0 shadow-none ring-0",
|
|
100
|
+
};
|
|
101
|
+
</script>
|
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<label
|
|
3
|
-
class="inline-flex items-center"
|
|
4
|
-
:class="[hasLabel ? 'gap-[var(--ads-switch-gap)]' : '', isDisabled ? 'cursor-not-allowed' : 'cursor-pointer']"
|
|
5
|
-
>
|
|
6
|
-
<span class="relative h-[var(--ads-switch-height)] w-[var(--ads-switch-width)] shrink-0">
|
|
7
|
-
<input
|
|
8
|
-
v-bind="attrs"
|
|
9
|
-
type="checkbox"
|
|
10
|
-
role="switch"
|
|
11
|
-
class="peer absolute inset-0 appearance-none rounded-[var(--ads-switch-radius)] bg-[var(--ads-switch-off)] transition-colors duration-150 ease-out checked:bg-[var(--ads-switch-on)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)] disabled:cursor-not-allowed disabled:bg-[var(--ads-switch-disabled)] disabled:checked:bg-[var(--ads-switch-disabled-on)]"
|
|
12
|
-
:checked="model"
|
|
13
|
-
:disabled="isDisabled"
|
|
14
|
-
:aria-checked="model"
|
|
15
|
-
@change="onChange"
|
|
16
|
-
>
|
|
17
|
-
<span
|
|
18
|
-
class="pointer-events-none absolute top-[var(--ads-switch-inset)] left-0 size-[var(--ads-switch-thumb)] translate-x-[var(--ads-switch-inset)] rounded-full bg-[var(--ads-switch-thumb-color)] transition-transform duration-150 ease-out peer-checked:translate-x-[calc(var(--ads-switch-width)-var(--ads-switch-thumb)-var(--ads-switch-inset))]"
|
|
19
|
-
/>
|
|
20
|
-
</span>
|
|
21
|
-
<span
|
|
22
|
-
v-if="hasLabel"
|
|
23
|
-
class="text-[length:var(--ads-switch-label-size)] font-normal leading-[1.3]"
|
|
24
|
-
:class="isDisabled ? 'text-[var(--ads-text-tertiary)]' : 'text-[var(--ads-switch-label)]'"
|
|
25
|
-
>
|
|
26
|
-
<slot>{{ label }}</slot>
|
|
27
|
-
</span>
|
|
28
|
-
</label>
|
|
29
|
-
</template>
|
|
30
|
-
|
|
31
|
-
<script setup lang="ts">
|
|
32
|
-
import { computed, useAttrs, useSlots } from "vue";
|
|
33
|
-
|
|
34
|
-
defineOptions({ inheritAttrs: false });
|
|
35
|
-
|
|
36
|
-
const props = withDefaults(
|
|
37
|
-
defineProps<{
|
|
38
|
-
label?: string;
|
|
39
|
-
disabled?: boolean;
|
|
40
|
-
}>(),
|
|
41
|
-
{
|
|
42
|
-
label: "",
|
|
43
|
-
disabled: false,
|
|
44
|
-
},
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
const model = defineModel<boolean>({ default: false });
|
|
48
|
-
const attrs = useAttrs();
|
|
49
|
-
const slots = useSlots();
|
|
50
|
-
|
|
51
|
-
const hasLabel = computed(() => props.label.length > 0 || Boolean(slots.default));
|
|
52
|
-
const isDisabled = computed(() => props.disabled);
|
|
53
|
-
|
|
54
|
-
const onChange = (event: Event) => {
|
|
55
|
-
const input = event.target;
|
|
56
|
-
if (!(input instanceof HTMLInputElement) || isDisabled.value) return;
|
|
57
|
-
model.value = input.checked;
|
|
58
|
-
};
|
|
59
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<label
|
|
3
|
+
class="inline-flex items-center"
|
|
4
|
+
:class="[hasLabel ? 'gap-[var(--ads-switch-gap)]' : '', isDisabled ? 'cursor-not-allowed' : 'cursor-pointer']"
|
|
5
|
+
>
|
|
6
|
+
<span class="relative h-[var(--ads-switch-height)] w-[var(--ads-switch-width)] shrink-0">
|
|
7
|
+
<input
|
|
8
|
+
v-bind="attrs"
|
|
9
|
+
type="checkbox"
|
|
10
|
+
role="switch"
|
|
11
|
+
class="peer absolute inset-0 appearance-none rounded-[var(--ads-switch-radius)] bg-[var(--ads-switch-off)] transition-colors duration-150 ease-out checked:bg-[var(--ads-switch-on)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ads-text-brand)] disabled:cursor-not-allowed disabled:bg-[var(--ads-switch-disabled)] disabled:checked:bg-[var(--ads-switch-disabled-on)]"
|
|
12
|
+
:checked="model"
|
|
13
|
+
:disabled="isDisabled"
|
|
14
|
+
:aria-checked="model"
|
|
15
|
+
@change="onChange"
|
|
16
|
+
>
|
|
17
|
+
<span
|
|
18
|
+
class="pointer-events-none absolute top-[var(--ads-switch-inset)] left-0 size-[var(--ads-switch-thumb)] translate-x-[var(--ads-switch-inset)] rounded-full bg-[var(--ads-switch-thumb-color)] transition-transform duration-150 ease-out peer-checked:translate-x-[calc(var(--ads-switch-width)-var(--ads-switch-thumb)-var(--ads-switch-inset))]"
|
|
19
|
+
/>
|
|
20
|
+
</span>
|
|
21
|
+
<span
|
|
22
|
+
v-if="hasLabel"
|
|
23
|
+
class="text-[length:var(--ads-switch-label-size)] font-normal leading-[1.3]"
|
|
24
|
+
:class="isDisabled ? 'text-[var(--ads-text-tertiary)]' : 'text-[var(--ads-switch-label)]'"
|
|
25
|
+
>
|
|
26
|
+
<slot>{{ label }}</slot>
|
|
27
|
+
</span>
|
|
28
|
+
</label>
|
|
29
|
+
</template>
|
|
30
|
+
|
|
31
|
+
<script setup lang="ts">
|
|
32
|
+
import { computed, useAttrs, useSlots } from "vue";
|
|
33
|
+
|
|
34
|
+
defineOptions({ inheritAttrs: false });
|
|
35
|
+
|
|
36
|
+
const props = withDefaults(
|
|
37
|
+
defineProps<{
|
|
38
|
+
label?: string;
|
|
39
|
+
disabled?: boolean;
|
|
40
|
+
}>(),
|
|
41
|
+
{
|
|
42
|
+
label: "",
|
|
43
|
+
disabled: false,
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const model = defineModel<boolean>({ default: false });
|
|
48
|
+
const attrs = useAttrs();
|
|
49
|
+
const slots = useSlots();
|
|
50
|
+
|
|
51
|
+
const hasLabel = computed(() => props.label.length > 0 || Boolean(slots.default));
|
|
52
|
+
const isDisabled = computed(() => props.disabled);
|
|
53
|
+
|
|
54
|
+
const onChange = (event: Event) => {
|
|
55
|
+
const input = event.target;
|
|
56
|
+
if (!(input instanceof HTMLInputElement) || isDisabled.value) return;
|
|
57
|
+
model.value = input.checked;
|
|
58
|
+
};
|
|
59
|
+
</script>
|
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<UTable
|
|
3
|
-
:data="data"
|
|
4
|
-
:columns="columns"
|
|
5
|
-
:loading="loading"
|
|
6
|
-
:sticky="sticky"
|
|
7
|
-
:ui="tableUi"
|
|
8
|
-
>
|
|
9
|
-
<template v-if="!$slots.loading" #loading>
|
|
10
|
-
<AdsLoading size="sm" :label="loadingLabel" />
|
|
11
|
-
</template>
|
|
12
|
-
|
|
13
|
-
<template v-if="!$slots.empty" #empty>
|
|
14
|
-
<div class="flex flex-col items-center gap-2">
|
|
15
|
-
<UIcon
|
|
16
|
-
:name="emptyIcon"
|
|
17
|
-
class="size-6 text-[var(--ads-border-icon-support)]"
|
|
18
|
-
aria-hidden="true"
|
|
19
|
-
/>
|
|
20
|
-
<p class="text-sm text-[var(--ads-text-secondary)]">{{ emptyLabel }}</p>
|
|
21
|
-
</div>
|
|
22
|
-
</template>
|
|
23
|
-
|
|
24
|
-
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
25
|
-
<slot :name="name" v-bind="slotProps ?? {}" />
|
|
26
|
-
</template>
|
|
27
|
-
</UTable>
|
|
28
|
-
</template>
|
|
29
|
-
|
|
30
|
-
<script setup lang="ts" generic="T extends TableData">
|
|
31
|
-
import { computed, useSlots } from "vue";
|
|
32
|
-
import type { TableColumn, TableData } from "@nuxt/ui";
|
|
33
|
-
import AdsLoading from "./AdsLoading.vue";
|
|
34
|
-
import {
|
|
35
|
-
buildAdsTableUi,
|
|
36
|
-
type AdsTableBorders,
|
|
37
|
-
type AdsTableDensity,
|
|
38
|
-
type AdsTableUi,
|
|
39
|
-
} from "../utils/ads-table";
|
|
40
|
-
|
|
41
|
-
const props = withDefaults(
|
|
42
|
-
defineProps<{
|
|
43
|
-
data: T[];
|
|
44
|
-
columns: TableColumn<T>[];
|
|
45
|
-
/** md: lista e gestão. sm: grade de dados larga ou prévia. */
|
|
46
|
-
density?: AdsTableDensity;
|
|
47
|
-
/** Zebra só em grade numérica larga. */
|
|
48
|
-
striped?: boolean;
|
|
49
|
-
/** Borda e raio próprios. Dentro de AdsCard, false. */
|
|
50
|
-
framed?: boolean;
|
|
51
|
-
/** lines: só horizontal. grid: horizontal e vertical. */
|
|
52
|
-
borders?: AdsTableBorders;
|
|
53
|
-
/** Degradê por nível. A célula marca `data-depth` de 0 a 2. */
|
|
54
|
-
depth?: boolean;
|
|
55
|
-
loading?: boolean;
|
|
56
|
-
/** Cabeçalho (e rodapé) fixos quando a tabela tem altura máxima. */
|
|
57
|
-
sticky?: boolean | "header" | "footer";
|
|
58
|
-
emptyLabel?: string;
|
|
59
|
-
emptyIcon?: string;
|
|
60
|
-
loadingLabel?: string;
|
|
61
|
-
/** Ajuste pontual de um slot; soma ao padrão. */
|
|
62
|
-
ui?: Partial<AdsTableUi>;
|
|
63
|
-
}>(),
|
|
64
|
-
{
|
|
65
|
-
density: "md",
|
|
66
|
-
striped: false,
|
|
67
|
-
framed: true,
|
|
68
|
-
borders: "lines",
|
|
69
|
-
depth: false,
|
|
70
|
-
loading: false,
|
|
71
|
-
sticky: false,
|
|
72
|
-
emptyLabel: "Nenhum registro encontrado",
|
|
73
|
-
emptyIcon: "i-lucide-inbox",
|
|
74
|
-
loadingLabel: undefined,
|
|
75
|
-
ui: undefined,
|
|
76
|
-
},
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
const slots = useSlots();
|
|
80
|
-
|
|
81
|
-
const tableUi = computed(() => {
|
|
82
|
-
const base = buildAdsTableUi({
|
|
83
|
-
density: props.density,
|
|
84
|
-
striped: props.striped,
|
|
85
|
-
framed: props.framed,
|
|
86
|
-
borders: props.borders,
|
|
87
|
-
depth: props.depth,
|
|
88
|
-
});
|
|
89
|
-
if (!slots.expanded) {
|
|
90
|
-
// Árvore via getSubRows: o UTable sempre desenha a linha de detalhe da linha aberta.
|
|
91
|
-
base.tbody = `${base.tbody} [&>tr[data-slot=tr]:not([data-selectable])]:hidden`;
|
|
92
|
-
}
|
|
93
|
-
if (!props.ui) return base;
|
|
94
|
-
const merged = { ...base };
|
|
95
|
-
for (const key of Object.keys(props.ui) as (keyof AdsTableUi)[]) {
|
|
96
|
-
merged[key] = `${base[key]} ${props.ui[key] ?? ""}`.trim();
|
|
97
|
-
}
|
|
98
|
-
return merged;
|
|
99
|
-
});
|
|
100
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<UTable
|
|
3
|
+
:data="data"
|
|
4
|
+
:columns="columns"
|
|
5
|
+
:loading="loading"
|
|
6
|
+
:sticky="sticky"
|
|
7
|
+
:ui="tableUi"
|
|
8
|
+
>
|
|
9
|
+
<template v-if="!$slots.loading" #loading>
|
|
10
|
+
<AdsLoading size="sm" :label="loadingLabel" />
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<template v-if="!$slots.empty" #empty>
|
|
14
|
+
<div class="flex flex-col items-center gap-2">
|
|
15
|
+
<UIcon
|
|
16
|
+
:name="emptyIcon"
|
|
17
|
+
class="size-6 text-[var(--ads-border-icon-support)]"
|
|
18
|
+
aria-hidden="true"
|
|
19
|
+
/>
|
|
20
|
+
<p class="text-sm text-[var(--ads-text-secondary)]">{{ emptyLabel }}</p>
|
|
21
|
+
</div>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
|
25
|
+
<slot :name="name" v-bind="slotProps ?? {}" />
|
|
26
|
+
</template>
|
|
27
|
+
</UTable>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts" generic="T extends TableData">
|
|
31
|
+
import { computed, useSlots } from "vue";
|
|
32
|
+
import type { TableColumn, TableData } from "@nuxt/ui";
|
|
33
|
+
import AdsLoading from "./AdsLoading.vue";
|
|
34
|
+
import {
|
|
35
|
+
buildAdsTableUi,
|
|
36
|
+
type AdsTableBorders,
|
|
37
|
+
type AdsTableDensity,
|
|
38
|
+
type AdsTableUi,
|
|
39
|
+
} from "../utils/ads-table";
|
|
40
|
+
|
|
41
|
+
const props = withDefaults(
|
|
42
|
+
defineProps<{
|
|
43
|
+
data: T[];
|
|
44
|
+
columns: TableColumn<T>[];
|
|
45
|
+
/** md: lista e gestão. sm: grade de dados larga ou prévia. */
|
|
46
|
+
density?: AdsTableDensity;
|
|
47
|
+
/** Zebra só em grade numérica larga. */
|
|
48
|
+
striped?: boolean;
|
|
49
|
+
/** Borda e raio próprios. Dentro de AdsCard, false. */
|
|
50
|
+
framed?: boolean;
|
|
51
|
+
/** lines: só horizontal. grid: horizontal e vertical. */
|
|
52
|
+
borders?: AdsTableBorders;
|
|
53
|
+
/** Degradê por nível. A célula marca `data-depth` de 0 a 2. */
|
|
54
|
+
depth?: boolean;
|
|
55
|
+
loading?: boolean;
|
|
56
|
+
/** Cabeçalho (e rodapé) fixos quando a tabela tem altura máxima. */
|
|
57
|
+
sticky?: boolean | "header" | "footer";
|
|
58
|
+
emptyLabel?: string;
|
|
59
|
+
emptyIcon?: string;
|
|
60
|
+
loadingLabel?: string;
|
|
61
|
+
/** Ajuste pontual de um slot; soma ao padrão. */
|
|
62
|
+
ui?: Partial<AdsTableUi>;
|
|
63
|
+
}>(),
|
|
64
|
+
{
|
|
65
|
+
density: "md",
|
|
66
|
+
striped: false,
|
|
67
|
+
framed: true,
|
|
68
|
+
borders: "lines",
|
|
69
|
+
depth: false,
|
|
70
|
+
loading: false,
|
|
71
|
+
sticky: false,
|
|
72
|
+
emptyLabel: "Nenhum registro encontrado",
|
|
73
|
+
emptyIcon: "i-lucide-inbox",
|
|
74
|
+
loadingLabel: undefined,
|
|
75
|
+
ui: undefined,
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const slots = useSlots();
|
|
80
|
+
|
|
81
|
+
const tableUi = computed(() => {
|
|
82
|
+
const base = buildAdsTableUi({
|
|
83
|
+
density: props.density,
|
|
84
|
+
striped: props.striped,
|
|
85
|
+
framed: props.framed,
|
|
86
|
+
borders: props.borders,
|
|
87
|
+
depth: props.depth,
|
|
88
|
+
});
|
|
89
|
+
if (!slots.expanded) {
|
|
90
|
+
// Árvore via getSubRows: o UTable sempre desenha a linha de detalhe da linha aberta.
|
|
91
|
+
base.tbody = `${base.tbody} [&>tr[data-slot=tr]:not([data-selectable])]:hidden`;
|
|
92
|
+
}
|
|
93
|
+
if (!props.ui) return base;
|
|
94
|
+
const merged = { ...base };
|
|
95
|
+
for (const key of Object.keys(props.ui) as (keyof AdsTableUi)[]) {
|
|
96
|
+
merged[key] = `${base[key]} ${props.ui[key] ?? ""}`.trim();
|
|
97
|
+
}
|
|
98
|
+
return merged;
|
|
99
|
+
});
|
|
100
|
+
</script>
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
3
|
-
<div v-if="title || description || $slots.title" class="min-w-0">
|
|
4
|
-
<slot name="title">
|
|
5
|
-
<p class="font-bold text-[var(--ads-text-primary)]">{{ title }}</p>
|
|
6
|
-
<p v-if="description" class="mt-1 text-sm text-[var(--ads-text-secondary)]">{{ description }}</p>
|
|
7
|
-
</slot>
|
|
8
|
-
</div>
|
|
9
|
-
|
|
10
|
-
<div class="flex min-w-0 flex-wrap items-center gap-3">
|
|
11
|
-
<AdsSearchInput
|
|
12
|
-
v-if="searchable"
|
|
13
|
-
v-model="search"
|
|
14
|
-
size="sm"
|
|
15
|
-
class="w-full sm:w-64"
|
|
16
|
-
:placeholder="searchPlaceholder"
|
|
17
|
-
/>
|
|
18
|
-
<slot />
|
|
19
|
-
</div>
|
|
20
|
-
</div>
|
|
21
|
-
</template>
|
|
22
|
-
|
|
23
|
-
<script setup lang="ts">
|
|
24
|
-
import AdsSearchInput from "./AdsSearchInput.vue";
|
|
25
|
-
|
|
26
|
-
withDefaults(
|
|
27
|
-
defineProps<{
|
|
28
|
-
/** Título à esquerda. Sem título, a busca e os filtros ocupam a faixa. */
|
|
29
|
-
title?: string;
|
|
30
|
-
description?: string;
|
|
31
|
-
/** Mostra o AdsSearchInput. O filtro entra no slot padrão, com AdsFilterSelect. */
|
|
32
|
-
searchable?: boolean;
|
|
33
|
-
searchPlaceholder?: string;
|
|
34
|
-
}>(),
|
|
35
|
-
{
|
|
36
|
-
title: "",
|
|
37
|
-
description: "",
|
|
38
|
-
searchable: true,
|
|
39
|
-
searchPlaceholder: "Pesquisar",
|
|
40
|
-
},
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const search = defineModel<string>("search", { default: "" });
|
|
44
|
-
</script>
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
3
|
+
<div v-if="title || description || $slots.title" class="min-w-0">
|
|
4
|
+
<slot name="title">
|
|
5
|
+
<p class="font-bold text-[var(--ads-text-primary)]">{{ title }}</p>
|
|
6
|
+
<p v-if="description" class="mt-1 text-sm text-[var(--ads-text-secondary)]">{{ description }}</p>
|
|
7
|
+
</slot>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div class="flex min-w-0 flex-wrap items-center gap-3">
|
|
11
|
+
<AdsSearchInput
|
|
12
|
+
v-if="searchable"
|
|
13
|
+
v-model="search"
|
|
14
|
+
size="sm"
|
|
15
|
+
class="w-full sm:w-64"
|
|
16
|
+
:placeholder="searchPlaceholder"
|
|
17
|
+
/>
|
|
18
|
+
<slot />
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import AdsSearchInput from "./AdsSearchInput.vue";
|
|
25
|
+
|
|
26
|
+
withDefaults(
|
|
27
|
+
defineProps<{
|
|
28
|
+
/** Título à esquerda. Sem título, a busca e os filtros ocupam a faixa. */
|
|
29
|
+
title?: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
/** Mostra o AdsSearchInput. O filtro entra no slot padrão, com AdsFilterSelect. */
|
|
32
|
+
searchable?: boolean;
|
|
33
|
+
searchPlaceholder?: string;
|
|
34
|
+
}>(),
|
|
35
|
+
{
|
|
36
|
+
title: "",
|
|
37
|
+
description: "",
|
|
38
|
+
searchable: true,
|
|
39
|
+
searchPlaceholder: "Pesquisar",
|
|
40
|
+
},
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const search = defineModel<string>("search", { default: "" });
|
|
44
|
+
</script>
|