UCLALibrary / ucla-library-website-components

This is a library of Vue components that will be used in UCLA Library Nuxt websites.
Other
7 stars 1 forks source link

Component Request - FTVA TabList & Tab #597

Open farosFreed opened 3 weeks ago

farosFreed commented 3 weeks ago

Component Description

This component is used on several FTVA pages to display a tabbed menu that shows content corresponding to each tab. It consists of a parent component TabList.vue, which renders a list of Tabs and handles positioning the tab menu to the left or the right of content

Something like this: (this sample uses options API, may want to refactor to composition API)

<template>
  <div class='tabs-list'>
    <ul class='tabs-header'>
      <li v-for='(tab, index) in tabs'
        :key='tab.title'
        @click='selectTab(index)'
        :class='{"tab-selected": (index == selectedIndex)}'>
        {{ tab.icon? }} {{ tab.title }}
      </li>
    </ul>
  </div>
</template>
<script>
export default {
  props: {
    // shown below in props section
  },
  data () {
    return {
      selectedIndex: 0, // the index of the selected tab,
      tabs: []         // all of the tabs
    }
  },
  created () {
    this.tabs = this.$children // will automatically get the tab objects inside tablist
  },
  mounted () {
    this.selectedIndex = initialSelectedTab  // in case the page needs to set tab selection
  },
  methods: {
    selectTab (i) {
      this.selectedIndex = i

      // loop over all the tabs
      this.tabs.forEach((tab, index) => {
        tab.isActive = (index === i)
      })
    }
  }
}
</script>

and a child component Tab.vue, which provides a slot for tab content and shows the active slot.

<template>
  <div class='tab' v-show='isActive'>
    <slot></slot>
  </div>
</template>

<script>
const isActive = ref(false)
</script>

Both components together on a page with content for each tab. (Include a story to show a pattern like this)

<div class='page-layout-wrapper'>
    <tablist>
      <tab title="Tab 1 w Icon" :icon="someIconObjectOrString">Tab 1 content goes here</tab>
      <tab title="Tab 2">Tab 2 content goes here</tab>
      <tab title="Tab 3 w Icon" :icon="someOtherIconObjectOrString"><some-component-tab-3-uses /></tab>
      <tab title="Tab 4"><some-other-tab-4-component/></tab>
    </tablist>
 </div>

Design

Design File https://www.figma.com/design/EKazRIMP4B15bD16UDbOwR/UCLA-Library-Design-System?node-id=11358-810&t=06eB6mjVVAW0i11F-0

This file includes full page designs that include this tablist

https://www.figma.com/design/EKazRIMP4B15bD16UDbOwR/UCLA-Library-Design-System?node-id=2347-31817&t=o4K7FZaDlCWjVARz-0

Please also see attached screenshots for quick reference.

Variations:

Animations: Serena described that the active tab highlight should animate from left to right as the tab changes. Please see images in the design file

Slots

Include a slot for each tabs content in the Tab Component.

Include a slot for additional-filters that the parent can use to add additional filter controls for the visible list (see screenshot)

Screenshot 2024-08-21 at 3 01 13 PM

Props

Props for the tabs array have an optional 'icon' field. This field can either A) allow an icon object to be passed, in which case this component does not need to import all possible icons, but the parent component or page using the tablist would need to provide the icon or B) just take a string to represent an icon name, and have the TabsList component import all possible icons and load the correct one.

Tablist:

props: {
   // number corresponding to the index item in the tabs array that is initially selected
    initialSelectedTab: {
        type: Number,
        default: 0
    },
    tabs: {
        type: Array as PropType[]<{ title:string, icon?:string }>,   // could also be <{ title:string, icon?:Object }>
        default: () => []
    },
    tabAlignment: {
        type: String as PropType<'left' | 'right'>,
        default: "left"
    },
}

Tab

props: {
    // the text displayed on the tab
    title: {
        type: string,
        default: 'tab'
    },
}

Styles

As this is a new component, please include 2 styles files for each component:

Developer Tips

  1. If implementing icons as strings, you will need to use defineAsyncComponent like BlockAmenities.vue does to load all possible icons.

  2. When implementing tab hide/show functionality, use v-show instead of v-if where possible to take advantage of vue 'keep alive' behavior for quick tab/switching

Events

Just in case we need to use this component to toggle content elsewhere on the page (instead of directly below tabs), TabList can emit an event when the active tab is changed. This will give us the option to react to a tab change at the page level.

  1. update:tab-selected when the activeTab is changed, include the new active tab index like:

emit('update:tab-selected', _activeTabIndex_)

pghorpade commented 2 weeks ago

@axamei Do we need any css transition or animation for this toggle or tab component? Is this annotated or captured in design system in Figma?

pghorpade commented 2 weeks ago

@farosFreed Yes, The Tablist should emit an event, and the selected tabIndex to the page where it is added, and the event handler on the page will pass the right data to the SectionTeaserList component. We could also replicate this behavior in a sample vue component in the component library and add a storybook file to test the interaction between these components.