feat: add Theme component and enhance Button with href and target props
parent
73dd6d7625
commit
428fdfc182
|
@ -1,37 +1,37 @@
|
|||
<template>
|
||||
<button :class="rootClass" @click="$emit('click', $event)" :disabled="disabled" :style="cssVars">
|
||||
<slot name="loading"></slot>
|
||||
<button :class="rootClass" @click="handleClick" :disabled="disabled" :style="cssVars">
|
||||
<slot name="loading">
|
||||
<LoadingOutlined v-if="loading" />
|
||||
</slot>
|
||||
<slot name="icon"></slot>
|
||||
<slot></slot>
|
||||
<span><slot></slot></span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, Fragment } from 'vue'
|
||||
import { buttonProps, buttonEmits, ButtonSlots } from './meta'
|
||||
import { getCssVarColor } from '@/utils/colorAlgorithm'
|
||||
import { useThemeInject } from '../theme/hook'
|
||||
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'
|
||||
|
||||
const props = defineProps(buttonProps)
|
||||
|
||||
defineEmits(buttonEmits)
|
||||
const emit = defineEmits(buttonEmits)
|
||||
defineSlots<ButtonSlots>()
|
||||
|
||||
// todo: color value should from theme provider
|
||||
const theme = useThemeInject()
|
||||
|
||||
const color = computed(() => {
|
||||
if (props.disabled) {
|
||||
return 'rgba(0,0,0,0.25)'
|
||||
}
|
||||
if (props.color) {
|
||||
return props.color
|
||||
}
|
||||
|
||||
if (props.danger) {
|
||||
return '#ff4d4f'
|
||||
}
|
||||
if (props.variant === 'text') {
|
||||
return '#000000'
|
||||
return theme.dangerColor
|
||||
}
|
||||
|
||||
return '#1677ff'
|
||||
return theme.primaryColor
|
||||
})
|
||||
|
||||
const rootClass = computed(() => {
|
||||
|
@ -42,9 +42,17 @@ const rootClass = computed(() => {
|
|||
'ant-btn-danger': props.danger,
|
||||
'ant-btn-loading': props.loading,
|
||||
'ant-btn-disabled': props.disabled,
|
||||
'ant-btn-custom-color': props.color || props.danger,
|
||||
}
|
||||
})
|
||||
const cssVars = computed(() => {
|
||||
return getCssVarColor(color.value)
|
||||
})
|
||||
|
||||
const handleClick = (event: MouseEvent) => {
|
||||
emit('click', event)
|
||||
if (props.href) {
|
||||
window.open(props.href, props.target)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -62,6 +62,20 @@ export const buttonProps = {
|
|||
color: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies the href of the button
|
||||
*/
|
||||
href: {
|
||||
type: String,
|
||||
},
|
||||
|
||||
/**
|
||||
* Specifies the target of the button
|
||||
*/
|
||||
target: {
|
||||
type: String,
|
||||
},
|
||||
} as const
|
||||
|
||||
export type ButtonProps = ExtractPublicPropTypes<typeof buttonProps>
|
||||
|
|
|
@ -2,40 +2,55 @@
|
|||
|
||||
.ant-btn {
|
||||
@apply relative;
|
||||
@apply inline-flex shrink-0 cursor-pointer items-center justify-center gap-1 whitespace-nowrap;
|
||||
@apply inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap;
|
||||
@apply border-1 text-sm;
|
||||
@apply box-border rounded-md px-4 transition-all duration-200 select-none;
|
||||
&:where(.ant-btn-disabled) {
|
||||
@apply cursor-not-allowed;
|
||||
}
|
||||
|
||||
&:where(.ant-btn-loading) {
|
||||
@apply cursor-default opacity-50;
|
||||
@apply cursor-default opacity-65;
|
||||
}
|
||||
|
||||
&:where(.ant-btn-solid) {
|
||||
@apply border-none bg-[var(--bg-color)] text-[var(--bg-color-content)];
|
||||
@apply not-disabled:hover:bg-[var(--bg-color-hover)] not-disabled:active:bg-[var(--bg-color-active)];
|
||||
&:where(.ant-btn-solid:not(:disabled)) {
|
||||
@apply border-none bg-[var(--accent-color)] text-[var(--accent-color-content)];
|
||||
@apply hover:bg-[var(--accent-color-hover)] active:bg-[var(--accent-color-active)];
|
||||
}
|
||||
&:where(.ant-btn-outlined),
|
||||
&:where(.ant-btn-dashed) {
|
||||
@apply border-[var(--border-color-tint-30)] bg-transparent text-[var(--text-color)];
|
||||
@apply not-disabled:hover:border-[var(--border-color-hover)] not-disabled:hover:text-[var(--text-color-hover)] not-disabled:active:border-[var(--border-color-active)] not-disabled:active:text-[var(--text-color-active)];
|
||||
@apply disabled:border-[var(--border-color-tint-80)];
|
||||
&:where(.ant-btn-outlined:not(:disabled)),
|
||||
&:where(.ant-btn-dashed:not(:disabled)) {
|
||||
@apply border-[var(--accent-color)] bg-transparent text-[var(--accent-color)];
|
||||
@apply hover:text-[var(--accent-color-hover)] active:border-[var(--accent-color-active)] active:text-[var(--accent-color-active)];
|
||||
@apply border-[var(--accent-color-active)] hover:border-[var(--accent-color-hover)];
|
||||
}
|
||||
&:where(.ant-btn-text) {
|
||||
@apply border-none bg-transparent text-[var(--text-color)];
|
||||
@apply not-disabled:hover:bg-[var(--bg-color-tint-90)] not-disabled:hover:text-[var(--text-color-hover)];
|
||||
&:where(.ant-btn-text:not(.ant-btn-custom-color):not(:disabled)) {
|
||||
@apply border-none bg-transparent text-[var(--neutral-color)];
|
||||
@apply hover:bg-[var(--neutral-disabled-bg)];
|
||||
}
|
||||
&:where(.ant-btn-text.ant-btn-custom-color:not(:disabled)) {
|
||||
@apply border-none bg-transparent text-[var(--accent-color)];
|
||||
@apply hover:bg-[var(--accent-color-1)] hover:text-[var(--accent-color-hover)];
|
||||
}
|
||||
|
||||
&:where(.ant-btn-link) {
|
||||
@apply border-none bg-transparent text-[var(--text-color)] not-disabled:hover:text-[var(--text-color-hover)];
|
||||
&:where(.ant-btn-link:not(:disabled)) {
|
||||
@apply border-none bg-transparent text-[var(--accent-color)] hover:text-[var(--accent-color-hover)];
|
||||
}
|
||||
&:where(.ant-btn-dashed) {
|
||||
@apply border-dashed;
|
||||
}
|
||||
&:where(.ant-btn-filled) {
|
||||
@apply border-none bg-[var(--bg-color-tint-90)] text-[var(--text-color)] not-disabled:hover:text-[var(--text-color-hover)];
|
||||
@apply not-disabled:hover:bg-[var(--bg-color-tint-80)] not-disabled:active:bg-[var(--bg-color-tint-80)];
|
||||
&:where(.ant-btn-filled:not(:disabled)) {
|
||||
@apply border-none bg-[var(--accent-color-1)] text-[var(--accent-color)] hover:text-[var(--accent-color-hover)];
|
||||
@apply hover:bg-[var(--accent-color-2)] active:bg-[var(--accent-color-3)] active:text-[var(--accent-color-active)];
|
||||
}
|
||||
|
||||
&:where(.ant-btn-disabled) {
|
||||
@apply cursor-not-allowed;
|
||||
@apply border-[var(--neutral-border)] bg-[var(--neutral-disabled-bg)] text-[var(--neutral-disabled)];
|
||||
}
|
||||
&:where(.ant-btn-disabled.ant-btn-text),
|
||||
&:where(.ant-btn-disabled.ant-btn-link) {
|
||||
@apply border-none bg-transparent;
|
||||
}
|
||||
|
||||
&:where(.ant-btn-disabled.ant-btn-filled) {
|
||||
@apply border-none;
|
||||
}
|
||||
|
||||
&:where(.ant-btn-sm) {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export { default as Button } from './button'
|
||||
export { default as Input } from './input'
|
||||
export { default as Theme } from './theme'
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<template>
|
||||
<slot />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { themeProps } from './meta'
|
||||
import { useThemeProvide } from './hook'
|
||||
|
||||
const props = defineProps(themeProps)
|
||||
|
||||
useThemeProvide(props)
|
||||
</script>
|
|
@ -0,0 +1,21 @@
|
|||
import { inject, InjectionKey, provide, Reactive } from 'vue'
|
||||
|
||||
type ThemeType = Reactive<{
|
||||
appearance: 'light' | 'dark'
|
||||
primaryColor: string
|
||||
dangerColor: string
|
||||
}>
|
||||
|
||||
const ThemeSymbol: InjectionKey<ThemeType> = Symbol('theme')
|
||||
|
||||
export const useThemeInject = () => {
|
||||
return inject(ThemeSymbol, {
|
||||
appearance: 'light',
|
||||
primaryColor: '#1677ff',
|
||||
dangerColor: '#ff4d4f',
|
||||
})
|
||||
}
|
||||
|
||||
export const useThemeProvide = (theme: ThemeType) => {
|
||||
provide(ThemeSymbol, theme)
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
export * from './hook'
|
||||
|
||||
import { App, Plugin } from 'vue'
|
||||
import Theme from './Theme.vue'
|
||||
|
||||
export { Theme }
|
||||
|
||||
/* istanbul ignore next */
|
||||
Theme.install = function (app: App) {
|
||||
app.component('ATheme', Theme)
|
||||
return app
|
||||
}
|
||||
|
||||
export default Theme as typeof Theme & Plugin
|
|
@ -0,0 +1,31 @@
|
|||
import { PropType, ExtractPublicPropTypes } from 'vue'
|
||||
|
||||
// Theme Props
|
||||
export const themeProps = {
|
||||
/**
|
||||
* Specifies the theme of the component
|
||||
* @default 'light'
|
||||
*/
|
||||
appearance: {
|
||||
type: String as PropType<'light' | 'dark'>,
|
||||
default: 'light',
|
||||
},
|
||||
/**
|
||||
* Specifies the primary color of the component
|
||||
* @default '#1677FF'
|
||||
*/
|
||||
primaryColor: {
|
||||
type: String,
|
||||
default: '#1677FF',
|
||||
},
|
||||
/**
|
||||
* Specifies the danger color of the component
|
||||
* @default '#ff4d4f'
|
||||
*/
|
||||
dangerColor: {
|
||||
type: String,
|
||||
default: '#ff4d4f',
|
||||
},
|
||||
} as const
|
||||
|
||||
export type ThemeProps = ExtractPublicPropTypes<typeof themeProps>
|
|
@ -19,4 +19,11 @@
|
|||
--color-warning-content: #ffffff;
|
||||
--color-error: #ff3333;
|
||||
--color-error-content: #ffffff;
|
||||
|
||||
--neutral-color: #000000e0;
|
||||
--neutral-secondary: #000000a6;
|
||||
--neutral-disabled: #00000040;
|
||||
--neutral-border: #d9d9d9;
|
||||
--neutral-separator: #0505050f;
|
||||
--neutral-bg: #f5f5f5;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { TinyColor } from '@ctrl/tinycolor'
|
||||
import { generate, presetPalettes, presetDarkPalettes } from '@ant-design/colors'
|
||||
|
||||
export const getAlphaColor = (baseColor: string, alpha: number) =>
|
||||
new TinyColor(baseColor).setAlpha(alpha).toRgbString()
|
||||
|
@ -16,12 +17,65 @@ export const getShadeColor = (baseColor: string, shadeNumber: number) => {
|
|||
return new TinyColor(baseColor).shade(shadeNumber).toString()
|
||||
}
|
||||
|
||||
export const getCssVarColor = (baseColor: string) => {
|
||||
export const getLightNeutralColor = () => {
|
||||
return {
|
||||
'--neutral-color': '#000000e0',
|
||||
'--neutral-secondary': '#000000a6',
|
||||
'--neutral-disabled': '#00000040',
|
||||
'--neutral-disabled-bg': '#0000000a',
|
||||
'--neutral-border': '#d9d9d9',
|
||||
'--neutral-separator': '#0505050f',
|
||||
'--neutral-bg': '#f5f5f5',
|
||||
}
|
||||
}
|
||||
|
||||
export const getDarkNeutralColor = () => {
|
||||
return {
|
||||
'--neutral-color': '#FFFFFFD9',
|
||||
'--neutral-secondary': '#FFFFFFA6',
|
||||
'--neutral-disabled': '#FFFFFF40',
|
||||
'--neutral-disabled-bg': 'rgba(255, 255, 255, 0.08)',
|
||||
'--neutral-border': '#424242',
|
||||
'--neutral-separator': '#FDFDFD1F',
|
||||
'--neutral-bg': '#000000',
|
||||
}
|
||||
}
|
||||
|
||||
export const getCssVarColor = (
|
||||
baseColor: string,
|
||||
opts?: { appearance: 'light' | 'dark'; backgroundColor: string },
|
||||
) => {
|
||||
const { appearance = 'light', backgroundColor = '#141414' } = opts || {}
|
||||
const color = new TinyColor(baseColor)
|
||||
const preset = appearance === 'dark' ? presetDarkPalettes : presetPalettes
|
||||
const colors =
|
||||
preset[baseColor] ||
|
||||
generate(
|
||||
color.toHexString(),
|
||||
appearance === 'dark' ? { theme: appearance, backgroundColor } : undefined,
|
||||
)
|
||||
const accentColor = colors[5]
|
||||
return {
|
||||
'--accent-color-1': colors[0],
|
||||
'--accent-color-2': colors[1],
|
||||
'--accent-color-3': colors[2],
|
||||
'--accent-color-4': colors[3],
|
||||
'--accent-color-5': colors[4],
|
||||
'--accent-color-6': colors[5],
|
||||
'--accent-color-7': colors[6],
|
||||
'--accent-color-8': colors[7],
|
||||
'--accent-color-9': colors[8],
|
||||
'--accent-color-10': colors[9],
|
||||
'--accent-color': accentColor,
|
||||
'--accent-color-hover': colors[4],
|
||||
'--accent-color-active': colors[5],
|
||||
'--accent-color-content': '#ffffff',
|
||||
...(appearance === 'dark' ? getDarkNeutralColor() : getLightNeutralColor()),
|
||||
'--bg-color': baseColor,
|
||||
'--bg-color-hover': getTintColor(baseColor, 10),
|
||||
'--bg-color-active': getTintColor(baseColor, 20),
|
||||
'--bg-color-content': '#ffffff',
|
||||
|
||||
'--border-color': baseColor,
|
||||
'--border-color-hover': getTintColor(baseColor, 10),
|
||||
'--border-color-active': getTintColor(baseColor, 20),
|
||||
|
|
Loading…
Reference in New Issue