From 3c342e4473734efe318addc9dea4eea4c195e674 Mon Sep 17 00:00:00 2001 From: Alexandr Date: Tue, 5 Jul 2022 19:31:05 +0300 Subject: [PATCH] feat: made all components reactive --- .../tabs/examples/TabsDefaultExample.vue | 20 ++++- src/components/Alert/Alert.vue | 4 +- src/components/Alert/useAlertClasses.ts | 32 ++++---- src/components/Button/Button.vue | 6 +- src/components/Button/useButtonClasses.ts | 82 +++++++++---------- src/components/Button/useButtonSpinner.ts | 36 ++++---- src/components/Spinner/Spinner.vue | 3 +- src/components/Spinner/useSpinnerClasses.ts | 8 +- src/components/Tabs/Tabs.vue | 73 +++++++++++------ src/components/Tabs/components/Tab/Tab.vue | 41 +++++----- .../Tabs/components/Tab/useTabClasses.ts | 0 .../Tabs/components/TabPane/TabPane.vue | 53 ++++++++++++ .../Tabs/components/TabPane/useTabClasses.ts | 48 +++++++++++ src/components/Tabs/config.ts | 2 + src/utils/flatten.ts | 33 ++++++++ 15 files changed, 310 insertions(+), 131 deletions(-) delete mode 100644 src/components/Tabs/components/Tab/useTabClasses.ts create mode 100644 src/components/Tabs/components/TabPane/TabPane.vue create mode 100644 src/components/Tabs/components/TabPane/useTabClasses.ts create mode 100644 src/components/Tabs/config.ts create mode 100644 src/utils/flatten.ts diff --git a/docs/guide/tabs/examples/TabsDefaultExample.vue b/docs/guide/tabs/examples/TabsDefaultExample.vue index 1e601b6..a56462e 100644 --- a/docs/guide/tabs/examples/TabsDefaultExample.vue +++ b/docs/guide/tabs/examples/TabsDefaultExample.vue @@ -1,8 +1,24 @@ diff --git a/src/components/Alert/Alert.vue b/src/components/Alert/Alert.vue index 9ff4653..7921ba5 100644 --- a/src/components/Alert/Alert.vue +++ b/src/components/Alert/Alert.vue @@ -28,7 +28,7 @@ diff --git a/src/components/Button/useButtonClasses.ts b/src/components/Button/useButtonClasses.ts index 74c09f7..8fe417d 100644 --- a/src/components/Button/useButtonClasses.ts +++ b/src/components/Button/useButtonClasses.ts @@ -144,15 +144,15 @@ const buttonShadowClasses: Record = { } export type UseButtonClassesProps = { - pill: boolean - disabled: boolean - loading: boolean - outline: boolean - size: ButtonSize - square: boolean - color: ButtonVariant - gradient: ButtonGradient | null - shadow: ButtonMonochromeGradient | '' | null + pill: Ref + disabled: Ref + loading: Ref + outline: Ref + size: Ref + square: Ref + color: Ref + gradient: Ref + shadow: Ref } const simpleGradients = ['blue', 'green', 'cyan', 'teal', 'lime', 'red', 'pink', 'purple'] @@ -162,64 +162,64 @@ export function useButtonClasses(props: UseButtonClassesProps): { wrapperClasses const slots = useSlots() const sizeClasses = computed(() => { - if (props.square) return buttonSquareSizeClasses[props.size] - return buttonSizeClasses[props.size] + if (props.square.value) return buttonSquareSizeClasses[props.size.value] + return buttonSizeClasses[props.size.value] }) const bindClasses = computed(() => { - const isGradient = !!props.gradient - const isColor = !!props.color - const isOutline = props.outline + const isGradient = !!props.gradient.value + const isColor = !!props.color.value + const isOutline = props.outline.value let hoverClass = '' let backgroundClass = '' if (isGradient && isOutline) { // GRADIENT AND OUTLINE - if (!simpleGradients.includes(props.gradient!)) { - backgroundClass = buttonOutlineGradientClasses.default[props.gradient as unknown as keyof typeof buttonOutlineGradientClasses.default] + if (!simpleGradients.includes(props.gradient.value!)) { + backgroundClass = buttonOutlineGradientClasses.default[props.gradient.value as unknown as keyof typeof buttonOutlineGradientClasses.default] - if(!props.disabled) - hoverClass = buttonOutlineGradientClasses.hover[props.gradient as unknown as keyof typeof buttonOutlineGradientClasses.hover] + if(!props.disabled.value) + hoverClass = buttonOutlineGradientClasses.hover[props.gradient.value as unknown as keyof typeof buttonOutlineGradientClasses.hover] } 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 - backgroundClass = buttonGradientClasses.default[props.gradient!] + backgroundClass = buttonGradientClasses.default[props.gradient.value!] - if(!props.disabled) - hoverClass = buttonGradientClasses.hover[props.gradient!] + if(!props.disabled.value) + hoverClass = buttonGradientClasses.hover[props.gradient.value!] } else if (isColor && isOutline) { // COLOR AND OUTLINE - if (!alternativeColors.includes(props.color)) { - backgroundClass = buttonOutlineColorClasses.default[props.color as unknown as keyof typeof buttonOutlineColorClasses.default] + if (!alternativeColors.includes(props.color.value)) { + backgroundClass = buttonOutlineColorClasses.default[props.color.value as unknown as keyof typeof buttonOutlineColorClasses.default] - if(!props.disabled) - hoverClass = buttonOutlineColorClasses.hover[props.color as unknown as keyof typeof buttonOutlineColorClasses.hover] + if(!props.disabled.value) + hoverClass = buttonOutlineColorClasses.hover[props.color.value as unknown as keyof typeof buttonOutlineColorClasses.hover] } 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 - backgroundClass = buttonColorClasses.default[props.color] + backgroundClass = buttonColorClasses.default[props.color.value] - if(!props.disabled) - hoverClass = buttonColorClasses.hover[props.color] + if(!props.disabled.value) + hoverClass = buttonColorClasses.hover[props.color.value] } let shadowClass = '' - if (props.shadow === '') { + if (props.shadow.value === '') { // if shadow prop passed without value - try to find color for shadow by gradient - if (props.gradient && simpleGradients.includes(props.gradient)) { - shadowClass = buttonShadowClasses[props.gradient as unknown as keyof typeof buttonShadowClasses] + if (props.gradient.value && simpleGradients.includes(props.gradient.value!)) { + 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 (simpleGradients.includes(props.shadow)) { - shadowClass = buttonShadowClasses[props.shadow as unknown as keyof typeof buttonShadowClasses] + if (simpleGradients.includes(props.shadow.value)) { + shadowClass = buttonShadowClasses[props.shadow.value as unknown as keyof typeof buttonShadowClasses] } } @@ -227,19 +227,19 @@ export function useButtonClasses(props: UseButtonClassesProps): { wrapperClasses backgroundClass, hoverClass, shadowClass, - props.pill ? '!rounded-full' : '', - props.disabled ? 'cursor-not-allowed opacity-50' : '', + props.pill.value ? '!rounded-full' : '', + props.disabled.value ? 'cursor-not-allowed opacity-50' : '', (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(() => { - if (!!props.gradient && props.outline) { // ONLY FOR GRADIENT OUTLINE BUTTON + if (!!props.gradient.value && props.outline.value) { // ONLY FOR GRADIENT OUTLINE BUTTON return classNames( 'relative bg-white dark:bg-gray-900 rounded-md inline-flex items-center', 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 '' diff --git a/src/components/Button/useButtonSpinner.ts b/src/components/Button/useButtonSpinner.ts index 15503b6..6726a80 100644 --- a/src/components/Button/useButtonSpinner.ts +++ b/src/components/Button/useButtonSpinner.ts @@ -1,13 +1,13 @@ -import type {ButtonGradient, ButtonSize, ButtonVariant} from './Button.vue' -import type {SpinnerColor, SpinnerSize} from '../Spinner/Spinner.vue' -import type {Ref} from 'vue' -import {computed} from 'vue' +import type { ButtonGradient, ButtonSize, ButtonVariant } from './Button.vue' +import type { SpinnerColor, SpinnerSize } from '../Spinner/Spinner.vue' +import type { Ref } from 'vue' +import { computed } from 'vue' export type UseButtonSpinnerProps = { - outline: boolean - size: ButtonSize - color: ButtonVariant - gradient: ButtonGradient | null + outline: Ref + size: Ref + color: Ref + gradient: Ref } export function useButtonSpinner(props: UseButtonSpinnerProps): { size: Ref, color: Ref } { @@ -15,26 +15,26 @@ export function useButtonSpinner(props: UseButtonSpinnerProps): { size: Ref(() => { - return btnSizeSpinnerSizeMap[props.size] + return btnSizeSpinnerSizeMap[props.size.value] }) const color = computed(() => { - if(!props.outline) return 'white' + if(!props.outline.value) return 'white' - if(props.gradient) { - if(props.gradient.includes('purple')) return 'purple' - else if(props.gradient.includes('blue')) return 'blue' - else if(props.gradient.includes('pink')) return 'pink' - else if(props.gradient.includes('red')) return 'red' + if(props.gradient.value) { + if(props.gradient.value.includes('purple')) return 'purple' + else if(props.gradient.value.includes('blue')) return 'blue' + else if(props.gradient.value.includes('pink')) return 'pink' + else if(props.gradient.value.includes('red')) return 'red' return 'white' } - if(['alternative', 'dark', 'light'].includes(props.color)) { + if(['alternative', 'dark', 'light'].includes(props.color.value)) { return 'white' - } else if(props.color === 'default') { + } else if(props.color.value === 'default') { return 'blue' } - return props.color as SpinnerColor + return props.color.value as SpinnerColor }) return { diff --git a/src/components/Spinner/Spinner.vue b/src/components/Spinner/Spinner.vue index c6d4c34..94c4c65 100644 --- a/src/components/Spinner/Spinner.vue +++ b/src/components/Spinner/Spinner.vue @@ -7,6 +7,7 @@ \ No newline at end of file diff --git a/src/components/Spinner/useSpinnerClasses.ts b/src/components/Spinner/useSpinnerClasses.ts index 77d55dd..71534b5 100644 --- a/src/components/Spinner/useSpinnerClasses.ts +++ b/src/components/Spinner/useSpinnerClasses.ts @@ -35,14 +35,14 @@ const colors: Record = { } export type UseSpinnerClassesProps = { - size: SpinnerSize - color: SpinnerColor + size: Ref + color: Ref } export function useSpinnerClasses(props: UseSpinnerClassesProps): { spinnerClasses: Ref } { - const sizeClasses = computed(() => sizes[props.size]) - const colorClasses = computed(() => colors[props.color]) + const sizeClasses = computed(() => sizes[props.size.value]) + const colorClasses = computed(() => colors[props.color.value]) const bgColorClasses = computed(() => 'text-gray-200 dark:text-gray-600') const animateClasses = computed(() => 'animate-spin') diff --git a/src/components/Tabs/Tabs.vue b/src/components/Tabs/Tabs.vue index 1105c95..247eb86 100644 --- a/src/components/Tabs/Tabs.vue +++ b/src/components/Tabs/Tabs.vue @@ -1,31 +1,27 @@ \ No newline at end of file + +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) + diff --git a/src/components/Tabs/components/Tab/Tab.vue b/src/components/Tabs/components/Tab/Tab.vue index 38597d5..c3b7845 100644 --- a/src/components/Tabs/components/Tab/Tab.vue +++ b/src/components/Tabs/components/Tab/Tab.vue @@ -1,28 +1,27 @@ + \ No newline at end of file diff --git a/src/components/Tabs/components/Tab/useTabClasses.ts b/src/components/Tabs/components/Tab/useTabClasses.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/Tabs/components/TabPane/TabPane.vue b/src/components/Tabs/components/TabPane/TabPane.vue new file mode 100644 index 0000000..f762cc5 --- /dev/null +++ b/src/components/Tabs/components/TabPane/TabPane.vue @@ -0,0 +1,53 @@ + + \ No newline at end of file diff --git a/src/components/Tabs/components/TabPane/useTabClasses.ts b/src/components/Tabs/components/TabPane/useTabClasses.ts new file mode 100644 index 0000000..204e6cf --- /dev/null +++ b/src/components/Tabs/components/TabPane/useTabClasses.ts @@ -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 + disabled: Ref +} + +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, +} { + + 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, + } +} \ No newline at end of file diff --git a/src/components/Tabs/config.ts b/src/components/Tabs/config.ts new file mode 100644 index 0000000..76d42b0 --- /dev/null +++ b/src/components/Tabs/config.ts @@ -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' \ No newline at end of file diff --git a/src/utils/flatten.ts b/src/utils/flatten.ts new file mode 100644 index 0000000..268b460 --- /dev/null +++ b/src/utils/flatten.ts @@ -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 +} \ No newline at end of file