Skip to content

Commit

Permalink
Merge pull request #251 from shariquerik/tabs-component-fix-3
Browse files Browse the repository at this point in the history
  • Loading branch information
shariquerik authored Jan 7, 2025
2 parents 3234543 + ade8016 commit ba40631
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 148 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"typescript": "^5.0.2"
},
"peerDependencies": {
"vue": ">=3.3.0",
"vue": ">=3.5.0",
"vue-router": "^4.1.6"
},
"devDependencies": {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
<script setup>
import { reactive, h, ref } from 'vue'
import Avatar from './Avatar.vue'
import Badge from './Badge.vue'
import { Button } from './Button'
import FeatherIcon from './FeatherIcon.vue'
import ListHeader from './ListView/ListHeader.vue'
import ListHeaderItem from './ListView/ListHeaderItem.vue'
import ListRow from './ListView/ListRow.vue'
import ListRowItem from './ListView/ListRowItem.vue'
import ListRows from './ListView/ListRows.vue'
import ListGroups from './ListView/ListGroups.vue'
import ListSelectBanner from './ListView/ListSelectBanner.vue'
import ListView from './ListView/ListView.vue'
import Avatar from '../Avatar.vue'
import Badge from '../Badge.vue'
import { Button } from '../Button'
import FeatherIcon from '../FeatherIcon.vue'
import ListHeader from './ListHeader.vue'
import ListHeaderItem from './ListHeaderItem.vue'
import ListRow from './ListRow.vue'
import ListRowItem from './ListRowItem.vue'
import ListRows from './ListRows.vue'
import ListSelectBanner from './ListSelectBanner.vue'
import ListView from './ListView.vue'
const state = reactive({
selectable: true,
Expand Down
7 changes: 7 additions & 0 deletions src/components/ListView/ListView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,11 @@ provide(
toggleAllRows,
})),
)
defineExpose({
selections,
allRowsSelected,
toggleRow,
toggleAllRows,
})
</script>
15 changes: 0 additions & 15 deletions src/components/Tabs.story.md

This file was deleted.

110 changes: 0 additions & 110 deletions src/components/Tabs.vue

This file was deleted.

81 changes: 81 additions & 0 deletions src/components/Tabs/TabList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<template>
<TabList
class="relative flex"
:class="
vertical
? 'flex-col border-r overflow-y-auto'
: 'gap-7.5 border-b overflow-x-auto items-center px-5'
"
>
<Tab
ref="tabRef"
as="template"
v-for="(tab, i) in tabs"
:key="i"
v-slot="{ selected }"
class="focus:outline-none focus:transition-none"
>
<slot v-bind="{ tab, selected }">
<button
class="flex items-center gap-1.5 text-base text-ink-gray-5 duration-300 ease-in-out hover:text-ink-gray-9"
:class="[
selected ? 'text-ink-gray-9' : '',
vertical
? 'py-2.5 px-4 border-r border-transparent hover:border-outline-gray-3'
: 'py-3 border-b border-transparent hover:border-outline-gray-3',
]"
>
<component v-if="tab.icon" :is="tab.icon" class="size-4" />
{{ tab.label }}
</button>
</slot>
</Tab>
<div
ref="indicator"
class="tab-indicator absolute bg-surface-gray-7"
:class="[vertical ? 'right-0 w-px' : 'bottom-0 h-px', transitionClass]"
/>
</TabList>
</template>
<script setup>
import { TabList, Tab } from '@headlessui/vue'
import { ref, watch, computed, onMounted, nextTick, inject } from 'vue'
const tabIndex = inject('tabIndex')
const tabs = inject('tabs')
const vertical = inject('vertical')
const tabRef = ref([])
const indicator = ref(null)
const tabsLength = computed(() => tabs.value?.length)
const transitionClass = ref('')
function moveIndicator(index) {
if (index >= tabsLength.value) {
index = tabsLength.value - 1
}
const selectedTab = tabRef.value[index].el
if (vertical) {
indicator.value.style.height = `${selectedTab.offsetHeight}px`
indicator.value.style.top = `${selectedTab.offsetTop}px`
} else {
indicator.value.style.width = `${selectedTab.offsetWidth}px`
indicator.value.style.left = `${selectedTab.offsetLeft}px`
}
}
watch(tabIndex, (index) => {
if (index >= tabsLength.value) {
tabIndex.value = tabsLength.value - 1
}
transitionClass.value = 'transition-all duration-300 ease-in-out'
nextTick(() => moveIndicator(index))
})
onMounted(() => {
nextTick(() => moveIndicator(tabIndex.value))
// Fix for indicator not moving on initial load
setTimeout(() => moveIndicator(tabIndex.value), 100)
})
</script>
17 changes: 17 additions & 0 deletions src/components/Tabs/TabPanel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<template>
<TabPanels class="flex flex-1 overflow-hidden">
<TabPanel
class="flex flex-1 flex-col overflow-y-auto focus:outline-none"
v-for="(tab, i) in tabs"
:key="i"
>
<slot v-bind="{ tab }" />
</TabPanel>
</TabPanels>
</template>
<script setup>
import { TabPanels, TabPanel } from '@headlessui/vue'
import { inject } from 'vue'
const tabs = inject('tabs')
</script>
97 changes: 97 additions & 0 deletions src/components/Tabs/Tabs.story.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
## Props

### tabs

It is an array of objects which contains the following attributes:

1. `label` is the name of the tab, it is required.
2. `icon` is the icon to be shown in the tab, it accept component and it is
optional.
3. You can add more attributes which can be used for custom rendering in the tab
header or content.

### v-model

It is used to set the active tab or change the active tab. It is required.

### vertical

It is used to show the tabs vertically. It is optional.

### as

You can set it to `div` to wrap tabs in a `div`. It can be any valid HTML tag.
This is useful to control the layout of the tabs. It is optional.

1. `as="div"` or any valid HTML tag

```html
<div>
<!-- container div -->
<div>
<div active>Tab 1</div>
<div>Tab 2</div>
<div>Tab 3</div>
</div>
<div>
<div active>Content 1</div>
<div>Content 2</div>
<div>Content 3</div>
</div>
</div>
```

2. `as` is not set

```html
<div>
<div active>Tab 1</div>
<div>Tab 2</div>
<div>Tab 3</div>
</div>
<div>
<div active>Content 1</div>
<div>Content 2</div>
<div>Content 3</div>
</div>
```

## Slots

1. **tab-item:** You can use this slot to render custom tab items. It is
optional.
2. **tab-panel:** You can use this slot to render custom tab panels. It is
required. Example:

```vue
<Tabs v-model="tabIndex" :tabs="tabs">
<template #tab-item="{ tab, selected }">
<div :class="{ 'text-gray-900 font-semibold': selected }">
<span>{{ tab.label }}</span>
<span>{{ tab.icon }}</span>
</div>
</template>
<template #tab-panel="{ tab }">
<div>{{ tab.content }}</div>
</template>
</Tabs>
```

## Layout Customization

You can customize the layout of the tabs by using `<TabList />` and `<TabPanels />`
components.

```vue
<Tabs v-model="tabIndex" :tabs="tabs">
<TabList v-slot="{ tab, selected }">
<div :class="{ 'text-gray-900 font-semibold': selected }">
<span>{{ tab.label }}</span>
<span>{{ tab.icon }}</span>
</div>
</TabList>
<TabPanel v-slot="{ tab }">
<div>{{ tab.content }}</div>
</TabPanel>
</Tabs>
```
Loading

0 comments on commit ba40631

Please sign in to comment.