Skip to content

Commit 428fdfc

Browse files
committed
feat: add Theme component and enhance Button with href and target props
1 parent 73dd6d7 commit 428fdfc

File tree

10 files changed

+213
-36
lines changed

10 files changed

+213
-36
lines changed
Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
11
<template>
2-
<button :class="rootClass" @click="$emit('click', $event)" :disabled="disabled" :style="cssVars">
3-
<slot name="loading"></slot>
2+
<button :class="rootClass" @click="handleClick" :disabled="disabled" :style="cssVars">
3+
<slot name="loading">
4+
<LoadingOutlined v-if="loading" />
5+
</slot>
46
<slot name="icon"></slot>
5-
<slot></slot>
7+
<span><slot></slot></span>
68
</button>
79
</template>
810

911
<script setup lang="ts">
10-
import { computed } from 'vue'
12+
import { computed, Fragment } from 'vue'
1113
import { buttonProps, buttonEmits, ButtonSlots } from './meta'
1214
import { getCssVarColor } from '@/utils/colorAlgorithm'
15+
import { useThemeInject } from '../theme/hook'
16+
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'
1317
1418
const props = defineProps(buttonProps)
1519
16-
defineEmits(buttonEmits)
20+
const emit = defineEmits(buttonEmits)
1721
defineSlots<ButtonSlots>()
1822
19-
// todo: color value should from theme provider
23+
const theme = useThemeInject()
24+
2025
const color = computed(() => {
21-
if (props.disabled) {
22-
return 'rgba(0,0,0,0.25)'
23-
}
2426
if (props.color) {
2527
return props.color
2628
}
29+
2730
if (props.danger) {
28-
return '#ff4d4f'
29-
}
30-
if (props.variant === 'text') {
31-
return '#000000'
31+
return theme.dangerColor
3232
}
3333
34-
return '#1677ff'
34+
return theme.primaryColor
3535
})
3636
3737
const rootClass = computed(() => {
@@ -42,9 +42,17 @@ const rootClass = computed(() => {
4242
'ant-btn-danger': props.danger,
4343
'ant-btn-loading': props.loading,
4444
'ant-btn-disabled': props.disabled,
45+
'ant-btn-custom-color': props.color || props.danger,
4546
}
4647
})
4748
const cssVars = computed(() => {
4849
return getCssVarColor(color.value)
4950
})
51+
52+
const handleClick = (event: MouseEvent) => {
53+
emit('click', event)
54+
if (props.href) {
55+
window.open(props.href, props.target)
56+
}
57+
}
5058
</script>

packages/ui/src/components/button/meta.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ export const buttonProps = {
6262
color: {
6363
type: String,
6464
},
65+
66+
/**
67+
* Specifies the href of the button
68+
*/
69+
href: {
70+
type: String,
71+
},
72+
73+
/**
74+
* Specifies the target of the button
75+
*/
76+
target: {
77+
type: String,
78+
},
6579
} as const
6680

6781
export type ButtonProps = ExtractPublicPropTypes<typeof buttonProps>

packages/ui/src/components/button/style/index.css

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,55 @@
22

33
.ant-btn {
44
@apply relative;
5-
@apply inline-flex shrink-0 cursor-pointer items-center justify-center gap-1 whitespace-nowrap;
5+
@apply inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 whitespace-nowrap;
66
@apply border-1 text-sm;
77
@apply box-border rounded-md px-4 transition-all duration-200 select-none;
8-
&:where(.ant-btn-disabled) {
9-
@apply cursor-not-allowed;
10-
}
8+
119
&:where(.ant-btn-loading) {
12-
@apply cursor-default opacity-50;
10+
@apply cursor-default opacity-65;
1311
}
1412

15-
&:where(.ant-btn-solid) {
16-
@apply border-none bg-[var(--bg-color)] text-[var(--bg-color-content)];
17-
@apply not-disabled:hover:bg-[var(--bg-color-hover)] not-disabled:active:bg-[var(--bg-color-active)];
13+
&:where(.ant-btn-solid:not(:disabled)) {
14+
@apply border-none bg-[var(--accent-color)] text-[var(--accent-color-content)];
15+
@apply hover:bg-[var(--accent-color-hover)] active:bg-[var(--accent-color-active)];
1816
}
19-
&:where(.ant-btn-outlined),
20-
&:where(.ant-btn-dashed) {
21-
@apply border-[var(--border-color-tint-30)] bg-transparent text-[var(--text-color)];
22-
@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)];
23-
@apply disabled:border-[var(--border-color-tint-80)];
17+
&:where(.ant-btn-outlined:not(:disabled)),
18+
&:where(.ant-btn-dashed:not(:disabled)) {
19+
@apply border-[var(--accent-color)] bg-transparent text-[var(--accent-color)];
20+
@apply hover:text-[var(--accent-color-hover)] active:border-[var(--accent-color-active)] active:text-[var(--accent-color-active)];
21+
@apply border-[var(--accent-color-active)] hover:border-[var(--accent-color-hover)];
22+
}
23+
&:where(.ant-btn-text:not(.ant-btn-custom-color):not(:disabled)) {
24+
@apply border-none bg-transparent text-[var(--neutral-color)];
25+
@apply hover:bg-[var(--neutral-disabled-bg)];
2426
}
25-
&:where(.ant-btn-text) {
26-
@apply border-none bg-transparent text-[var(--text-color)];
27-
@apply not-disabled:hover:bg-[var(--bg-color-tint-90)] not-disabled:hover:text-[var(--text-color-hover)];
27+
&:where(.ant-btn-text.ant-btn-custom-color:not(:disabled)) {
28+
@apply border-none bg-transparent text-[var(--accent-color)];
29+
@apply hover:bg-[var(--accent-color-1)] hover:text-[var(--accent-color-hover)];
2830
}
2931

30-
&:where(.ant-btn-link) {
31-
@apply border-none bg-transparent text-[var(--text-color)] not-disabled:hover:text-[var(--text-color-hover)];
32+
&:where(.ant-btn-link:not(:disabled)) {
33+
@apply border-none bg-transparent text-[var(--accent-color)] hover:text-[var(--accent-color-hover)];
3234
}
3335
&:where(.ant-btn-dashed) {
3436
@apply border-dashed;
3537
}
36-
&:where(.ant-btn-filled) {
37-
@apply border-none bg-[var(--bg-color-tint-90)] text-[var(--text-color)] not-disabled:hover:text-[var(--text-color-hover)];
38-
@apply not-disabled:hover:bg-[var(--bg-color-tint-80)] not-disabled:active:bg-[var(--bg-color-tint-80)];
38+
&:where(.ant-btn-filled:not(:disabled)) {
39+
@apply border-none bg-[var(--accent-color-1)] text-[var(--accent-color)] hover:text-[var(--accent-color-hover)];
40+
@apply hover:bg-[var(--accent-color-2)] active:bg-[var(--accent-color-3)] active:text-[var(--accent-color-active)];
41+
}
42+
43+
&:where(.ant-btn-disabled) {
44+
@apply cursor-not-allowed;
45+
@apply border-[var(--neutral-border)] bg-[var(--neutral-disabled-bg)] text-[var(--neutral-disabled)];
46+
}
47+
&:where(.ant-btn-disabled.ant-btn-text),
48+
&:where(.ant-btn-disabled.ant-btn-link) {
49+
@apply border-none bg-transparent;
50+
}
51+
52+
&:where(.ant-btn-disabled.ant-btn-filled) {
53+
@apply border-none;
3954
}
4055

4156
&:where(.ant-btn-sm) {

packages/ui/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { default as Button } from './button'
22
export { default as Input } from './input'
3+
export { default as Theme } from './theme'
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<template>
2+
<slot />
3+
</template>
4+
5+
<script setup lang="ts">
6+
import { themeProps } from './meta'
7+
import { useThemeProvide } from './hook'
8+
9+
const props = defineProps(themeProps)
10+
11+
useThemeProvide(props)
12+
</script>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { inject, InjectionKey, provide, Reactive } from 'vue'
2+
3+
type ThemeType = Reactive<{
4+
appearance: 'light' | 'dark'
5+
primaryColor: string
6+
dangerColor: string
7+
}>
8+
9+
const ThemeSymbol: InjectionKey<ThemeType> = Symbol('theme')
10+
11+
export const useThemeInject = () => {
12+
return inject(ThemeSymbol, {
13+
appearance: 'light',
14+
primaryColor: '#1677ff',
15+
dangerColor: '#ff4d4f',
16+
})
17+
}
18+
19+
export const useThemeProvide = (theme: ThemeType) => {
20+
provide(ThemeSymbol, theme)
21+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export * from './hook'
2+
3+
import { App, Plugin } from 'vue'
4+
import Theme from './Theme.vue'
5+
6+
export { Theme }
7+
8+
/* istanbul ignore next */
9+
Theme.install = function (app: App) {
10+
app.component('ATheme', Theme)
11+
return app
12+
}
13+
14+
export default Theme as typeof Theme & Plugin
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { PropType, ExtractPublicPropTypes } from 'vue'
2+
3+
// Theme Props
4+
export const themeProps = {
5+
/**
6+
* Specifies the theme of the component
7+
* @default 'light'
8+
*/
9+
appearance: {
10+
type: String as PropType<'light' | 'dark'>,
11+
default: 'light',
12+
},
13+
/**
14+
* Specifies the primary color of the component
15+
* @default '#1677FF'
16+
*/
17+
primaryColor: {
18+
type: String,
19+
default: '#1677FF',
20+
},
21+
/**
22+
* Specifies the danger color of the component
23+
* @default '#ff4d4f'
24+
*/
25+
dangerColor: {
26+
type: String,
27+
default: '#ff4d4f',
28+
},
29+
} as const
30+
31+
export type ThemeProps = ExtractPublicPropTypes<typeof themeProps>

packages/ui/src/style/base.css

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,11 @@
1919
--color-warning-content: #ffffff;
2020
--color-error: #ff3333;
2121
--color-error-content: #ffffff;
22+
23+
--neutral-color: #000000e0;
24+
--neutral-secondary: #000000a6;
25+
--neutral-disabled: #00000040;
26+
--neutral-border: #d9d9d9;
27+
--neutral-separator: #0505050f;
28+
--neutral-bg: #f5f5f5;
2229
}

packages/ui/src/utils/colorAlgorithm.ts

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TinyColor } from '@ctrl/tinycolor'
2+
import { generate, presetPalettes, presetDarkPalettes } from '@ant-design/colors'
23

34
export const getAlphaColor = (baseColor: string, alpha: number) =>
45
new TinyColor(baseColor).setAlpha(alpha).toRgbString()
@@ -16,12 +17,65 @@ export const getShadeColor = (baseColor: string, shadeNumber: number) => {
1617
return new TinyColor(baseColor).shade(shadeNumber).toString()
1718
}
1819

19-
export const getCssVarColor = (baseColor: string) => {
20+
export const getLightNeutralColor = () => {
2021
return {
22+
'--neutral-color': '#000000e0',
23+
'--neutral-secondary': '#000000a6',
24+
'--neutral-disabled': '#00000040',
25+
'--neutral-disabled-bg': '#0000000a',
26+
'--neutral-border': '#d9d9d9',
27+
'--neutral-separator': '#0505050f',
28+
'--neutral-bg': '#f5f5f5',
29+
}
30+
}
31+
32+
export const getDarkNeutralColor = () => {
33+
return {
34+
'--neutral-color': '#FFFFFFD9',
35+
'--neutral-secondary': '#FFFFFFA6',
36+
'--neutral-disabled': '#FFFFFF40',
37+
'--neutral-disabled-bg': 'rgba(255, 255, 255, 0.08)',
38+
'--neutral-border': '#424242',
39+
'--neutral-separator': '#FDFDFD1F',
40+
'--neutral-bg': '#000000',
41+
}
42+
}
43+
44+
export const getCssVarColor = (
45+
baseColor: string,
46+
opts?: { appearance: 'light' | 'dark'; backgroundColor: string },
47+
) => {
48+
const { appearance = 'light', backgroundColor = '#141414' } = opts || {}
49+
const color = new TinyColor(baseColor)
50+
const preset = appearance === 'dark' ? presetDarkPalettes : presetPalettes
51+
const colors =
52+
preset[baseColor] ||
53+
generate(
54+
color.toHexString(),
55+
appearance === 'dark' ? { theme: appearance, backgroundColor } : undefined,
56+
)
57+
const accentColor = colors[5]
58+
return {
59+
'--accent-color-1': colors[0],
60+
'--accent-color-2': colors[1],
61+
'--accent-color-3': colors[2],
62+
'--accent-color-4': colors[3],
63+
'--accent-color-5': colors[4],
64+
'--accent-color-6': colors[5],
65+
'--accent-color-7': colors[6],
66+
'--accent-color-8': colors[7],
67+
'--accent-color-9': colors[8],
68+
'--accent-color-10': colors[9],
69+
'--accent-color': accentColor,
70+
'--accent-color-hover': colors[4],
71+
'--accent-color-active': colors[5],
72+
'--accent-color-content': '#ffffff',
73+
...(appearance === 'dark' ? getDarkNeutralColor() : getLightNeutralColor()),
2174
'--bg-color': baseColor,
2275
'--bg-color-hover': getTintColor(baseColor, 10),
2376
'--bg-color-active': getTintColor(baseColor, 20),
2477
'--bg-color-content': '#ffffff',
78+
2579
'--border-color': baseColor,
2680
'--border-color-hover': getTintColor(baseColor, 10),
2781
'--border-color-active': getTintColor(baseColor, 20),

0 commit comments

Comments
 (0)