mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-19 22:41:42 +01:00
feat(Tabs): new component (#450)
This commit is contained in:
@@ -765,6 +765,40 @@ const pagination = {
|
||||
}
|
||||
}
|
||||
|
||||
const tabs = {
|
||||
wrapper: 'relative space-y-2',
|
||||
container: 'relative w-full',
|
||||
base: 'focus:outline-none',
|
||||
list: {
|
||||
base: 'relative',
|
||||
background: 'bg-gray-100 dark:bg-gray-800',
|
||||
rounded: 'rounded-lg',
|
||||
shadow: '',
|
||||
padding: 'p-1',
|
||||
height: 'h-10',
|
||||
width: 'w-full',
|
||||
marker: {
|
||||
wrapper: 'absolute top-[4px] left-[4px] duration-200 ease-out focus:outline-none',
|
||||
base: 'w-full h-full',
|
||||
background: 'bg-white dark:bg-gray-900',
|
||||
rounded: 'rounded-md',
|
||||
shadow: 'shadow-sm'
|
||||
},
|
||||
tab: {
|
||||
base: 'relative inline-flex items-center justify-center flex-shrink-0 w-full whitespace-nowrap focus:outline-none disabled:cursor-not-allowed disabled:opacity-75 transition-colors duration-200 ease-out',
|
||||
background: '',
|
||||
active: 'text-gray-900 dark:text-white',
|
||||
inactive: 'text-gray-500 dark:text-gray-400',
|
||||
height: 'h-8',
|
||||
padding: 'px-3',
|
||||
size: 'text-sm',
|
||||
font: 'font-medium',
|
||||
rounded: 'rounded-md',
|
||||
shadow: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Overlays
|
||||
|
||||
const modal = {
|
||||
@@ -984,6 +1018,7 @@ export default {
|
||||
verticalNavigation,
|
||||
commandPalette,
|
||||
pagination,
|
||||
tabs,
|
||||
modal,
|
||||
slideover,
|
||||
popover,
|
||||
|
||||
117
src/runtime/components/navigation/Tabs.vue
Normal file
117
src/runtime/components/navigation/Tabs.vue
Normal file
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<HTabGroup :vertical="orientation === 'vertical'" :default-index="defaultIndex" as="div" :class="ui.wrapper" @change="onChange">
|
||||
<HTabList
|
||||
:class="[ui.list.base, ui.list.background, ui.list.rounded, ui.list.shadow, ui.list.padding, ui.list.width, orientation === 'horizontal' && ui.list.height, orientation === 'horizontal' && 'inline-grid items-center']"
|
||||
:style="[orientation === 'horizontal' && `grid-template-columns: repeat(${items.length}, minmax(0, 1fr))`]"
|
||||
>
|
||||
<div ref="markerRef" :class="ui.list.marker.wrapper">
|
||||
<div :class="[ui.list.marker.base, ui.list.marker.background, ui.list.marker.rounded, ui.list.marker.shadow]" />
|
||||
</div>
|
||||
|
||||
<HTab
|
||||
v-for="(item, index) of items"
|
||||
:key="index"
|
||||
ref="itemRefs"
|
||||
v-slot="{ selected, disabled }"
|
||||
:disabled="item.disabled"
|
||||
as="template"
|
||||
>
|
||||
<button :class="[ui.list.tab.base, ui.list.tab.background, ui.list.tab.height, ui.list.tab.padding, ui.list.tab.size, ui.list.tab.font, ui.list.tab.rounded, ui.list.tab.shadow, selected ? ui.list.tab.active : ui.list.tab.inactive]">
|
||||
<slot :item="item" :index="index" :selected="selected" :disabled="disabled">
|
||||
{{ item.label }}
|
||||
</slot>
|
||||
</button>
|
||||
</HTab>
|
||||
</HTabList>
|
||||
|
||||
<HTabPanels :class="ui.container">
|
||||
<HTabPanel
|
||||
v-for="(item, index) of items"
|
||||
:key="index"
|
||||
v-slot="{ selected }"
|
||||
:class="ui.base"
|
||||
>
|
||||
<slot :name="item.slot || 'item'" :item="item" :index="index" :selected="selected">
|
||||
{{ item.content }}
|
||||
</slot>
|
||||
</HTabPanel>
|
||||
</HTabPanels>
|
||||
</HTabGroup>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import type { PropType } from 'vue'
|
||||
import { TabGroup as HTabGroup, TabList as HTabList, Tab as HTab, TabPanels as HTabPanels, TabPanel as HTabPanel } from '@headlessui/vue'
|
||||
import { defu } from 'defu'
|
||||
import type { TabItem } from '../../types/tabs'
|
||||
import { useAppConfig } from '#imports'
|
||||
// TODO: Remove
|
||||
// @ts-expect-error
|
||||
import appConfig from '#build/app.config'
|
||||
|
||||
// const appConfig = useAppConfig()
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
HTabGroup,
|
||||
HTabList,
|
||||
HTab,
|
||||
HTabPanels,
|
||||
HTabPanel
|
||||
},
|
||||
props: {
|
||||
orientation: {
|
||||
type: String as PropType<'horizontal' | 'vertical'>,
|
||||
default: 'horizontal',
|
||||
validator: (value: string) => ['horizontal', 'vertical'].includes(value)
|
||||
},
|
||||
defaultIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
items: {
|
||||
type: Array as PropType<TabItem[]>,
|
||||
default: () => []
|
||||
},
|
||||
ui: {
|
||||
type: Object as PropType<Partial<typeof appConfig.ui.tabs>>,
|
||||
default: () => appConfig.ui.tabs
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
// TODO: Remove
|
||||
const appConfig = useAppConfig()
|
||||
|
||||
const ui = computed<Partial<typeof appConfig.ui.tabs>>(() => defu({}, props.ui, appConfig.ui.tabs))
|
||||
|
||||
const itemRefs = ref<HTMLElement[]>([])
|
||||
const markerRef = ref<HTMLElement>()
|
||||
|
||||
// Methods
|
||||
|
||||
function onChange (index) {
|
||||
// @ts-ignore
|
||||
const tab = itemRefs.value[index]?.$el
|
||||
if (!tab) {
|
||||
return
|
||||
}
|
||||
|
||||
markerRef.value.style.top = `${tab.offsetTop}px`
|
||||
markerRef.value.style.left = `${tab.offsetLeft}px`
|
||||
markerRef.value.style.width = `${tab.offsetWidth}px`
|
||||
markerRef.value.style.height = `${tab.offsetHeight}px`
|
||||
}
|
||||
|
||||
onMounted(() => onChange(props.defaultIndex))
|
||||
|
||||
return {
|
||||
// eslint-disable-next-line vue/no-dupe-keys
|
||||
ui,
|
||||
itemRefs,
|
||||
markerRef,
|
||||
onChange
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
1
src/runtime/types/index.d.ts
vendored
1
src/runtime/types/index.d.ts
vendored
@@ -6,4 +6,5 @@ export * from './command-palette'
|
||||
export * from './dropdown'
|
||||
export * from './notification'
|
||||
export * from './popper'
|
||||
export * from './tabs'
|
||||
export * from './vertical-navigation'
|
||||
|
||||
6
src/runtime/types/tabs.d.ts
vendored
Normal file
6
src/runtime/types/tabs.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface TabItem {
|
||||
label: string
|
||||
slot?: string
|
||||
disabled?: boolean
|
||||
content?: string
|
||||
}
|
||||
Reference in New Issue
Block a user