feat: made all components reactive
This commit is contained in:
@@ -1,8 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="vp-raw">
|
<div class="vp-raw">
|
||||||
<tabs />
|
<tabs v-model="activeTab">
|
||||||
|
<tab name="first" title="Проверка">
|
||||||
|
Проверка
|
||||||
|
</tab>
|
||||||
|
<tab name="second" title="Проверка1">
|
||||||
|
Проверка 2
|
||||||
|
</tab>
|
||||||
|
<tab name="third" title="Проверка2">
|
||||||
|
Проверка 3
|
||||||
|
</tab>
|
||||||
|
<tab name="fourth" title="Проверка3" :disabled="true">
|
||||||
|
Проверка 4
|
||||||
|
</tab>
|
||||||
|
</tabs>
|
||||||
|
current tab: {{ activeTab }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Tabs } from '../../../../src/index'
|
import { ref } from 'vue'
|
||||||
|
import { Tabs, Tab } from '../../../../src/index'
|
||||||
|
const activeTab = ref('')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import { useAlertClasses } from './useAlertClasses'
|
import { useAlertClasses } from './useAlertClasses'
|
||||||
import { onBeforeUnmount, ref } from 'vue'
|
import { onBeforeUnmount, ref, toRefs } from 'vue'
|
||||||
|
|
||||||
export type AlertType = 'info' | 'danger' | 'success' | 'warning' | 'dark'
|
export type AlertType = 'info' | 'danger' | 'success' | 'warning' | 'dark'
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { alertClasses, textClasses, closeClasses, contentClasses, titleClasses } = useAlertClasses(props)
|
const { alertClasses, textClasses, closeClasses, contentClasses, titleClasses } = useAlertClasses(toRefs(props))
|
||||||
|
|
||||||
const visible = ref(true)
|
const visible = ref(true)
|
||||||
|
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ const closeButtonClasses: Record<AlertType, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UseAlertClassesProps = {
|
export type UseAlertClassesProps = {
|
||||||
type: AlertType
|
type: Ref<AlertType>
|
||||||
border: boolean
|
border: Ref<boolean>
|
||||||
icon: boolean
|
icon: Ref<boolean>
|
||||||
inline: boolean
|
inline: Ref<boolean>
|
||||||
title: string
|
title: Ref<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAlertClasses(props: UseAlertClassesProps): {
|
export function useAlertClasses(props: UseAlertClassesProps): {
|
||||||
@@ -59,40 +59,40 @@ export function useAlertClasses(props: UseAlertClassesProps): {
|
|||||||
const alertClasses = computed<string>(() => {
|
const alertClasses = computed<string>(() => {
|
||||||
return classNames(
|
return classNames(
|
||||||
defaultAlertClasses,
|
defaultAlertClasses,
|
||||||
alertTypeClasses[props.type],
|
alertTypeClasses[props.type.value],
|
||||||
textClasses.value,
|
textClasses.value,
|
||||||
props.border ? alertBorderClasses[props.type] : 'rounded-lg', // rounded only if no border
|
props.border.value ? alertBorderClasses[props.type.value] : 'rounded-lg', // rounded only if no border
|
||||||
props.inline ? 'flex' : '',
|
props.inline.value ? 'flex' : '',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const textClasses = computed<string>(() => {
|
const textClasses = computed<string>(() => {
|
||||||
return classNames(
|
return classNames(
|
||||||
alertTextClasses[props.type],
|
alertTextClasses[props.type.value],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const closeClasses = computed<string>(() => {
|
const closeClasses = computed<string>(() => {
|
||||||
return classNames(
|
return classNames(
|
||||||
defaultCloseButtonClasses,
|
defaultCloseButtonClasses,
|
||||||
closeButtonClasses[props.type],
|
closeButtonClasses[props.type.value],
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const contentClasses = computed<string>(() => {
|
const contentClasses = computed<string>(() => {
|
||||||
if(!props.inline) return classNames('mt-2 mb-4')
|
if(!props.inline.value) return classNames('mt-2 mb-4')
|
||||||
if(!props.icon && !props.title) return ''
|
if(!props.icon.value && !props.title.value) return ''
|
||||||
return classNames(!props.title ? 'ml-3' : 'ml-1')
|
return classNames(!props.title.value ? 'ml-3' : 'ml-1')
|
||||||
})
|
})
|
||||||
|
|
||||||
const titleClasses = computed<string>(() => {
|
const titleClasses = computed<string>(() => {
|
||||||
if(!props.icon || !props.inline) return classNames(
|
if(!props.icon.value || !props.inline.value) return classNames(
|
||||||
'font-medium',
|
'font-medium',
|
||||||
!props.inline ? 'text-lg ml-2' : '',
|
!props.inline.value ? 'text-lg ml-2' : '',
|
||||||
)
|
)
|
||||||
return classNames(
|
return classNames(
|
||||||
'font-medium ml-3',
|
'font-medium ml-3',
|
||||||
!props.inline ? 'text-lg' : '',
|
!props.inline.value ? 'text-lg' : '',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue'
|
import { computed, toRefs } from 'vue'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import Spinner from '../Spinner/Spinner.vue'
|
import Spinner from '../Spinner/Spinner.vue'
|
||||||
import { useButtonClasses } from './useButtonClasses'
|
import { useButtonClasses } from './useButtonClasses'
|
||||||
@@ -89,7 +89,7 @@ const isOutlineGradient = computed(() => props.outline && props.gradient)
|
|||||||
const loadingPrefix = computed(() => props.loading && props.loadingPosition === 'prefix')
|
const loadingPrefix = computed(() => props.loading && props.loadingPosition === 'prefix')
|
||||||
const loadingSuffix = computed(() => props.loading && props.loadingPosition === 'suffix')
|
const loadingSuffix = computed(() => props.loading && props.loadingPosition === 'suffix')
|
||||||
|
|
||||||
const { wrapperClasses, spanClasses } = useButtonClasses(props)
|
const { wrapperClasses, spanClasses } = useButtonClasses(toRefs(props))
|
||||||
const { color: spinnerColor, size: spinnerSize } = useButtonSpinner(props)
|
const { color: spinnerColor, size: spinnerSize } = useButtonSpinner(toRefs(props))
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -144,15 +144,15 @@ const buttonShadowClasses: Record<ButtonMonochromeGradient, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UseButtonClassesProps = {
|
export type UseButtonClassesProps = {
|
||||||
pill: boolean
|
pill: Ref<boolean>
|
||||||
disabled: boolean
|
disabled: Ref<boolean>
|
||||||
loading: boolean
|
loading: Ref<boolean>
|
||||||
outline: boolean
|
outline: Ref<boolean>
|
||||||
size: ButtonSize
|
size: Ref<ButtonSize>
|
||||||
square: boolean
|
square: Ref<boolean>
|
||||||
color: ButtonVariant
|
color: Ref<ButtonVariant>
|
||||||
gradient: ButtonGradient | null
|
gradient: Ref<ButtonGradient | null>
|
||||||
shadow: ButtonMonochromeGradient | '' | null
|
shadow: Ref<ButtonMonochromeGradient | '' | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
const simpleGradients = ['blue', 'green', 'cyan', 'teal', 'lime', 'red', 'pink', 'purple']
|
const simpleGradients = ['blue', 'green', 'cyan', 'teal', 'lime', 'red', 'pink', 'purple']
|
||||||
@@ -162,64 +162,64 @@ export function useButtonClasses(props: UseButtonClassesProps): { wrapperClasses
|
|||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
|
|
||||||
const sizeClasses = computed(() => {
|
const sizeClasses = computed(() => {
|
||||||
if (props.square) return buttonSquareSizeClasses[props.size]
|
if (props.square.value) return buttonSquareSizeClasses[props.size.value]
|
||||||
return buttonSizeClasses[props.size]
|
return buttonSizeClasses[props.size.value]
|
||||||
})
|
})
|
||||||
|
|
||||||
const bindClasses = computed(() => {
|
const bindClasses = computed(() => {
|
||||||
const isGradient = !!props.gradient
|
const isGradient = !!props.gradient.value
|
||||||
const isColor = !!props.color
|
const isColor = !!props.color.value
|
||||||
const isOutline = props.outline
|
const isOutline = props.outline.value
|
||||||
|
|
||||||
let hoverClass = ''
|
let hoverClass = ''
|
||||||
let backgroundClass = ''
|
let backgroundClass = ''
|
||||||
|
|
||||||
if (isGradient && isOutline) { // GRADIENT AND OUTLINE
|
if (isGradient && isOutline) { // GRADIENT AND OUTLINE
|
||||||
if (!simpleGradients.includes(props.gradient!)) {
|
if (!simpleGradients.includes(props.gradient.value!)) {
|
||||||
backgroundClass = buttonOutlineGradientClasses.default[props.gradient as unknown as keyof typeof buttonOutlineGradientClasses.default]
|
backgroundClass = buttonOutlineGradientClasses.default[props.gradient.value as unknown as keyof typeof buttonOutlineGradientClasses.default]
|
||||||
|
|
||||||
if(!props.disabled)
|
if(!props.disabled.value)
|
||||||
hoverClass = buttonOutlineGradientClasses.hover[props.gradient as unknown as keyof typeof buttonOutlineGradientClasses.hover]
|
hoverClass = buttonOutlineGradientClasses.hover[props.gradient.value as unknown as keyof typeof buttonOutlineGradientClasses.hover]
|
||||||
} else {
|
} else {
|
||||||
console.warn(`cannot use outline prop with "${props.gradient}" gradient`) // TODO: prettify
|
console.warn(`cannot use outline prop with "${props.gradient.value}" gradient`) // TODO: prettify
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else if (isGradient) { // JUST GRADIENT
|
} else if (isGradient) { // JUST GRADIENT
|
||||||
backgroundClass = buttonGradientClasses.default[props.gradient!]
|
backgroundClass = buttonGradientClasses.default[props.gradient.value!]
|
||||||
|
|
||||||
if(!props.disabled)
|
if(!props.disabled.value)
|
||||||
hoverClass = buttonGradientClasses.hover[props.gradient!]
|
hoverClass = buttonGradientClasses.hover[props.gradient.value!]
|
||||||
|
|
||||||
|
|
||||||
} else if (isColor && isOutline) { // COLOR AND OUTLINE
|
} else if (isColor && isOutline) { // COLOR AND OUTLINE
|
||||||
if (!alternativeColors.includes(props.color)) {
|
if (!alternativeColors.includes(props.color.value)) {
|
||||||
backgroundClass = buttonOutlineColorClasses.default[props.color as unknown as keyof typeof buttonOutlineColorClasses.default]
|
backgroundClass = buttonOutlineColorClasses.default[props.color.value as unknown as keyof typeof buttonOutlineColorClasses.default]
|
||||||
|
|
||||||
if(!props.disabled)
|
if(!props.disabled.value)
|
||||||
hoverClass = buttonOutlineColorClasses.hover[props.color as unknown as keyof typeof buttonOutlineColorClasses.hover]
|
hoverClass = buttonOutlineColorClasses.hover[props.color.value as unknown as keyof typeof buttonOutlineColorClasses.hover]
|
||||||
} else {
|
} else {
|
||||||
console.warn(`cannot use outline prop with "${props.color}" color`) // TODO: prettify
|
console.warn(`cannot use outline prop with "${props.color.value}" color`) // TODO: prettify
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} else { // JUST COLOR
|
} else { // JUST COLOR
|
||||||
backgroundClass = buttonColorClasses.default[props.color]
|
backgroundClass = buttonColorClasses.default[props.color.value]
|
||||||
|
|
||||||
if(!props.disabled)
|
if(!props.disabled.value)
|
||||||
hoverClass = buttonColorClasses.hover[props.color]
|
hoverClass = buttonColorClasses.hover[props.color.value]
|
||||||
}
|
}
|
||||||
|
|
||||||
let shadowClass = ''
|
let shadowClass = ''
|
||||||
if (props.shadow === '') {
|
if (props.shadow.value === '') {
|
||||||
// if shadow prop passed without value - try to find color for shadow by gradient
|
// if shadow prop passed without value - try to find color for shadow by gradient
|
||||||
if (props.gradient && simpleGradients.includes(props.gradient)) {
|
if (props.gradient.value && simpleGradients.includes(props.gradient.value!)) {
|
||||||
shadowClass = buttonShadowClasses[props.gradient as unknown as keyof typeof buttonShadowClasses]
|
shadowClass = buttonShadowClasses[props.gradient.value as unknown as keyof typeof buttonShadowClasses]
|
||||||
}
|
}
|
||||||
} else if (typeof props.shadow === 'string') {
|
} else if (typeof props.shadow.value === 'string') {
|
||||||
// if provided color for shadow - use it
|
// if provided color for shadow - use it
|
||||||
if (simpleGradients.includes(props.shadow)) {
|
if (simpleGradients.includes(props.shadow.value)) {
|
||||||
shadowClass = buttonShadowClasses[props.shadow as unknown as keyof typeof buttonShadowClasses]
|
shadowClass = buttonShadowClasses[props.shadow.value as unknown as keyof typeof buttonShadowClasses]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,19 +227,19 @@ export function useButtonClasses(props: UseButtonClassesProps): { wrapperClasses
|
|||||||
backgroundClass,
|
backgroundClass,
|
||||||
hoverClass,
|
hoverClass,
|
||||||
shadowClass,
|
shadowClass,
|
||||||
props.pill ? '!rounded-full' : '',
|
props.pill.value ? '!rounded-full' : '',
|
||||||
props.disabled ? 'cursor-not-allowed opacity-50' : '',
|
props.disabled.value ? 'cursor-not-allowed opacity-50' : '',
|
||||||
(isGradient && isOutline) ? 'p-0.5' : sizeClasses.value,
|
(isGradient && isOutline) ? 'p-0.5' : sizeClasses.value,
|
||||||
(slots.prefix || slots.suffix || props.loading) ? 'inline-flex items-center' : '',
|
(slots.prefix || slots.suffix || props.loading.value) ? 'inline-flex items-center' : '',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const spanClasses = computed(() => {
|
const spanClasses = computed(() => {
|
||||||
if (!!props.gradient && props.outline) { // ONLY FOR GRADIENT OUTLINE BUTTON
|
if (!!props.gradient.value && props.outline.value) { // ONLY FOR GRADIENT OUTLINE BUTTON
|
||||||
return classNames(
|
return classNames(
|
||||||
'relative bg-white dark:bg-gray-900 rounded-md inline-flex items-center',
|
'relative bg-white dark:bg-gray-900 rounded-md inline-flex items-center',
|
||||||
sizeClasses.value,
|
sizeClasses.value,
|
||||||
!props.disabled ? 'group-hover:bg-opacity-0 transition-all ease-in duration-75' : '',
|
!props.disabled.value ? 'group-hover:bg-opacity-0 transition-all ease-in duration-75' : '',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import type {Ref} from 'vue'
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
export type UseButtonSpinnerProps = {
|
export type UseButtonSpinnerProps = {
|
||||||
outline: boolean
|
outline: Ref<boolean>
|
||||||
size: ButtonSize
|
size: Ref<ButtonSize>
|
||||||
color: ButtonVariant
|
color: Ref<ButtonVariant>
|
||||||
gradient: ButtonGradient | null
|
gradient: Ref<ButtonGradient | null>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useButtonSpinner(props: UseButtonSpinnerProps): { size: Ref<SpinnerSize>, color: Ref<SpinnerColor> } {
|
export function useButtonSpinner(props: UseButtonSpinnerProps): { size: Ref<SpinnerSize>, color: Ref<SpinnerColor> } {
|
||||||
@@ -15,26 +15,26 @@ export function useButtonSpinner(props: UseButtonSpinnerProps): { size: Ref<Spin
|
|||||||
lg: '5', md: '4', sm: '3', xl: '6', xs: '2.5',
|
lg: '5', md: '4', sm: '3', xl: '6', xs: '2.5',
|
||||||
}
|
}
|
||||||
const size = computed<SpinnerSize>(() => {
|
const size = computed<SpinnerSize>(() => {
|
||||||
return btnSizeSpinnerSizeMap[props.size]
|
return btnSizeSpinnerSizeMap[props.size.value]
|
||||||
})
|
})
|
||||||
const color = computed<SpinnerColor>(() => {
|
const color = computed<SpinnerColor>(() => {
|
||||||
|
|
||||||
if(!props.outline) return 'white'
|
if(!props.outline.value) return 'white'
|
||||||
|
|
||||||
if(props.gradient) {
|
if(props.gradient.value) {
|
||||||
if(props.gradient.includes('purple')) return 'purple'
|
if(props.gradient.value.includes('purple')) return 'purple'
|
||||||
else if(props.gradient.includes('blue')) return 'blue'
|
else if(props.gradient.value.includes('blue')) return 'blue'
|
||||||
else if(props.gradient.includes('pink')) return 'pink'
|
else if(props.gradient.value.includes('pink')) return 'pink'
|
||||||
else if(props.gradient.includes('red')) return 'red'
|
else if(props.gradient.value.includes('red')) return 'red'
|
||||||
return 'white'
|
return 'white'
|
||||||
}
|
}
|
||||||
|
|
||||||
if(['alternative', 'dark', 'light'].includes(props.color)) {
|
if(['alternative', 'dark', 'light'].includes(props.color.value)) {
|
||||||
return 'white'
|
return 'white'
|
||||||
} else if(props.color === 'default') {
|
} else if(props.color.value === 'default') {
|
||||||
return 'blue'
|
return 'blue'
|
||||||
}
|
}
|
||||||
return props.color as SpinnerColor
|
return props.color.value as SpinnerColor
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
import { useSpinnerClasses } from './useSpinnerClasses'
|
import { useSpinnerClasses } from './useSpinnerClasses'
|
||||||
|
import { toRefs } from 'vue'
|
||||||
|
|
||||||
export type SpinnerSize = '0' | 'px' | '0.5' | '1' | '1.5' | '2' | '2.5' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'
|
export type SpinnerSize = '0' | 'px' | '0.5' | '1' | '1.5' | '2' | '2.5' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '10' | '11' | '12'
|
||||||
export type SpinnerColor = 'blue' | 'gray' | 'green' | 'red' | 'yellow' | 'pink' | 'purple' | 'white'
|
export type SpinnerColor = 'blue' | 'gray' | 'green' | 'red' | 'yellow' | 'pink' | 'purple' | 'white'
|
||||||
@@ -22,5 +23,5 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { spinnerClasses } = useSpinnerClasses(props)
|
const { spinnerClasses } = useSpinnerClasses(toRefs(props))
|
||||||
</script>
|
</script>
|
||||||
@@ -35,14 +35,14 @@ const colors: Record<SpinnerColor, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UseSpinnerClassesProps = {
|
export type UseSpinnerClassesProps = {
|
||||||
size: SpinnerSize
|
size: Ref<SpinnerSize>
|
||||||
color: SpinnerColor
|
color: Ref<SpinnerColor>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSpinnerClasses(props: UseSpinnerClassesProps): { spinnerClasses: Ref<string> } {
|
export function useSpinnerClasses(props: UseSpinnerClassesProps): { spinnerClasses: Ref<string> } {
|
||||||
|
|
||||||
const sizeClasses = computed(() => sizes[props.size])
|
const sizeClasses = computed(() => sizes[props.size.value])
|
||||||
const colorClasses = computed(() => colors[props.color])
|
const colorClasses = computed(() => colors[props.color.value])
|
||||||
const bgColorClasses = computed(() => 'text-gray-200 dark:text-gray-600')
|
const bgColorClasses = computed(() => 'text-gray-200 dark:text-gray-600')
|
||||||
const animateClasses = computed(() => 'animate-spin')
|
const animateClasses = computed(() => 'animate-spin')
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
<div :class="divClasses">
|
<div :class="divClasses">
|
||||||
<ul :class="ulClasses">
|
<ul :class="ulClasses">
|
||||||
<li class="mr-2">
|
<tab-pane
|
||||||
<a href="#" aria-current="page"
|
v-for="(item, id) in tabsChildren"
|
||||||
class="inline-block p-4 text-blue-600 bg-gray-100 rounded-t-lg active dark:bg-gray-800 dark:text-blue-500">Profile</a>
|
:key="id"
|
||||||
</li>
|
:active="modelValueRef === item.props.name"
|
||||||
<li class="mr-2">
|
:name="item.props.name"
|
||||||
<a href="#"
|
:disabled="item.props.disabled"
|
||||||
class="inline-block p-4 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300">Dashboard</a>
|
:title="item.props.title"
|
||||||
</li>
|
/>
|
||||||
<li class="mr-2">
|
|
||||||
<a href="#"
|
|
||||||
class="inline-block p-4 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li class="mr-2">
|
|
||||||
<a href="#"
|
|
||||||
class="inline-block p-4 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300">Contacts</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="inline-block p-4 text-gray-400 rounded-t-lg cursor-not-allowed dark:text-gray-500">Disabled</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
<slot/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { TAB_ACTIVATE_INJECTION_KEY, TAB_STYLE_INJECTION_KEY } from './config'
|
||||||
import { useTabsClasses } from './useTabsClasses'
|
import { useTabsClasses } from './useTabsClasses'
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
|
import { computed, provide, useSlots } from 'vue'
|
||||||
|
import { flatten } from '../../utils/flatten'
|
||||||
|
import TabPane from './components/TabPane/TabPane.vue'
|
||||||
|
|
||||||
export type TabsVariant = 'default' | 'underline' | 'pills'
|
export type TabsVariant = 'default' | 'underline' | 'pills'
|
||||||
|
|
||||||
@@ -34,7 +30,38 @@ const props = defineProps({
|
|||||||
type: String as PropType<TabsVariant>,
|
type: String as PropType<TabsVariant>,
|
||||||
default: 'default',
|
default: 'default',
|
||||||
},
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const { ulClasses, divClasses } = useTabsClasses(props)
|
const { ulClasses, divClasses } = useTabsClasses(props)
|
||||||
|
|
||||||
|
provide(TAB_STYLE_INJECTION_KEY, props.variant)
|
||||||
|
|
||||||
|
const slots = useSlots()
|
||||||
|
const defaultSlot = slots.default
|
||||||
|
|
||||||
|
const tabsChildren = computed(() => {
|
||||||
|
return defaultSlot
|
||||||
|
? flatten(defaultSlot()).filter((v) => {
|
||||||
|
return (v.type as { __FLOWBITE_TAB__?: true }).__FLOWBITE_TAB__
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
})
|
||||||
|
|
||||||
|
const modelValueRef = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (value: string) => emit('update:modelValue', value),
|
||||||
|
})
|
||||||
|
|
||||||
|
const onActivate = (value: string) => {
|
||||||
|
modelValueRef.value = value
|
||||||
|
}
|
||||||
|
|
||||||
|
provide(TAB_ACTIVATE_INJECTION_KEY, onActivate)
|
||||||
</script>
|
</script>
|
||||||
@@ -1,28 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ul class="">
|
<slot />
|
||||||
<li class="mr-2">
|
|
||||||
<a href="#" aria-current="page"
|
|
||||||
class="inline-block p-4 text-blue-600 bg-gray-100 rounded-t-lg active dark:bg-gray-800 dark:text-blue-500">Profile</a>
|
|
||||||
</li>
|
|
||||||
<li class="mr-2">
|
|
||||||
<a href="#"
|
|
||||||
class="inline-block p-4 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300">Dashboard</a>
|
|
||||||
</li>
|
|
||||||
<li class="mr-2">
|
|
||||||
<a href="#"
|
|
||||||
class="inline-block p-4 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300">Settings</a>
|
|
||||||
</li>
|
|
||||||
<li class="mr-2">
|
|
||||||
<a href="#"
|
|
||||||
class="inline-block p-4 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300">Contacts</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="inline-block p-4 text-gray-400 rounded-t-lg cursor-not-allowed dark:text-gray-500">Disabled</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
defineProps({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
__FLOWBITE_TAB__: true, // add this to easily find tab components from tabs
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
53
src/components/Tabs/components/TabPane/TabPane.vue
Normal file
53
src/components/Tabs/components/TabPane/TabPane.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template>
|
||||||
|
<li>
|
||||||
|
<div :class="tabClasses" @click="tryActivateTab">
|
||||||
|
{{ title }}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { inject, toRefs } from 'vue'
|
||||||
|
import { TAB_ACTIVATE_INJECTION_KEY, TAB_STYLE_INJECTION_KEY } from '../../config'
|
||||||
|
import type { TabsVariant } from '../../Tabs.vue'
|
||||||
|
import { useTabClasses } from './useTabClasses'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
active: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const variant = inject<TabsVariant>(TAB_STYLE_INJECTION_KEY)
|
||||||
|
if(!variant) {
|
||||||
|
console.warn('you can\'t use Tab outside of Tabs component. No tab style injection found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onActivate = inject<(value: string) => void>(TAB_ACTIVATE_INJECTION_KEY)
|
||||||
|
if(!onActivate) {
|
||||||
|
console.warn('you can\'t use Tab outside of Tabs component. No tab activate injection found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const tryActivateTab = () => {
|
||||||
|
if(props.disabled) return
|
||||||
|
if(!onActivate) return console.warn('no onActivate')
|
||||||
|
onActivate(props.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { tabClasses } = useTabClasses({
|
||||||
|
...toRefs(props),
|
||||||
|
variant,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
48
src/components/Tabs/components/TabPane/useTabClasses.ts
Normal file
48
src/components/Tabs/components/TabPane/useTabClasses.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import type { TabsVariant } from '../../Tabs.vue'
|
||||||
|
|
||||||
|
export type TabClassMap = { disabled: string, default: string, active: string }
|
||||||
|
|
||||||
|
export type UseTabClassesProps = {
|
||||||
|
variant?: TabsVariant
|
||||||
|
active: Ref<boolean>
|
||||||
|
disabled: Ref<boolean>
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTabClasses: TabClassMap = {
|
||||||
|
default: 'cursor-pointer inline-block p-4 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark:hover:bg-gray-800 dark:hover:text-gray-300',
|
||||||
|
active: 'cursor-pointer inline-block p-4 text-blue-600 bg-gray-100 rounded-t-lg active dark:bg-gray-800 dark:text-blue-500',
|
||||||
|
disabled: 'inline-block p-4 text-gray-400 rounded-t-lg cursor-not-allowed dark:text-gray-500',
|
||||||
|
}
|
||||||
|
const underlineTabClasses: TabClassMap = {
|
||||||
|
default: 'cursor-pointer inline-block p-4 rounded-t-lg border-b-2 border-transparent hover:text-gray-600 hover:border-gray-300 dark:hover:text-gray-300',
|
||||||
|
active: 'cursor-pointer inline-block p-4 text-blue-600 rounded-t-lg border-b-2 border-blue-600 active dark:text-blue-500 dark:border-blue-500',
|
||||||
|
disabled: 'inline-block p-4 text-gray-400 rounded-t-lg cursor-not-allowed dark:text-gray-500',
|
||||||
|
}
|
||||||
|
const pillsTabClasses: TabClassMap = {
|
||||||
|
default: 'cursor-pointer inline-block py-3 px-4 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white',
|
||||||
|
active: 'cursor-pointer inline-block py-3 px-4 text-white bg-blue-600 rounded-lg active',
|
||||||
|
disabled: 'inline-block py-3 px-4 text-gray-400 cursor-not-allowed dark:text-gray-500',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTabClasses(props: UseTabClassesProps): {
|
||||||
|
tabClasses: Ref<string>,
|
||||||
|
} {
|
||||||
|
|
||||||
|
const tabClasses = computed(() => {
|
||||||
|
const tabClassType: keyof TabClassMap = props.active.value ? 'active' : props.disabled.value ? 'disabled' : 'default'
|
||||||
|
|
||||||
|
if(props.variant === 'default')
|
||||||
|
return defaultTabClasses[tabClassType]
|
||||||
|
else if(props.variant === 'underline')
|
||||||
|
return underlineTabClasses[tabClassType]
|
||||||
|
else if (props.variant === 'pills')
|
||||||
|
return pillsTabClasses[tabClassType]
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
tabClasses,
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/components/Tabs/config.ts
Normal file
2
src/components/Tabs/config.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const TAB_STYLE_INJECTION_KEY = 'flowbite-tab-style-injection'
|
||||||
|
export const TAB_ACTIVATE_INJECTION_KEY = 'flowbite-tab-activate-func-injection'
|
||||||
33
src/utils/flatten.ts
Normal file
33
src/utils/flatten.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Fragment, createTextVNode, Comment } from 'vue'
|
||||||
|
import type { VNodeChild, VNode } from 'vue'
|
||||||
|
|
||||||
|
// o(n) flatten
|
||||||
|
export function flatten (
|
||||||
|
vNodes: VNodeChild[],
|
||||||
|
filterCommentNode = true,
|
||||||
|
result: VNode[] = [],
|
||||||
|
): VNode[] {
|
||||||
|
vNodes.forEach((vNode) => {
|
||||||
|
if (vNode === null) return
|
||||||
|
if (typeof vNode !== 'object') {
|
||||||
|
if (typeof vNode === 'string' || typeof vNode === 'number') {
|
||||||
|
result.push(createTextVNode(String(vNode)))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (Array.isArray(vNode)) {
|
||||||
|
flatten(vNode, filterCommentNode, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (vNode.type === Fragment) {
|
||||||
|
if (vNode.children === null) return
|
||||||
|
if (Array.isArray(vNode.children)) {
|
||||||
|
flatten(vNode.children, filterCommentNode, result)
|
||||||
|
}
|
||||||
|
// rawSlot
|
||||||
|
} else if (vNode.type !== Comment) {
|
||||||
|
result.push(vNode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user