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 Oct 1, 2022
1 parent c0b0772 commit 6e147eb
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 18 deletions.
3 changes: 2 additions & 1 deletion src/components/VContentSwitcher/VSearchTypes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
:size="size"
:bordered="bordered"
type="radiogroup"
class="z-10 max-w-full md:w-[260px]"
class="z-10 max-w-full"
>
<div
v-for="(category, index) in contentTypeGroups"
Expand All @@ -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/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
19 changes: 17 additions & 2 deletions src/components/VTabs/VTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
size="disabled"
variant="plain--avoid"
v-bind="tabProps"
class="label-bold md:description-bold rounded-none border-0 bg-white py-3 px-4 focus-visible:shadow-[0_0_0_1.5px_#c52b9b_inset] md:px-6"
:class="[$style[variant], isSelected && $style[`${variant}-selected`]]"
class="label-bold md:description-bold rounded-none border-0 bg-white focus-visible:shadow-[0_0_0_1.5px_#c52b9b_inset]"
:class="[
$style[variant],
$style[`size-${size}`],
isSelected && $style[`${variant}-selected`],
]"
@click="handleSelection"
@focus="handleFocus"
@mousedown="handleMouseDown"
Expand All @@ -25,6 +29,7 @@ import {
inject,
onMounted,
onUnmounted,
type PropType,
ref,
} from '@nuxtjs/composition-api'
Expand Down Expand Up @@ -54,6 +59,10 @@ export default defineComponent({
type: String,
required: true,
},
size: {
type: String as PropType<'default' | 'large'>,
default: 'default',
},
},
setup(props) {
const tabContext = inject(tabsContextKey)
Expand Down Expand Up @@ -199,4 +208,10 @@ export default defineComponent({
.plain-selected {
@apply rounded-none border-dark-charcoal;
}
.size-default {
@apply py-3 px-4 md:px-6;
}
.size-large {
@apply h-16;
}
</style>
17 changes: 14 additions & 3 deletions src/components/VTabs/VTabs.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<template>
<div>
<div role="tablist" class="flex flex-row" v-bind="accessibleLabel">
<div
role="tablist"
class="flex flex-row items-stretch"
v-bind="accessibleLabel"
>
<slot name="tabs" />
</div>
<slot name="default" />
Expand All @@ -16,9 +20,11 @@ import {
ref,
} from '@nuxtjs/composition-api'
import { tabsContextKey, TabsState } from '~/models/tabs'
import { tabsContextKey, type TabsState, TabVariant } from '~/models/tabs'
import { defineEvent } from '~/types/emits'
import closeIcon from '~/assets/icons/close-small.svg'
/**
* VTabs is an accessible implementation of tabs component that displays one panel at a time.
* @see { https://www.w3.org/TR/wai-aria-practices/#tabpanel }
Expand Down Expand Up @@ -60,7 +66,7 @@ export default defineComponent({
* `plain` tabs only have a line under the tabs, and a thicker line under the selected tab.
*/
variant: {
type: String as PropType<TabsState['variant']['value'][number]>,
type: String as PropType<TabVariant>,
default: 'bordered',
},
/**
Expand All @@ -73,11 +79,13 @@ export default defineComponent({
},
emits: {
change: defineEvent<[string]>(),
close: defineEvent(),
},
setup(props, { emit }) {
const selectedId = ref<TabsState['selectedId']['value']>(props.selectedId)
const tabs = ref<TabsState['tabs']['value']>([])
const panels = ref<TabsState['panels']['value']>([])
const closeButtonRef = ref<HTMLElement | null>(null)
const tabGroupContext: TabsState = {
selectedId,
Expand Down Expand Up @@ -114,9 +122,12 @@ export default defineComponent({
? { 'aria-labelledby': props.label.slice(1) }
: { 'aria-label': props.label }
)
return {
accessibleLabel,
selectedTabId: tabGroupContext.selectedId,
closeIcon,
closeButtonRef,
}
},
})
Expand Down
39 changes: 37 additions & 2 deletions src/components/VTabs/meta/VTabs.stories.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,25 @@ import VTabs from '~/components/VTabs/VTabs.vue'
import VTabPanel from '~/components/VTabs/VTabPanel.vue'
import VTab from '~/components/VTabs/VTab.vue'

<Meta title="Components/VTabs" component={VTabs} />
<Meta
title="Components/VTabs"
component={VTabs}
argTypes={{
variant: {
options: ['bordered', 'plain'],
control: { type: 'radio' },
},
close: {
action: 'close',
},
change: {
action: 'change',
},
}}
/>

export const Template = (args) => ({
template: `<VTabs v-bind="args"><template #tabs>
template: `<VTabs v-bind="args" v-on="args"><template #tabs>
<VTab id='1'>Tab1</VTab><VTab id='2'>Tab2</VTab><VTab id='3'>Tab3</VTab>
</template>
<VTabPanel id='1'>Page 1 content</VTabPanel>
Expand Down Expand Up @@ -43,6 +58,7 @@ such as the attribution panels in Openverse.
name="Default"
args={{
label: 'Default tabs story',
selectedId: '1',
}}
>
{Template.bind({})}
Expand All @@ -69,10 +85,29 @@ These tabs also use the second available variant, `plain`.
name="Manual plain tabs"
args={{
label: 'Manual plain tabs',
selectedId: '1',
manual: true,
variant: 'plain',
}}
>
{Template.bind({})}
</Story>
</Canvas>

### Closeable tabs

If you pass the `showCloseButton` prop, the tab will have a close button at the top end side that will emit a `close` event when clicked.

<Canvas>
<Story
name="Closeable tabs"
args={{
label: 'Closeable tabs',
showCloseButton: true,
variant: 'plain',
selectedId: '1',
}}
>
{Template.bind({})}
</Story>
</Canvas>
3 changes: 2 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"filter-button": {
"simple": "Filters",
"with-count": "{count} Filter|{count} Filters"
}
},
"see-results": "See results"
},
"navigation": {
"about": "About",
Expand Down
6 changes: 4 additions & 2 deletions src/models/tabs.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import type { ComputedRef, InjectionKey, Ref } from '@nuxtjs/composition-api'
import type { ComponentPublicInstance } from '@vue/runtime-dom'

export type TabActivation = 'manual' | 'auto'
export type TabVariant = 'bordered' | 'plain'
export type TabsState = {
// State
selectedId: Ref<string>

activation: ComputedRef<'manual' | 'auto'>
variant: ComputedRef<'bordered' | 'plain'>
activation: ComputedRef<TabActivation>
variant: ComputedRef<TabVariant>

tabs: Ref<Ref<HTMLElement | ComponentPublicInstance | null>[]>
panels: Ref<Ref<HTMLElement | null>[]>
Expand Down

0 comments on commit 6e147eb

Please sign in to comment.