Skip to content
This repository has been archived by the owner on Jan 27, 2025. It is now read-only.

poc(stor-1893): sb tab menu prime #503

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
763f7ea
feat(work-1517): add auto grow in textarea
fernandacanto Jan 18, 2024
b409ffc
Merge pull request #500 from storyblok/develop
lisilinhart Jan 19, 2024
e9c5b4d
refactor(work-1517): update input method
fernandacanto Jan 19, 2024
68bc79e
poc: introduce PV tab menu
Jan 19, 2024
c0783d9
poc: introduce PV tab menu
Jan 19, 2024
38d84e7
chore: update design
Jan 19, 2024
65d41f5
chore: update design
Jan 19, 2024
332e36a
fix: design issues
Jan 19, 2024
30af77a
Merge pull request #501 from storyblok/feat/work-1517-add-auto-grow-i…
lisilinhart Jan 19, 2024
506b148
chore: update vertical styling
Jan 22, 2024
c998ef1
fix: css settings
Jan 22, 2024
251e3ad
refactor: base tabs functionality to this component
Jan 22, 2024
d60cd0c
chore: update component
Jan 22, 2024
6c33a2b
chore: fix unmounting
Jan 22, 2024
bbce916
fix: cleanUpResize.stop() function call
Jan 22, 2024
af024c9
chore: add the loading
Jan 22, 2024
3cacf80
chore: update comonent dependency
Jan 22, 2024
43406d2
chore: update css
Jan 22, 2024
52fbe76
chore: update focus style
Jan 22, 2024
1f375c5
chore: merge develop
Jan 22, 2024
7920402
fix: vertical design
Jan 22, 2024
b9a5ddb
fix: story section
Jan 23, 2024
a45425f
chore: update docs
Jan 23, 2024
0c73c3a
chore: remove legacy file
Jan 23, 2024
0cd0e76
Merge branch 'main' into poc/stor-1893-sb-tabs-prime
Jan 25, 2024
de2ae7f
Merge branch 'develop' into poc/stor-1893-sb-tabs-prime
Jan 25, 2024
c638116
fix: broken template
Jan 25, 2024
b49ed04
Merge branch 'develop' into poc/stor-1893-sb-tabs-prime
Jan 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
],
"dependencies": {
"@popperjs/core": "^2.11.8",
"@vueuse/core": "^10.7.2",
"click-outside-vue3": "^4.0.1",
"dayjs": "^1.11.10",
"dompurify": "^3.0.6",
Expand Down
106 changes: 106 additions & 0 deletions src/components/TabMenu/SbTabMenu.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import SbTabMenu from './index'
import type { Args, Meta, StoryObj } from '@storybook/vue3'
import type { MenuItem } from 'primevue/menuitem'
import SbIcon from '../Icon'
import SbAvatar from '../Avatar'

const defaultItems = [
{
label: 'Option 1',
},
{
label: 'Option 2',
},
{
label: 'Option Disabled',
disabled: true,
},
{
label: 'Option Invisible',
visible: false,
},
{
label: 'Customized Option',
class: 'sb-custom-class',
},
{
label: 'Option 3',
},
{
label: 'Option 4',
},
{
label: 'Option 5',
},
{
label: 'Option 6',
},
{
label: 'Option 7',
},
] as MenuItem[]

type Story = StoryObj<typeof SbTabMenu>

const meta: Meta<typeof SbTabMenu> = {
title: 'Navigation/SbTabMenu',
args: {
model: defaultItems,
},
render: (args): unknown => ({
components: { SbTabMenu },
setup(): Args {
return { args }
},
template: `<SbTabMenu v-bind="args" />`,
}),
}

export default meta

export const Default: Story = {}

export const VerticalMenu: Story = {
args: {
vertical: true,
},
}

export const LoadingMenu: Story = {
args: {
isLoading: true,
},
}

export const WithSlot = (): unknown => ({
components: { SbTabMenu, SbIcon, SbAvatar },
setup(): Args {
const model = [
{
label: 'Option 1',
iconLeft: 'chevron-down-circle',
},
{
label: 'Option 2',
iconRight: 'search',
},
{
label: 'Option 3',
avatar: 'https://avatars.githubusercontent.com/u/6871945?v=4',
},
]
return { model }
},
template: `<SbTabMenu :model="model">
<template #item="{ item, props }">
<a v-bind="props.action" role="menuitem">
<p class="custom-slot">
<SbAvatar v-if="item.avatar" :src="item.avatar" />
<SbIcon v-if="item.iconLeft" :name="item.iconLeft" />
{{ item.label }}
<SbIcon v-if="item.iconRight" :name="item.iconRight" />
</p>
</a>
</template>
</SbTabMenu>`,
})
179 changes: 179 additions & 0 deletions src/components/TabMenu/SbTabMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<template>
<div class="sb-tab-menu" :class="computedClasses">
<template v-if="isLoading">
<SbLoading type="placeholder" class="sb-tab-menu__loading">
<SbLoadingPlaceholder width="100px" height="24px" />
<SbLoadingPlaceholder width="100px" height="24px" />
<SbLoadingPlaceholder width="100px" height="24px" />
</SbLoading>
</template>
<template v-else>
<slot name="left-slot"></slot>
<SbIconButton
v-if="showLeftArrow"
class="sb-tab-menu__arrow"
aria-label="Go to previous tab"
icon-name="chevron-left"
icon-color="light-gray"
@focus="scrollTabs('left')"
@mouseover="scrollTabs('left')"
@click="scrollTabs('left')"
/>
<PrimeTabMenu
ref="tabContainer"
v-bind="props"
:pt="{
root: 'sb-tab-menu__container',
menu: 'sb-tab-menu__menu',
menuitem: 'sb-tab-menu__menu-item',
action: 'sb-tab-menu__action',
icon: 'sb-tab-menu__icon',
label: 'sb-tab-menu__label',
inkbar: 'sb-tab-menu__inkbar',
hooks: 'sb-tab-menu__hooks',
...$props.pt,
}"
unstyled
@tab-change="handleTabChange"
@update:active-index="handleActiveIndexChange"
>
<template v-if="$slots.item" #item="{ item, props }">
<slot name="item" :item="item" :props="props" />
</template>
</PrimeTabMenu>
<SbIconButton
v-if="showRightArrow"
class="sb-tab-menu__arrow sb-tab-menu__arrow--right"
aria-label="Go to next tab"
icon-name="chevron-right"
icon-color="light-gray"
@focus="scrollTabs('right')"
@mouseover="scrollTabs('right')"
@click="scrollTabs('right')"
/>
<slot name="right-slot"></slot>
</template>
</div>
</template>

<script lang="ts" setup>
import { SbIconButton, SbLoading, SbLoadingPlaceholder } from '..'
import PrimeTabMenu, { type TabMenuChangeEvent } from 'primevue/tabmenu'
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { useEventListener, useResizeObserver } from '@vueuse/core'

const props = defineProps({
model: {
type: Array,
default: null,
},
activeIndex: {
type: Number,
default: 0,
},
ariaLabelledby: {
type: String,
default: null,
},
ariaLabel: {
type: String,
default: null,
},
vertical: {
type: Boolean,
default: false,
},
scrollable: {
type: Boolean,
default: true,
},
isLoading: {
type: Boolean,
default: false,
},
pt: {
type: Object,
default: () => ({}),
},
})

const emits = defineEmits(['update:activeIndex', 'tab-change'])

// styling computation
const computedClasses = computed(() => {
return {
'sb-tab-menu--vertical': props.vertical,
}
})

// methods
const handleTabChange = (e: TabMenuChangeEvent): void => {
emits('tab-change', props.model[e.index])
}

const handleActiveIndexChange = (e: number): void => {
emits('update:activeIndex', e)
}

// scrolling behaviours
const tabContainer = ref<HTMLElement | null>(null)
const containerWidth = ref(0)
const scrollWidth = ref(0)
const scrollPosition = ref(0)
let cleanUpScroll = null
let cleanUpResize = null

const showTabArrows = computed(() => {
return scrollWidth.value >= containerWidth.value && props.scrollable
})

const showLeftArrow = computed(() => {
return (
showTabArrows.value && containerWidth.value > 0 && scrollPosition.value > 1
)
})

const showRightArrow = computed(() => {
return (
showTabArrows.value &&
scrollPosition.value + containerWidth.value < scrollWidth.value - 10
)
})

const scrollTabs = (direction: string): void => {
const scrollContainer = tabContainer.value?.$el

if (!scrollContainer) return

const scrollPosition = scrollContainer.scrollLeft
const scrollAmount = Math.floor(scrollWidth.value / 4)
const scrollAdd = direction === 'left' ? -scrollAmount : scrollAmount
scrollContainer.scrollTo({
top: 0,
left: scrollPosition + scrollAdd,
behavior: 'smooth',
})
}

onMounted(() => {
const container = tabContainer.value?.$el
containerWidth.value = container?.clientWidth || 0
scrollWidth.value = container?.scrollWidth || 0
scrollPosition.value = container?.scrollLeft || 0

cleanUpResize = useResizeObserver(container, () => {
containerWidth.value = container?.clientWidth || 0
scrollWidth.value = container?.scrollWidth || 0
})
cleanUpScroll = useEventListener(container, 'scroll', () => {
scrollPosition.value = container?.scrollLeft
})
})

onBeforeUnmount(() => {
if (typeof cleanUpScroll === 'function') cleanUpScroll()
if (typeof cleanUpResize?.stop === 'function') cleanUpResize.stop()
})
</script>

<style lang="scss" src="./tab-menu.scss" />
3 changes: 3 additions & 0 deletions src/components/TabMenu/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import SbTabMenu from './SbTabMenu.vue'

export default SbTabMenu
Loading
Loading