Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Create a tabbed mobile modal
Browse files Browse the repository at this point in the history
  • Loading branch information
obulat committed Sep 28, 2022
1 parent 5e82520 commit a5c8f35
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 45 deletions.
138 changes: 111 additions & 27 deletions src/components/VContentSwitcher/VMobileMenuModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,170 @@
<VModal
ref="nodeRef"
class="mobile-menu ms-auto md:ms-0"
variant="two-thirds"
:label="$t('header.filter-button.simple').toString()"
:initial-focus-element="initialFocusElement"
>
<template #trigger="{ a11yProps, visible }">
<VSearchTypeButton
ref="triggerRef"
:a11y-props="a11yProps"
:visible="visible"
:active-item="activeItem"
aria-controls="content-switcher-modal"
/>
</template>
<nav
id="content-switcher-modal"
class="p-6"
aria-labelledby="content-switcher-heading"
<VTabs
variant="plain"
:selected-id="selectedMenu"
:label="$t('header.aria.menu').toString()"
@change="$emit('change', $event)"
@close="closeModal"
>
<VSearchTypes
ref="searchTypesRef"
size="small"
:active-item="content.activeType.value"
:use-links="true"
@select="selectItem"
/>
<VPageList layout="columns" class="mt-10" />
</nav>
<template #tabs>
<VTab id="content-type" class="!text-sr uppercase ms-8" size="large">
{{ $t(`search-type.heading`) }}
</VTab>
<VTab id="filters" class="!text-sr !uppercase ms-8" size="large">
{{ $t(`header.filter-button.simple`) }}
</VTab>
<VIconButton
class="my-auto border-none text-dark-charcoal-70 ms-auto hover:text-dark-charcoal"
size="search-medium"
:icon-props="{ iconPath: closeIcon }"
:button-props="{
'aria-label': $t('modal.close').toString(),
variant: 'plain',
}"
@click="closeModal"
/>
</template>
<VTabPanel id="content-type" class="h-120 flex-grow overflow-y-auto px-8">
<nav
id="content-switcher-modal"
aria-labelledby="content-switcher-heading"
>
<VSearchTypes
ref="searchTypesRef"
size="small"
:active-item="content.activeType.value"
:use-links="true"
@select="selectItem"
/>
</nav>
</VTabPanel>
<VTabPanel id="filters" class="h-120 flex-grow overflow-y-auto px-8 pt-0">
<VSearchGridFilter
class="!px-0"
:show-filter-header="false"
@close="closeModal"
/>
</VTabPanel>
</VTabs>
<footer
class="mt-auto flex justify-between border-t border-dark-charcoal-20 px-6 py-4"
>
<VButton variant="secondary">{{ $t('filter-list.clear') }}</VButton>
<VButton @click="close">{{ $t('header.see-results') }}</VButton>
</footer>
</VModal>
</template>

<script lang="ts">
import { computed, defineComponent, ref } from '@nuxtjs/composition-api'
import {
computed,
defineComponent,
PropType,
ref,
watch,
} from '@nuxtjs/composition-api'
import usePages from '~/composables/use-pages'
import useSearchType from '~/composables/use-search-type'
import { isMinScreen } from '~/composables/use-media-query'
import type { SearchType, SupportedSearchType } from '~/constants/media'
import type { SupportedSearchType } from '~/constants/media'
import { defineEvent } from '~/types/emits'
import VButton from '~/components/VButton.vue'
import VIconButton from '~/components/VIconButton/VIconButton.vue'
import VModal from '~/components/VModal/VModal.vue'
import VPageList from '~/components/VHeaderOld/VPageMenu/VPageList.vue'
import VSearchTypeButton from '~/components/VContentSwitcher/VSearchTypeButton.vue'
import VSearchTypes from '~/components/VContentSwitcher/VSearchTypes.vue'
import VTabs from '~/components/VTabs/VTabs.vue'
import VTab from '~/components/VTabs/VTab.vue'
import VTabPanel from '~/components/VTabs/VTabPanel.vue'
import VSearchGridFilter from '~/components/VFilters/VSearchGridFilter.vue'
import closeIcon from '~/assets/icons/close-small.svg'
export default defineComponent({
name: 'VMobileMenuModal',
components: {
VButton,
VIconButton,
VModal,
VPageList,
VSearchGridFilter,
VSearchTypeButton,
VSearchTypes,
VTab,
VTabPanel,
VTabs,
},
props: {
activeItem: {
type: String,
required: true,
openTab: {
type: String as PropType<'content-type' | 'filters'>,
default: 'content-type',
},
},
emits: {
select: defineEvent<[item: SearchType]>(),
},
setup(_, { emit }) {
const content = useSearchType()
const pages = usePages()
const searchTypesRef = ref<InstanceType<typeof VSearchTypes> | null>(null)
const nodeRef = ref(null)
const nodeRef = ref<InstanceType<typeof VModal>>(null)
const triggerRef = ref<InstanceType<typeof VSearchTypeButton> | null>(null)
const isMinScreenLg = isMinScreen('lg')
const initialFocusElement = computed(() => {
return searchTypesRef.value?.$el
? (searchTypesRef.value.$el as HTMLElement).querySelector<HTMLElement>(
'[aria-checked="true"]'
)
: null
const searchTypesElement = searchTypesRef.value?.$el
if (!searchTypesElement) return null
return (searchTypesElement as HTMLElement).querySelector<HTMLElement>(
'[aria-checked="true"]'
)
})
const selectItem = (item: SupportedSearchType) => emit('select', item)
const selectedMenu = computed(() => 'content-type')
const closeModal = () => {
nodeRef.value?.close()
}
watch(isMinScreenLg, (isLg) => {
if (isLg) {
closeModal()
}
})
return {
pages,
content,
nodeRef,
searchTypesRef,
closeIcon,
selectItem,
closeModal,
close: closeModal,
initialFocusElement,
selectedMenu,
triggerRef,
}
},
})
Expand Down
1 change: 1 addition & 0 deletions src/components/VContentSwitcher/VSearchTypes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
}"
>
<h4
v-if="index !== 0"
:class="bordered ? 'ps-0' : 'ps-6'"
class="pt-6 pb-4 text-sr font-semibold uppercase pe-6"
>
Expand Down
46 changes: 46 additions & 0 deletions src/components/VContentSwitcher/meta/VMobileMenuModal.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {
ArgsTable,
Canvas,
Description,
Meta,
Story,
} from '@storybook/addon-docs'

import VMobileMenuModal from '~/components/VContentSwitcher/VMobileMenuModal.vue'
import VModalTarget from '~/components/VModal/VModalTarget.vue'

<Meta
title="Components/VMobileMenuModal"
components={VMobileMenuModal}
argTypes={{
close: {
action: 'close',
},
select: {
action: 'select',
},
change: {
action: 'change',
},
}}
/>

export const Template = (args) => ({
template: `<div><VMobileMenuModal v-bind="args" v-on="args"/><VModalTarget /></div>`,
components: { VMobileMenuModal, VModalTarget },
setup() {
return { args }
},
})

# VMobileMenuModal

<Description of={VMobileMenuModal} />

<ArgsTable of={VMobileMenuModal} />

<Canvas>
<Story height="480px" name="Default">
{Template.bind({})}
</Story>
</Canvas>
21 changes: 18 additions & 3 deletions src/components/VFilters/VSearchGridFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
aria-labelledby="filters-heading"
class="filters py-8 px-10"
>
<div class="mt-2 mb-6 flex items-center justify-between">
<header
v-if="showFilterHeader"
class="mt-2 mb-6 flex items-center justify-between"
>
<h4
id="filters-heading"
class="py-2 text-sr font-semibold uppercase leading-8"
Expand All @@ -21,7 +24,7 @@
>
{{ $t('filter-list.clear') }}
</VButton>
</div>
</header>
<form
ref="filtersFormRef"
class="filters-form"
Expand All @@ -37,7 +40,10 @@
@toggle-filter="toggleFilter"
/>
</form>
<footer v-if="isAnyFilterApplied" class="flex justify-between md:hidden">
<footer
v-if="showFilterHeader && isAnyFilterApplied"
class="flex justify-between md:hidden"
>
<VButton variant="primary" @click="$emit('close')">
{{ $t('filter-list.show') }}
</VButton>
Expand Down Expand Up @@ -73,6 +79,15 @@ export default defineComponent({
VButton,
VFilterChecklist,
},
props: {
/**
* Whether to show the header with the title and the clear button.
*/
showFilterHeader: {
type: Boolean,
default: true,
},
},
emits: {
close: defineEvent(),
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/VHeader/VHeaderMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default defineComponent({
isMounted.value = true
})
const selectSearchType = async (type) => {
menuModalRef.value?.closeMenu()
menuModalRef.value?.close()
content.setActiveType(type)
const newPath = app.localePath({
Expand Down
2 changes: 1 addition & 1 deletion src/components/VModal/VModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export default defineComponent({
* @default 'default'
*/
variant: {
type: /** @type {import('@nuxtjs/composition-api').PropType<'default' | 'full'>} */ (
type: /** @type {import('@nuxtjs/composition-api').PropType<'default' | 'full' | 'two-thirds'>} */ (
String
),
default: 'default',
Expand Down
23 changes: 20 additions & 3 deletions src/components/VModal/VModalContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
jarring "shifting" effect when opening the mobile modal.
-->
<div
class="flex w-full shrink-0 justify-between bg-white py-4 pe-3 ps-4 md:justify-end md:bg-tx md:px-0 md:py-3"
v-if="variant === 'default'"
class="flex w-full shrink-0 justify-between py-4 pe-3 ps-4 md:justify-end md:bg-tx md:px-0 md:py-3"
:class="[$style[`top-bar-${variant}`], $style[`top-bar-${mode}`]]"
>
<VLogoButtonOld
Expand All @@ -52,7 +53,7 @@
</slot>

<div
class="w-full flex-grow text-left align-bottom md:rounded-t-md"
class="w-full flex-grow"
:class="[
$style[`modal-content-${variant}`],
$style[`modal-content-${mode}`],
Expand Down Expand Up @@ -125,7 +126,7 @@ export default defineComponent({
required: false,
},
variant: {
type: /** @type {import('@nuxtjs/composition-api').PropType<'default' | 'full'>} */ (
type: /** @type {import('@nuxtjs/composition-api').PropType<'default' | 'full' | 'two-thirds'>} */ (
String
),
default: 'default',
Expand Down Expand Up @@ -187,18 +188,34 @@ export default defineComponent({
.top-bar-full {
@apply flex h-20 w-full shrink-0 justify-between bg-dark-charcoal px-4 py-3 md:items-stretch md:justify-start md:py-4 md:px-7;
}
.top-bar-two-thirds {
@apply bg-tx;
}
.modal-backdrop-two-thirds {
@apply bg-dark-charcoal bg-opacity-75;
}
.modal-default {
@apply md:max-w-[768px] lg:w-[768px] xl:w-[1024px] xl:max-w-[1024px];
}
.modal-dark {
@apply bg-dark-charcoal text-white;
}
.modal-light {
@apply bg-white text-dark-charcoal;
}
.modal-content-default {
@apply text-left align-bottom md:rounded-t-md;
}
.modal-content-full {
@apply flex w-full flex-col justify-between px-6 pb-10;
}
.modal-two-thirds {
@apply mt-50 h-full w-full rounded-t-lg bg-white;
}
.modal-content-two-thirds {
@apply rounded-t-md;
}
.modal-content-dark {
@apply bg-dark-charcoal text-white;
}
Expand Down
Loading

0 comments on commit a5c8f35

Please sign in to comment.