@nexdom/uimed-vue 2.5.0 → 2.6.0

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.
@@ -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 { computed, createBlock, createCommentVNode, createSlots, createVNode, defineComponent, mergeModels, mergeProps, normalizeClass, normalizeProps, openBlock, ref, renderSlot, unref, useModel, useSlots, withCtx } from "vue";
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
- createVNode(unref(VMain), null, {
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"]);
@@ -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 };
@@ -50,6 +50,7 @@ export declare const icons: {
50
50
  showPassword: string;
51
51
  hidePassword: string;
52
52
  close: string;
53
+ backToTop: string;
53
54
  clear: string;
54
55
  dropdown: string;
55
56
  subgroup: string;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexdom/uimed-vue",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "A ui framework for NEXDOM applications.",
5
5
  "keywords": [
6
6
  "lib",
@@ -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 `close`.
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`.