@nexdom/uimed-vue 2.5.0 → 2.6.1
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/dist/components/main/back-to-top.js +5 -0
- package/dist/components/main/back-to-top.vue_vue_type_script_setup_true_lang.js +35 -0
- package/dist/components/main/main.vue_vue_type_script_setup_true_lang.js +17 -4
- package/dist/components/navigation-menu/navigation-menu.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/composables/navigation/use-back-to-top.js +36 -0
- package/dist/consts/icons.d.ts +1 -0
- package/dist/consts/icons.js +2 -1
- package/package.json +1 -1
- package/skills/uimed-vue/SKILL.md +1 -0
- package/skills/uimed-vue/references/api/components/main.md +4 -0
- package/skills/uimed-vue/references/guide/components/main.md +35 -0
- package/skills/uimed-vue/references/guide/icons.md +1 -1
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import back_to_top_vue_vue_type_script_setup_true_lang_default from "./back-to-top.vue_vue_type_script_setup_true_lang.js";
|
|
2
|
+
//#region src/components/main/back-to-top.vue
|
|
3
|
+
var back_to_top_default = back_to_top_vue_vue_type_script_setup_true_lang_default;
|
|
4
|
+
//#endregion
|
|
5
|
+
export { back_to_top_default as default };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { icons } from "../../consts/icons.js";
|
|
2
|
+
import { createBlock, createCommentVNode, createElementVNode, createVNode, defineComponent, openBlock, unref, withCtx } from "vue";
|
|
3
|
+
import { VFab, VIcon } from "vuetify/components";
|
|
4
|
+
var back_to_top_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
5
|
+
inheritAttrs: false,
|
|
6
|
+
__name: "back-to-top",
|
|
7
|
+
props: { active: {
|
|
8
|
+
type: Boolean,
|
|
9
|
+
required: true
|
|
10
|
+
} },
|
|
11
|
+
emits: ["click"],
|
|
12
|
+
setup(__props, { emit: __emit }) {
|
|
13
|
+
const props = __props;
|
|
14
|
+
const emit = __emit;
|
|
15
|
+
return (_ctx, _cache) => {
|
|
16
|
+
return openBlock(), createBlock(unref(VFab), {
|
|
17
|
+
active: props.active,
|
|
18
|
+
icon: unref(icons).backToTop,
|
|
19
|
+
color: "tertiary",
|
|
20
|
+
location: "bottom end",
|
|
21
|
+
app: "",
|
|
22
|
+
onClick: _cache[0] || (_cache[0] = ($event) => emit("click"))
|
|
23
|
+
}, {
|
|
24
|
+
default: withCtx(() => [
|
|
25
|
+
createVNode(unref(VIcon), { icon: unref(icons).backToTop }, null, 8, ["icon"]),
|
|
26
|
+
createCommentVNode(" The FAB gives its attributes to its wrapper instead of its button, so the button can't take\n an aria-label: its name is a text that only screen readers read, which no prop provides "),
|
|
27
|
+
_cache[1] || (_cache[1] = createElementVNode("span", { class: "d-sr-only" }, "Retornar ao topo", -1))
|
|
28
|
+
]),
|
|
29
|
+
_: 1
|
|
30
|
+
}, 8, ["active", "icon"]);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
//#endregion
|
|
35
|
+
export { back_to_top_vue_vue_type_script_setup_true_lang_default as default };
|
|
@@ -2,12 +2,15 @@ import app_bar_default from "../app-bar/app-bar.js";
|
|
|
2
2
|
import { useMobileNavigation } from "../../composables/navigation/use-navigation-rail.js";
|
|
3
3
|
import navigation_menu_default from "../navigation-menu/navigation-menu.js";
|
|
4
4
|
import footer_default from "../footer/footer.js";
|
|
5
|
+
import back_to_top_default from "./back-to-top.js";
|
|
5
6
|
import container_default from "../grid/container/container.js";
|
|
6
7
|
import toast_default from "../dialogs/toast.js";
|
|
7
8
|
import dialog_host_default from "../dialogs/dialog-host.js";
|
|
8
9
|
import useVuetifyBackgroundColor from "../../composables/colors/use-vuetify-background-color.js";
|
|
9
|
-
import
|
|
10
|
+
import useBackToTop from "../../composables/navigation/use-back-to-top.js";
|
|
11
|
+
import { computed, createBlock, createCommentVNode, createSlots, createVNode, defineComponent, mergeModels, mergeProps, normalizeClass, normalizeProps, openBlock, ref, renderSlot, unref, useModel, useSlots, useTemplateRef, withCtx, withDirectives } from "vue";
|
|
10
12
|
import { VApp, VMain } from "vuetify/components";
|
|
13
|
+
import { Scroll } from "vuetify/directives";
|
|
11
14
|
var main_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
12
15
|
inheritAttrs: false,
|
|
13
16
|
__name: "main",
|
|
@@ -61,6 +64,7 @@ var main_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
61
64
|
const backgroundClass = useVuetifyBackgroundColor(() => props.background);
|
|
62
65
|
const notificationsOpen = useModel(__props, "notificationsOpen");
|
|
63
66
|
const navigationMenuOpen = ref(false);
|
|
67
|
+
const { visible, onScroll, backToTop } = useBackToTop(useTemplateRef("main"));
|
|
64
68
|
return (_ctx, _cache) => {
|
|
65
69
|
return openBlock(), createBlock(unref(VApp), {
|
|
66
70
|
class: normalizeClass(unref(backgroundClass)),
|
|
@@ -82,7 +86,11 @@ var main_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
82
86
|
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => navigationMenuOpen.value = $event),
|
|
83
87
|
loading: props.loading
|
|
84
88
|
}), null, 16, ["modelValue", "loading"])) : createCommentVNode("v-if", true),
|
|
85
|
-
|
|
89
|
+
createCommentVNode(" Focusable, so the back-to-top button can move the focus to the start of the content "),
|
|
90
|
+
createVNode(unref(VMain), {
|
|
91
|
+
ref: "main",
|
|
92
|
+
tabindex: "-1"
|
|
93
|
+
}, {
|
|
86
94
|
default: withCtx(() => [createCommentVNode(" Full height, so the page's rows and containers with `fullHeight` have a height to fill "), createVNode(container_default, { "full-height": "" }, {
|
|
87
95
|
default: withCtx(() => [
|
|
88
96
|
renderSlot(_ctx.$slots, "default"),
|
|
@@ -92,8 +100,13 @@ var main_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
|
|
|
92
100
|
_: 3
|
|
93
101
|
})]),
|
|
94
102
|
_: 3
|
|
95
|
-
}),
|
|
96
|
-
props.footer?.description ? (openBlock(), createBlock(footer_default, normalizeProps(mergeProps({ key: 2 }, props.footer)), null, 16)) : createCommentVNode("v-if", true)
|
|
103
|
+
}, 512),
|
|
104
|
+
props.footer?.description ? (openBlock(), createBlock(footer_default, normalizeProps(mergeProps({ key: 2 }, props.footer)), null, 16)) : createCommentVNode("v-if", true),
|
|
105
|
+
createCommentVNode(" After the footer, so the layout places it above the footer "),
|
|
106
|
+
withDirectives(createVNode(back_to_top_default, {
|
|
107
|
+
active: unref(visible),
|
|
108
|
+
onClick: unref(backToTop)
|
|
109
|
+
}, null, 8, ["active", "onClick"]), [[unref(Scroll), unref(onScroll)]])
|
|
97
110
|
]),
|
|
98
111
|
_: 3
|
|
99
112
|
}, 8, ["class", "data-testid"]);
|
package/dist/components/navigation-menu/navigation-menu.vue_vue_type_script_setup_true_lang.js
CHANGED
|
@@ -149,11 +149,12 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
149
149
|
type: "list-item@6",
|
|
150
150
|
color: "tertiary"
|
|
151
151
|
}, {
|
|
152
|
-
default: withCtx(() => [createCommentVNode(" Vuetify's `list` role only allows list items, but the items are links.\n `indent` aligns the items of a group with its title, which the 24px between the icons and\n the titles (`settings.scss`) move 8px to the left of Vuetify's default indentation. It\n replaces the group's prepend width too, so it's the icon (24px) plus that gap (24px) "), createVNode(unref(VList), {
|
|
152
|
+
default: withCtx(() => [createCommentVNode(" Vuetify's `list` role only allows list items, but the items are links.\n Vuetify makes the list the only tab stop and takes its items out of the tab order, for the arrow\n keys: the items are links and buttons, so each one is a tab stop instead (the arrow keys still\n move between them), and the list stays out of the tab order.\n `indent` aligns the items of a group with its title, which the 24px between the icons and\n the titles (`settings.scss`) move 8px to the left of Vuetify's default indentation. It\n replaces the group's prepend width too, so it's the icon (24px) plus that gap (24px) "), createVNode(unref(VList), {
|
|
153
153
|
opened: unref(opened),
|
|
154
154
|
"onUpdate:opened": _cache[1] || (_cache[1] = ($event) => isRef(opened) ? opened.value = $event : null),
|
|
155
155
|
nav: "",
|
|
156
156
|
role: "group",
|
|
157
|
+
tabindex: "-1",
|
|
157
158
|
indent: 48
|
|
158
159
|
}, {
|
|
159
160
|
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(filteredItems.value, (item, index) => {
|
|
@@ -168,6 +169,7 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
168
169
|
"aria-expanded": isOpen,
|
|
169
170
|
"aria-selected": void 0,
|
|
170
171
|
role: void 0,
|
|
172
|
+
tabindex: "0",
|
|
171
173
|
tag: "button",
|
|
172
174
|
type: "button",
|
|
173
175
|
class: "w-100 text-start v-treeview-item--activatable-group-activator"
|
|
@@ -180,6 +182,7 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
180
182
|
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(item.items, (childItem, childIndex) => {
|
|
181
183
|
return openBlock(), createBlock(unref(VListItem), mergeProps({ key: childIndex }, { ref_for: true }, unref(routeToListItemProps)(childItem.route), {
|
|
182
184
|
title: childItem.description,
|
|
185
|
+
tabindex: "0",
|
|
183
186
|
onClick: childItem.action
|
|
184
187
|
}), null, 16, ["title", "onClick"]);
|
|
185
188
|
}), 128))]),
|
|
@@ -188,7 +191,10 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
|
|
|
188
191
|
key: 1,
|
|
189
192
|
title: item.description,
|
|
190
193
|
"prepend-icon": unref(icons)[item.icon]
|
|
191
|
-
}, { ref_for: true }, unref(routeToListItemProps)(item.route), {
|
|
194
|
+
}, { ref_for: true }, unref(routeToListItemProps)(item.route), {
|
|
195
|
+
tabindex: "0",
|
|
196
|
+
onClick: item.action
|
|
197
|
+
}), null, 16, [
|
|
192
198
|
"title",
|
|
193
199
|
"prepend-icon",
|
|
194
200
|
"onClick"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { onMounted, ref, toValue } from "vue";
|
|
2
|
+
//#region src/composables/navigation/use-back-to-top.ts
|
|
3
|
+
/**
|
|
4
|
+
* Back-to-top button's state for `content`, the template ref of the component whose element the
|
|
5
|
+
* button brings into view and focuses (which must be focusable, e.g. with `tabindex="-1"`). The
|
|
6
|
+
* button shows once the top of the content is a screen above the viewport, instead of after the page
|
|
7
|
+
* scrolls a screen: in apps both are the same, but on pages with more than one `UMain` (like the
|
|
8
|
+
* docs) only the one scrolled past shows it.
|
|
9
|
+
*/
|
|
10
|
+
function useBackToTop(content) {
|
|
11
|
+
const visible = ref(false);
|
|
12
|
+
function onScroll() {
|
|
13
|
+
const element = toValue(content)?.$el;
|
|
14
|
+
visible.value = element ? -element.getBoundingClientRect().top > globalThis.innerHeight : false;
|
|
15
|
+
}
|
|
16
|
+
function backToTop() {
|
|
17
|
+
const element = toValue(content)?.$el;
|
|
18
|
+
if (!element) return;
|
|
19
|
+
globalThis.scrollTo({
|
|
20
|
+
top: globalThis.scrollY + element.getBoundingClientRect().top,
|
|
21
|
+
behavior: prefersReducedMotion() ? "instant" : "smooth"
|
|
22
|
+
});
|
|
23
|
+
element.focus({ preventScroll: true });
|
|
24
|
+
}
|
|
25
|
+
onMounted(onScroll);
|
|
26
|
+
return {
|
|
27
|
+
visible,
|
|
28
|
+
onScroll,
|
|
29
|
+
backToTop
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function prefersReducedMotion() {
|
|
33
|
+
return globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { useBackToTop as default };
|
package/dist/consts/icons.d.ts
CHANGED
package/dist/consts/icons.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mdiAccount, mdiAccountGroup, mdiAccountGroupOutline, mdiAccountOutline, mdiAlertCircle, mdiBellOutline, mdiCalendar, mdiCalendarOutline, mdiChartBox, mdiChartBoxOutline, mdiCheckCircle, mdiCheckboxBlankOutline, mdiCheckboxMarked, mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiClipboardText, mdiClipboardTextOutline, mdiClose, mdiCloseCircle, mdiCog, mdiCogOutline, mdiEye, mdiEyeOff, mdiFileDocument, mdiFileDocumentOutline, mdiFlask, mdiFlaskOutline, mdiFolder, mdiFolderOutline, mdiHelpCircleOutline, mdiHome, mdiHomeOutline, mdiHospitalBuilding, mdiInformation, mdiMagnify, mdiMenu, mdiMenuDown, mdiPill, mdiStethoscope, mdiWallet, mdiWalletOutline } from "@mdi/js";
|
|
1
|
+
import { mdiAccount, mdiAccountGroup, mdiAccountGroupOutline, mdiAccountOutline, mdiAlertCircle, mdiArrowUp, mdiBellOutline, mdiCalendar, mdiCalendarOutline, mdiChartBox, mdiChartBoxOutline, mdiCheckCircle, mdiCheckboxBlankOutline, mdiCheckboxMarked, mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiClipboardText, mdiClipboardTextOutline, mdiClose, mdiCloseCircle, mdiCog, mdiCogOutline, mdiEye, mdiEyeOff, mdiFileDocument, mdiFileDocumentOutline, mdiFlask, mdiFlaskOutline, mdiFolder, mdiFolderOutline, mdiHelpCircleOutline, mdiHome, mdiHomeOutline, mdiHospitalBuilding, mdiInformation, mdiMagnify, mdiMenu, mdiMenuDown, mdiPill, mdiStethoscope, mdiWallet, mdiWalletOutline } from "@mdi/js";
|
|
2
2
|
//#region src/consts/icons.ts
|
|
3
3
|
/**
|
|
4
4
|
* SVG path of each icon, by its name, in the order the "Ícones" guide page lists them. The paths
|
|
@@ -51,6 +51,7 @@ const icons = {
|
|
|
51
51
|
showPassword: mdiEye,
|
|
52
52
|
hidePassword: mdiEyeOff,
|
|
53
53
|
close: mdiClose,
|
|
54
|
+
backToTop: mdiArrowUp,
|
|
54
55
|
clear: mdiCloseCircle,
|
|
55
56
|
dropdown: mdiMenuDown,
|
|
56
57
|
subgroup: mdiMenuDown,
|
package/package.json
CHANGED
|
@@ -83,6 +83,7 @@ createApp(App).use(createUimed()).mount("#app");
|
|
|
83
83
|
type SectionAction = NonNullable<ComponentProps<typeof USection>["actions"]>[number];
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
- `UMain` shows a "back to top" floating button on its own once the page scrolls past a screen, so don't add one to the screens.
|
|
86
87
|
- Icons are passed by name (e.g. `icon="cog"`), from the list in [`references/guide/icons.md`](references/guide/icons.md).
|
|
87
88
|
- The components' internal texts, such as their messages and the names read by screen readers, are in Portuguese (pt-BR).
|
|
88
89
|
|
|
@@ -105,6 +105,10 @@ Item dentro de um [grupo](#navigationmenuparent). Tem as mesmas propriedades do
|
|
|
105
105
|
| `dataTestid` | `string` | | Aplica atributo `data-testid` para testes sobre o rodapé. |
|
|
106
106
|
| `description` | `string` | | Texto exibido no rodapé, por exemplo a versão da aplicação (`"Versão 1.4.2"`). **O rodapé será ocultado quando omitido ou vazio.** |
|
|
107
107
|
|
|
108
|
+
## Retornar ao topo
|
|
109
|
+
|
|
110
|
+
O componente exibe um botão flutuante "Retornar ao topo" no canto inferior direito quando o usuário rola a página por mais de uma tela, sem props para configurá-lo. Veja [Retornar ao topo](../../guide/components/main.md#retornar-ao-topo) no guia.
|
|
111
|
+
|
|
108
112
|
## Eventos
|
|
109
113
|
|
|
110
114
|
| Event | Retorno | Descrição |
|
|
@@ -391,6 +391,41 @@ const appBar: NonNullable<MainProps["appBar"]> = {
|
|
|
391
391
|
</script>
|
|
392
392
|
```
|
|
393
393
|
|
|
394
|
+
## Retornar ao topo
|
|
395
|
+
|
|
396
|
+
Em páginas longas, como listagens e formulários extensos, o `UMain` exibe um botão flutuante no canto inferior direito para voltar ao início do conteúdo. Ele aparece quando o usuário rola a página por mais de uma tela e some ao voltar ao topo, sem nenhuma prop: páginas curtas nunca o exibem.
|
|
397
|
+
|
|
398
|
+
O botão fica acima do rodapé, quando exibido, e abaixo dos diálogos e dos toasts. Ao ser acionado, ele rola a página até o início do conteúdo de forma suave, ou imediata quando o usuário prefere reduzir os movimentos nas configurações do sistema, e move o foco para o início do conteúdo, para que quem navega pelo teclado continue a partir dali. Os leitores de tela o anunciam como "Retornar ao topo".
|
|
399
|
+
|
|
400
|
+
Role a demonstração abaixo para ver o botão:
|
|
401
|
+
|
|
402
|
+
```vue
|
|
403
|
+
<template>
|
|
404
|
+
<u-main :appBar :footer>
|
|
405
|
+
<h2>Manual do usuário</h2>
|
|
406
|
+
<p v-for="step in 12" :key="step">{{ step }}. {{ manualText }}</p>
|
|
407
|
+
</u-main>
|
|
408
|
+
</template>
|
|
409
|
+
|
|
410
|
+
<script lang="ts" setup>
|
|
411
|
+
import type { ComponentProps } from "vue-component-type-helpers";
|
|
412
|
+
import { UMain } from "@nexdom/uimed-vue/components";
|
|
413
|
+
|
|
414
|
+
type MainProps = ComponentProps<typeof UMain>;
|
|
415
|
+
|
|
416
|
+
const manualText =
|
|
417
|
+
"Este manual descreve cada etapa do atendimento, do agendamento da consulta à emissão do relatório final, com as orientações para preencher os formulários, conferir os dados do paciente antes de salvar e resolver as pendências mais comuns. Siga as etapas na ordem em que aparecem e, em caso de dúvida, procure a equipe de suporte da sua unidade.";
|
|
418
|
+
|
|
419
|
+
const appBar: NonNullable<MainProps["appBar"]> = {
|
|
420
|
+
title: "Menu superior",
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const footer: NonNullable<MainProps["footer"]> = {
|
|
424
|
+
description: "Versão 1.4.2",
|
|
425
|
+
};
|
|
426
|
+
</script>
|
|
427
|
+
```
|
|
428
|
+
|
|
394
429
|
## Eventos
|
|
395
430
|
|
|
396
431
|
### Notificações
|
|
@@ -57,7 +57,7 @@ A lista começa pelos ícones de conteúdo, como `home` e `calendar`, cujos nome
|
|
|
57
57
|
|
|
58
58
|
Em seguida vêm os ícones que os próprios componentes exibem, cujos nomes descrevem a função com que os componentes os usam:
|
|
59
59
|
|
|
60
|
-
- Ações, como `help`, `search`, `showPassword` e `
|
|
60
|
+
- Ações, como `help`, `search`, `showPassword`, `close` e `backToTop`.
|
|
61
61
|
- Expansão de menus e grupos, como `dropdown`, `expand` e `collapse`, e navegação entre páginas, com `prev` e `next`.
|
|
62
62
|
- Marcações da caixa de seleção: `checkboxOn` e `checkboxOff`.
|
|
63
63
|
- Retorno ao usuário, nas cores das mensagens: `success`, `info`, `warning` e `error`.
|