@nextcloud/vue
Version:
Nextcloud vue components
1 lines • 16.1 kB
Source Map (JSON)
{"version":3,"file":"NcAppNavigation-NSjIVdnb.mjs","sources":["../../src/components/NcAppNavigation/NcAppNavigationToggle.vue","../../src/components/NcAppNavigation/NcAppNavigation.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n -->\n\n<script setup lang=\"ts\">\nimport { mdiMenu, mdiMenuOpen } from '@mdi/js'\nimport { computed } from 'vue'\nimport NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'\nimport { t } from '../../l10n.ts'\nimport NcButton from '../NcButton/index.ts'\n\n/**\n * Tracks whether the toggle has been clicked or not.\n * If it has been clicked, switches between the different MenuIcons\n * and emits a boolean indicating its opened status\n */\nconst open = defineModel<boolean>('open', { required: true })\n\nconst title = computed(() => open.value ? t('Close navigation') : t('Open navigation'))\n</script>\n\n<template>\n\t<div class=\"app-navigation-toggle-wrapper\">\n\t\t<NcButton\n\t\t\tclass=\"app-navigation-toggle\"\n\t\t\taria-controls=\"app-navigation-vue\"\n\t\t\t:aria-expanded=\"open ? 'true' : 'false'\"\n\t\t\t:aria-label=\"title\"\n\t\t\t:title\n\t\t\tvariant=\"tertiary\"\n\t\t\t@click=\"open = !open\">\n\t\t\t<template #icon>\n\t\t\t\t<NcIconSvgWrapper :path=\"open ? mdiMenuOpen : mdiMenu\" />\n\t\t\t</template>\n\t\t</NcButton>\n\t</div>\n</template>\n\n<style scoped lang=\"scss\">\n.app-navigation-toggle-wrapper {\n\tposition: absolute;\n\ttop: var(--app-navigation-padding);\n\tinset-inline-end: calc(0px - var(--app-navigation-padding));\n\tmargin-inline-end: calc(-1 * var(--default-clickable-area));\n}\n\nbutton.app-navigation-toggle {\n\tbackground-color: var(--color-main-background);\n}\n</style>\n","<!--\n - SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n\n<docs>\n```vue\n<template>\n\t<div class=\"styleguide-nc-content\">\n\t\t<NcAppNavigation>\n\t\t\t<template #search>\n\t\t\t\t<div class=\"navigation__header\">\n\t\t\t\t\t<NcAppNavigationSearch v-model=\"searchValue\" label=\"Search …\" />\n\t\t\t\t\t<NcActions>\n\t\t\t\t\t\t<NcActionButton close-after-click @click=\"showModal = true\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<IconCogOutline />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\tApp settings (close after click)\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t<NcActionButton @click=\"showModal = true\">\n\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t<IconCogOutline />\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\tApp settings (handle only click)\n\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t</NcActions>\n\t\t\t\t</div>\n\t\t\t</template>\n\t\t\t<template #list>\n\t\t\t\t<NcAppNavigationItem v-for=\"item in items\" :key=\"item\" :name=\"item\">\n\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t<IconCheck :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t</NcAppNavigationItem>\n\t\t\t</template>\n\t\t\t<template #footer>\n\t\t\t\t<div class=\"navigation__footer\">\n\t\t\t\t\t<NcButton wide @click=\"showModal = true\">\n\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t<IconCogOutline />\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\tApp settings\n\t\t\t\t\t</NcButton>\n\t\t\t\t\t<NcModal v-if=\"showModal\" name=\"Modal for focus-trap check\" @close=\"showModal = false\">\n\t\t\t\t\t\t<div class=\"modal-content\">\n\t\t\t\t\t\t\t<h4>Focus-trap should be locked inside the modal</h4>\n\t\t\t\t\t\t\t<NcTextField v-model=\"modalValue\" label=\"Focus me\" />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</NcModal>\n\t\t\t\t</div>\n\t\t\t</template>\n\t\t</NcAppNavigation>\n\t</div>\n</template>\n\n<script>\n\timport IconCheck from 'vue-material-design-icons/Check'\n\timport IconCogOutline from 'vue-material-design-icons/CogOutline'\n\n\texport default {\n\t\tcomponents: {\n\t\t\tIconCheck,\n\t\t\tIconCogOutline,\n\t\t},\n\t\tprovide() {\n\t\t\treturn {\n\t\t\t\t'NcContent:setHasAppNavigation': () => {},\n\t\t\t}\n\t\t},\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\titems: Array.from({ length: 5 }, (v, i) => `Item ${i+1}`),\n\t\t\t\tsearchValue: '',\n\t\t\t\tmodalValue: '',\n\t\t\t\tshowModal: false,\n\t\t\t}\n\t\t},\n\t}\n</script>\n\n<style scoped>\n\t/* Mock NcContent */\n\t.styleguide-nc-content {\n\t\tposition: relative;\n\t\theight: 300px;\n\t\tbackground-color: var(--color-background-plain);\n\t\toverflow: hidden;\n\t}\n\n\t.navigation__header,\n\t.navigation__footer {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tgap: 4px;\n\t\tpadding: 4px;\n\t}\n\n\t.modal-content {\n\t\theight: 120px;\n\t\tpadding: 10px;\n\t}\n</style>\n```\n\nThe navigation bar can be open and closed from anywhere in the app using the\nnextcloud event bus.\n\n### Install the event bus package\n\n```bash\nnpm i -S @nextcloud/event-bus\n```\n\n### Usage\n\n#### Open the navigation\n\n```js static\nimport { emit } from '@nextcloud/event-bus'\nemit('toggle-navigation', {\n\topen: true,\n})\n```\n\n#### Close the navigation\n\n```js static\nimport { emit } from '@nextcloud/event-bus'\nemit('toggle-navigation', {\n\topen: false,\n})\n```\n\n</docs>\n\n<script setup lang=\"ts\">\nimport type { FocusTrap } from 'focus-trap'\nimport type { Slot } from 'vue'\n\nimport { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'\nimport { createFocusTrap } from 'focus-trap'\nimport { inject, onMounted, onUnmounted, ref, useTemplateRef, warn, watch, watchEffect } from 'vue'\nimport NcAppNavigationList from '../NcAppNavigationList/NcAppNavigationList.vue'\nimport NcAppNavigationToggle from './NcAppNavigationToggle.vue'\nimport { useIsMobile } from '../../composables/useIsMobile/index.ts'\nimport { getTrapStack } from '../../utils/focusTrap.ts'\nimport { HAS_APP_NAVIGATION_KEY } from '../NcContent/constants.ts'\n\nconst props = defineProps<{\n\t/**\n\t * The aria label to describe the navigation\n\t */\n\tariaLabel?: string\n\n\t/**\n\t * aria-labelledby attribute to describe the navigation\n\t */\n\tariaLabelledby?: string\n}>()\n\ndefineSlots<{\n\t/**\n\t * The main content of the navigation.\n\t * If no list is passed to the `#list` slot, stretched vertically.\n\t */\n\tdefault?: Slot\n\t/**\n\t * Footer for e.g. `NcAppNavigationSettings`\n\t */\n\tfooter?: Slot\n\t/**\n\t * List for Navigation list items.\n\t * Stretched between the main content and the footer\n\t */\n\tlist?: Slot\n\t/**\n\t * For in-app search you can pass a `NcAppNavigationSearch` component as the slot content.\n\t */\n\tsearch?: Slot\n}>()\n\nlet focusTrap: FocusTrap\nconst setHasAppNavigation = inject(\n\tHAS_APP_NAVIGATION_KEY,\n\t() => warn('NcAppNavigation is not mounted inside NcContent, this is probably an error.'),\n\tfalse,\n)\n\nconst appNavigationContainerElement = useTemplateRef('appNavigationContainer')\nconst isMobile = useIsMobile()\nconst open = ref(!isMobile.value)\n\nwatchEffect(() => {\n\tif (!props.ariaLabel && !props.ariaLabelledby) {\n\t\twarn('NcAppNavigation requires either `ariaLabel` or `ariaLabelledby` to be set for accessibility.')\n\t}\n})\n\nwatch(isMobile, () => {\n\topen.value = !isMobile.value\n})\n\nwatch(open, () => {\n\ttoggleFocusTrap()\n})\n\nonMounted(() => {\n\tsetHasAppNavigation(true)\n\tsubscribe('toggle-navigation', toggleNavigationByEventBus)\n\t// Emit an event with the initial state of the navigation\n\temit('navigation-toggled', {\n\t\topen: open.value,\n\t})\n\n\tfocusTrap = createFocusTrap(appNavigationContainerElement.value!, {\n\t\tallowOutsideClick: true,\n\t\tfallbackFocus: appNavigationContainerElement.value!,\n\t\ttrapStack: getTrapStack(),\n\t\tescapeDeactivates: false,\n\t})\n\ttoggleFocusTrap()\n})\n\nonUnmounted(() => {\n\tsetHasAppNavigation(false)\n\tunsubscribe('toggle-navigation', toggleNavigationByEventBus)\n\tfocusTrap.deactivate()\n})\n\n/**\n * Toggle the navigation\n *\n * @param state set the state instead of inverting the current one\n */\nfunction toggleNavigation(state?: boolean): void {\n\t// Early return if already in that state\n\tif (open.value === state) {\n\t\temit('navigation-toggled', {\n\t\t\topen: open.value,\n\t\t})\n\t\treturn\n\t}\n\n\topen.value = state === undefined ? !open.value : state\n\tconst bodyStyles = getComputedStyle(document.body)\n\tconst animationLength = parseInt(bodyStyles.getPropertyValue('--animation-quick')) || 100\n\n\tsetTimeout(() => {\n\t\temit('navigation-toggled', {\n\t\t\topen: open.value,\n\t\t})\n\t// We wait for 1.5 times the animation length to give the animation time to really finish.\n\t}, 1.5 * animationLength)\n}\n\n/**\n * Handler for the event-bus navigation event.\n *\n * @param context - The event bus context\n * @param context.open - The new navigation open state\n */\nfunction toggleNavigationByEventBus({ open }: { open: boolean }): void {\n\treturn toggleNavigation(open)\n}\n\n/**\n * Activate focus trap if it is currently needed, otherwise deactivate\n */\nfunction toggleFocusTrap(): void {\n\tif (isMobile.value && open.value) {\n\t\tfocusTrap.activate()\n\t} else {\n\t\tfocusTrap.deactivate()\n\t}\n}\n\n/**\n * Handle hotkey for closing the navigation.\n */\nfunction handleEsc(): void {\n\tif (isMobile.value) {\n\t\ttoggleNavigation(false)\n\t}\n}\n</script>\n\n<template>\n\t<div\n\t\tref=\"appNavigationContainer\"\n\t\tclass=\"app-navigation\"\n\t\t:class=\"{ 'app-navigation--closed': !open }\">\n\t\t<nav\n\t\t\tid=\"app-navigation-vue\"\n\t\t\t:aria-hidden=\"open ? 'false' : 'true'\"\n\t\t\t:aria-label=\"ariaLabel || undefined\"\n\t\t\t:aria-labelledby=\"ariaLabelledby || undefined\"\n\t\t\tclass=\"app-navigation__content\"\n\t\t\t:inert=\"!open || undefined\"\n\t\t\t@keydown.esc=\"handleEsc\">\n\t\t\t<div class=\"app-navigation__search\">\n\t\t\t\t<slot name=\"search\" />\n\t\t\t</div>\n\n\t\t\t<div class=\"app-navigation__body\" :class=\"{ 'app-navigation__body--no-list': !$slots.list }\">\n\t\t\t\t<slot />\n\t\t\t</div>\n\n\t\t\t<NcAppNavigationList v-if=\"$slots.list\" class=\"app-navigation__list\">\n\t\t\t\t<slot name=\"list\" />\n\t\t\t</NcAppNavigationList>\n\n\t\t\t<slot name=\"footer\" />\n\t\t</nav>\n\t\t<NcAppNavigationToggle :open @update:open=\"toggleNavigation\" />\n\t</div>\n</template>\n\n<style lang=\"scss\">\n.app-navigation,\n.app-content {\n\t/** Distance of the app navigation toggle and the first navigation item to the top edge of the app content container */\n\t--app-navigation-padding: #{$app-navigation-padding};\n}\n</style>\n\n<style lang=\"scss\" scoped>\n.app-navigation {\n\t// Set scoped variable override\n\t// Using --color-text-maxcontrast as a fallback evaluates to an invalid value as it references itself in this scope instead of the variable defined higher up\n\t--color-text-maxcontrast: var(--color-text-maxcontrast-background-blur, var(--color-text-maxcontrast-default));\n\ttransition: transform var(--animation-quick), margin var(--animation-quick);\n\twidth: $navigation-width;\n\t// Left toggle button padding + toggle button + right padding from NcAppContent\n\t--app-navigation-max-width: calc(100vw - (var(--app-navigation-padding) + var(--default-clickable-area) + var(--default-grid-baseline)));\n\tmax-width: var(--app-navigation-max-width);\n\tposition: relative;\n\ttop: 0;\n\tinset-inline-start: 0;\n\tpadding: 0px;\n\t// Above NcAppContent\n\tz-index: 1800;\n\theight: 100%;\n\t-webkit-user-select: none;\n\t-moz-user-select: none;\n\t-ms-user-select: none;\n\tuser-select: none;\n\tflex-grow: 0;\n\tflex-shrink: 0;\n\tbackground-color: var(--color-main-background-blur, var(--color-main-background));\n\t-webkit-backdrop-filter: var(--filter-background-blur, none);\n\tbackdrop-filter: var(--filter-background-blur, none);\n\n\t&--closed {\n\t\tmargin-inline-start: calc(-1 * min($navigation-width, var(--app-navigation-max-width)));\n\t}\n\n\t&__search {\n\t\twidth: 100%;\n\t}\n\n\t&__body {\n\t\toverflow-y: scroll;\n\t}\n\n\t// For legacy purposes support passing a bare list to the content in #default slot and including #footer slot\n\t// Same styles as NcAppNavigationList\n\t&__content > ul {\n\t\tposition: relative;\n\t\twidth: 100%;\n\t\toverflow-x: hidden;\n\t\toverflow-y: auto;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tgap: var(--default-grid-baseline, 4px);\n\t\tpadding: var(--app-navigation-padding);\n\t}\n\n\t// Always stretch the navigation list\n\t& &__list {\n\t\theight: 100%;\n\t}\n\n\t// Stretch the main content if there is no stretched list\n\t&__body--no-list {\n\t\tflex: 1 1 auto;\n\t\toverflow: auto;\n\t\theight: 100%;\n\t}\n\n\t&__content {\n\t\theight: 100%;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t}\n}\n\n// Add extra border for high contrast mode\n[data-themes*=\"highcontrast\"] {\n\t.app-navigation {\n\t\tborder-inline-end: 1px solid var(--color-border);\n\t}\n}\n\n// When on mobile, we make the navigation slide over the NcAppContent\n@media only screen and (max-width: $breakpoint-mobile) {\n\t.app-navigation {\n\t\tposition: absolute;\n\t\tborder-inline-end: 1px solid var(--color-border);\n\t}\n}\n\n// Put the toggle behind NcAppSidebar on small screens\n@media only screen and (max-width: $breakpoint-small-mobile) {\n\t.app-navigation {\n\t\tz-index: 1400;\n\t}\n}\n\n</style>\n"],"names":["_useModel","_openBlock","_createElementBlock","_hoisted_1","_createVNode","_unref","open","_normalizeClass","_createElementVNode","ariaLabel","ariaLabelledby","_renderSlot","$slots","_createBlock"],"mappings":"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"}